From ffa31730cb4677218ab40884ddf1e2db224724d1 Mon Sep 17 00:00:00 2001 From: knightmare2600 Date: Sun, 9 Aug 2026 10:03:02 +0100 Subject: [PATCH 1/5] 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 2/5] 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 3/5] 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 4/5] 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 5/5] 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: