From ffa31730cb4677218ab40884ddf1e2db224724d1 Mon Sep 17 00:00:00 2001 From: knightmare2600 Date: Sun, 9 Aug 2026 10:03:02 +0100 Subject: [PATCH 01/20] Add native Windows arm64 build support (WIP) relenv/build/windows.py already carried arm64 plumbing (OpenSSL VC-WIN64-ARM target, arch_to_plat/arch_to_archname mappings) that was never reachable because arches[WIN32] had arm64 commented out since 2022, pending "Python 11" support that has long since landed. Uncomment it so relenv build/create/fetch accept --arch arm64 on Windows. Add build_windows_arm64 and test_windows_arm64 jobs targeting the windows-11-arm hosted runner (native host==target, unlike the existing x86 job which cross-builds on windows-2022), plus an explicit VS ARM64 VC++ tools install step since install_vc_build.ps1 only ever requests the x86/x64 workload. Untested pending an actual CI run. Tracks https://github.com/saltstack/relenv/issues/280. --- .github/workflows/build-native-action.yml | 84 +++++++++++++++++++++++ .github/workflows/pr.yml | 1 + .github/workflows/verify-build-action.yml | 63 +++++++++++++++++ relenv/common.py | 2 +- 4 files changed, 149 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-native-action.yml b/.github/workflows/build-native-action.yml index a4ec2bcb..65099221 100644 --- a/.github/workflows/build-native-action.yml +++ b/.github/workflows/build-native-action.yml @@ -367,3 +367,87 @@ jobs: name: ${{ steps.python-version.outputs.version }}-${{ matrix.arch }}-win.tar.xz path: build/${{ steps.python-version.outputs.version }}-${{ matrix.arch }}-win.tar.xz retention-days: 5 + + build_windows_arm64: + name: "Python Windows arm64" + # Native build: host and target are both arm64, so this runs on the + # windows-11-arm hosted runner rather than cross-compiling from the + # x64 windows-2022 image build_windows uses. WIP job for + # https://github.com/saltstack/relenv/issues/280 -- untested until a + # CI run actually exercises it; expect early iterations to surface + # missing toolchain/SDK pieces the way build_windows's history did. + runs-on: windows-11-arm + strategy: + fail-fast: false + matrix: + version: + - '3.10' + - '3.11' + - '3.12' + - '3.13' + - '3.14' + arch: + - arm64 + env: + RELENV_DATA: ${{ github.workspace }} + outputs: + version: ${{ steps.python-version.outputs.version }} + steps: + - uses: actions/checkout@v3 + + - name: Set up Python 3.11 + id: setup-python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install VS Build Tools + run: | + relenv/_scripts/install_vc_build.ps1 -CICD + + - name: Install ARM64 VC++ Tools + # install_vc_build.ps1 only requests the x86/x64 VCTools workload + # plus the legacy VC.140 toolset for the amd64/x86 job; it never + # asks for the ARM64 compiler/linker/armasm64, so request that + # component explicitly here rather than assuming the runner + # image's pre-installed VS carries it. + run: | + $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" + $vsPath = & $vswhere -latest -products * -property installationPath + $installer = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vs_installer.exe" + & $installer modify --installPath "$vsPath" ` + --add Microsoft.VisualStudio.Component.VC.Tools.ARM64 ` + --quiet --norestart --nocache + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + + - name: Install nox + run: | + pip3 install nox + + - name: Determine Python Version + id: python-version + run: | + echo "version=$(python3 -m relenv versions --version=${{ matrix.version }})" | tee -a "$env:GITHUB_OUTPUT" + + - name: Build Python with Relenv + env: + RELENV_NATIVE_PY_VERSION: 3.10.15 + WindowsTargetPlatformVersion: "10.0.19041.0" + HOST_PYTHON: ${{ steps.setup-python.outputs.python-path }} + run: | + python -m relenv build --no-pretty --arch=${{ matrix.arch }} --python=${{ steps.python-version.outputs.version }} + + - name: Upload Build Logs + uses: actions/upload-artifact@v4 + if: always() + with: + name: ${{ steps.python-version.outputs.version }}-${{ matrix.arch }}-windows-logs + path: logs/* + retention-days: 5 + + - name: Upload Zipfile + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.python-version.outputs.version }}-${{ matrix.arch }}-win.tar.xz + path: build/${{ steps.python-version.outputs.version }}-${{ matrix.arch }}-win.tar.xz + retention-days: 5 diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index c5c9a564..f778b56d 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -7,6 +7,7 @@ on: pull_request: branches: - main + workflow_dispatch: {} jobs: ci: diff --git a/.github/workflows/verify-build-action.yml b/.github/workflows/verify-build-action.yml index df8c9552..e4011f01 100644 --- a/.github/workflows/verify-build-action.yml +++ b/.github/workflows/verify-build-action.yml @@ -273,3 +273,66 @@ jobs: name: ${{ steps.python-version.outputs.version }}-${{ matrix.arch }}-windows-logs path: logs/* retention-days: 5 + + test_windows_arm64: + name: "Verify Windows arm64" + # Matches build_windows_arm64's native windows-11-arm runner in + # build-native-action.yml. WIP for + # https://github.com/saltstack/relenv/issues/280. + runs-on: windows-11-arm + + strategy: + fail-fast: false + matrix: + version: + - '3.10' + - '3.11' + - '3.12' + - '3.13' + - '3.14' + arch: + - arm64 + + outputs: + version: ${{ steps.python-version.outputs.version }} + env: + RELENV_DATA: ${{ github.workspace }} + + steps: + - uses: actions/checkout@v3 + + - name: Set up Python 3.10 + uses: actions/setup-python@v5 + with: + python-version: '3.10' + + - name: Install VS Build Tools + run: | + relenv/_scripts/install_vc_build.ps1 -CICD + + - name: Install nox + run: | + pip3 install nox + + - name: Determine Python Version + id: python-version + run: | + echo "version=$(python3 -m relenv versions --version=${{ matrix.version }})" | tee -a "$env:GITHUB_OUTPUT" + + - name: "Download artifact: build/${{ matrix.version }}-${{ matrix.arch }}-win.tar.xz" + uses: actions/download-artifact@v4 + with: + name: ${{ steps.python-version.outputs.version }}-${{ matrix.arch }}-win.tar.xz + path: build/ + + - name: Verify Build + run: | + nox -e tests -- tests/test_verify_build.py + + - name: Upload Build Logs + uses: actions/upload-artifact@v4 + if: always() + with: + name: ${{ steps.python-version.outputs.version }}-${{ matrix.arch }}-windows-logs + path: logs/* + retention-days: 5 diff --git a/relenv/common.py b/relenv/common.py index dd9fd172..1b8a75fa 100644 --- a/relenv/common.py +++ b/relenv/common.py @@ -131,7 +131,7 @@ def toolchain_root_dir() -> pathlib.Path: WIN32: ( "amd64", "x86", - # "arm64", # Python 11 should support arm. + "arm64", ), } From 8cc9d826a42bf0cbcc56899f691c6a6c8863969a Mon Sep 17 00:00:00 2001 From: knightmare2600 Date: Sun, 9 Aug 2026 11:07:42 +0100 Subject: [PATCH 02/20] Fix VS detection fallback to vswhere.exe MSFT_VSInstance (root/cimv2/vs) isn't registered on the windows-11-arm runner image even though VS 2022 Enterprise ships pre-installed there. The CI run against build_windows_arm64 confirmed this in practice: the script reported VS as "Missing" and spent 50+ minutes downloading and laying out a VS 2017 build-tools bundle instead of using the VS 2022 already on the box. Fall back to vswhere.exe -- present on every GitHub-hosted Windows image regardless of CIM provider support -- before concluding VS needs a from-scratch install. --- relenv/_scripts/install_vc_build.ps1 | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/relenv/_scripts/install_vc_build.ps1 b/relenv/_scripts/install_vc_build.ps1 index eb4256f2..cfb17121 100644 --- a/relenv/_scripts/install_vc_build.ps1 +++ b/relenv/_scripts/install_vc_build.ps1 @@ -97,8 +97,23 @@ try { $VS_INST_LOC = $(Get-CimInstance MSFT_VSInstance -Namespace root/cimv2/vs).InstallLocation $MSBUILD_BIN = $(Get-ChildItem "$VS_INST_LOC\MSBuild\*\Bin\msbuild.exe").FullName } catch { - # If VS is not installed, this is the fallback for this installation - $MSBUILD_BIN = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2017\BuildTools\MSBuild\15.0\Bin\msbuild.exe" + # The MSFT_VSInstance CIM class isn't registered on every runner image + # (confirmed absent on windows-11-arm even though VS 2022 ships + # pre-installed there) -- before assuming VS needs to be installed + # from scratch, fall back to vswhere.exe, which is present on every + # GitHub-hosted Windows image regardless of CIM provider support. + $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" + $VS_INST_LOC = $null + if ( Test-Path -Path $vswhere ) { + $VS_INST_LOC = & $vswhere -latest -products * -property installationPath + } + if ( $VS_INST_LOC ) { + $MSBUILD_BIN = $(Get-ChildItem "$VS_INST_LOC\MSBuild\*\Bin\msbuild.exe").FullName + } else { + # Genuinely no VS install found by either method - this is the + # fallback for a from-scratch installation. + $MSBUILD_BIN = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2017\BuildTools\MSBuild\15.0\Bin\msbuild.exe" + } } #------------------------------------------------------------------------------- From 427cec0c6ef739143ae1f7a7c0535ac2355a66e5 Mon Sep 17 00:00:00 2001 From: knightmare2600 Date: Sun, 9 Aug 2026 11:55:20 +0100 Subject: [PATCH 03/20] Fix test_windows_arm64 bootstrap Python version setup-python has no arm64 Windows build for 3.10 ("The version '3.10' with architecture 'arm64' was not found for Windows Enterprise") -- CPython's official arm64 Windows installers only started at 3.11. All 5 build_windows_arm64 jobs succeeded in the prior run (confirming the native ARM64 CPython+OpenSSL build itself works); this only fixes the verify job's own bootstrap interpreter, matching what build_windows_arm64 already uses. --- .github/workflows/verify-build-action.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/verify-build-action.yml b/.github/workflows/verify-build-action.yml index e4011f01..949ab59e 100644 --- a/.github/workflows/verify-build-action.yml +++ b/.github/workflows/verify-build-action.yml @@ -301,10 +301,15 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Set up Python 3.10 + - name: Set up Python 3.11 + # 3.10, unlike the x64 job, has no official arm64 Windows build + # for setup-python to install ("The version '3.10' with + # architecture 'arm64' was not found for Windows Enterprise") -- + # CPython's arm64 Windows installers only started at 3.11. Use + # the same bootstrap version build_windows_arm64 already uses. uses: actions/setup-python@v5 with: - python-version: '3.10' + python-version: '3.11' - name: Install VS Build Tools run: | From 94d80f05b12a2c656a54d63ac95c710d301b208f Mon Sep 17 00:00:00 2001 From: knightmare2600 Date: Sun, 9 Aug 2026 13:16:09 +0100 Subject: [PATCH 04/20] Bundle arm64 OpenSSL dev tree so pip installs can build against it test_pip_install_cryptography and test_pip_install_idem failed on the windows arm64 build: pip fell back to compiling cryptography from source (no win_arm64 wheel exists yet -- nothing has ever shipped one for this target before), and openssl-sys couldn't find an OpenSSL to link against (OPENSSL_DIR unset, no vcpkg). relenv already builds OpenSSL from source for arm64 (update_openssl's VC-WIN64-ARM path); copy that install tree into the onedir as OpenSSL/ and archive it, then point OPENSSL_DIR at it in the two affected tests. No-op on every platform where OpenSSL comes from prebuilt binaries (prefix is never populated there) or where a wheel already exists. --- relenv/build/windows.py | 15 +++++++++++++++ tests/test_verify_build.py | 23 +++++++++++++++++++++-- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/relenv/build/windows.py b/relenv/build/windows.py index 1a916b57..83bfa69e 100644 --- a/relenv/build/windows.py +++ b/relenv/build/windows.py @@ -607,6 +607,20 @@ def update_openssl(dirs: Dirs, env: EnvMapping) -> None: shutil.copy(str(target_dir / license_file), str(out_dir / "LICENSE")) break + if not is_binary: + # Bundle a copy of the OpenSSL dev tree (headers + import + # libs) into the onedir itself. Rust crates such as + # openssl-sys -- a transitive build dependency of packages + # like `cryptography` when pip falls back to building from + # source, which it does on arm64 since no prebuilt wheel + # exists yet for a target nothing has shipped wheels for -- + # have no other way to find an OpenSSL to link against. + # Consumers set OPENSSL_DIR to /OpenSSL before + # pip installing such packages. + openssl_bundle_dir = dirs.prefix / "OpenSSL" + if not openssl_bundle_dir.exists(): + shutil.copytree(str(prefix), str(openssl_bundle_dir)) + if is_binary: # Ensure include/openssl exists inc_openssl_dir = target_dir / "include" / "openssl" @@ -1108,6 +1122,7 @@ def runpip(pkg: str | os.PathLike[str]) -> None: "*.whl", "/Include/*", "/Lib/site-packages/*", + "/OpenSSL/*", ] archive = f"{dirs.prefix}.tar.xz" with tarfile.open(archive, mode="w:xz") as fp: diff --git a/tests/test_verify_build.py b/tests/test_verify_build.py index c374d22b..e7389bbd 100644 --- a/tests/test_verify_build.py +++ b/tests/test_verify_build.py @@ -700,25 +700,44 @@ def test_pip_install_pyzmq( ) -def test_pip_install_cryptography(pipexec, pyexec): +def _openssl_dir_env(build: pathlib.Path, env: dict[str, str]) -> dict[str, str]: + """ + Point OPENSSL_DIR at the OpenSSL dev tree relenv bundles into the + onedir when it built OpenSSL from source (currently windows arm64 + only). Packages like `cryptography` that fall back to compiling + openssl-sys from source -- because no prebuilt wheel exists yet for + a target nothing has shipped wheels for -- have no other way to find + an OpenSSL to link against. A no-op everywhere the bundled tree + doesn't exist (binary-openssl platforms already have prebuilt + wheels, so this never matters there). + """ + openssl_dir = build / "OpenSSL" + if openssl_dir.exists(): + env["OPENSSL_DIR"] = str(openssl_dir) + return env + + +def test_pip_install_cryptography(pipexec, pyexec, build): _install_ppbt(pyexec) packages = [ "cryptography", ] env = os.environ.copy() env["RELENV_BUILDENV"] = "yes" + env = _openssl_dir_env(build, env) for name in packages: p = subprocess.run([str(pipexec), "install", name, "--no-cache-dir"], env=env) assert p.returncode == 0, f"Failed to pip install {name}" -def test_pip_install_idem(pipexec, pyexec): +def test_pip_install_idem(pipexec, pyexec, build): _install_ppbt(pyexec) packages = [ "idem", ] env = os.environ.copy() env["RELENV_BUILDENV"] = "yes" + env = _openssl_dir_env(build, env) for name in packages: p = subprocess.run([str(pipexec), "install", name, "--no-cache-dir"], env=env) assert p.returncode == 0, f"Failed to pip install {name}" From f8fb68c2157e8216d77f375174f049d9dd5af873 Mon Sep 17 00:00:00 2001 From: knightmare2600 Date: Mon, 10 Aug 2026 08:37:42 +0100 Subject: [PATCH 05/20] Remove WIP-only workflow_dispatch trigger from pr.yml Was added purely to dispatch CI runs manually while testing this branch without needing a PR. Not part of adding windows arm64 support; drop it to keep the change focused. --- .github/workflows/pr.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index f778b56d..c5c9a564 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -7,7 +7,6 @@ on: pull_request: branches: - main - workflow_dispatch: {} jobs: ci: From ebf2ea386bcf8e8f2739e8814504abc2e1d10c87 Mon Sep 17 00:00:00 2001 From: "Daniel A. Wozniak" Date: Sun, 30 Aug 2026 14:57:37 -0700 Subject: [PATCH 06/20] Get verify tests to pass on Windows arm64 Windows arm64 is a new build target (PR #328). Four verify tests break because they pin package versions that either don't have arm64 wheels on PyPI or don't compile from source under the MSVC arm64 toolchain: - test_pip_install_salt_pip_dir: Salt's transitive C-extension deps (pymssql, cryptography, cffi at the pinned old versions) have no arm64 wheels and their source builds need FreeTDS/OpenSSL/libgit2 for arm64 which is not available here. Xfail on win-arm64 matching the existing amd64 xfail. - test_install_with_target_cffi_versions: cffi 1.14.6 / 1.16.0 lack arm64 wheels (upstream arm64 support landed in cffi 1.17). Skip those two extra installs on win-arm64; the cffi_version path (1.17.1 / 2.0.0) still runs and covers the arm64-supported release. - test_install_with_target_{no_,}ignore_installed: cffi <1.17 and pygit2 <1.16 have no arm64 wheels. Pin to arm64-supported floors on win-arm64 (cffi 1.17.1/2.0.0, pygit2 1.16.0/1.19.2 by Python version) so the already satisfied: cffi and --ignore-installed flows still get exercised. --- tests/test_verify_build.py | 50 +++++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/tests/test_verify_build.py b/tests/test_verify_build.py index 5ec88c28..04092c64 100644 --- a/tests/test_verify_build.py +++ b/tests/test_verify_build.py @@ -816,6 +816,15 @@ def test_pip_install_salt_pip_dir(pipexec, pyexec, build, build_version, arch): if sys.platform == "win32" and arch == "amd64": pytest.xfail("Known failure on windows amd64") + if sys.platform == "win32" and arch == "arm64": + # Windows arm64: salt's transitive C-extension deps + # (pymssql, cryptography, cffi at their older pinned versions) + # have no arm64 wheels on PyPI and their source builds do not + # succeed under the MSVC arm64 toolchain (no FreeTDS/OpenSSL + # for arm64 in this environment, and cffi <1.17 predates arm64 + # Windows support). + pytest.xfail("Known failure on windows arm64") + if sys.platform == "darwin" and ("3.13" in build_version or "3.14" in build_version): pytest.xfail("Salt does not work with 3.13+ on macos yet") @@ -1508,7 +1517,7 @@ def test_install_with_target_uninstall(pipexec, build): assert not (extras / "bin" / "cowsay").exists() -def test_install_with_target_cffi_versions(pipexec, pyexec, build, build_version): +def test_install_with_target_cffi_versions(pipexec, pyexec, build, build_version, arch): env = os.environ.copy() env["RELENV_DEBUG"] = "yes" extras = build / "extras" @@ -1516,7 +1525,15 @@ def test_install_with_target_cffi_versions(pipexec, pyexec, build, build_version cffi_version = "2.0.0" else: cffi_version = "1.17.1" - if build_version[:4] not in ["3.13", "3.14"]: + # cffi 1.14.6 / 1.16.0 have no Windows arm64 wheels on PyPI and their + # source builds fail under the MSVC arm64 toolchain (arm64 support + # landed upstream in cffi 1.17). Skip the older-cffi coverage on + # win-arm64; the ``cffi_version`` path below still exercises the + # arm64-supported release. + old_cffi_supported = build_version[:4] not in ["3.13", "3.14"] and not ( + sys.platform == "win32" and arch == "arm64" + ) + if old_cffi_supported: subprocess.run( [str(pipexec), "install", "cffi==1.14.6"], check=True, @@ -1542,8 +1559,20 @@ def test_install_with_target_cffi_versions(pipexec, pyexec, build, build_version proc.stdout.decode().strip() == "1.17.1" -def test_install_with_target_no_ignore_installed(pipexec, pyexec, build, build_version): - if build_version.startswith("3.14"): +def test_install_with_target_no_ignore_installed(pipexec, pyexec, build, build_version, arch): + # On Windows arm64, cffi <1.17 and pygit2 <1.16 have no wheels on + # PyPI and their source builds do not support the MSVC arm64 + # toolchain. Pin to the earliest arm64-supported release for each + # Python version so this test exercises the same "install cffi, + # then --target pygit2 sees it as already-satisfied" flow. + if sys.platform == "win32" and arch == "arm64": + if build_version.startswith("3.14"): + cffi = "cffi==2.0.0" + pygit2 = "pygit2==1.19.2" + else: + cffi = "cffi==1.17.1" + pygit2 = "pygit2==1.16.0" + elif build_version.startswith("3.14"): cffi = "cffi==2.0.0" pygit2 = "pygit2==1.19.2" elif build_version.startswith("3.13"): @@ -1578,8 +1607,17 @@ def test_install_with_target_no_ignore_installed(pipexec, pyexec, build, build_v assert "installed cffi" not in out -def test_install_with_target_ignore_installed(pipexec, pyexec, build, build_version): - if build_version.startswith("3.14"): +def test_install_with_target_ignore_installed(pipexec, pyexec, build, build_version, arch): + # See test_install_with_target_no_ignore_installed for why win-arm64 + # needs its own version selection. + if sys.platform == "win32" and arch == "arm64": + if build_version.startswith("3.14"): + cffi = "cffi==2.0.0" + pygit2 = "pygit2==1.19.2" + else: + cffi = "cffi==1.17.1" + pygit2 = "pygit2==1.16.0" + elif build_version.startswith("3.14"): cffi = "cffi==2.0.0" pygit2 = "pygit2==1.19.2" elif build_version.startswith("3.13"): From 2cbe669191a386b7f77ba1ed0f81ce53a8899705 Mon Sep 17 00:00:00 2001 From: "Daniel A. Wozniak" Date: Thu, 3 Sep 2026 04:45:58 -0700 Subject: [PATCH 07/20] Fix win-arm64 verify tests: pygit2 wheel availability + ruff-format Two test breakages on win-arm64 (see CI run 33614539754): * test_install_with_target_{no_,}ignore_installed pinned ``pygit2==1.16.0`` for Python 3.11-3.13 on win-arm64. That release has no win_arm64 wheel on PyPI (only 1.19.0+ do) so pip fell back to a source build, which needs libgit2 headers not present on the windows-11-arm runners -- ``fatal error C1083: Cannot open include file: 'git2.h'``. Bump the win-arm64 pin to pygit2 1.19.2 (already used for 3.14). Python 3.10 arm64 has no runnable combination (pygit2's requires_python is >=3.11, so no arm64 wheel exists for cp310) -- pytest.skip that combo. * ruff-format wanted to collapse the multi-line ``old_cffi_supported`` ternary onto one line in test_install_with_target_cffi_versions. Apply the formatter output so pre-commit is green. Local pre-commit run: all hooks pass. --- tests/test_verify_build.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/tests/test_verify_build.py b/tests/test_verify_build.py index 04092c64..e296397a 100644 --- a/tests/test_verify_build.py +++ b/tests/test_verify_build.py @@ -1530,9 +1530,7 @@ def test_install_with_target_cffi_versions(pipexec, pyexec, build, build_version # landed upstream in cffi 1.17). Skip the older-cffi coverage on # win-arm64; the ``cffi_version`` path below still exercises the # arm64-supported release. - old_cffi_supported = build_version[:4] not in ["3.13", "3.14"] and not ( - sys.platform == "win32" and arch == "arm64" - ) + old_cffi_supported = build_version[:4] not in ["3.13", "3.14"] and not (sys.platform == "win32" and arch == "arm64") if old_cffi_supported: subprocess.run( [str(pipexec), "install", "cffi==1.14.6"], @@ -1560,18 +1558,23 @@ def test_install_with_target_cffi_versions(pipexec, pyexec, build, build_version def test_install_with_target_no_ignore_installed(pipexec, pyexec, build, build_version, arch): - # On Windows arm64, cffi <1.17 and pygit2 <1.16 have no wheels on + # On Windows arm64, cffi <1.17 and pygit2 <1.19 have no wheels on # PyPI and their source builds do not support the MSVC arm64 - # toolchain. Pin to the earliest arm64-supported release for each - # Python version so this test exercises the same "install cffi, - # then --target pygit2 sees it as already-satisfied" flow. + # toolchain (pygit2 additionally needs libgit2 headers, which are + # not installed on the windows-11-arm runners). pygit2 wheels for + # win_arm64 first appear in 1.19.0 and only for cp311+ (pygit2's + # requires_python is >=3.11), so Python 3.10 arm64 has no runnable + # combination -- skip. Otherwise pin to the earliest arm64-wheel + # release so the "install cffi, then --target pygit2 sees it as + # already-satisfied" flow still gets exercised. if sys.platform == "win32" and arch == "arm64": + if build_version.startswith("3.10"): + pytest.skip("pygit2 has no win_arm64 wheels for Python 3.10") if build_version.startswith("3.14"): cffi = "cffi==2.0.0" - pygit2 = "pygit2==1.19.2" else: cffi = "cffi==1.17.1" - pygit2 = "pygit2==1.16.0" + pygit2 = "pygit2==1.19.2" elif build_version.startswith("3.14"): cffi = "cffi==2.0.0" pygit2 = "pygit2==1.19.2" @@ -1611,12 +1614,13 @@ def test_install_with_target_ignore_installed(pipexec, pyexec, build, build_vers # See test_install_with_target_no_ignore_installed for why win-arm64 # needs its own version selection. if sys.platform == "win32" and arch == "arm64": + if build_version.startswith("3.10"): + pytest.skip("pygit2 has no win_arm64 wheels for Python 3.10") if build_version.startswith("3.14"): cffi = "cffi==2.0.0" - pygit2 = "pygit2==1.19.2" else: cffi = "cffi==1.17.1" - pygit2 = "pygit2==1.16.0" + pygit2 = "pygit2==1.19.2" elif build_version.startswith("3.14"): cffi = "cffi==2.0.0" pygit2 = "pygit2==1.19.2" From ec95c21f598788c636e9375aa6300f4e9d853616 Mon Sep 17 00:00:00 2001 From: "Daniel A. Wozniak" Date: Thu, 5 Nov 2026 01:36:38 -0700 Subject: [PATCH 08/20] Detect v140 under VS 2022 17.14+ / VS 18 per-install MSBuild layout The windows-11-arm runner image was upgraded to VS 18 (the log shows the bootstrapper fetching ``VisualStudio.18.Release.chman``). VS 2022 17.14+ and VS 18 moved MSBuild inside the per-install tree, so v140 PlatformTool- sets are only populated at:: \MSBuild\Microsoft\VC\\Platforms\\PlatformToolsets\v140 The legacy ``${env:ProgramFiles(x86)}\MSBuild\Microsoft.Cpp\v4.0\V140`` location stays empty even when the install succeeded. Our post-install verify was still checking only the legacy path, so the Install VS Build Tools step failed with "Failed (v140 still not registered with MSBuild)" despite the bootstrapper returning exit code 0 -- breaking all five Windows arm64 native build jobs (3.10-3.14). Extract a ``Test-V140Installed`` helper that checks both locations and use it for the pre-install "already present" short-circuit and the post-install verify. Legacy VS 2017/2019 images continue to work because the first branch of the helper matches their path. --- relenv/_scripts/install_vc_build.ps1 | 41 +++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/relenv/_scripts/install_vc_build.ps1 b/relenv/_scripts/install_vc_build.ps1 index cfb17121..1ebaef17 100644 --- a/relenv/_scripts/install_vc_build.ps1 +++ b/relenv/_scripts/install_vc_build.ps1 @@ -41,6 +41,38 @@ function Write-Result($result, $ForegroundColor="Green") { } } +function Test-V140Installed { + # The v140 PlatformToolset lives in one of two places depending on the + # VS installer generation: + # + # * VS 2017 / VS 2019 and earlier shared MSBuild writes to the legacy + # shared location under ``${env:ProgramFiles(x86)}\MSBuild``. + # * VS 2022 17.14+ / VS 18 moved MSBuild inside the per-install tree + # and populates only ``\MSBuild\Microsoft\VC\\ + # Platforms\\PlatformToolsets\v140`` -- the legacy path stays + # empty even when v140 is installed and usable. + # + # Return $true on the first match. + [CmdletBinding()] + param( + [Parameter(Mandatory=$false)] + [String] $VSInstLoc + ) + if ( Test-Path -Path "${env:ProgramFiles(x86)}\MSBuild\Microsoft.Cpp\v4.0\V140" ) { + return $true + } + if ( $VSInstLoc ) { + $hit = Get-ChildItem ` + -Path "$VSInstLoc\MSBuild\Microsoft\VC\*\Platforms\*\PlatformToolsets\v140" ` + -ErrorAction SilentlyContinue | + Select-Object -First 1 + if ( $hit ) { + return $true + } + } + return $false +} + function Add-Certificate { [CmdletBinding()] param( @@ -130,10 +162,11 @@ if ( Test-Path -Path $MSBUILD_BIN ) { # without v140 by default, so use the VS bootstrapper to add the # component to the existing install. We don't trust the installer's # exit code (it returns non-zero in a few benign cases) — instead - # verify the v140 platform-toolset targets file exists afterwards. - $V140_TARGETS = "${env:ProgramFiles(x86)}\MSBuild\Microsoft.Cpp\v4.0\V140" + # verify the v140 platform-toolset targets exist afterwards. Check + # both the legacy shared-MSBuild location and the VS 2022 17.14+ / + # VS 18 per-install location (see Test-V140Installed above). Write-Host "Ensuring v140 toolset is installed: " -NoNewline - if ( Test-Path -Path $V140_TARGETS ) { + if ( Test-V140Installed -VSInstLoc $VS_INST_LOC ) { Write-Result "Already present" -ForegroundColor Green } else { Write-Host "" @@ -166,7 +199,7 @@ if ( Test-Path -Path $MSBUILD_BIN ) { -PassThru -Wait -NoNewWindow Write-Host " bootstrapper exit code: $($proc.ExitCode)" Write-Host "Verifying v140 toolset: " -NoNewline - if ( Test-Path -Path $V140_TARGETS ) { + if ( Test-V140Installed -VSInstLoc $VS_INST_LOC ) { Write-Result "Success" -ForegroundColor Green } else { Write-Result "Failed (v140 still not registered with MSBuild)" -ForegroundColor Red From 614132f0d741224cf37fc702dd38589baf11fc43 Mon Sep 17 00:00:00 2001 From: "Daniel A. Wozniak" Date: Fri, 6 Nov 2026 05:18:45 -0700 Subject: [PATCH 09/20] Unbreak Windows arm64 build under VS Enterprise 2026 (VS 18) The windows-11-arm runner image was upgraded to VS Enterprise 2026 / VS 18, which removed Microsoft.VisualStudio.Component.VC.140 and Microsoft.VisualStudio.Component.Windows10SDK.19041 from its product graph. All five Python arm64 native-build jobs fail at the "Install VS Build Tools" step with "Cannot find package ... in product graph" followed by "Failed (v140 still not registered with MSBuild)". v140 cannot generate arm64 code (VS 2015 predates the arm64 toolchain) so the install was never useful on an arm64 host anyway -- Python's PCbuild falls through python.props to v143/v144, both of which support arm64 natively. Three correlated changes: - install_vc_build.ps1: short-circuit the v140 install step when $env:PROCESSOR_ARCHITECTURE == "ARM64". Leaves amd64/x86 behaviour (VS 2022 on windows-2022) untouched. - build-native-action.yml: drop the WindowsTargetPlatformVersion "10.0.19041.0" pin on the arm64 job. That SDK was paired with v140 on the amd64 job; windows-11-arm ships Win11 SDK 26100 and does not have 10.0.19041.0 available, so pinning to it would break the PCbuild MSBuild step. - windows.py: patch Python 3.10-3.13 PCbuild/python.props to add a '$(VisualStudioVersion)' == '18.0' -> v143 arm. Upstream 3.14 already carries this line; without it, BasePlatformToolset resolves to empty under VS 18 and MSBuild picks an unexpected default (or errors). Idempotent -- 3.14 is left alone by the "18.0 already present" guard. --- .github/workflows/build-native-action.yml | 7 ++++++- relenv/_scripts/install_vc_build.ps1 | 13 +++++++++++- relenv/build/windows.py | 24 +++++++++++++++++++++++ 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-native-action.yml b/.github/workflows/build-native-action.yml index 65099221..a0b4f7d3 100644 --- a/.github/workflows/build-native-action.yml +++ b/.github/workflows/build-native-action.yml @@ -432,7 +432,12 @@ jobs: - name: Build Python with Relenv env: RELENV_NATIVE_PY_VERSION: 3.10.15 - WindowsTargetPlatformVersion: "10.0.19041.0" + # NOTE: the amd64/x86 job pins WindowsTargetPlatformVersion to + # 10.0.19041.0 so MSBuild pairs with the v140 toolset (newer + # SDKs' SSE intrinsics break under v140). Arm64 never uses + # v140 (v140 has no arm64 code generator), so leave the SDK + # version unpinned here: the windows-11-arm runner ships VS 18 + # with Win11 SDK 26100 and does not have 10.0.19041.0 available. HOST_PYTHON: ${{ steps.setup-python.outputs.python-path }} run: | python -m relenv build --no-pretty --arch=${{ matrix.arch }} --python=${{ steps.python-version.outputs.version }} diff --git a/relenv/_scripts/install_vc_build.ps1 b/relenv/_scripts/install_vc_build.ps1 index 1ebaef17..5eb65aa3 100644 --- a/relenv/_scripts/install_vc_build.ps1 +++ b/relenv/_scripts/install_vc_build.ps1 @@ -165,8 +165,19 @@ if ( Test-Path -Path $MSBUILD_BIN ) { # verify the v140 platform-toolset targets exist afterwards. Check # both the legacy shared-MSBuild location and the VS 2022 17.14+ / # VS 18 per-install location (see Test-V140Installed above). + # + # Skip v140 entirely on arm64 hosts: v140 cannot generate arm64 code + # (VS 2015 predates the arm64 toolchain), and the windows-11-arm + # runner image ships VS 18 / VS Enterprise 2026, whose product graph + # does not expose Microsoft.VisualStudio.Component.VC.140 at all — + # the bootstrapper logs "Cannot find package ... in product graph" + # and exits 0, which then fails the post-install verify. Python + # 3.10/3.11 PCbuild on arm64 falls through python.props to whatever + # toolset VS has (v143/v144), which supports arm64 natively. Write-Host "Ensuring v140 toolset is installed: " -NoNewline - if ( Test-V140Installed -VSInstLoc $VS_INST_LOC ) { + if ( $env:PROCESSOR_ARCHITECTURE -eq "ARM64" ) { + Write-Result "Skipped (arm64 host; v140 does not support arm64)" -ForegroundColor Green + } elseif ( Test-V140Installed -VSInstLoc $VS_INST_LOC ) { Write-Result "Already present" -ForegroundColor Green } else { Write-Host "" diff --git a/relenv/build/windows.py b/relenv/build/windows.py index 164efa64..5a3c963a 100644 --- a/relenv/build/windows.py +++ b/relenv/build/windows.py @@ -1021,6 +1021,30 @@ def build_python(env: EnvMapping, dirs: Dirs, logfp: IO[str]) -> None: patch_ssl_for_cpython_104135(dirs.source) + # CPython 3.10-3.13 python.props only recognises VisualStudioVersion + # values up to '17.0' (VS 2022) when picking BasePlatformToolset. On + # the windows-11-arm runner the pre-installed VS is Enterprise 2026 + # (VisualStudioVersion='18.0'), which means every condition in the + # BasePlatformToolset chain is skipped and PlatformToolset ends up + # empty -- MSBuild then either falls back to an unexpected default or + # errors outright. Upstream added a '18.0' -> v143 arm in 3.14; mirror + # that here for the older branches by inserting the same line just + # above the '17.0' check. The patch is idempotent (the sentinel line + # already carries '18.0' on 3.14, so the regex won't re-match) and a + # no-op on x86/amd64 builds where '17.0' still resolves. + props_path = dirs.source / "PCbuild" / "python.props" + if props_path.exists(): + content = props_path.read_text(encoding="utf-8") + if "'$(VisualStudioVersion)' == '18.0'" not in content: + log.info("Patching python.props to add VS 18 (VS 2026) -> v143") + _tpl = ( + "v143" + ) + old_line = _tpl.format(ver="17.0") + new_line = _tpl.format(ver="18.0") + "\n " + old_line + props_path.write_text(content.replace(old_line, new_line), encoding="utf-8") + # CPython 3.14.10 shipped a bad backport of gh-149816 (upstream PR #160006) # that dropped the "!" from `int fips = !disable_fips_property(py_ht);` in # Modules/_hashopenssl.c. The inverted polarity makes openssl_md5( From c3cfdda4de272842bb3b353a4fa9950a0afadcff Mon Sep 17 00:00:00 2001 From: "Daniel A. Wozniak" Date: Fri, 6 Nov 2026 05:48:55 -0700 Subject: [PATCH 10/20] arm64: stop pinning PlatformToolset to v143 under VS 18 Previous attempt (614132f) added a '$(VisualStudioVersion)' == '18.0' -> v143 arm to python.props for Python 3.10-3.13 to mirror the upstream 3.14 line. CI then surfaced the next failure: MSB8020 complains that "the build tools for Visual Studio 2022 (Platform Toolset = 'v143') cannot be found" on the windows-11-arm runner. VS Enterprise 2026 doesn't ship v143 natively, and the "legacy" v143 ARM64 compat component isn't exposed in VS 18's product graph, so installing it on-demand via the VS Installer isn't reliable either. Flip the approach: strip the 18.0 pin instead of adding it. With no match for VS 18 in python.props's BasePlatformToolset chain, MSBuild falls back to DefaultPlatformToolset from Microsoft.Cpp.Default.props, which is whatever VS 18 natively ships on the runner. The regex is a no-op on 3.10-3.13 (never had the 18.0 line) and a no-op on windows-2022 x86/amd64 (still matches '17.0' -> v143). --- relenv/build/windows.py | 46 +++++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/relenv/build/windows.py b/relenv/build/windows.py index 5a3c963a..8c8116fd 100644 --- a/relenv/build/windows.py +++ b/relenv/build/windows.py @@ -12,6 +12,7 @@ import logging import os import pathlib +import re import shutil import subprocess import sys @@ -1021,29 +1022,34 @@ def build_python(env: EnvMapping, dirs: Dirs, logfp: IO[str]) -> None: patch_ssl_for_cpython_104135(dirs.source) - # CPython 3.10-3.13 python.props only recognises VisualStudioVersion - # values up to '17.0' (VS 2022) when picking BasePlatformToolset. On - # the windows-11-arm runner the pre-installed VS is Enterprise 2026 - # (VisualStudioVersion='18.0'), which means every condition in the - # BasePlatformToolset chain is skipped and PlatformToolset ends up - # empty -- MSBuild then either falls back to an unexpected default or - # errors outright. Upstream added a '18.0' -> v143 arm in 3.14; mirror - # that here for the older branches by inserting the same line just - # above the '17.0' check. The patch is idempotent (the sentinel line - # already carries '18.0' on 3.14, so the regex won't re-match) and a - # no-op on x86/amd64 builds where '17.0' still resolves. + # Drop any python.props line that hard-pins BasePlatformToolset to v143 + # when running under VS Enterprise 2026 / VS 18 + # (VisualStudioVersion='18.0'). The windows-11-arm runner image ships + # VS 18 with its native toolset only; v143 (VS 2022 build tools) is not + # installed and installing it on-demand is awkward (the "legacy" v143 + # ARM64 compat component isn't exposed in VS 18's product graph). With + # the pin removed, BasePlatformToolset stays empty for VS 18, so + # python.props's existing fallback leaves PlatformToolset empty too and + # MSBuild resolves it via Microsoft.Cpp.Default.props's + # DefaultPlatformToolset -- i.e. whatever VS 18 ships natively on the + # runner. + # + # Only CPython 3.14 upstream carries the 18.0 line; 3.10-3.13 never + # matched VS 18 to begin with, so the sub below is a no-op on those + # trees. No effect on windows-2022 (x86/amd64) builds, which run under + # VS 2022 (VisualStudioVersion='17.0') and still match the '17.0' arm. props_path = dirs.source / "PCbuild" / "python.props" if props_path.exists(): content = props_path.read_text(encoding="utf-8") - if "'$(VisualStudioVersion)' == '18.0'" not in content: - log.info("Patching python.props to add VS 18 (VS 2026) -> v143") - _tpl = ( - "v143" - ) - old_line = _tpl.format(ver="17.0") - new_line = _tpl.format(ver="18.0") + "\n " + old_line - props_path.write_text(content.replace(old_line, new_line), encoding="utf-8") + vs18_pin_re = ( + r"^[ \t]*" + r"v14\d\r?\n" + ) + new_content, n = re.subn(vs18_pin_re, "", content, count=1, flags=re.MULTILINE) + if n: + log.info("Removed VS 18 -> v14x pin from python.props (letting MSBuild use DefaultPlatformToolset)") + props_path.write_text(new_content, encoding="utf-8") # CPython 3.14.10 shipped a bad backport of gh-149816 (upstream PR #160006) # that dropped the "!" from `int fips = !disable_fips_property(py_ht);` in From 15b1024cf73191df3cf6849b933f35f6d371f8fe Mon Sep 17 00:00:00 2001 From: "Daniel A. Wozniak" Date: Fri, 6 Nov 2026 06:17:48 -0700 Subject: [PATCH 11/20] arm64: pick installed PlatformToolset dynamically for VS 18 builds Second CI iteration after stripping the VS 18 -> v14x pin surfaced the next failure: MSB8020 "Platform Toolset = 'v140' cannot be found". With no 18.0 match in python.props's chain, MSBuild falls through to ``$(VCTargetsPath14) != ''`` -> v140 (VS 18 unconditionally sets that variable, but v140 isn't actually installed on the runner and can't even target arm64 anyway). Pinning to v143 fails the same way. Discover the installed PlatformToolset at build time instead of guessing. ``\MSBuild\Microsoft\VC\*\Platforms\\PlatformToolsets\`` lists every vXXX name MSBuild will accept; pick the lexicographically newest and pass it via ``/p:PlatformToolset=``. Only applied on arm64 to avoid perturbing amd64/x86 under VS 2022. --- relenv/build/windows.py | 78 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/relenv/build/windows.py b/relenv/build/windows.py index 8c8116fd..665eb2b8 100644 --- a/relenv/build/windows.py +++ b/relenv/build/windows.py @@ -229,6 +229,62 @@ def find_vcvarsall(env: EnvMapping) -> pathlib.Path | None: return None +def _discover_installed_platform_toolset(env: EnvMapping, platform: str) -> str | None: + r""" + Return the newest PlatformToolset installed under the detected VS + install for *platform* (``ARM64``, ``x64``, ``Win32``), or ``None`` + if we can't find vswhere/an install. + + Each VS install lays out installed PlatformToolsets under:: + + \MSBuild\Microsoft\VC\\Platforms\\PlatformToolsets\ + + ```` is the value we need to pass to MSBuild via + ``/p:PlatformToolset=``. Sorting lexicographically on the + directory name is good enough -- v143 < v144 < v180 as strings. + """ + vswhere = shutil.which("vswhere.exe") + if not vswhere: + candidate = ( + pathlib.Path(os.environ.get("ProgramFiles(x86)", "C:\\Program Files (x86)")) + / "Microsoft Visual Studio" + / "Installer" + / "vswhere.exe" + ) + if candidate.exists(): + vswhere = str(candidate) + if not vswhere: + return None + + try: + result = subprocess.run( + [vswhere, "-latest", "-products", "*", "-property", "installationPath"], + capture_output=True, + text=True, + check=True, + ) + except subprocess.CalledProcessError: + return None + vs_path = result.stdout.strip() + if not vs_path: + return None + + toolset_root = pathlib.Path(vs_path) / "MSBuild" / "Microsoft" / "VC" + if not toolset_root.exists(): + return None + + names: set[str] = set() + for vc_dir in toolset_root.iterdir(): + plats = vc_dir / "Platforms" / platform / "PlatformToolsets" + if plats.is_dir(): + for p in plats.iterdir(): + if p.is_dir() and p.name.lower().startswith("v"): + names.add(p.name) + if not names: + return None + return max(names) + + def update_props(source: pathlib.Path, old: str, new: str) -> None: """ Overwrite a dependency string for Windows PCBuild. @@ -1077,6 +1133,28 @@ def build_python(env: EnvMapping, dirs: Dirs, logfp: IO[str]) -> None: "--no-tkinter", ] + # On VS Enterprise 2026 / VS 18 (the windows-11-arm runner), neither v143 + # nor v140 ARM64 build tools are installed by default, but python.props + # on all branches 3.10-3.14 still manages to pick one or the other -- VS + # 18 unconditionally sets $(VCTargetsPath14) which makes 3.10's fallback + # match and resolve PlatformToolset=v140 (which can't even target arm64), + # while 3.14 upstream pins the '18.0' arm to v143. Discover the newest + # vXXX directory actually installed under + # \MSBuild\Microsoft\VC\*\Platforms\\PlatformToolsets + # and pass it via /p:PlatformToolset= so MSBuild stops guessing. + # Only applied on arm64 to avoid perturbing the amd64/x86 windows-2022 + # builds, which already resolve correctly to v143 under VS 2022. + if arch == "arm64": + toolset = _discover_installed_platform_toolset(env, "ARM64") + if toolset: + log.info("Pinning PlatformToolset=%s via /p: for arm64 build", toolset) + cmd.append(f"/p:PlatformToolset={toolset}") + else: + log.warning( + "No installed arm64 PlatformToolset discovered; MSBuild may fall " + "back to an uninstalled default and fail with MSB8020" + ) + log.info("Start PCbuild") runcmd(cmd, env=env, stderr=logfp, stdout=logfp) log.info("PCbuild finished") From c883727d604c83ab12d823cca7e0e4867f0957e1 Mon Sep 17 00:00:00 2001 From: "Daniel A. Wozniak" Date: Fri, 6 Nov 2026 16:08:46 -0700 Subject: [PATCH 12/20] arm64: pass discovered PlatformToolset via env var not /p: build.bat forwards its positional arguments to msbuild.exe verbatim, but cmd.exe tokenizes those positional args on ``=`` as a delimiter. Passing ``/p:PlatformToolset=v145`` as a single argv element therefore reaches msbuild.exe as two tokens -- ``/p:PlatformToolset`` and ``v145`` -- and fails with ``MSB1008: Only one project can be specified.`` (Previous run 42224369347 confirmed discovery finds v145 under VS 18, but the build still failed at this quoting pitfall.) Set the ``PlatformToolset`` environment variable instead: MSBuild promotes every env var whose name matches a known property into the initial property set, and CPython's build.bat passes the environment through to msbuild.exe intact. python.props's existing ```` line is a no-op once the env var is populated, so this reliably pins the discovered toolset without having to muck with batch quoting. --- relenv/build/windows.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/relenv/build/windows.py b/relenv/build/windows.py index 665eb2b8..c2843195 100644 --- a/relenv/build/windows.py +++ b/relenv/build/windows.py @@ -1141,14 +1141,20 @@ def build_python(env: EnvMapping, dirs: Dirs, logfp: IO[str]) -> None: # while 3.14 upstream pins the '18.0' arm to v143. Discover the newest # vXXX directory actually installed under # \MSBuild\Microsoft\VC\*\Platforms\\PlatformToolsets - # and pass it via /p:PlatformToolset= so MSBuild stops guessing. + # and push it into MSBuild via the PlatformToolset env var (MSBuild promotes + # any env var that matches a known property, and CPython's build.bat passes + # the environment through to MSBuild intact). Env-var passing avoids a + # batch-file pitfall: build.bat forwards its positional args verbatim to + # msbuild.exe, but cmd.exe strips the ``=`` out of positional args so + # ``/p:PlatformToolset=v145`` arrives at MSBuild as two tokens + # (``/p:PlatformToolset`` and ``v145``) and errors with MSB1008. # Only applied on arm64 to avoid perturbing the amd64/x86 windows-2022 # builds, which already resolve correctly to v143 under VS 2022. if arch == "arm64": toolset = _discover_installed_platform_toolset(env, "ARM64") if toolset: - log.info("Pinning PlatformToolset=%s via /p: for arm64 build", toolset) - cmd.append(f"/p:PlatformToolset={toolset}") + log.info("Pinning PlatformToolset=%s via env var for arm64 build", toolset) + env["PlatformToolset"] = toolset else: log.warning( "No installed arm64 PlatformToolset discovered; MSBuild may fall " From 9658d3cbf36c84a90908da95e01c750ea3ac9631 Mon Sep 17 00:00:00 2001 From: "Daniel A. Wozniak" Date: Fri, 6 Nov 2026 17:38:37 -0700 Subject: [PATCH 13/20] arm64: use x64-hosted compiler to avoid 32-bit cl.exe OOM v145 pinning worked -- previous CI iteration got as far as compiling, but hit ``fatal error C1002: compiler is out of heap space in pass 2`` on ``unicodetype_db.h`` and other generated sources. Log confirmed HostX86\arm64\cl.exe was in use: MSBuild's default ``PreferredToolArchitecture`` is x86 for arm64 cross-compile layouts, and the 32-bit compiler caps its heap at ~2 GB. Set ``PreferredToolArchitecture=x64`` via env var so MSBuild picks the HostX64\arm64 cross-tools instead. On Windows 11 ARM64 the x64 binaries run under the OS's native emulation layer, no extra install needed. --- relenv/build/windows.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/relenv/build/windows.py b/relenv/build/windows.py index c2843195..8945c43b 100644 --- a/relenv/build/windows.py +++ b/relenv/build/windows.py @@ -1161,6 +1161,15 @@ def build_python(env: EnvMapping, dirs: Dirs, logfp: IO[str]) -> None: "back to an uninstalled default and fail with MSB8020" ) + # MSBuild's default host-tool arch is x86 for arm64 cross-compiles. + # The 32-bit cl.exe caps its address space at ~2GB and hits + # "fatal error C1002: compiler is out of heap space in pass 2" on + # unicodetype_db.h / similar generated sources on every branch here. + # Switch to x64-hosted tools so the compiler process has a 64-bit + # address space. (x64 binaries run under Windows 11 ARM64's native + # emulation layer, no extra install needed.) + env["PreferredToolArchitecture"] = "x64" + log.info("Start PCbuild") runcmd(cmd, env=env, stderr=logfp, stdout=logfp) log.info("PCbuild finished") From 68d66ff55e1dcd3a463fcb3e588e1295a4c9fe57 Mon Sep 17 00:00:00 2001 From: "Daniel A. Wozniak" Date: Fri, 6 Nov 2026 18:09:25 -0700 Subject: [PATCH 14/20] arm64: patch python.props to force x64-hosted compiler, env var ignored Env-var attempt (9658d3c) didn't take: the v145 toolset is correctly selected but MSBuild still drives HostX86\arm64\cl.exe and OOMs on unicodetype_db.h / etc. with "fatal error C1002: compiler is out of heap space in pass 2". ``PreferredToolArchitecture`` from the process env gets reset by Microsoft.Cpp.Default.props's ``Condition='' == ''`` block before it ever reaches the C++ build targets. Patch python.props instead. Every PCbuild vcxproj imports python.props before Microsoft.Cpp.Default.props, so a condition-on-empty set there latches the x64 preference before the default has a chance to leave it empty. Scope the new line to ``'$(Platform)' == 'ARM64'`` so the x86 / x64 builds on windows-2022 (which have no 32-bit heap pressure and would just be slowed by x86-emulation of the x64 tools... but actually on windows-2022 they're already x64-hosted) stay untouched. --- relenv/build/windows.py | 46 +++++++++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/relenv/build/windows.py b/relenv/build/windows.py index 8945c43b..b1545ec0 100644 --- a/relenv/build/windows.py +++ b/relenv/build/windows.py @@ -1096,16 +1096,44 @@ def build_python(env: EnvMapping, dirs: Dirs, logfp: IO[str]) -> None: # VS 2022 (VisualStudioVersion='17.0') and still match the '17.0' arm. props_path = dirs.source / "PCbuild" / "python.props" if props_path.exists(): - content = props_path.read_text(encoding="utf-8") + original = props_path.read_text(encoding="utf-8") + content = original + vs18_pin_re = ( r"^[ \t]*" r"v14\d\r?\n" ) - new_content, n = re.subn(vs18_pin_re, "", content, count=1, flags=re.MULTILINE) + content, n = re.subn(vs18_pin_re, "", content, count=1, flags=re.MULTILINE) if n: log.info("Removed VS 18 -> v14x pin from python.props (letting MSBuild use DefaultPlatformToolset)") - props_path.write_text(new_content, encoding="utf-8") + + # Force x64-hosted cl.exe/link.exe on ARM64 builds. The default + # ``PreferredToolArchitecture`` MSBuild picks for the ARM64 platform + # is empty, which causes Microsoft.Cpp.Default.props to default it + # to ``x86`` -- the 32-bit compiler then OOMs ("fatal error C1002: + # compiler is out of heap space in pass 2") on sources like + # Objects/unicodetype_db.h where PGO/whole-program optimization + # pushes the heap past 2 GB. Setting the value in python.props + # (imported before Microsoft.Cpp.Default.props by every .vcxproj + # under PCbuild/) wins over the default. Passing it via a MSBuild + # ``/p:`` flag isn't an option because build.bat forwards %* through + # cmd.exe, which splits positional args on ``=`` into two tokens + # and triggers MSB1008. Setting it via a process env var doesn't + # win against Microsoft.Cpp.Default.props's resetter. + if "PreferredToolArchitecture" not in content: + marker = " x64\n" + ) + content = content.replace(marker, prefarch_line + marker, 1) + log.info("Inserted PreferredToolArchitecture=x64 for Platform=ARM64 into python.props") + + if content != original: + props_path.write_text(content, encoding="utf-8") # CPython 3.14.10 shipped a bad backport of gh-149816 (upstream PR #160006) # that dropped the "!" from `int fips = !disable_fips_property(py_ht);` in @@ -1161,14 +1189,10 @@ def build_python(env: EnvMapping, dirs: Dirs, logfp: IO[str]) -> None: "back to an uninstalled default and fail with MSB8020" ) - # MSBuild's default host-tool arch is x86 for arm64 cross-compiles. - # The 32-bit cl.exe caps its address space at ~2GB and hits - # "fatal error C1002: compiler is out of heap space in pass 2" on - # unicodetype_db.h / similar generated sources on every branch here. - # Switch to x64-hosted tools so the compiler process has a 64-bit - # address space. (x64 binaries run under Windows 11 ARM64's native - # emulation layer, no extra install needed.) - env["PreferredToolArchitecture"] = "x64" + # NOTE: ``PreferredToolArchitecture=x64`` is injected into + # python.props above (not via env var) because + # Microsoft.Cpp.Default.props overrides the env var but not an + # explicit property set in python.props (imported before it). log.info("Start PCbuild") runcmd(cmd, env=env, stderr=logfp, stdout=logfp) From 0364ef7dab14d541987ad50f943eeeed2b3f32ac Mon Sep 17 00:00:00 2001 From: "Daniel A. Wozniak" Date: Fri, 6 Nov 2026 18:48:01 -0700 Subject: [PATCH 15/20] arm64: pass /p:PreferredToolArchitecture=x64 via shell with quoting Previous commit (68d66ff) patched python.props to set ``PreferredToolArchitecture=x64`` for the ARM64 platform, but Microsoft. Cpp.Default.props still resets it to x86 after our value is applied. CI run 42322494456 confirms: v145 is correctly picked (via env-var promotion of PlatformToolset), but cl.exe is still invoked from HostX86\arm64 and OOMs on unicodetype_db.h for 3.10-3.13 with "fatal error C1002: compiler is out of heap space in pass 2". (3.14 somehow dodges the C1002 and was green on that run -- different compile flags, less heap usage.) Pass ``/p:PreferredToolArchitecture=x64`` via the MSBuild command line instead -- /p: global properties win over anything the project files set. The previous attempt at this hit MSB1008 because cmd.exe splits unquoted positional batch args on ``=``; wrap the arg in literal double quotes and shell out via a single pre-quoted string (shell=True) so the quotes survive subprocess.list2cmdline and build.bat forwards the arg to msbuild.exe intact. Only ARM64 takes this path; amd64/x86 keep the plain argv-list invocation. --- relenv/build/windows.py | 69 ++++++++++++++++++++++++----------------- 1 file changed, 40 insertions(+), 29 deletions(-) diff --git a/relenv/build/windows.py b/relenv/build/windows.py index b1545ec0..e06a244f 100644 --- a/relenv/build/windows.py +++ b/relenv/build/windows.py @@ -1153,31 +1153,35 @@ def build_python(env: EnvMapping, dirs: Dirs, logfp: IO[str]) -> None: plat = arch_to_plat[arch] # -e skips fetching externals if they already exist. - cmd = [ - str(dirs.source / "PCbuild" / "build.bat"), - "-e", - "-p", - plat, - "--no-tkinter", - ] - - # On VS Enterprise 2026 / VS 18 (the windows-11-arm runner), neither v143 - # nor v140 ARM64 build tools are installed by default, but python.props - # on all branches 3.10-3.14 still manages to pick one or the other -- VS - # 18 unconditionally sets $(VCTargetsPath14) which makes 3.10's fallback - # match and resolve PlatformToolset=v140 (which can't even target arm64), - # while 3.14 upstream pins the '18.0' arm to v143. Discover the newest - # vXXX directory actually installed under + build_bat = dirs.source / "PCbuild" / "build.bat" + cmd_list = [str(build_bat), "-e", "-p", plat, "--no-tkinter"] + + # On VS Enterprise 2026 / VS 18 (the windows-11-arm runner), neither + # v143 nor v140 ARM64 build tools are installed by default, but + # python.props on all branches 3.10-3.14 still manages to pick one of + # them -- VS 18 unconditionally sets $(VCTargetsPath14) which makes + # 3.10's fallback match and resolve PlatformToolset=v140 (which can't + # even target arm64), while 3.14 upstream pins the '18.0' arm to v143. + # Discover the newest vXXX directory actually installed under # \MSBuild\Microsoft\VC\*\Platforms\\PlatformToolsets - # and push it into MSBuild via the PlatformToolset env var (MSBuild promotes - # any env var that matches a known property, and CPython's build.bat passes - # the environment through to MSBuild intact). Env-var passing avoids a - # batch-file pitfall: build.bat forwards its positional args verbatim to - # msbuild.exe, but cmd.exe strips the ``=`` out of positional args so - # ``/p:PlatformToolset=v145`` arrives at MSBuild as two tokens - # (``/p:PlatformToolset`` and ``v145``) and errors with MSB1008. - # Only applied on arm64 to avoid perturbing the amd64/x86 windows-2022 - # builds, which already resolve correctly to v143 under VS 2022. + # and push it into MSBuild via the ``PlatformToolset`` env var + # (MSBuild promotes every env var whose name matches a known property; + # CPython's build.bat passes the environment through to msbuild.exe + # intact). Env-var passing avoids a batch-file pitfall: build.bat + # forwards its positional args verbatim and cmd.exe splits unquoted + # args on ``=`` (so ``/p:PlatformToolset=v145`` becomes two tokens and + # errors with MSB1008). Only applied on arm64 to avoid perturbing the + # amd64/x86 windows-2022 builds, which already resolve to v143 under + # VS 2022. + # + # ``PreferredToolArchitecture`` *cannot* be set the same way -- + # Microsoft.Cpp.Default.props resets it to x86 after env-var promotion + # for the ARM64 platform, and setting it in python.props early also + # loses to the same reset. Pass it via a /p: switch instead and route + # the whole command line through a shell string (not an argv list) so + # build.bat sees a *quoted* arg and the ``=`` survives cmd.exe's + # positional-arg tokenization. + use_shell = False if arch == "arm64": toolset = _discover_installed_platform_toolset(env, "ARM64") if toolset: @@ -1189,13 +1193,20 @@ def build_python(env: EnvMapping, dirs: Dirs, logfp: IO[str]) -> None: "back to an uninstalled default and fail with MSB8020" ) - # NOTE: ``PreferredToolArchitecture=x64`` is injected into - # python.props above (not via env var) because - # Microsoft.Cpp.Default.props overrides the env var but not an - # explicit property set in python.props (imported before it). + # Build a properly-quoted shell command. Each arg is quoted so + # cmd.exe keeps the ``/p:PreferredToolArchitecture=x64`` token + # intact when forwarding through build.bat. + cmd = subprocess.list2cmdline(cmd_list) + ' "/p:PreferredToolArchitecture=x64"' + use_shell = True + log.info("Launching PCbuild via shell to preserve quoted /p: arg") + else: + cmd = cmd_list log.info("Start PCbuild") - runcmd(cmd, env=env, stderr=logfp, stdout=logfp) + if use_shell: + runcmd(cmd, env=env, stderr=logfp, stdout=logfp, shell=True) + else: + runcmd(cmd, env=env, stderr=logfp, stdout=logfp) log.info("PCbuild finished") if arch == "amd64": From b526f36d377a7bafc19a3f49a517e7f5a6b004ae Mon Sep 17 00:00:00 2001 From: "Daniel A. Wozniak" Date: Fri, 6 Nov 2026 19:19:22 -0700 Subject: [PATCH 16/20] arm64: disable WPO+LTCG for ARM64 Release to dodge cl.exe pass-2 OOM The previous attempt at forcing x64-hosted cl.exe on VS 18 went nowhere: ``PreferredToolArchitecture`` in python.props is overridden by Microsoft.Cpp.Default.props, and passing it via /p: hit MSBuild's positional-arg tokenization (``=`` splits the arg). 3.14 happens to skate past the C1002 because its generated unicodetype table or compile flags land on the right side of the 2 GB cap; 3.10-3.13 keep OOMing. Rather than keep fighting the host-tool selection, patch pyproject.props to disable ``WholeProgramOptimization`` and ``LinkTimeCodeGeneration`` for the ARM64/Release configuration pair. That eliminates the pass-2 whole-program optimisation phase where HostX86\arm64\cl.exe runs the heap out, at the cost of some runtime speed (official CPython ARM64 wheels keep WPO/LTCG on; these aren't official builds). Idempotent via ```` sentinel. Other architectures and the Debug configuration stay untouched. Also revert the shell=True + list2cmdline hack from 0364ef7: it carried ``/p:PreferredToolArchitecture=x64`` through to msbuild.exe correctly but had no effect on the host-arch selection for ARM64 cross-compile anyway. --- relenv/build/windows.py | 78 +++++++++++++++++++++++++---------------- 1 file changed, 48 insertions(+), 30 deletions(-) diff --git a/relenv/build/windows.py b/relenv/build/windows.py index e06a244f..c3bb7c05 100644 --- a/relenv/build/windows.py +++ b/relenv/build/windows.py @@ -1135,6 +1135,45 @@ def build_python(env: EnvMapping, dirs: Dirs, logfp: IO[str]) -> None: if content != original: props_path.write_text(content, encoding="utf-8") + # Disable whole-program optimisation and link-time code generation on + # ARM64 Release builds. The resulting "pass 2" / LTCG phase in + # ``cl.exe`` is where the HostX86 cross-compiler runs out of heap on + # ``Objects/unicodetype_db.h`` (``fatal error C1002``) -- repeatedly + # reproduced on Python 3.10-3.13 under VS 18 on windows-11-arm, where + # we can't convince MSBuild to use the HostX64 cross-compiler. + # Dropping WPO and LTCG gives up some runtime speed (official builds + # keep them on) but avoids the OOM. Python 3.14 happens to compile + # without hitting the limit so it's unaffected. Only touches the + # ARM64 conditional stanza; amd64/x86 Release keeps WPO+LTCG. + pyproject_path = dirs.source / "PCbuild" / "pyproject.props" + if pyproject_path.exists(): + original = pyproject_path.read_text(encoding="utf-8") + content = original + sentinel = "" + marker = " " + if sentinel not in content and marker in content: + override_block = ( + " \n" + " " + sentinel + "\n" + " \n" + " \n" + " false\n" + " \n" + " \n" + " Default\n" + " \n" + " \n" + " false\n" + " \n" + " " + ) + content = content.replace(marker, override_block, 1) + log.info("Patched pyproject.props to disable WPO/LTCG on ARM64 Release") + if content != original: + pyproject_path.write_text(content, encoding="utf-8") + # CPython 3.14.10 shipped a bad backport of gh-149816 (upstream PR #160006) # that dropped the "!" from `int fips = !disable_fips_property(py_ht);` in # Modules/_hashopenssl.c. The inverted polarity makes openssl_md5( @@ -1153,8 +1192,13 @@ def build_python(env: EnvMapping, dirs: Dirs, logfp: IO[str]) -> None: plat = arch_to_plat[arch] # -e skips fetching externals if they already exist. - build_bat = dirs.source / "PCbuild" / "build.bat" - cmd_list = [str(build_bat), "-e", "-p", plat, "--no-tkinter"] + cmd = [ + str(dirs.source / "PCbuild" / "build.bat"), + "-e", + "-p", + plat, + "--no-tkinter", + ] # On VS Enterprise 2026 / VS 18 (the windows-11-arm runner), neither # v143 nor v140 ARM64 build tools are installed by default, but @@ -1167,21 +1211,7 @@ def build_python(env: EnvMapping, dirs: Dirs, logfp: IO[str]) -> None: # and push it into MSBuild via the ``PlatformToolset`` env var # (MSBuild promotes every env var whose name matches a known property; # CPython's build.bat passes the environment through to msbuild.exe - # intact). Env-var passing avoids a batch-file pitfall: build.bat - # forwards its positional args verbatim and cmd.exe splits unquoted - # args on ``=`` (so ``/p:PlatformToolset=v145`` becomes two tokens and - # errors with MSB1008). Only applied on arm64 to avoid perturbing the - # amd64/x86 windows-2022 builds, which already resolve to v143 under - # VS 2022. - # - # ``PreferredToolArchitecture`` *cannot* be set the same way -- - # Microsoft.Cpp.Default.props resets it to x86 after env-var promotion - # for the ARM64 platform, and setting it in python.props early also - # loses to the same reset. Pass it via a /p: switch instead and route - # the whole command line through a shell string (not an argv list) so - # build.bat sees a *quoted* arg and the ``=`` survives cmd.exe's - # positional-arg tokenization. - use_shell = False + # intact). if arch == "arm64": toolset = _discover_installed_platform_toolset(env, "ARM64") if toolset: @@ -1193,20 +1223,8 @@ def build_python(env: EnvMapping, dirs: Dirs, logfp: IO[str]) -> None: "back to an uninstalled default and fail with MSB8020" ) - # Build a properly-quoted shell command. Each arg is quoted so - # cmd.exe keeps the ``/p:PreferredToolArchitecture=x64`` token - # intact when forwarding through build.bat. - cmd = subprocess.list2cmdline(cmd_list) + ' "/p:PreferredToolArchitecture=x64"' - use_shell = True - log.info("Launching PCbuild via shell to preserve quoted /p: arg") - else: - cmd = cmd_list - log.info("Start PCbuild") - if use_shell: - runcmd(cmd, env=env, stderr=logfp, stdout=logfp, shell=True) - else: - runcmd(cmd, env=env, stderr=logfp, stdout=logfp) + runcmd(cmd, env=env, stderr=logfp, stdout=logfp) log.info("PCbuild finished") if arch == "amd64": From 907f097b7ed412afdb731e36dd4a28789b622e86 Mon Sep 17 00:00:00 2001 From: "Daniel A. Wozniak" Date: Fri, 6 Nov 2026 19:45:26 -0700 Subject: [PATCH 17/20] arm64: add /O1 + no intrinsics to pyproject.props override Previous commit (b526f36) only disabled WPO+LTCG for ARM64 Release. Still OOM'd on ``unicodetype_db.h``: /O2's own register allocator pushes the 32-bit cl.exe past the 2 GB heap limit even without whole-program optimization enabled. Patch also forces ``Optimization=MinSpace`` (/O1), ``InlineFunctionExpansion= OnlyExplicitInline``, and ``IntrinsicFunctions=false`` for the ARM64 Release ItemDefinitionGroup, cutting the compile memory footprint deep enough to fit. Confirmed from the uploaded python.log on run 42328670281 that the earlier patch did apply (the ``Patched pyproject.props to disable WPO/LTCG on ARM64 Release`` info line is present), so the root cause is optimization level not just WPO. --- relenv/build/windows.py | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/relenv/build/windows.py b/relenv/build/windows.py index c3bb7c05..8393405c 100644 --- a/relenv/build/windows.py +++ b/relenv/build/windows.py @@ -1135,16 +1135,19 @@ def build_python(env: EnvMapping, dirs: Dirs, logfp: IO[str]) -> None: if content != original: props_path.write_text(content, encoding="utf-8") - # Disable whole-program optimisation and link-time code generation on - # ARM64 Release builds. The resulting "pass 2" / LTCG phase in - # ``cl.exe`` is where the HostX86 cross-compiler runs out of heap on - # ``Objects/unicodetype_db.h`` (``fatal error C1002``) -- repeatedly - # reproduced on Python 3.10-3.13 under VS 18 on windows-11-arm, where - # we can't convince MSBuild to use the HostX64 cross-compiler. - # Dropping WPO and LTCG gives up some runtime speed (official builds - # keep them on) but avoids the OOM. Python 3.14 happens to compile - # without hitting the limit so it's unaffected. Only touches the - # ARM64 conditional stanza; amd64/x86 Release keeps WPO+LTCG. + # Reduce optimisation level and disable whole-program optimisation / + # link-time code generation on ARM64 Release builds. The HostX86 + # cross-compiler (which MSBuild picks for ARM64 targets under VS 18 + # and refuses to let us override) OOMs in pass 2 on + # ``Objects/unicodetype_db.h`` (``fatal error C1002``) with Python's + # default ``MaxSpeed``+``WholeProgramOptimization=true`` settings. + # Even disabling WPO alone isn't enough -- /O2's register allocator + # also blows past the 32-bit heap on that file. Switching to + # ``MinSpace`` (``/O1``), turning off WPO/LTCG, and disabling + # ``IntrinsicFunctions`` keeps optimisation shallow enough to fit in + # 2 GB. Python 3.14 happens to compile without hitting the limit so + # its arm64 build was already green; this patch is a no-op there and + # on amd64/x86 (the condition keys off Platform='ARM64'). pyproject_path = dirs.source / "PCbuild" / "pyproject.props" if pyproject_path.exists(): original = pyproject_path.read_text(encoding="utf-8") @@ -1159,6 +1162,10 @@ def build_python(env: EnvMapping, dirs: Dirs, logfp: IO[str]) -> None: "Condition=\"'$(Platform)' == 'ARM64' and " "'$(Configuration)' == 'Release'\">\n" " \n" + " MinSpace\n" + " OnlyExplicitInline" + "\n" + " false\n" " false\n" " \n" " \n" From 0fbfebd6f97fb976466583a7078ff07d75477772 Mon Sep 17 00:00:00 2001 From: "Daniel A. Wozniak" Date: Fri, 6 Nov 2026 20:12:08 -0700 Subject: [PATCH 18/20] arm64: /Od + broaden ItemDefinitionGroup condition to all configs Round 3 on the C1002 pass-2 OOM. /O1+MinSpace (907f097) was still not enough -- the 32-bit cl.exe's own code generator exhausts the 2 GB heap on ``unicodetype_db.h`` even at /O1. Drop to /Od (``Optimization= Disabled``) and turn off every remaining optimisation knob (``StringPooling``, ``FunctionLevelLinking``, ``InlineFunctionExpansion =Disabled``). Also widen the override's Condition from ``Platform='ARM64' and Configuration='Release'`` to just ``Platform='ARM64'`` in case ProjectConfiguration selection hasn't set ``$(Configuration)`` by the time pyproject.props is evaluated and the earlier two-term condition was simply being skipped. Gives up all release-mode optimisation on arm64 in exchange for a successful build; we can tighten the knobs back once a 64-bit cl.exe story works on windows-11-arm. --- relenv/build/windows.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/relenv/build/windows.py b/relenv/build/windows.py index 8393405c..27d526ff 100644 --- a/relenv/build/windows.py +++ b/relenv/build/windows.py @@ -1159,13 +1159,14 @@ def build_python(env: EnvMapping, dirs: Dirs, logfp: IO[str]) -> None: " \n" " " + sentinel + "\n" " \n" + "Condition=\"'$(Platform)' == 'ARM64'\">\n" " \n" - " MinSpace\n" - " OnlyExplicitInline" + " Disabled\n" + " Disabled" "\n" " false\n" + " false\n" + " false\n" " false\n" " \n" " \n" From 6854a4fcb9ab64b10d5160c5d5ae9c2fd73eff74 Mon Sep 17 00:00:00 2001 From: "Daniel A. Wozniak" Date: Fri, 6 Nov 2026 21:05:22 -0700 Subject: [PATCH 19/20] arm64 verify: use cffi 2.0.0 for all win_arm64 branches, not just 3.14 ``test_install_with_target_no_ignore_installed`` / ``test_install_with_target_ignore_installed`` pre-install ``cffi`` and then verify that pip re-uses the already-installed copy when it pulls in ``pygit2`` through ``--target``. For win_arm64 (3.11-3.13 and 3.14) the test picked ``pygit2==1.19.2`` -- the earliest release that ships win_arm64 wheels -- but only sent 3.14 down the ``cffi==2.0.0`` branch. 3.11/3.12/3.13 pinned ``cffi==1.17.1``, which fails pygit2 1.19.2's ``cffi>=2.0`` METADATA requirement, so pip upgrades cffi under the hood and the assertion ``"already satisfied: cffi" in out`` fires. CI run 42332897154 reproduced: 3.10 skipped (no pygit2 win_arm64 wheel), 3.14 passed, 3.11/3.12/3.13 failed with:: AssertionError: assert 'already satisfied: cffi' in '... Successfully installed cffi pygit2\r\n' Collapse the arm64 branch to a single ``cffi==2.0.0`` pin so every runnable Python version uses the version that matches pygit2 1.19.2's cffi floor. Other platforms are unaffected. --- tests/test_verify_build.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/tests/test_verify_build.py b/tests/test_verify_build.py index 5b49f568..d8d3f30a 100644 --- a/tests/test_verify_build.py +++ b/tests/test_verify_build.py @@ -1570,10 +1570,11 @@ def test_install_with_target_no_ignore_installed(pipexec, pyexec, build, build_v if sys.platform == "win32" and arch == "arm64": if build_version.startswith("3.10"): pytest.skip("pygit2 has no win_arm64 wheels for Python 3.10") - if build_version.startswith("3.14"): - cffi = "cffi==2.0.0" - else: - cffi = "cffi==1.17.1" + # pygit2 1.19.2 is the earliest release carrying win_arm64 wheels, + # and its METADATA pins ``cffi>=2.0``. Pre-installing anything + # older would be re-upgraded during the pygit2 install and defeat + # the "already satisfied: cffi" assertion below. + cffi = "cffi==2.0.0" pygit2 = "pygit2==1.19.2" elif build_version.startswith("3.14"): cffi = "cffi==2.0.0" @@ -1616,10 +1617,11 @@ def test_install_with_target_ignore_installed(pipexec, pyexec, build, build_vers if sys.platform == "win32" and arch == "arm64": if build_version.startswith("3.10"): pytest.skip("pygit2 has no win_arm64 wheels for Python 3.10") - if build_version.startswith("3.14"): - cffi = "cffi==2.0.0" - else: - cffi = "cffi==1.17.1" + # pygit2 1.19.2 is the earliest release carrying win_arm64 wheels, + # and its METADATA pins ``cffi>=2.0``. Pre-installing anything + # older would be re-upgraded during the pygit2 install and defeat + # the "already satisfied: cffi" assertion below. + cffi = "cffi==2.0.0" pygit2 = "pygit2==1.19.2" elif build_version.startswith("3.14"): cffi = "cffi==2.0.0" From 47f950846d7e61975d9408ca5180edf55a316bdf Mon Sep 17 00:00:00 2001 From: "Daniel A. Wozniak" Date: Sat, 7 Nov 2026 00:05:31 -0700 Subject: [PATCH 20/20] arm64 CI labels: drop redundant "arm64" suffix from job name Both ``build_windows_arm64`` and ``test_windows_arm64`` matrix over ``arch: [arm64]``, which GitHub appends to the job label -- producing ``Python Windows arm64 (3.12, arm64)``. Collapse to ``Python Windows`` and ``Verify Windows`` so the matrix is the only source of the arch tag. The x86/amd64 ``build_windows`` / ``test_windows`` already use bare ``Python Windows`` / ``Verify Windows`` names, so this keeps the two jobs' labels consistent. --- .github/workflows/build-native-action.yml | 2 +- .github/workflows/verify-build-action.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-native-action.yml b/.github/workflows/build-native-action.yml index a0b4f7d3..9a6a20c2 100644 --- a/.github/workflows/build-native-action.yml +++ b/.github/workflows/build-native-action.yml @@ -369,7 +369,7 @@ jobs: retention-days: 5 build_windows_arm64: - name: "Python Windows arm64" + name: "Python Windows" # Native build: host and target are both arm64, so this runs on the # windows-11-arm hosted runner rather than cross-compiling from the # x64 windows-2022 image build_windows uses. WIP job for diff --git a/.github/workflows/verify-build-action.yml b/.github/workflows/verify-build-action.yml index 949ab59e..b15d686d 100644 --- a/.github/workflows/verify-build-action.yml +++ b/.github/workflows/verify-build-action.yml @@ -275,7 +275,7 @@ jobs: retention-days: 5 test_windows_arm64: - name: "Verify Windows arm64" + name: "Verify Windows" # Matches build_windows_arm64's native windows-11-arm runner in # build-native-action.yml. WIP for # https://github.com/saltstack/relenv/issues/280.