port: advance MSVC600 CPython compatibility frontier
This commit is contained in:
@@ -7,15 +7,16 @@
|
||||
* three distinct patterns:
|
||||
*
|
||||
* 1. `status = _PyStatus_OK();` -- a compound-literal macro used as a
|
||||
* plain assignment's right-hand side. Fixed by calling the existing
|
||||
* public API function PyStatus_Ok() instead, which
|
||||
* Python/initconfig.c defines as `return _PyStatus_OK();` -- the
|
||||
* identical value, no header change needed.
|
||||
* 2. `cf = _PyCompilerFlags_INIT;` -- likewise a compound-literal macro
|
||||
* plain assignment's right-hand side. The shared macro now expands to
|
||||
* a VC6-only internal helper that assigns PyStatus fields explicitly,
|
||||
* while newer compilers keep the original compound-literal form.
|
||||
* 2. `status = _PyStatus_ERR("...");` -- same compound-literal macro
|
||||
* pattern for ERROR statuses, now using a VC6-only explicit helper.
|
||||
* 3. `cf = _PyCompilerFlags_INIT;` -- likewise a compound-literal macro
|
||||
* used as a plain assignment. Fixed by assigning PyCompilerFlags's
|
||||
* two fields explicitly (cf_flags, cf_feature_version), matching the
|
||||
* macro's own definition field-for-field.
|
||||
* 3. `_PyArgv args = { .argc = argc, ... };` -- a designated-initializer
|
||||
* 4. `_PyArgv args = { .argc = argc, ... };` -- a designated-initializer
|
||||
* aggregate initializer with no macro involved. Fixed by declaring
|
||||
* the variable bare and assigning each field explicitly; field order
|
||||
* does not matter for plain assignment, unlike an initializer list.
|
||||
@@ -35,8 +36,18 @@ vc6_status_ok_smoke(void)
|
||||
{
|
||||
PyStatus status;
|
||||
|
||||
/* pattern 1: was `status = _PyStatus_OK();` */
|
||||
status = PyStatus_Ok();
|
||||
/* pattern 1: VC6-only _PyStatus_OK() macro fallback helper. */
|
||||
status = _PyStatus_OK();
|
||||
return status;
|
||||
}
|
||||
|
||||
PyStatus
|
||||
vc6_status_error_smoke(void)
|
||||
{
|
||||
PyStatus status;
|
||||
|
||||
/* pattern 2: VC6-only _PyStatus_ERR() macro fallback helper. */
|
||||
status = _PyStatus_ERR("vc6 status error smoke");
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/* Focused compile smoke test for compat/msvc600/pathcch.h.
|
||||
*
|
||||
* The VC6 compatibility header intentionally implements only the single
|
||||
* PathCch API used by cpython/Modules/getpath.c: PathCchFindExtension().
|
||||
* This fixture keeps the representative path cases in one place for the
|
||||
* VC6 probe. The probe is compile-only, so the checks are ordinary C code
|
||||
* that can also be run later by a tiny Windows harness if needed.
|
||||
*/
|
||||
#include <windows.h>
|
||||
#include <pathcch.h>
|
||||
|
||||
#ifndef ARRAYSIZE
|
||||
#define ARRAYSIZE(a) (sizeof(a) / sizeof((a)[0]))
|
||||
#endif
|
||||
|
||||
static int
|
||||
pathcch_expect_ext(PCWSTR path, size_t cch, size_t offset)
|
||||
{
|
||||
PCWSTR ext;
|
||||
HRESULT hr;
|
||||
|
||||
ext = NULL;
|
||||
hr = PathCchFindExtension(path, cch, &ext);
|
||||
return SUCCEEDED(hr) && ext == path + offset;
|
||||
}
|
||||
|
||||
static int
|
||||
pathcch_expect_invalid(PCWSTR path, size_t cch)
|
||||
{
|
||||
PCWSTR ext;
|
||||
HRESULT hr;
|
||||
|
||||
ext = (PCWSTR)1;
|
||||
hr = PathCchFindExtension(path, cch, &ext);
|
||||
return FAILED(hr);
|
||||
}
|
||||
|
||||
int
|
||||
vc6_pathcch_find_extension_smoke(void)
|
||||
{
|
||||
static const WCHAR exe_path[] = L"C:\\Python311\\python.exe";
|
||||
static const WCHAR no_ext[] = L"C:\\Python311\\python";
|
||||
static const WCHAR dir_dot[] = L"C:\\tmp.dir\\python";
|
||||
static const WCHAR dotfile[] = L"C:\\Python311\\.pythonrc";
|
||||
static const WCHAR trailing_dir[] = L"C:\\bin\\python.exe\\";
|
||||
static const WCHAR multi_dot[] = L"C:\\tmp\\archive.tar.gz";
|
||||
int ok;
|
||||
|
||||
ok = 1;
|
||||
ok = ok && pathcch_expect_ext(exe_path, ARRAYSIZE(exe_path), 20); /* .exe */
|
||||
ok = ok && pathcch_expect_ext(no_ext, ARRAYSIZE(no_ext), 19); /* NUL */
|
||||
ok = ok && pathcch_expect_ext(dir_dot, ARRAYSIZE(dir_dot), 17); /* NUL, directory dot ignored */
|
||||
ok = ok && pathcch_expect_ext(dotfile, ARRAYSIZE(dotfile), 13); /* dotfile extension */
|
||||
ok = ok && pathcch_expect_ext(trailing_dir, ARRAYSIZE(trailing_dir), 18); /* NUL after separator */
|
||||
ok = ok && pathcch_expect_ext(multi_dot, ARRAYSIZE(multi_dot), 18); /* last suffix */
|
||||
ok = ok && pathcch_expect_invalid(exe_path, 4); /* too small: no NUL */
|
||||
ok = ok && pathcch_expect_invalid(NULL, ARRAYSIZE(exe_path));
|
||||
return ok ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/* Focused compile smoke test for compat/msvc600/stdint.h.
|
||||
*
|
||||
* The VC6 compatibility header forwards to the existing
|
||||
* compat/msvc600/inttypes.h typedefs. This fixture exercises exactly the
|
||||
* subset that cpython/Modules/_blake2/impl/blake2-impl.h needs: uint8_t,
|
||||
* uint32_t, and uint64_t, in the same load/store/rotate shapes that file
|
||||
* uses, plus a compile-time width check for each. It also includes
|
||||
* <inttypes.h> alongside <stdint.h> in the same translation unit, the way
|
||||
* a Python.h + blake2.h translation unit would, to guard against the
|
||||
* duplicate-typedef failure mode the stdint.h header is designed to avoid.
|
||||
*/
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef char vc6_stdint_check_uint8_is_1_byte[(sizeof(uint8_t) == 1) ? 1 : -1];
|
||||
typedef char vc6_stdint_check_uint32_is_4_bytes[(sizeof(uint32_t) == 4) ? 1 : -1];
|
||||
typedef char vc6_stdint_check_uint64_is_8_bytes[(sizeof(uint64_t) == 8) ? 1 : -1];
|
||||
|
||||
static uint32_t
|
||||
vc6_stdint_load32(const void *src)
|
||||
{
|
||||
const uint8_t *p = (const uint8_t *)src;
|
||||
uint32_t w = *p++;
|
||||
w |= ((uint32_t)(*p++)) << 8;
|
||||
w |= ((uint32_t)(*p++)) << 16;
|
||||
w |= ((uint32_t)(*p++)) << 24;
|
||||
return w;
|
||||
}
|
||||
|
||||
static uint64_t
|
||||
vc6_stdint_load64(const void *src)
|
||||
{
|
||||
const uint8_t *p = (const uint8_t *)src;
|
||||
uint64_t w = *p++;
|
||||
w |= ((uint64_t)(*p++)) << 8;
|
||||
w |= ((uint64_t)(*p++)) << 16;
|
||||
w |= ((uint64_t)(*p++)) << 24;
|
||||
w |= ((uint64_t)(*p++)) << 32;
|
||||
w |= ((uint64_t)(*p++)) << 40;
|
||||
w |= ((uint64_t)(*p++)) << 48;
|
||||
w |= ((uint64_t)(*p++)) << 56;
|
||||
return w;
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
vc6_stdint_rotl32(const uint32_t w, const unsigned c)
|
||||
{
|
||||
return (w << c) | (w >> (32 - c));
|
||||
}
|
||||
|
||||
static uint64_t
|
||||
vc6_stdint_rotl64(const uint64_t w, const unsigned c)
|
||||
{
|
||||
return (w << c) | (w >> (64 - c));
|
||||
}
|
||||
|
||||
int
|
||||
vc6_stdint_smoke(void)
|
||||
{
|
||||
static const uint8_t bytes32[4] = { 0x01, 0x02, 0x03, 0x04 };
|
||||
static const uint8_t bytes64[8] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 };
|
||||
uint32_t w32;
|
||||
uint64_t w64;
|
||||
int ok;
|
||||
|
||||
w32 = vc6_stdint_load32(bytes32);
|
||||
w64 = vc6_stdint_load64(bytes64);
|
||||
|
||||
ok = 1;
|
||||
ok = ok && (w32 == 0x04030201UL);
|
||||
ok = ok && (vc6_stdint_rotl32(w32, 8) == ((w32 << 8) | (w32 >> 24)));
|
||||
ok = ok && (vc6_stdint_rotl64(w64, 8) == ((w64 << 8) | (w64 >> 56)));
|
||||
return ok ? 0 : 1;
|
||||
}
|
||||
Executable
+63
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Generate the CPython frozen getpath.py header into ignored build output.
|
||||
#
|
||||
# CPython's build documents this target in Makefile.pre.in:
|
||||
# Python/frozen_modules/getpath.h: Modules/getpath.py ...
|
||||
# Programs/_freeze_module.py getpath Modules/getpath.py Python/frozen_modules/getpath.h
|
||||
#
|
||||
# This script intentionally writes under build/generated/cpython/... so the
|
||||
# repository does not track generated bytecode headers or vendor files.
|
||||
#
|
||||
# Usage:
|
||||
# scripts/generate-getpath-frozen.sh [output-root]
|
||||
#
|
||||
# Output:
|
||||
# <output-root>/cpython/Python/frozen_modules/getpath.h
|
||||
# default output-root: build/generated
|
||||
|
||||
set -u
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
CPYTHON_ROOT="$REPO_ROOT/cpython"
|
||||
OUTPUT_ROOT="${1:-$REPO_ROOT/build/generated}"
|
||||
OUT_DIR="$OUTPUT_ROOT/cpython/Python/frozen_modules"
|
||||
OUT_HEADER="$OUT_DIR/getpath.h"
|
||||
FREEZE_SCRIPT="$CPYTHON_ROOT/Programs/_freeze_module.py"
|
||||
INPUT="$CPYTHON_ROOT/Modules/getpath.py"
|
||||
|
||||
usage() {
|
||||
sed -n '11,19p' "$REPO_ROOT/scripts/generate-getpath-frozen.sh"
|
||||
}
|
||||
|
||||
if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then
|
||||
usage
|
||||
exit 0
|
||||
fi
|
||||
|
||||
for required in "$CPYTHON_ROOT" "$FREEZE_SCRIPT" "$INPUT"; do
|
||||
if [ ! -e "$required" ]; then
|
||||
echo "generate-getpath-frozen: missing prerequisite: $required" >&2
|
||||
exit 2
|
||||
fi
|
||||
done
|
||||
|
||||
if ! command -v python3 >/dev/null 2>&1; then
|
||||
echo "generate-getpath-frozen: missing prerequisite: python3" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
mkdir -p "$OUT_DIR"
|
||||
python3 "$FREEZE_SCRIPT" getpath "$INPUT" "$OUT_HEADER"
|
||||
status=$?
|
||||
if [ "$status" -ne 0 ]; then
|
||||
echo "generate-getpath-frozen: _freeze_module.py failed with exit $status" >&2
|
||||
exit "$status"
|
||||
fi
|
||||
|
||||
if [ ! -s "$OUT_HEADER" ]; then
|
||||
echo "generate-getpath-frozen: expected nonempty output missing: $OUT_HEADER" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
printf '%s\n' "${OUT_HEADER#$REPO_ROOT/}"
|
||||
Executable
+119
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Regression tests for reproducible getpath.py frozen-header generation.
|
||||
|
||||
set -u
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
GENERATOR="$REPO_ROOT/scripts/generate-getpath-frozen.sh"
|
||||
TMP_ROOT="$REPO_ROOT/build/test-getpath-frozen"
|
||||
OUT_HEADER="$TMP_ROOT/cpython/Python/frozen_modules/getpath.h"
|
||||
DIRECT_HEADER="$TMP_ROOT/direct/getpath.h"
|
||||
|
||||
fail=0
|
||||
pass() { echo "ok - $1"; }
|
||||
bad() { echo "not ok - $1"; fail=1; }
|
||||
|
||||
rm -rf "$TMP_ROOT"
|
||||
out="$($GENERATOR "$TMP_ROOT" 2>&1)"
|
||||
status=$?
|
||||
if [ "$status" -eq 0 ]; then
|
||||
pass "generator exits 0"
|
||||
else
|
||||
bad "generator exits 0 (got exit $status)"
|
||||
echo "$out"
|
||||
fi
|
||||
|
||||
if [ "$out" = "build/test-getpath-frozen/cpython/Python/frozen_modules/getpath.h" ]; then
|
||||
pass "generator reports the project-local build-relative output path"
|
||||
else
|
||||
bad "generator reports the project-local build-relative output path (got: $out)"
|
||||
fi
|
||||
|
||||
if [ -s "$OUT_HEADER" ]; then
|
||||
pass "generator writes a nonempty getpath.h under build/"
|
||||
else
|
||||
bad "generator writes a nonempty getpath.h under build/"
|
||||
fi
|
||||
|
||||
first_line="$(sed -n '1p' "$OUT_HEADER" 2>/dev/null)"
|
||||
if [ "$first_line" = '/* Auto-generated by Programs/_freeze_module.py */' ]; then
|
||||
pass "generated header records the host-Python _freeze_module.py path"
|
||||
else
|
||||
bad "generated header records the host-Python _freeze_module.py path (got: $first_line)"
|
||||
fi
|
||||
|
||||
second_line="$(sed -n '2p' "$OUT_HEADER" 2>/dev/null)"
|
||||
if [ "$second_line" = 'const unsigned char _Py_M__getpath[] = {' ]; then
|
||||
pass "generated header declares the expected getpath frozen symbol"
|
||||
else
|
||||
bad "generated header declares the expected getpath frozen symbol (got: $second_line)"
|
||||
fi
|
||||
|
||||
python3 - "$OUT_HEADER" <<'PY'
|
||||
import marshal
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
path = Path(sys.argv[1])
|
||||
text = path.read_text()
|
||||
if not text.endswith('};\n'):
|
||||
raise SystemExit('header does not end with closing array marker')
|
||||
match = re.search(r'const unsigned char _Py_M__getpath\[\] = \{\n(?P<body>.*?)\};\n\Z', text, re.S)
|
||||
if not match:
|
||||
raise SystemExit('array body not found')
|
||||
lines = match.group('body').splitlines()
|
||||
values = []
|
||||
for index, line in enumerate(lines, 1):
|
||||
if not line.startswith(' '):
|
||||
raise SystemExit(f'line {index} is not four-space indented')
|
||||
payload = line.strip()
|
||||
if not payload.endswith(','):
|
||||
raise SystemExit(f'line {index} is missing trailing comma')
|
||||
nums = [part for part in payload[:-1].split(',') if part]
|
||||
if len(nums) > 16:
|
||||
raise SystemExit(f'line {index} has more than 16 bytes')
|
||||
if index < len(lines) and len(nums) != 16:
|
||||
raise SystemExit(f'non-final line {index} has {len(nums)} bytes, expected 16')
|
||||
for num in nums:
|
||||
value = int(num)
|
||||
if not 0 <= value <= 255:
|
||||
raise SystemExit(f'byte out of range: {value}')
|
||||
values.append(value)
|
||||
code = marshal.loads(bytes(values))
|
||||
if code.co_filename != '<frozen getpath>':
|
||||
raise SystemExit(f'unexpected code filename: {code.co_filename!r}')
|
||||
if code.co_name != '<module>':
|
||||
raise SystemExit(f'unexpected code name: {code.co_name!r}')
|
||||
print('format-ok')
|
||||
PY
|
||||
format_status=$?
|
||||
if [ "$format_status" -eq 0 ]; then
|
||||
pass "generated byte array unmarshals as frozen getpath code with CPython format"
|
||||
else
|
||||
bad "generated byte array unmarshals as frozen getpath code with CPython format"
|
||||
fi
|
||||
|
||||
mkdir -p "$(dirname "$DIRECT_HEADER")"
|
||||
python3 "$REPO_ROOT/cpython/Programs/_freeze_module.py" getpath \
|
||||
"$REPO_ROOT/cpython/Modules/getpath.py" "$DIRECT_HEADER"
|
||||
if cmp -s "$OUT_HEADER" "$DIRECT_HEADER"; then
|
||||
pass "generator output matches direct Programs/_freeze_module.py invocation exactly"
|
||||
else
|
||||
bad "generator output matches direct Programs/_freeze_module.py invocation exactly"
|
||||
fi
|
||||
|
||||
frontier_out="$(bash "$REPO_ROOT/scripts/vc6-probe-pythoncore-frontier.sh" --list-only 2>&1)"
|
||||
frontier_status=$?
|
||||
if [ "$frontier_status" -eq 0 ] && printf '%s\n' "$frontier_out" | grep -q $'^compile\tcpython/Modules/getpath.c\t'; then
|
||||
pass "frontier list still starts from Modules/getpath.c without requiring generated output"
|
||||
else
|
||||
bad "frontier list still starts from Modules/getpath.c without requiring generated output"
|
||||
echo "$frontier_out"
|
||||
fi
|
||||
|
||||
if [ "$fail" -ne 0 ]; then
|
||||
echo "FAILED"
|
||||
exit 1
|
||||
fi
|
||||
echo "ALL TESTS PASSED"
|
||||
@@ -261,11 +261,17 @@ EOF
|
||||
# --- positive case: the isolated compound-literal/designated-
|
||||
# initializer smoke fixture compiles clean. It exercises, self-
|
||||
# contained (aside from the real Python.h/pycore_initconfig.h types
|
||||
# it needs), the three replacement patterns applied to main.c: calling
|
||||
# PyStatus_Ok() instead of the _PyStatus_OK() compound-literal macro,
|
||||
# explicit field assignment instead of the _PyCompilerFlags_INIT
|
||||
# compound-literal macro, and explicit field assignment instead of a
|
||||
# _PyArgv designated-initializer aggregate. ---
|
||||
# it needs), the four replacement patterns now in use: the VC6-only
|
||||
# _PyStatus_OK() helper (not PyStatus_Ok(), avoiding recursion), the
|
||||
# VC6-only _PyStatus_ERR() helper, explicit field assignment instead of
|
||||
# the _PyCompilerFlags_INIT compound-literal macro, and explicit field
|
||||
# assignment instead of a _PyArgv designated-initializer aggregate. ---
|
||||
if grep -q '#[[:space:]]*define[[:space:]]*_PyStatus_OK().*PyStatus_Ok' "$REPO_ROOT/cpython/Include/internal/pycore_initconfig.h"; then
|
||||
bad "pycore_initconfig.h: VC6 _PyStatus_OK() helper does not recurse through PyStatus_Ok()"
|
||||
else
|
||||
pass "pycore_initconfig.h: VC6 _PyStatus_OK() helper does not recurse through PyStatus_Ok()"
|
||||
fi
|
||||
|
||||
out_compound="$("$PROBE" scripts/fixtures/vc6_compound_literal_smoke.c 2>&1)"
|
||||
status_compound=$?
|
||||
|
||||
|
||||
Executable
+140
@@ -0,0 +1,140 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Minimal VC6 link-frontier probe for the Windows 9x port.
|
||||
#
|
||||
# This is deliberately smaller than CPython's full PCbuild/pythoncore.vcxproj:
|
||||
# it compiles only the translation units already proven by the core compile
|
||||
# probes plus the tiny Programs/python.c executable entry point, then invokes
|
||||
# LINK.EXE under Wine. A successful executable link is not expected yet; the
|
||||
# purpose of this milestone is to verify that the build can get past CL.EXE
|
||||
# for the current frontier and reach a real VC6 linker diagnostic with a
|
||||
# reproducible command line.
|
||||
#
|
||||
# The MSVC600 bundle is an external, local-only prerequisite; nothing under
|
||||
# MSVC600/ is part of this project's source.
|
||||
#
|
||||
# Usage:
|
||||
# scripts/vc6-probe-minlink.sh
|
||||
#
|
||||
# Environment:
|
||||
# MSVC600_ROOT override the MSVC600 location (default: <repo>/MSVC600)
|
||||
# WINEPREFIX override the Wine prefix (default: ~/.wine-win9xport)
|
||||
|
||||
set -u
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
# Reuse winpath()/strip_prefix() from vc6-probe.sh instead of duplicating
|
||||
# them; sourcing it does not run the probe itself (see its own guard).
|
||||
# shellcheck source=scripts/vc6-probe.sh
|
||||
source "$REPO_ROOT/scripts/vc6-probe.sh"
|
||||
|
||||
run_probe_minlink() {
|
||||
MSVC600_ROOT="${MSVC600_ROOT:-$REPO_ROOT/MSVC600}"
|
||||
BUILD_DIR="$REPO_ROOT/build/vc6-probe-minlink"
|
||||
|
||||
CL="$MSVC600_ROOT/VC98/Bin/CL.EXE"
|
||||
LINK="$MSVC600_ROOT/VC98/Bin/LINK.EXE"
|
||||
VCVARS="$MSVC600_ROOT/VC98/Bin/VCVARS32.BAT"
|
||||
|
||||
# Keep this list intentionally tiny and aligned with the post-core-probe
|
||||
# milestone: python.vcxproj's single executable TU plus the three core TUs
|
||||
# that are currently known to compile under scripts/vc6-probe-core.sh.
|
||||
SOURCES=(
|
||||
"cpython/Programs/python.c"
|
||||
"cpython/Modules/main.c"
|
||||
"cpython/Python/pylifecycle.c"
|
||||
"cpython/Python/pystate.c"
|
||||
)
|
||||
|
||||
for required in "$CL" "$LINK" "$VCVARS" "$MSVC600_ROOT/VC98/Include" \
|
||||
"$MSVC600_ROOT/VC98/Lib" "$MSVC600_ROOT/Common/MSDev98/Bin"; do
|
||||
if [ ! -e "$required" ]; then
|
||||
echo "vc6-probe-minlink: missing prerequisite: $required" >&2
|
||||
return 2
|
||||
fi
|
||||
done
|
||||
local source_rel
|
||||
for source_rel in "${SOURCES[@]}"; do
|
||||
if [ ! -e "$REPO_ROOT/$source_rel" ]; then
|
||||
echo "vc6-probe-minlink: missing source: $REPO_ROOT/$source_rel" >&2
|
||||
return 2
|
||||
fi
|
||||
done
|
||||
|
||||
if ! command -v wine >/dev/null 2>&1; then
|
||||
echo "vc6-probe-minlink: wine is not installed" >&2
|
||||
return 2
|
||||
fi
|
||||
|
||||
export WINEPREFIX="${WINEPREFIX:-$HOME/.wine-win9xport}"
|
||||
export WINEDEBUG="${WINEDEBUG:--all}"
|
||||
|
||||
# CL.EXE loads C1/C2 from VC98/Bin and MSPDB60.DLL from Common/MSDev98/Bin;
|
||||
# LINK.EXE also needs the VC98 tools and default libraries.
|
||||
export WINEPATH="$(winpath "$MSVC600_ROOT/VC98/Bin");$(winpath "$MSVC600_ROOT/Common/MSDev98/Bin")"
|
||||
|
||||
rm -rf "$BUILD_DIR"
|
||||
mkdir -p "$BUILD_DIR"
|
||||
|
||||
INCLUDE_ARGS=(
|
||||
"$(winpath "$REPO_ROOT/compat/msvc600")"
|
||||
"$(winpath "$REPO_ROOT/cpython/Include/internal")"
|
||||
"$(winpath "$REPO_ROOT/cpython/Include")"
|
||||
"$(winpath "$REPO_ROOT/cpython/PC")"
|
||||
"$(winpath "$MSVC600_ROOT/VC98/Include")"
|
||||
)
|
||||
|
||||
echo "== MSVC600 minimal link-frontier probe =="
|
||||
echo "sources:"
|
||||
for source_rel in "${SOURCES[@]}"; do
|
||||
echo " - $source_rel"
|
||||
done
|
||||
echo "prefix : $WINEPREFIX"
|
||||
echo
|
||||
|
||||
local obj_args=""
|
||||
local source_abs obj_name obj_abs cmd status
|
||||
for source_rel in "${SOURCES[@]}"; do
|
||||
source_abs="$REPO_ROOT/$source_rel"
|
||||
obj_name="${source_rel#cpython/}"
|
||||
obj_name="${obj_name//\//_}"
|
||||
obj_name="${obj_name%.c}.obj"
|
||||
obj_abs="$BUILD_DIR/$obj_name"
|
||||
obj_args+=" $(winpath "$obj_abs")"
|
||||
|
||||
cmd="call $(winpath "$VCVARS") && cd /d $(winpath "$REPO_ROOT/cpython") && cl /nologo /D Py_BUILD_CORE /D Py_BUILD_CORE_BUILTIN /D Py_ENABLE_SHARED /D _USRDLL"
|
||||
for include_arg in "${INCLUDE_ARGS[@]}"; do
|
||||
cmd+=" /I $include_arg"
|
||||
done
|
||||
cmd+=" /c $(winpath "$source_abs") /Fo$(winpath "$obj_abs")"
|
||||
|
||||
wine cmd /c "$cmd" 2>&1 |
|
||||
grep -v '^[0-9a-f]\{4\}:err:' |
|
||||
strip_prefix "$(winpath "$REPO_ROOT")\\"
|
||||
status="${PIPESTATUS[0]}"
|
||||
if [ "$status" -ne 0 ]; then
|
||||
echo
|
||||
echo "== cl.exe exit status: $status =="
|
||||
echo "vc6-probe-minlink: compile failed before link at $source_rel" >&2
|
||||
return "$status"
|
||||
fi
|
||||
done
|
||||
|
||||
echo
|
||||
echo "== LINK.EXE attempt =="
|
||||
cmd="call $(winpath "$VCVARS") && cd /d $(winpath "$REPO_ROOT/cpython") && link /nologo /subsystem:console /out:$(winpath "$BUILD_DIR/python-min.exe") /libpath:$(winpath "$MSVC600_ROOT/VC98/Lib")$obj_args kernel32.lib user32.lib advapi32.lib shell32.lib version.lib ws2_32.lib"
|
||||
|
||||
wine cmd /c "$cmd" 2>&1 |
|
||||
grep -v '^[0-9a-f]\{4\}:err:' |
|
||||
strip_prefix "$(winpath "$REPO_ROOT")\\"
|
||||
status="${PIPESTATUS[0]}"
|
||||
echo
|
||||
echo "== link.exe exit status: $status =="
|
||||
return "$status"
|
||||
}
|
||||
|
||||
if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
|
||||
run_probe_minlink "$@"
|
||||
exit "$?"
|
||||
fi
|
||||
Executable
+58
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Regression tests for the narrow VC6 compat/msvc600/pathcch.h shim.
|
||||
#
|
||||
# Skipped (not failed) when Wine + MSVC600 are unavailable, matching the other
|
||||
# VC6 probe test scripts. The live check is compile-only: it verifies that the
|
||||
# fixture using representative PathCchFindExtension paths reaches cl.exe exit 0.
|
||||
#
|
||||
# Usage: scripts/vc6-probe-pathcch.test.sh
|
||||
|
||||
set -u
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
PROBE="$REPO_ROOT/scripts/vc6-probe-core.sh"
|
||||
|
||||
fail=0
|
||||
pass() { echo "ok - $1"; }
|
||||
bad() { echo "not ok - $1"; fail=1; }
|
||||
|
||||
if grep -q 'PathCchFindExtension' "$REPO_ROOT/compat/msvc600/pathcch.h" && \
|
||||
grep -q 'defined(_MSC_VER).*1200' "$REPO_ROOT/compat/msvc600/pathcch.h"; then
|
||||
pass "compat/msvc600/pathcch.h provides a VC6-scoped PathCchFindExtension shim"
|
||||
else
|
||||
bad "compat/msvc600/pathcch.h provides a VC6-scoped PathCchFindExtension shim"
|
||||
fi
|
||||
|
||||
MSVC600_ROOT="${MSVC600_ROOT:-$REPO_ROOT/MSVC600}"
|
||||
if command -v wine >/dev/null 2>&1 && [ -e "$MSVC600_ROOT/VC98/Bin/CL.EXE" ]; then
|
||||
out="$($PROBE scripts/fixtures/vc6_pathcch_smoke.c 2>&1)"
|
||||
status=$?
|
||||
|
||||
if [ "$status" -eq 0 ]; then
|
||||
pass "PathCchFindExtension smoke fixture compiles under VC6 (exit 0)"
|
||||
else
|
||||
bad "PathCchFindExtension smoke fixture compiles under VC6 (exit $status)"
|
||||
echo "$out"
|
||||
fi
|
||||
|
||||
if printf '%s' "$out" | grep -q "Cannot open include file: 'pathcch.h'"; then
|
||||
bad "pathcch smoke fixture does not regress to missing pathcch.h"
|
||||
else
|
||||
pass "pathcch smoke fixture does not regress to missing pathcch.h"
|
||||
fi
|
||||
|
||||
if printf '%s' "$out" | grep -qi 'error'; then
|
||||
bad "pathcch smoke fixture: no compiler errors in output"
|
||||
else
|
||||
pass "pathcch smoke fixture: no compiler errors in output"
|
||||
fi
|
||||
else
|
||||
echo "skip - wine/MSVC600 not available; skipping live pathcch integration check"
|
||||
fi
|
||||
|
||||
if [ "$fail" -ne 0 ]; then
|
||||
echo "FAILED"
|
||||
exit 1
|
||||
fi
|
||||
echo "ALL TESTS PASSED"
|
||||
Executable
+376
@@ -0,0 +1,376 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Source-list-driven VC6 compile frontier for CPython's pythoncore project.
|
||||
#
|
||||
# This reads cpython/PCbuild/pythoncore.vcxproj <ClCompile> entries, safely
|
||||
# normalizes their Windows-style paths into project-local cpython/... sources,
|
||||
# reports entries that this VC6/Wine frontier intentionally cannot consume
|
||||
# yet, and compiles supported sources one at a time into build artifacts. It
|
||||
# stops at the first cl.exe failure and returns cl.exe's real exit status.
|
||||
#
|
||||
# This is a compile-only (/c) frontier: no pythoncore.dll/python.exe link or
|
||||
# runtime success is claimed.
|
||||
#
|
||||
# The MSVC600 bundle is an external, local-only prerequisite; nothing under
|
||||
# MSVC600/ is part of this project's source.
|
||||
#
|
||||
# Usage:
|
||||
# scripts/vc6-probe-pythoncore-frontier.sh
|
||||
# scripts/vc6-probe-pythoncore-frontier.sh --list-only
|
||||
# scripts/vc6-probe-pythoncore-frontier.sh --limit N
|
||||
#
|
||||
# Environment:
|
||||
# MSVC600_ROOT override the MSVC600 location (default: <repo>/MSVC600)
|
||||
# WINEPREFIX override the Wine prefix (default: ~/.wine-win9xport)
|
||||
# VC6_FRONTIER_FORCE set to 1 to ignore incremental object freshness
|
||||
|
||||
set -u
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
# Reuse winpath()/strip_prefix() from vc6-probe.sh instead of duplicating
|
||||
# them; sourcing it does not run the probe itself (see its own guard).
|
||||
# shellcheck source=scripts/vc6-probe.sh
|
||||
source "$REPO_ROOT/scripts/vc6-probe.sh"
|
||||
|
||||
FRONTIER_SCRIPT="$REPO_ROOT/scripts/vc6-probe-pythoncore-frontier.sh"
|
||||
PROJECT_REL="cpython/PCbuild/pythoncore.vcxproj"
|
||||
PROJECT_ABS="$REPO_ROOT/$PROJECT_REL"
|
||||
BUILD_DIR="$REPO_ROOT/build/vc6-probe-pythoncore-frontier"
|
||||
SOURCE_LIST_TSV="$BUILD_DIR/pythoncore-sources.tsv"
|
||||
GENERATED_ROOT="$REPO_ROOT/build/generated"
|
||||
GENERATED_CPYTHON_ROOT="$GENERATED_ROOT/cpython"
|
||||
GETPATH_GENERATOR="$REPO_ROOT/scripts/generate-getpath-frozen.sh"
|
||||
GETPATH_FROZEN_HEADER="$GENERATED_CPYTHON_ROOT/Python/frozen_modules/getpath.h"
|
||||
|
||||
usage() {
|
||||
sed -n '15,27p' "$FRONTIER_SCRIPT"
|
||||
}
|
||||
|
||||
emit_source_list() {
|
||||
python3 - "$PROJECT_ABS" "$REPO_ROOT" <<'PY'
|
||||
import os
|
||||
import posixpath
|
||||
import sys
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
project = os.path.abspath(sys.argv[1])
|
||||
repo = os.path.abspath(sys.argv[2])
|
||||
source_root = os.path.join(repo, "cpython")
|
||||
project_dir = os.path.dirname(project)
|
||||
ns = "{http://schemas.microsoft.com/developer/msbuild/2003}"
|
||||
|
||||
substitutions = {
|
||||
"$(PyVPath)": r"..\\..",
|
||||
"$(PyDebugExt)": "",
|
||||
"$(SysWinVer)": "3.11",
|
||||
}
|
||||
|
||||
def expand_msbuild(value):
|
||||
for key, repl in substitutions.items():
|
||||
value = value.replace(key, repl)
|
||||
return value
|
||||
|
||||
def normalize_include(include):
|
||||
raw = include.strip()
|
||||
expanded = expand_msbuild(raw)
|
||||
if "$" in expanded or "%" in expanded:
|
||||
return None, "unsupported-msbuild-macro"
|
||||
path = expanded.replace("\\", "/")
|
||||
if len(path) >= 2 and path[1] == ":":
|
||||
return None, "absolute-windows-path"
|
||||
abs_path = os.path.abspath(os.path.join(project_dir, path))
|
||||
try:
|
||||
rel_to_source = os.path.relpath(abs_path, source_root)
|
||||
except ValueError:
|
||||
return None, "outside-cpython-tree"
|
||||
rel_posix = rel_to_source.replace(os.sep, "/")
|
||||
if rel_posix == ".." or rel_posix.startswith("../") or posixpath.isabs(rel_posix):
|
||||
return None, "outside-cpython-tree"
|
||||
norm = posixpath.normpath(rel_posix)
|
||||
if norm == "." or norm.startswith("../") or "/../" in f"/{norm}/":
|
||||
return None, "unsafe-normalized-path"
|
||||
return "cpython/" + norm, ""
|
||||
|
||||
def split_definitions(text):
|
||||
if not text:
|
||||
return []
|
||||
text = expand_msbuild(" ".join(text.split()))
|
||||
defs = []
|
||||
for chunk in text.split(";"):
|
||||
chunk = chunk.strip()
|
||||
if not chunk or chunk == "%(PreprocessorDefinitions)":
|
||||
continue
|
||||
if "$" in chunk or "%" in chunk:
|
||||
defs.append("UNSUPPORTED_MSBUILD_DEFINE=" + chunk)
|
||||
else:
|
||||
defs.append(chunk)
|
||||
return defs
|
||||
|
||||
def classify(rel, inherited_condition, own_condition):
|
||||
cond = f"{inherited_condition or ''} {own_condition or ''}"
|
||||
if "IncludeExternals" in cond:
|
||||
return "skip-external IncludeExternals-conditioned source"
|
||||
if rel is None:
|
||||
return "skip-unsupported"
|
||||
if rel == "cpython/Python/deepfreeze/deepfreeze.c":
|
||||
return "skip-generated deepfreeze source"
|
||||
return "compile"
|
||||
|
||||
def walk(node, inherited_condition=""):
|
||||
condition = node.attrib.get("Condition", "")
|
||||
combined = " ".join(x for x in (inherited_condition, condition) if x)
|
||||
if node.tag == ns + "ClCompile" and "Include" in node.attrib:
|
||||
include = node.attrib["Include"]
|
||||
rel, norm_error = normalize_include(include)
|
||||
defs = []
|
||||
for child in node:
|
||||
if child.tag == ns + "PreprocessorDefinitions":
|
||||
defs.extend(split_definitions(child.text or ""))
|
||||
action = classify(rel, inherited_condition, condition)
|
||||
reason = action
|
||||
if action == "compile" and norm_error:
|
||||
action = "skip-unsupported"
|
||||
reason = f"skip-unsupported {norm_error}"
|
||||
elif action == "skip-unsupported" and norm_error:
|
||||
reason = f"skip-unsupported {norm_error}"
|
||||
display_rel = rel or include.replace("\\", "/")
|
||||
print("\t".join([action, display_rel, include, " ".join(defs), reason]))
|
||||
for child in node:
|
||||
walk(child, combined)
|
||||
|
||||
root = ET.parse(project).getroot()
|
||||
walk(root)
|
||||
PY
|
||||
}
|
||||
|
||||
quote_define_for_cmd() {
|
||||
local define="$1"
|
||||
# Keep CL's /D string literal quotes after CL reads the generated response
|
||||
# file. CL interprets C-string backslashes in /D values while parsing the
|
||||
# define, so double the define's own backslashes before protecting literal
|
||||
# quotes for CL's command-line parser.
|
||||
# Example: VPATH="..\\.." must arrive at C preprocessing as "..\\..", not
|
||||
# "..\.." and not an unterminated string.
|
||||
define="${define//\\/\\\\}"
|
||||
define="${define//\"/\\\"}"
|
||||
printf '%s' "$define"
|
||||
}
|
||||
|
||||
render_compile_command_for_test() {
|
||||
local cmd='cl /nologo'
|
||||
local define
|
||||
for define in "$@"; do
|
||||
cmd+=" /D $(quote_define_for_cmd "$define")"
|
||||
done
|
||||
printf '%s' "$cmd"
|
||||
}
|
||||
|
||||
obj_name_for_source() {
|
||||
local source_rel="$1"
|
||||
source_rel="${source_rel#cpython/}"
|
||||
source_rel="${source_rel//\//_}"
|
||||
printf '%s' "${source_rel%.c}.obj"
|
||||
}
|
||||
|
||||
generate_getpath_frozen_header() {
|
||||
if [ ! -x "$GETPATH_GENERATOR" ]; then
|
||||
echo "vc6-probe-pythoncore-frontier: missing prerequisite: executable $GETPATH_GENERATOR" >&2
|
||||
return 2
|
||||
fi
|
||||
"$GETPATH_GENERATOR" "$GENERATED_ROOT" >/dev/null
|
||||
}
|
||||
|
||||
source_for_compile() {
|
||||
local source_rel="$1"
|
||||
local source_abs="$2"
|
||||
if [ "$source_rel" != "cpython/Modules/getpath.c" ]; then
|
||||
printf '%s' "$source_abs"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Modules/getpath.c includes "../Python/frozen_modules/getpath.h" relative
|
||||
# to the compiling source file. Keep generated files under ignored build/
|
||||
# output by compiling a byte-for-byte copy from a generated cpython-shaped
|
||||
# tree where that documented relative include resolves.
|
||||
if [ ! -s "$GETPATH_FROZEN_HEADER" ]; then
|
||||
echo "vc6-probe-pythoncore-frontier: missing generated getpath header: $GETPATH_FROZEN_HEADER" >&2
|
||||
return 2
|
||||
fi
|
||||
local generated_source="$GENERATED_CPYTHON_ROOT/Modules/getpath.c"
|
||||
mkdir -p "$(dirname "$generated_source")"
|
||||
cp "$source_abs" "$generated_source"
|
||||
printf '%s' "$generated_source"
|
||||
}
|
||||
|
||||
run_probe_pythoncore_frontier() {
|
||||
local list_only=0 limit=0 arg
|
||||
while [ "$#" -gt 0 ]; do
|
||||
arg="$1"
|
||||
case "$arg" in
|
||||
--list-only)
|
||||
list_only=1
|
||||
shift
|
||||
;;
|
||||
--limit)
|
||||
if [ "$#" -lt 2 ]; then
|
||||
echo "vc6-probe-pythoncore-frontier: --limit requires a number" >&2
|
||||
return 2
|
||||
fi
|
||||
limit="$2"
|
||||
case "$limit" in
|
||||
''|*[!0-9]*) echo "vc6-probe-pythoncore-frontier: invalid --limit: $limit" >&2; return 2 ;;
|
||||
esac
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
echo "vc6-probe-pythoncore-frontier: unknown argument: $arg" >&2
|
||||
usage >&2
|
||||
return 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
mkdir -p "$BUILD_DIR"
|
||||
emit_source_list > "$SOURCE_LIST_TSV"
|
||||
|
||||
if [ "$list_only" -eq 1 ]; then
|
||||
cat "$SOURCE_LIST_TSV"
|
||||
return 0
|
||||
fi
|
||||
|
||||
MSVC600_ROOT="${MSVC600_ROOT:-$REPO_ROOT/MSVC600}"
|
||||
local CL="$MSVC600_ROOT/VC98/Bin/CL.EXE"
|
||||
local VCVARS="$MSVC600_ROOT/VC98/Bin/VCVARS32.BAT"
|
||||
|
||||
generate_getpath_frozen_header || return $?
|
||||
|
||||
for required in "$PROJECT_ABS" "$CL" "$VCVARS" "$MSVC600_ROOT/VC98/Include" \
|
||||
"$MSVC600_ROOT/Common/MSDev98/Bin"; do
|
||||
if [ ! -e "$required" ]; then
|
||||
echo "vc6-probe-pythoncore-frontier: missing prerequisite: $required" >&2
|
||||
return 2
|
||||
fi
|
||||
done
|
||||
|
||||
if ! command -v wine >/dev/null 2>&1; then
|
||||
echo "vc6-probe-pythoncore-frontier: wine is not installed" >&2
|
||||
return 2
|
||||
fi
|
||||
|
||||
export WINEPREFIX="${WINEPREFIX:-$HOME/.wine-win9xport}"
|
||||
export WINEDEBUG="${WINEDEBUG:--all}"
|
||||
|
||||
# CL.EXE loads C1/C2 from VC98/Bin and MSPDB60.DLL from Common/MSDev98/Bin.
|
||||
export WINEPATH="$(winpath "$MSVC600_ROOT/VC98/Bin");$(winpath "$MSVC600_ROOT/Common/MSDev98/Bin")"
|
||||
|
||||
local INCLUDE_ARGS=(
|
||||
"$(winpath "$REPO_ROOT/compat/msvc600")"
|
||||
"$(winpath "$REPO_ROOT/cpython/Python")"
|
||||
"$(winpath "$REPO_ROOT/cpython/Include/internal")"
|
||||
"$(winpath "$REPO_ROOT/cpython/Include")"
|
||||
"$(winpath "$REPO_ROOT/cpython/PC")"
|
||||
"$(winpath "$MSVC600_ROOT/VC98/Include")"
|
||||
)
|
||||
|
||||
local COMMON_DEFINES=(
|
||||
"_USRDLL"
|
||||
"Py_BUILD_CORE"
|
||||
"Py_BUILD_CORE_BUILTIN"
|
||||
"Py_ENABLE_SHARED"
|
||||
'MS_DLL_ID="3.11"'
|
||||
)
|
||||
|
||||
echo "== MSVC600 pythoncore compile-frontier probe =="
|
||||
echo "project: $PROJECT_REL"
|
||||
echo "list : ${SOURCE_LIST_TSV#$REPO_ROOT/}"
|
||||
echo "prefix : $WINEPREFIX"
|
||||
echo
|
||||
|
||||
local action source_rel raw_include defs reason
|
||||
local total=0 skipped=0 compiled=0 cached=0 considered=0
|
||||
while IFS=$'\t' read -r action source_rel raw_include defs reason; do
|
||||
total=$((total + 1))
|
||||
if [ "$action" != "compile" ]; then
|
||||
skipped=$((skipped + 1))
|
||||
echo "skip: $source_rel ($reason; raw=$raw_include)"
|
||||
continue
|
||||
fi
|
||||
if [ "$limit" -ne 0 ] && [ "$considered" -ge "$limit" ]; then
|
||||
break
|
||||
fi
|
||||
considered=$((considered + 1))
|
||||
|
||||
local source_abs="$REPO_ROOT/$source_rel"
|
||||
if [ ! -f "$source_abs" ]; then
|
||||
echo "vc6-probe-pythoncore-frontier: missing source from project list: $source_rel" >&2
|
||||
echo "== frontier summary: total=$total compiled=$compiled cached=$cached skipped=$skipped =="
|
||||
return 2
|
||||
fi
|
||||
|
||||
local obj_abs="$BUILD_DIR/$(obj_name_for_source "$source_rel")"
|
||||
local compile_source_abs
|
||||
compile_source_abs="$(source_for_compile "$source_rel" "$source_abs")" || return $?
|
||||
if [ "${VC6_FRONTIER_FORCE:-0}" != "1" ] && [ -f "$obj_abs" ] && \
|
||||
[ "$obj_abs" -nt "$source_abs" ] && [ "$obj_abs" -nt "$PROJECT_ABS" ] && \
|
||||
[ "$obj_abs" -nt "$FRONTIER_SCRIPT" ] && [ "$obj_abs" -nt "$GETPATH_FROZEN_HEADER" ]; then
|
||||
cached=$((cached + 1))
|
||||
echo "cached: $source_rel -> ${obj_abs#$REPO_ROOT/}"
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "compile: $source_rel -> ${obj_abs#$REPO_ROOT/}"
|
||||
local rsp_abs="${obj_abs%.obj}.rsp"
|
||||
local include_arg define
|
||||
{
|
||||
printf '/nologo\r\n'
|
||||
for define in "${COMMON_DEFINES[@]}"; do
|
||||
printf '/D %s\r\n' "$(quote_define_for_cmd "$define")"
|
||||
done
|
||||
if [ -n "$defs" ]; then
|
||||
for define in $defs; do
|
||||
if [ "${define#UNSUPPORTED_MSBUILD_DEFINE=}" != "$define" ]; then
|
||||
echo "vc6-probe-pythoncore-frontier: unsupported per-file define in $source_rel: ${define#UNSUPPORTED_MSBUILD_DEFINE=}" >&2
|
||||
echo "== frontier summary: total=$total compiled=$compiled cached=$cached skipped=$skipped =="
|
||||
return 2
|
||||
fi
|
||||
printf '/D %s\r\n' "$(quote_define_for_cmd "$define")"
|
||||
done
|
||||
fi
|
||||
for include_arg in "${INCLUDE_ARGS[@]}"; do
|
||||
printf '/I %s\r\n' "$include_arg"
|
||||
done
|
||||
printf '/c\r\n'
|
||||
printf '%s\r\n' "$(winpath "$compile_source_abs")"
|
||||
printf '/Fo%s\r\n' "$(winpath "$obj_abs")"
|
||||
} > "$rsp_abs"
|
||||
local cmd="call $(winpath "$VCVARS") && cd /d $(winpath "$REPO_ROOT/cpython") && cl @$(winpath "$rsp_abs")"
|
||||
|
||||
wine cmd /c "$cmd" 2>&1 |
|
||||
grep -v '^[0-9a-f]\{4\}:err:' |
|
||||
strip_prefix "$(winpath "$REPO_ROOT")\\"
|
||||
local status="${PIPESTATUS[0]}"
|
||||
if [ "$status" -ne 0 ]; then
|
||||
echo
|
||||
echo "== cl.exe exit status: $status =="
|
||||
echo "vc6-probe-pythoncore-frontier: first compile blocker: $source_rel" >&2
|
||||
echo "== frontier summary: total=$total compiled=$compiled cached=$cached skipped=$skipped =="
|
||||
return "$status"
|
||||
fi
|
||||
compiled=$((compiled + 1))
|
||||
done < "$SOURCE_LIST_TSV"
|
||||
|
||||
echo
|
||||
echo "== frontier summary: total=$total compiled=$compiled cached=$cached skipped=$skipped =="
|
||||
echo "vc6-probe-pythoncore-frontier: supported pythoncore compile frontier completed"
|
||||
return 0
|
||||
}
|
||||
|
||||
if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
|
||||
run_probe_pythoncore_frontier "$@"
|
||||
exit "$?"
|
||||
fi
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Regression tests for scripts/vc6-probe-pythoncore-frontier.sh.
|
||||
#
|
||||
# The no-toolchain checks exercise the source-list parser/normalizer and the
|
||||
# per-file define handling that make the frontier source-list-driven. When
|
||||
# Wine + MSVC600 are available, a bounded live run verifies that the harness
|
||||
# reports cl.exe's real first-blocker status.
|
||||
#
|
||||
# Usage: scripts/vc6-probe-pythoncore-frontier.test.sh
|
||||
|
||||
set -u
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
PROBE="$REPO_ROOT/scripts/vc6-probe-pythoncore-frontier.sh"
|
||||
|
||||
fail=0
|
||||
pass() { echo "ok - $1"; }
|
||||
bad() { echo "not ok - $1"; fail=1; }
|
||||
|
||||
out="$($PROBE --list-only 2>&1)"
|
||||
status=$?
|
||||
|
||||
if [ "$status" -eq 0 ]; then
|
||||
pass "frontier --list-only exits 0"
|
||||
else
|
||||
bad "frontier --list-only exits 0 (got exit $status)"
|
||||
echo "$out"
|
||||
fi
|
||||
|
||||
count="$(printf '%s\n' "$out" | awk 'NF { n++ } END { print n+0 }')"
|
||||
if [ "$count" -eq 198 ]; then
|
||||
pass "frontier parses all 198 pythoncore.vcxproj ClCompile entries"
|
||||
else
|
||||
bad "frontier parses all 198 pythoncore.vcxproj ClCompile entries (got $count)"
|
||||
fi
|
||||
|
||||
first_fields="$(printf '%s\n' "$out" | awk -F '\t' 'NR == 1 { print $1 "|" $2 "|" $3 }')"
|
||||
if [ "$first_fields" = 'compile|cpython/Modules/getpath.c|..\Modules\getpath.c' ]; then
|
||||
pass "frontier normalizes the first Windows-style source path to cpython/Modules/getpath.c"
|
||||
else
|
||||
bad "frontier normalizes the first Windows-style source path to cpython/Modules/getpath.c (got: $first_fields)"
|
||||
fi
|
||||
|
||||
getpath_defs="$(printf '%s\n' "$out" | awk -F '\t' '$2 == "cpython/Modules/getpath.c" { print $4; exit }')"
|
||||
case "$getpath_defs" in
|
||||
*'PREFIX=NULL'*'EXEC_PREFIX=NULL'*'VERSION=NULL'*'VPATH="..\\.."'*'PYDEBUGEXT=""'*'PLATLIBDIR="DLLs"'*)
|
||||
pass "frontier preserves getpath.c per-file defines with project-local substitutions"
|
||||
;;
|
||||
*)
|
||||
bad "frontier preserves getpath.c per-file defines with project-local substitutions (got: $getpath_defs)"
|
||||
;;
|
||||
esac
|
||||
|
||||
sysmodule_defs="$(printf '%s\n' "$out" | awk -F '\t' '$2 == "cpython/Python/sysmodule.c" { print $4; exit }')"
|
||||
case "$sysmodule_defs" in
|
||||
*'VPATH="..\\.."'*)
|
||||
pass "frontier preserves sysmodule.c VPATH per-file define"
|
||||
;;
|
||||
*)
|
||||
bad "frontier preserves sysmodule.c VPATH per-file define (got: $sysmodule_defs)"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Source the probe after --list-only so the guarded helper definitions can be
|
||||
# tested without launching Wine or requiring MSVC600.
|
||||
# shellcheck source=scripts/vc6-probe-pythoncore-frontier.sh
|
||||
source "$PROBE"
|
||||
rendered_cmd="$(render_compile_command_for_test 'VPATH="..\\.."' 'PYDEBUGEXT=""' 'PLATLIBDIR="DLLs"')"
|
||||
case "$rendered_cmd" in
|
||||
*' /D VPATH=\"..\\\\..\"'*' /D PYDEBUGEXT=\"\"'*' /D PLATLIBDIR=\"DLLs\"'*)
|
||||
pass "frontier renders quoted per-file string defines safely for CL response files"
|
||||
;;
|
||||
*)
|
||||
bad "frontier renders quoted per-file string defines safely for CL response files (got: $rendered_cmd)"
|
||||
;;
|
||||
esac
|
||||
|
||||
if printf '%s\n' "$out" | grep -q $'^skip-generated deepfreeze source\tcpython/Python/deepfreeze/deepfreeze.c\t'; then
|
||||
pass "frontier reports the generated deepfreeze source instead of compiling it blindly"
|
||||
else
|
||||
bad "frontier reports the generated deepfreeze source instead of compiling it blindly"
|
||||
fi
|
||||
|
||||
if printf '%s\n' "$out" | grep -q $'^skip-external IncludeExternals-conditioned source\tcpython/Modules/zlibmodule.c\t'; then
|
||||
pass "frontier reports IncludeExternals-conditioned in-tree zlibmodule.c as external"
|
||||
else
|
||||
bad "frontier reports IncludeExternals-conditioned in-tree zlibmodule.c as external"
|
||||
fi
|
||||
|
||||
if printf '%s\n' "$out" | grep -q $'^skip-external IncludeExternals-conditioned source\t$(zlibDir)/adler32.c\t'; then
|
||||
pass "frontier reports macro-based zlibDir sources as external without normalizing outside the repo"
|
||||
else
|
||||
bad "frontier reports macro-based zlibDir sources as external without normalizing outside the repo"
|
||||
fi
|
||||
|
||||
if printf '%s\n' "$out" | cut -f2 | grep -q '\\'; then
|
||||
bad "frontier normalized source column contains no backslashes"
|
||||
else
|
||||
pass "frontier normalized source column contains no backslashes"
|
||||
fi
|
||||
|
||||
MSVC600_ROOT="${MSVC600_ROOT:-$REPO_ROOT/MSVC600}"
|
||||
if command -v wine >/dev/null 2>&1 && [ -e "$MSVC600_ROOT/VC98/Bin/CL.EXE" ]; then
|
||||
live_out="$($PROBE --limit 1 2>&1)"
|
||||
live_status=$?
|
||||
|
||||
if [ "$live_status" -ne 0 ]; then
|
||||
pass "frontier live run reports a nonzero first-blocker status for the current first source"
|
||||
else
|
||||
pass "frontier live run compiles the first supported source (exit 0)"
|
||||
fi
|
||||
|
||||
if printf '%s\n' "$live_out" | grep -q '^compile: cpython/Modules/getpath.c'; then
|
||||
pass "frontier live run starts from pythoncore.vcxproj's first supported source"
|
||||
else
|
||||
bad "frontier live run starts from pythoncore.vcxproj's first supported source"
|
||||
echo "$live_out"
|
||||
fi
|
||||
|
||||
if [ "$live_status" -ne 0 ] && printf '%s\n' "$live_out" | grep -q 'first compile blocker: cpython/Modules/getpath.c'; then
|
||||
pass "frontier live run names the first actionable blocker"
|
||||
elif [ "$live_status" -eq 0 ]; then
|
||||
pass "frontier live run had no blocker in the first supported source"
|
||||
else
|
||||
bad "frontier live run names the first actionable blocker"
|
||||
echo "$live_out"
|
||||
fi
|
||||
|
||||
if printf '%s\n' "$live_out" | grep -q "frozen_modules[/\\\\]getpath.h.*No such file or directory"; then
|
||||
bad "frontier live run should provision Python/frozen_modules/getpath.h before compiling getpath.c"
|
||||
echo "$live_out"
|
||||
elif [ -s "$REPO_ROOT/build/generated/cpython/Python/frozen_modules/getpath.h" ]; then
|
||||
pass "frontier live run provisions the generated getpath.h include under build/"
|
||||
else
|
||||
bad "frontier live run provisions the generated getpath.h include under build/"
|
||||
echo "$live_out"
|
||||
fi
|
||||
else
|
||||
echo "skip - wine/MSVC600 not available; skipping live frontier integration check"
|
||||
fi
|
||||
|
||||
if [ "$fail" -ne 0 ]; then
|
||||
echo "FAILED"
|
||||
exit 1
|
||||
fi
|
||||
echo "ALL TESTS PASSED"
|
||||
Executable
+61
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Regression tests for the narrow VC6 compat/msvc600/stdint.h shim.
|
||||
#
|
||||
# Skipped (not failed) when Wine + MSVC600 are unavailable, matching the other
|
||||
# VC6 probe test scripts. The live check is compile-only: it verifies that the
|
||||
# fixture using representative uint8_t/uint32_t/uint64_t load/store/rotate
|
||||
# patterns (the exact subset cpython/Modules/_blake2/impl/blake2-impl.h uses)
|
||||
# reaches cl.exe exit 0, and that <stdint.h> and <inttypes.h> can both be
|
||||
# included in the same translation unit without a duplicate-typedef error.
|
||||
#
|
||||
# Usage: scripts/vc6-probe-stdint.test.sh
|
||||
|
||||
set -u
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
PROBE="$REPO_ROOT/scripts/vc6-probe-core.sh"
|
||||
|
||||
fail=0
|
||||
pass() { echo "ok - $1"; }
|
||||
bad() { echo "not ok - $1"; fail=1; }
|
||||
|
||||
if grep -q 'include "inttypes.h"' "$REPO_ROOT/compat/msvc600/stdint.h" && \
|
||||
grep -q 'defined(_MSC_VER).*1200\|_MSC_VER >= 1300' "$REPO_ROOT/compat/msvc600/stdint.h"; then
|
||||
pass "compat/msvc600/stdint.h is a VC6-scoped forward to inttypes.h"
|
||||
else
|
||||
bad "compat/msvc600/stdint.h is a VC6-scoped forward to inttypes.h"
|
||||
fi
|
||||
|
||||
MSVC600_ROOT="${MSVC600_ROOT:-$REPO_ROOT/MSVC600}"
|
||||
if command -v wine >/dev/null 2>&1 && [ -e "$MSVC600_ROOT/VC98/Bin/CL.EXE" ]; then
|
||||
out="$($PROBE scripts/fixtures/vc6_stdint_smoke.c 2>&1)"
|
||||
status=$?
|
||||
|
||||
if [ "$status" -eq 0 ]; then
|
||||
pass "stdint smoke fixture compiles under VC6 (exit 0)"
|
||||
else
|
||||
bad "stdint smoke fixture compiles under VC6 (exit $status)"
|
||||
echo "$out"
|
||||
fi
|
||||
|
||||
if printf '%s' "$out" | grep -q "Cannot open include file: 'stdint.h'"; then
|
||||
bad "stdint smoke fixture does not regress to missing stdint.h"
|
||||
else
|
||||
pass "stdint smoke fixture does not regress to missing stdint.h"
|
||||
fi
|
||||
|
||||
if printf '%s' "$out" | grep -qi 'error'; then
|
||||
bad "stdint smoke fixture: no compiler errors in output"
|
||||
else
|
||||
pass "stdint smoke fixture: no compiler errors in output"
|
||||
fi
|
||||
else
|
||||
echo "skip - wine/MSVC600 not available; skipping live stdint integration check"
|
||||
fi
|
||||
|
||||
if [ "$fail" -ne 0 ]; then
|
||||
echo "FAILED"
|
||||
exit 1
|
||||
fi
|
||||
echo "ALL TESTS PASSED"
|
||||
Reference in New Issue
Block a user