From 8875d0653d9660faa7c50eff7a5dbba7443a33dd Mon Sep 17 00:00:00 2001 From: jaimergp Date: Thu, 16 Jul 2026 21:36:51 +0200 Subject: [PATCH 01/32] Wrap native win-arm64 scripts with a start /machine launcher --- conda_build/windows.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/conda_build/windows.py b/conda_build/windows.py index f9234f9797..1f83185ca7 100644 --- a/conda_build/windows.py +++ b/conda_build/windows.py @@ -7,6 +7,7 @@ # importing setuptools patches distutils so that it knows how to find VC for python 2.7 import setuptools # noqa +from conda.base.context import context # Leverage the hard work done by setuptools/distutils to find vcvarsall using # either the registry or the VS**COMNTOOLS environment variable @@ -390,7 +391,22 @@ def build(m, bld_bat, stats, provision_only=False): work_script, env_script = write_build_scripts(m, env, bld_bat) if not provision_only and os.path.isfile(work_script): - cmd = ["cmd.exe", "/d", "/c", os.path.basename(work_script)] + if m.config.build_subdir == "win-arm64" and context._native_subdir == "win-64": + # If conda-build is run from a win-64 environment on a win-arm64 machine + # users may want to build natively by setting build_platform="win-arm64". + # In those cases, we need to ensure that the CMD process is native ARM64 + # via this `start` wrapper. Otherwise Windows picks the AMD64 slice! + wrap = os.path.join( + os.path.dirname(work_script), "_win_arm64_native_wrapper.bat" + ) + with open(wrap, "w") as f: + f.write( + "@echo off\r\n" + f'start /b /wait /machine arm64 cmd.exe /d /c "{work_script}"\r\n' + ) + cmd = ["cmd.exe", "/d", "/c", os.path.basename(wrap)] + else: + cmd = ["cmd.exe", "/d", "/c", os.path.basename(work_script)] # rewrite long paths in stdout back to their env variables if m.config.debug or m.config.no_rewrite_stdout_env: rewrite_env = None From 82f54cb7d0d8698a79fd3a5457c1fcdb244a8bf6 Mon Sep 17 00:00:00 2001 From: jaimergp Date: Thu, 16 Jul 2026 22:17:17 +0200 Subject: [PATCH 02/32] add test --- conda_build/windows.py | 37 ++++++++++++++++++-------------- tests/test_build.py | 48 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 68 insertions(+), 17 deletions(-) diff --git a/conda_build/windows.py b/conda_build/windows.py index 1f83185ca7..8ac7b5f8f4 100644 --- a/conda_build/windows.py +++ b/conda_build/windows.py @@ -348,6 +348,26 @@ def write_build_scripts(m, env, bld_bat): return work_script, env_script +def build_command_arguments(m, script: str) -> list[str]: + if m.config.build_subdir != context._native_subdir(): + # If conda-build is run from e.f. a win-64 environment on a win-arm64 machine + # users may want to build natively by setting build_platform="win-arm64". + # In those cases, we need to ensure that the CMD process is native ARM64 + # via this `start` wrapper. Otherwise Windows picks the AMD64 slice! + wrapper = os.path.join(os.path.dirname(script), "_win_native_wrapper.bat") + machine = m.config.build_subdir.split("-")[1] + if machine == "64": + machine = "amd64" + with open(wrapper, "w") as f: + f.write( + "@echo off\r\n" + f'start /b /wait /machine {machine} cmd.exe /d /c "{script}"\r\n' + "exit /b %ERRORLEVEL%\r\n" + ) + return ["cmd.exe", "/d", "/c", os.path.basename(wrapper)] + return ["cmd.exe", "/d", "/c", os.path.basename(script)] + + def build(m, bld_bat, stats, provision_only=False): # TODO: Prepending the prefixes here should probably be guarded by # if not m.activate_build_script: @@ -391,22 +411,7 @@ def build(m, bld_bat, stats, provision_only=False): work_script, env_script = write_build_scripts(m, env, bld_bat) if not provision_only and os.path.isfile(work_script): - if m.config.build_subdir == "win-arm64" and context._native_subdir == "win-64": - # If conda-build is run from a win-64 environment on a win-arm64 machine - # users may want to build natively by setting build_platform="win-arm64". - # In those cases, we need to ensure that the CMD process is native ARM64 - # via this `start` wrapper. Otherwise Windows picks the AMD64 slice! - wrap = os.path.join( - os.path.dirname(work_script), "_win_arm64_native_wrapper.bat" - ) - with open(wrap, "w") as f: - f.write( - "@echo off\r\n" - f'start /b /wait /machine arm64 cmd.exe /d /c "{work_script}"\r\n' - ) - cmd = ["cmd.exe", "/d", "/c", os.path.basename(wrap)] - else: - cmd = ["cmd.exe", "/d", "/c", os.path.basename(work_script)] + cmd = build_command_arguments(m, work_script) # rewrite long paths in stdout back to their env variables if m.config.debug or m.config.no_rewrite_stdout_env: rewrite_env = None diff --git a/tests/test_build.py b/tests/test_build.py index 8e7f74c986..f50fd6e74f 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -17,9 +17,10 @@ from typing import TYPE_CHECKING import pytest +from conda.base.context import context from conda.common.compat import on_win -from conda_build import api, build +from conda_build import api, build, windows from conda_build.exceptions import CondaBuildUserError from conda_build.metadata import MetaData from conda_build.variants import get_default_variant @@ -434,3 +435,48 @@ def _minimal_meta_with_reqs(requirements: dict) -> defaultdict: build._warn_implicit_numpy_variant(m) n_warn = sum(1 for r in caplog.records if r.levelno == logging.WARNING) assert n_warn == (1 if expected_warning else 0) + + +@pytest.mark.parametrize( + "build_subdir,native_subdir,wrapped", + [ + ("win-arm64", "win-64", True), + ("win-64", "win-arm64", True), + ("win-arm64", "win-arm64", False), + ("win-64", "win-64", False), + ], +) +def test_build_command_win_arm64_wrapper( + testing_metadata: MetaData, + monkeypatch, + tmp_path, + build_subdir, + native_subdir, + wrapped, +): + testing_metadata.config.arch = build_subdir.split("-")[1] + monkeypatch.setattr(context, "_native_subdir", lambda: native_subdir) + + work_script = tmp_path / "conda_build.bat" + work_script.write_text("@echo off\r\n") + wrapper = tmp_path / "_win_native_wrapper.bat" + + cmd = windows.build_command_arguments(testing_metadata, str(work_script)) + + if not wrapped: + assert cmd == ["cmd.exe", "/d", "/c", "conda_build.bat"] + assert not wrapper.exists() + return + + assert cmd == ["cmd.exe", "/d", "/c", "_win_native_wrapper.bat"] + + contents = wrapper.read_text() + contents_bytes = wrapper.read_bytes() + machine = "amd64" if build_subdir == "win-64" else "arm64" + assert f"/machine {machine}" in contents + assert "/b" in contents + assert "/wait" in contents + assert "%ERRORLEVEL%" in contents + assert str(work_script) in contents + # batch files must be CRLF; a bare LF breaks cmd.exe + assert contents_bytes.count(b"\n") == contents_bytes.count(b"\r\n") From aabf7f7f2844fbe30fbc06df34b28697701b43a5 Mon Sep 17 00:00:00 2001 From: jaimergp Date: Fri, 17 Jul 2026 11:32:47 +0200 Subject: [PATCH 03/32] Add integration test on Windows ARM --- .github/workflows/tests.yml | 22 +++++++++++++++++----- conda_build/windows.py | 32 ++++++++++++++++++++++++++++++++ tests/test_build.py | 22 ++++++++++++++++++++++ 3 files changed, 71 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 807fb41460..7c567dde0b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -291,7 +291,7 @@ jobs: needs: changes if: github.event_name == 'schedule' || needs.changes.outputs.code == 'true' - runs-on: windows-2022 + runs-on: ${{ matrix.runs-on }} strategy: fail-fast: false matrix: @@ -300,21 +300,30 @@ jobs: - python-version: '3.10' conda-version: release test-type: serial + runs-on: windows-2022 - python-version: '3.10' conda-version: release test-type: parallel + runs-on: windows-2022 # upper version (w/ canary conda) - python-version: '3.14' conda-version: canary test-type: serial + runs-on: windows-2022 - python-version: '3.14' conda-version: canary test-type: parallel + runs-on: windows-2022 + - python-version: '3.14' + conda-version: release + test-type: custom + pytest-expression: 'test_build_command_win_arm64_wrapper or test_win_arm64_build_on_emulated_win_64' + runs-on: windows-11-arm env: ErrorActionPreference: Stop # powershell exit on first error CONDA_CHANNEL_LABEL: ${{ matrix.conda-version == 'canary' && 'conda-canary/label/dev' || 'defaults' }} # Exclude benchmark and slow tests from Windows parallel runs - PYTEST_MARKER: ${{ matrix.test-type == 'serial' && 'serial and not benchmark' || 'not serial and not slow and not benchmark' }} + PYTEST_MARKER: ${{ matrix.test-type == 'serial' && 'serial and not benchmark' || (matrix.test-type == 'parallel' && 'not serial and not slow and not benchmark' || '') }} steps: - name: Checkout Source @@ -343,10 +352,12 @@ jobs: with: condarc-file: .github\condarc run-post: false # skip post cleanup - pkgs-dirs: D:\conda_pkgs_dir - installation-dir: D:\conda + pkgs-dirs: ${{ runner.arch == 'ARM64' && 'C' || 'D'}}:\conda_pkgs_dir + installation-dir: ${{ runner.arch == 'ARM64' && 'C' || 'D'}}:\conda + architecture: x64 # force x64 even on ARM64 to test emulation - name: Choco Install + if: runner.arch != 'ARM64' # We may need the complete tooling so that cmake tests pass on windows run: choco install visualstudio2017-workload-vctools @@ -380,7 +391,8 @@ jobs: --basetemp=${{ runner.temp }} --durations-path=durations\${{ runner.os }}.json -n auto - -m "${{ env.PYTEST_MARKER }}" + ${{ env.PYTEST_MARKER && '-m "' || ''}}${{ env.PYTEST_MARKER }}${{ env.PYTEST_MARKER && '"' || ''}} + ${{ matrix.pytest-expression && '-k "' || ''}}${{ matrix.pytest-expression }}${{ matrix.pytest-expression && '"' || ''}} - name: Upload Coverage uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 diff --git a/conda_build/windows.py b/conda_build/windows.py index 8ac7b5f8f4..a722444178 100644 --- a/conda_build/windows.py +++ b/conda_build/windows.py @@ -1,9 +1,17 @@ # Copyright (C) 2014 Anaconda, Inc # SPDX-License-Identifier: BSD-3-Clause +from __future__ import annotations + import os import pprint +import sys +from functools import cache from itertools import product from os.path import dirname, isdir, isfile, join +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Literal # importing setuptools patches distutils so that it knows how to find VC for python 2.7 import setuptools # noqa @@ -75,6 +83,30 @@ def fix_staged_scripts(scripts_dir, config): os.remove(join(scripts_dir, fn)) +@cache +def get_native_windows_architecture() -> Literal['AMD64', 'ARM64', 'x86'] | None: + """ + Queries the Windows registry to determine the native machine architecture. + This works reliably even if the Python process is running under emulation + (e.g. win-64 environment on a Windows ARM laptop). + """ + if sys.platform != "win32": + raise OSError("This function is only supported on Windows.") + + import winreg + + registry_path = r"SYSTEM\CurrentControlSet\Control\Session Manager\Environment" + + try: + with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, registry_path, 0, winreg.KEY_READ) as key: + native_arch, _ = winreg.QueryValueEx(key, "PROCESSOR_ARCHITECTURE") + return native_arch + except Exception as e: + log = get_logger(__name__) + log.debug("Could not detect native architecture via Registry", exc_info=e) + return None + + def build_vcvarsall_vs_path(version): """ Given the Visual Studio version, returns the default path to the diff --git a/tests/test_build.py b/tests/test_build.py index f50fd6e74f..f6f810999d 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -480,3 +480,25 @@ def test_build_command_win_arm64_wrapper( assert str(work_script) in contents # batch files must be CRLF; a bare LF breaks cmd.exe assert contents_bytes.count(b"\n") == contents_bytes.count(b"\r\n") + + +@pytest.mark.skipif( + not (on_win and windows.get_native_windows_architecture() == "ARM64"), + reason="Windows ARM only test", +) +def test_win_arm64_build_on_emulated_win_64( + testing_metadata: MetaData, tmp_path, capsys +): + """ + This test checks the architecture of the launched CMD on Windows ARM + machines that are running emulated x64 processes. Critical for bootstrapping + win-arm64 distributions from their win-64 counterparts via emulation. + """ + (tmp_path / "bld.bat").write_text( + "echo PROCESSOR_ARCHITECTURE=%PROCESSOR_ARCHITECTURE%" + ) + testing_metadata.config.arch = "arm64" + windows.build(testing_metadata, str(tmp_path / "bld.bat"), {}) + out, err = capsys.readouterr() + print(out) + assert "PROCESSOR_ARCHITECTURE=ARM64" in out From 0f108a8b6d5a21ea724e7490bac752ccfe452859 Mon Sep 17 00:00:00 2001 From: jaimergp Date: Fri, 17 Jul 2026 11:37:43 +0200 Subject: [PATCH 04/32] reduce CI for debugging --- .github/workflows/tests.yml | 45 ++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7c567dde0b..fccaf6861b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -69,7 +69,8 @@ jobs: linux: # only run test suite if there are code changes needs: changes - if: github.event_name == 'schedule' || needs.changes.outputs.code == 'true' + if: false + # github.event_name == 'schedule' || needs.changes.outputs.code == 'true' runs-on: ubuntu-latest defaults: @@ -217,7 +218,8 @@ jobs: linux-benchmarks: # only run test suite if there are code changes needs: changes - if: needs.changes.outputs.code == 'true' + if: false + #needs.changes.outputs.code == 'true' runs-on: ubuntu-latest defaults: @@ -296,24 +298,24 @@ jobs: fail-fast: false matrix: include: - # test lower version (w/ stable conda) - - python-version: '3.10' - conda-version: release - test-type: serial - runs-on: windows-2022 - - python-version: '3.10' - conda-version: release - test-type: parallel - runs-on: windows-2022 - # upper version (w/ canary conda) - - python-version: '3.14' - conda-version: canary - test-type: serial - runs-on: windows-2022 - - python-version: '3.14' - conda-version: canary - test-type: parallel - runs-on: windows-2022 + # # test lower version (w/ stable conda) + # - python-version: '3.10' + # conda-version: release + # test-type: serial + # runs-on: windows-2022 + # - python-version: '3.10' + # conda-version: release + # test-type: parallel + # runs-on: windows-2022 + # # upper version (w/ canary conda) + # - python-version: '3.14' + # conda-version: canary + # test-type: serial + # runs-on: windows-2022 + # - python-version: '3.14' + # conda-version: canary + # test-type: parallel + # runs-on: windows-2022 - python-version: '3.14' conda-version: release test-type: custom @@ -415,7 +417,8 @@ jobs: macos: # only run test suite if there are code changes needs: changes - if: github.event_name == 'schedule' || needs.changes.outputs.code == 'true' + if: false + # github.event_name == 'schedule' || needs.changes.outputs.code == 'true' runs-on: ${{ matrix.runs-on }} defaults: From 6f01a09353d1c6f84714405d95fc926852b30e21 Mon Sep 17 00:00:00 2001 From: jaimergp Date: Fri, 17 Jul 2026 11:38:55 +0200 Subject: [PATCH 05/32] Force win-64 on setup-miniconda --- .github/workflows/tests.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index fccaf6861b..5ee4beecfa 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -356,7 +356,10 @@ jobs: run-post: false # skip post cleanup pkgs-dirs: ${{ runner.arch == 'ARM64' && 'C' || 'D'}}:\conda_pkgs_dir installation-dir: ${{ runner.arch == 'ARM64' && 'C' || 'D'}}:\conda - architecture: x64 # force x64 even on ARM64 to test emulation + # force x64 even on ARM64 to test emulation + architecture: x64 + env: + CONDA_SUBDIR: win-64 - name: Choco Install if: runner.arch != 'ARM64' From c9a732d9c46a47111b31341a44cbb6ff17e5d3b5 Mon Sep 17 00:00:00 2001 From: jaimergp Date: Fri, 17 Jul 2026 11:39:32 +0200 Subject: [PATCH 06/32] prek --- .github/workflows/tests.yml | 2 +- conda_build/windows.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5ee4beecfa..c3ce66dbc3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -319,7 +319,7 @@ jobs: - python-version: '3.14' conda-version: release test-type: custom - pytest-expression: 'test_build_command_win_arm64_wrapper or test_win_arm64_build_on_emulated_win_64' + pytest-expression: test_build_command_win_arm64_wrapper or test_win_arm64_build_on_emulated_win_64 runs-on: windows-11-arm env: ErrorActionPreference: Stop # powershell exit on first error diff --git a/conda_build/windows.py b/conda_build/windows.py index a722444178..5dce405944 100644 --- a/conda_build/windows.py +++ b/conda_build/windows.py @@ -84,7 +84,7 @@ def fix_staged_scripts(scripts_dir, config): @cache -def get_native_windows_architecture() -> Literal['AMD64', 'ARM64', 'x86'] | None: +def get_native_windows_architecture() -> Literal["AMD64", "ARM64", "x86"] | None: """ Queries the Windows registry to determine the native machine architecture. This works reliably even if the Python process is running under emulation @@ -98,7 +98,9 @@ def get_native_windows_architecture() -> Literal['AMD64', 'ARM64', 'x86'] | None registry_path = r"SYSTEM\CurrentControlSet\Control\Session Manager\Environment" try: - with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, registry_path, 0, winreg.KEY_READ) as key: + with winreg.OpenKey( + winreg.HKEY_LOCAL_MACHINE, registry_path, 0, winreg.KEY_READ + ) as key: native_arch, _ = winreg.QueryValueEx(key, "PROCESSOR_ARCHITECTURE") return native_arch except Exception as e: From 595c5c69ef5cb32e60feefc9fde5a8b7c889e6f5 Mon Sep 17 00:00:00 2001 From: jaimergp Date: Fri, 17 Jul 2026 11:54:10 +0200 Subject: [PATCH 07/32] test powershell output --- tests/test_build.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_build.py b/tests/test_build.py index f6f810999d..1cedf2ca14 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -494,11 +494,14 @@ def test_win_arm64_build_on_emulated_win_64( machines that are running emulated x64 processes. Critical for bootstrapping win-arm64 distributions from their win-64 counterparts via emulation. """ + cmdlet = "[System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture" (tmp_path / "bld.bat").write_text( - "echo PROCESSOR_ARCHITECTURE=%PROCESSOR_ARCHITECTURE%" + f'echo PROCESSOR_ARCHITECTURE=%PROCESSOR_ARCHITECTURE%\r\n"' + f'powershell -Command "echo ProcessArchitecture=({cmdlet})\r\n"' ) testing_metadata.config.arch = "arm64" windows.build(testing_metadata, str(tmp_path / "bld.bat"), {}) out, err = capsys.readouterr() print(out) assert "PROCESSOR_ARCHITECTURE=ARM64" in out + assert "ProcessArchitecture=ARM64" in out From fd3f4e9a39a1afac99b3de18e2e966e59bf49e05 Mon Sep 17 00:00:00 2001 From: jaimergp Date: Fri, 17 Jul 2026 12:04:27 +0200 Subject: [PATCH 08/32] Redefine variables --- conda_build/windows.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/conda_build/windows.py b/conda_build/windows.py index 5dce405944..4eb5e6ab76 100644 --- a/conda_build/windows.py +++ b/conda_build/windows.py @@ -383,18 +383,22 @@ def write_build_scripts(m, env, bld_bat): def build_command_arguments(m, script: str) -> list[str]: - if m.config.build_subdir != context._native_subdir(): + machine = m.config.build_subdir.split("-")[1] + machine = {"64": "AMD64", "32": "x86"}.get(machine, machine).upper() + if ( + machine == get_native_windows_architecture() + and m.config.build_subdir != context._native_subdir() + ): # If conda-build is run from e.f. a win-64 environment on a win-arm64 machine # users may want to build natively by setting build_platform="win-arm64". # In those cases, we need to ensure that the CMD process is native ARM64 # via this `start` wrapper. Otherwise Windows picks the AMD64 slice! wrapper = os.path.join(os.path.dirname(script), "_win_native_wrapper.bat") - machine = m.config.build_subdir.split("-")[1] - if machine == "64": - machine = "amd64" with open(wrapper, "w") as f: f.write( "@echo off\r\n" + f'set "PROCESSOR_ARCHITECTURE={machine}"\r\n' + f'set "PROCESSOR_ARCHITEW6432="\r\n' f'start /b /wait /machine {machine} cmd.exe /d /c "{script}"\r\n' "exit /b %ERRORLEVEL%\r\n" ) From e0c76718b2f385893a75b1b00967aa3612fee7e4 Mon Sep 17 00:00:00 2001 From: jaimergp Date: Fri, 17 Jul 2026 12:23:07 +0200 Subject: [PATCH 09/32] move quotes --- tests/test_build.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_build.py b/tests/test_build.py index 1cedf2ca14..c3c6f0e66b 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -496,8 +496,8 @@ def test_win_arm64_build_on_emulated_win_64( """ cmdlet = "[System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture" (tmp_path / "bld.bat").write_text( - f'echo PROCESSOR_ARCHITECTURE=%PROCESSOR_ARCHITECTURE%\r\n"' - f'powershell -Command "echo ProcessArchitecture=({cmdlet})\r\n"' + f'echo PROCESSOR_ARCHITECTURE=%PROCESSOR_ARCHITECTURE%\r\n' + f'powershell -Command "echo ProcessArchitecture=({cmdlet})"\r\n' ) testing_metadata.config.arch = "arm64" windows.build(testing_metadata, str(tmp_path / "bld.bat"), {}) From cf481da247df3495f7cdec84ab99a2e94149f125 Mon Sep 17 00:00:00 2001 From: jaimergp Date: Fri, 17 Jul 2026 12:23:10 +0200 Subject: [PATCH 10/32] fix test --- tests/test_build.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_build.py b/tests/test_build.py index c3c6f0e66b..3a74b2c14c 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -456,7 +456,8 @@ def test_build_command_win_arm64_wrapper( ): testing_metadata.config.arch = build_subdir.split("-")[1] monkeypatch.setattr(context, "_native_subdir", lambda: native_subdir) - + patched_native = "AMD64" if native_subdir == "win-64" else "ARM64" + monkeypatch.setattr(windows.get_native_windows_architecture, lambda: patched_native) work_script = tmp_path / "conda_build.bat" work_script.write_text("@echo off\r\n") wrapper = tmp_path / "_win_native_wrapper.bat" @@ -472,7 +473,7 @@ def test_build_command_win_arm64_wrapper( contents = wrapper.read_text() contents_bytes = wrapper.read_bytes() - machine = "amd64" if build_subdir == "win-64" else "arm64" + machine = "AMD64" if build_subdir == "win-64" else "ARM64" assert f"/machine {machine}" in contents assert "/b" in contents assert "/wait" in contents From ae310810562aa069fa3c10dff47afcaef446a5f2 Mon Sep 17 00:00:00 2001 From: jaimergp Date: Fri, 17 Jul 2026 12:39:35 +0200 Subject: [PATCH 11/32] more test fixes --- conda_build/windows.py | 28 +++++++++++++++++++--------- tests/test_build.py | 18 +++++++++++++++--- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/conda_build/windows.py b/conda_build/windows.py index 4eb5e6ab76..602546d6df 100644 --- a/conda_build/windows.py +++ b/conda_build/windows.py @@ -328,6 +328,11 @@ def write_build_scripts(m, env, bld_bat): env["PYTHONDONTWRITEBYTECODE"] = True import codecs + if _is_native_build_platform_on_emulated_interpreter(m): + # Can't trust the parent environment under these conditions + env["PROCESSOR_ARCHITECTURE"] = get_native_windows_architecture() + env.pop("PROCESSOR_ARCHITEW6432", None) + with codecs.getwriter("utf-8")(open(env_script, "wb")) as fo: # more debuggable with echo on fo.write("@echo on\n") @@ -382,23 +387,28 @@ def write_build_scripts(m, env, bld_bat): return work_script, env_script -def build_command_arguments(m, script: str) -> list[str]: +def _is_native_build_platform_on_emulated_interpreter(m) -> bool: + """ + If conda-build is run from e.f. a win-64 environment on a win-arm64 machine + users may want to build natively by setting build_platform="win-arm64". + In those cases, we need to ensure that the CMD process is native ARM64 + via this `start` wrapper. Otherwise Windows picks the AMD64 slice! + """ machine = m.config.build_subdir.split("-")[1] machine = {"64": "AMD64", "32": "x86"}.get(machine, machine).upper() - if ( + return ( machine == get_native_windows_architecture() and m.config.build_subdir != context._native_subdir() - ): - # If conda-build is run from e.f. a win-64 environment on a win-arm64 machine - # users may want to build natively by setting build_platform="win-arm64". - # In those cases, we need to ensure that the CMD process is native ARM64 - # via this `start` wrapper. Otherwise Windows picks the AMD64 slice! + ) + + +def build_command_arguments(m, script: str) -> list[str]: + if _is_native_build_platform_on_emulated_interpreter(m): wrapper = os.path.join(os.path.dirname(script), "_win_native_wrapper.bat") + machine = get_native_windows_architecture() with open(wrapper, "w") as f: f.write( "@echo off\r\n" - f'set "PROCESSOR_ARCHITECTURE={machine}"\r\n' - f'set "PROCESSOR_ARCHITEW6432="\r\n' f'start /b /wait /machine {machine} cmd.exe /d /c "{script}"\r\n' "exit /b %ERRORLEVEL%\r\n" ) diff --git a/tests/test_build.py b/tests/test_build.py index 3a74b2c14c..6fe1e05e51 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -17,7 +17,7 @@ from typing import TYPE_CHECKING import pytest -from conda.base.context import context +from conda.base.context import context, reset_context from conda.common.compat import on_win from conda_build import api, build, windows @@ -455,9 +455,13 @@ def test_build_command_win_arm64_wrapper( wrapped, ): testing_metadata.config.arch = build_subdir.split("-")[1] + testing_metadata.config.host_subdir = build_subdir + testing_metadata.config.target_subdir = build_subdir monkeypatch.setattr(context, "_native_subdir", lambda: native_subdir) patched_native = "AMD64" if native_subdir == "win-64" else "ARM64" - monkeypatch.setattr(windows.get_native_windows_architecture, lambda: patched_native) + monkeypatch.setattr( + windows, "get_native_windows_architecture", lambda: patched_native + ) work_script = tmp_path / "conda_build.bat" work_script.write_text("@echo off\r\n") wrapper = tmp_path / "_win_native_wrapper.bat" @@ -483,6 +487,12 @@ def test_build_command_win_arm64_wrapper( assert contents_bytes.count(b"\n") == contents_bytes.count(b"\r\n") +@pytest.fixture +def force_win_arm64(monkeypatch): + monkeypatch.setenv("CONDA_SUBDIR", "win-arm64") + reset_context() + + @pytest.mark.skipif( not (on_win and windows.get_native_windows_architecture() == "ARM64"), reason="Windows ARM only test", @@ -497,10 +507,12 @@ def test_win_arm64_build_on_emulated_win_64( """ cmdlet = "[System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture" (tmp_path / "bld.bat").write_text( - f'echo PROCESSOR_ARCHITECTURE=%PROCESSOR_ARCHITECTURE%\r\n' + f"echo PROCESSOR_ARCHITECTURE=%PROCESSOR_ARCHITECTURE%\r\n" f'powershell -Command "echo ProcessArchitecture=({cmdlet})"\r\n' ) testing_metadata.config.arch = "arm64" + testing_metadata.config.host_subdir = "win-arm64" + testing_metadata.config.target_subdir = "win-arm64" windows.build(testing_metadata, str(tmp_path / "bld.bat"), {}) out, err = capsys.readouterr() print(out) From 0a592603845b054531a3b08af163289b0228dc49 Mon Sep 17 00:00:00 2001 From: jaimergp Date: Fri, 17 Jul 2026 13:04:00 +0200 Subject: [PATCH 12/32] simplify --- conda_build/windows.py | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/conda_build/windows.py b/conda_build/windows.py index 602546d6df..e1879f00bd 100644 --- a/conda_build/windows.py +++ b/conda_build/windows.py @@ -84,7 +84,7 @@ def fix_staged_scripts(scripts_dir, config): @cache -def get_native_windows_architecture() -> Literal["AMD64", "ARM64", "x86"] | None: +def get_native_windows_architecture() -> Literal["AMD64", "ARM64", "X86"] | None: """ Queries the Windows registry to determine the native machine architecture. This works reliably even if the Python process is running under emulation @@ -102,7 +102,7 @@ def get_native_windows_architecture() -> Literal["AMD64", "ARM64", "x86"] | None winreg.HKEY_LOCAL_MACHINE, registry_path, 0, winreg.KEY_READ ) as key: native_arch, _ = winreg.QueryValueEx(key, "PROCESSOR_ARCHITECTURE") - return native_arch + return native_arch.upper() except Exception as e: log = get_logger(__name__) log.debug("Could not detect native architecture via Registry", exc_info=e) @@ -328,10 +328,12 @@ def write_build_scripts(m, env, bld_bat): env["PYTHONDONTWRITEBYTECODE"] = True import codecs - if _is_native_build_platform_on_emulated_interpreter(m): + if m.config.build_subdir != context._native_subdir(): # Can't trust the parent environment under these conditions - env["PROCESSOR_ARCHITECTURE"] = get_native_windows_architecture() - env.pop("PROCESSOR_ARCHITEW6432", None) + build_arch = _cmd_machine_flag(m) + env["PROCESSOR_ARCHITECTURE"] = build_arch + if build_arch == get_native_windows_architecture(): + env.pop("PROCESSOR_ARCHITEW6432", None) with codecs.getwriter("utf-8")(open(env_script, "wb")) as fo: # more debuggable with echo on @@ -387,29 +389,26 @@ def write_build_scripts(m, env, bld_bat): return work_script, env_script -def _is_native_build_platform_on_emulated_interpreter(m) -> bool: +def _cmd_machine_flag(m) -> Literal["AMD64", "ARM64", "X86"]: """ If conda-build is run from e.f. a win-64 environment on a win-arm64 machine users may want to build natively by setting build_platform="win-arm64". In those cases, we need to ensure that the CMD process is native ARM64 via this `start` wrapper. Otherwise Windows picks the AMD64 slice! + This gives you the adequate /machine flag value for `start`. """ - machine = m.config.build_subdir.split("-")[1] - machine = {"64": "AMD64", "32": "x86"}.get(machine, machine).upper() - return ( - machine == get_native_windows_architecture() - and m.config.build_subdir != context._native_subdir() - ) + build_arch = m.config.build_subdir.split("-")[1] + return {"64": "AMD64", "32": "X86"}.get(build_arch, build_arch).upper() def build_command_arguments(m, script: str) -> list[str]: - if _is_native_build_platform_on_emulated_interpreter(m): + if m.config.build_subdir != context._native_subdir(): + # See docstring of _cmd_machine_flag() wrapper = os.path.join(os.path.dirname(script), "_win_native_wrapper.bat") - machine = get_native_windows_architecture() with open(wrapper, "w") as f: f.write( "@echo off\r\n" - f'start /b /wait /machine {machine} cmd.exe /d /c "{script}"\r\n' + f'start /b /wait /machine {_cmd_machine_flag(m)} cmd.exe /d /c "{script}"\r\n' "exit /b %ERRORLEVEL%\r\n" ) return ["cmd.exe", "/d", "/c", os.path.basename(wrapper)] From 8be946dc277d74f7cc233075da8361e2ed1fe201 Mon Sep 17 00:00:00 2001 From: jaimergp Date: Fri, 17 Jul 2026 13:04:28 +0200 Subject: [PATCH 13/32] define target_platform --- tests/test_build.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_build.py b/tests/test_build.py index 6fe1e05e51..813db486e7 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -511,8 +511,7 @@ def test_win_arm64_build_on_emulated_win_64( f'powershell -Command "echo ProcessArchitecture=({cmdlet})"\r\n' ) testing_metadata.config.arch = "arm64" - testing_metadata.config.host_subdir = "win-arm64" - testing_metadata.config.target_subdir = "win-arm64" + testing_metadata.config.variant["target_platform"] = "win-arm64" windows.build(testing_metadata, str(tmp_path / "bld.bat"), {}) out, err = capsys.readouterr() print(out) From da6ec7040e67716745db31be62513d60d60a1150 Mon Sep 17 00:00:00 2001 From: jaimergp Date: Fri, 17 Jul 2026 13:20:29 +0200 Subject: [PATCH 14/32] Debug --- tests/test_build.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/test_build.py b/tests/test_build.py index 813db486e7..c727719053 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -515,5 +515,15 @@ def test_win_arm64_build_on_emulated_win_64( windows.build(testing_metadata, str(tmp_path / "bld.bat"), {}) out, err = capsys.readouterr() print(out) + print("---") + print(*os.listdir(testing_metadata.config.work_dir), sep="\n") + print("---") + print("build_env_setup.bat:") + print(Path(testing_metadata.config.work_dir, "build_env_setup.bat").read_text()) + print("---") + print("conda_build.bat:") + print(Path(testing_metadata.config.work_dir, "conda_build.bat").read_text()) + if (wrapper := Path(testing_metadata.config.work_dir, "_win_native_wrapper.bat")).exists(): + print(wrapper.read_text()) assert "PROCESSOR_ARCHITECTURE=ARM64" in out assert "ProcessArchitecture=ARM64" in out From 026a84a3861e43f3f83440c08145591ca50748b8 Mon Sep 17 00:00:00 2001 From: jaimergp Date: Fri, 17 Jul 2026 13:22:11 +0200 Subject: [PATCH 15/32] prek --- tests/test_build.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_build.py b/tests/test_build.py index c727719053..24fa600f07 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -523,7 +523,9 @@ def test_win_arm64_build_on_emulated_win_64( print("---") print("conda_build.bat:") print(Path(testing_metadata.config.work_dir, "conda_build.bat").read_text()) - if (wrapper := Path(testing_metadata.config.work_dir, "_win_native_wrapper.bat")).exists(): + if ( + wrapper := Path(testing_metadata.config.work_dir, "_win_native_wrapper.bat") + ).exists(): print(wrapper.read_text()) assert "PROCESSOR_ARCHITECTURE=ARM64" in out assert "ProcessArchitecture=ARM64" in out From 472a531d3f4bd33889093913f0f2e9f0ced6a1db Mon Sep 17 00:00:00 2001 From: jaimergp Date: Fri, 17 Jul 2026 13:58:02 +0200 Subject: [PATCH 16/32] Can't trust platform.machine() on windows arm --- conda_build/windows.py | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/conda_build/windows.py b/conda_build/windows.py index e1879f00bd..8bbb0a176c 100644 --- a/conda_build/windows.py +++ b/conda_build/windows.py @@ -3,6 +3,7 @@ from __future__ import annotations import os +import platform import pprint import sys from functools import cache @@ -328,9 +329,9 @@ def write_build_scripts(m, env, bld_bat): env["PYTHONDONTWRITEBYTECODE"] = True import codecs - if m.config.build_subdir != context._native_subdir(): + if m.config.build_subdir != _running_subdir(): # Can't trust the parent environment under these conditions - build_arch = _cmd_machine_flag(m) + build_arch = _build_arch_from_metadata(m) env["PROCESSOR_ARCHITECTURE"] = build_arch if build_arch == get_native_windows_architecture(): env.pop("PROCESSOR_ARCHITEW6432", None) @@ -389,7 +390,7 @@ def write_build_scripts(m, env, bld_bat): return work_script, env_script -def _cmd_machine_flag(m) -> Literal["AMD64", "ARM64", "X86"]: +def _build_arch_from_metadata(m) -> Literal["AMD64", "ARM64", "X86"]: """ If conda-build is run from e.f. a win-64 environment on a win-arm64 machine users may want to build natively by setting build_platform="win-arm64". @@ -401,14 +402,29 @@ def _cmd_machine_flag(m) -> Literal["AMD64", "ARM64", "X86"]: return {"64": "AMD64", "32": "X86"}.get(build_arch, build_arch).upper() +def _running_subdir(): + """ + Python platform.machine() (on which conda.base.context._native_subdir() relies) + reports ARM64 even when running from a win-64 installation. + This is different from what one can observe on macOS/Rosetta, where it reports x86_64. + The most obvious way is to check %PROCESSOR_ARCHITECTURE%, if available, but we need to hope + it was not overridden. + """ + if os.name == "nt": + arch = os.environ.get("PROCESSOR_ARCHITECTURE", platform.machine()).upper() + arch = {"AMD64": "64", "X86": "32"}.get(arch, arch).lower() + return f"win-{arch}" + return context._native_subdir() + + def build_command_arguments(m, script: str) -> list[str]: - if m.config.build_subdir != context._native_subdir(): + if m.config.build_subdir != _running_subdir(): # See docstring of _cmd_machine_flag() wrapper = os.path.join(os.path.dirname(script), "_win_native_wrapper.bat") with open(wrapper, "w") as f: f.write( "@echo off\r\n" - f'start /b /wait /machine {_cmd_machine_flag(m)} cmd.exe /d /c "{script}"\r\n' + f'start /b /wait /machine {_build_arch_from_metadata(m)} cmd.exe /d /c "{script}"\r\n' "exit /b %ERRORLEVEL%\r\n" ) return ["cmd.exe", "/d", "/c", os.path.basename(wrapper)] From 3d4127b411ef470ffa9bacca2c53f3c5ab17c03d Mon Sep 17 00:00:00 2001 From: jaimergp Date: Fri, 17 Jul 2026 14:10:33 +0200 Subject: [PATCH 17/32] Try like this? --- tests/test_build.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_build.py b/tests/test_build.py index 24fa600f07..ac6db23060 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -508,7 +508,7 @@ def test_win_arm64_build_on_emulated_win_64( cmdlet = "[System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture" (tmp_path / "bld.bat").write_text( f"echo PROCESSOR_ARCHITECTURE=%PROCESSOR_ARCHITECTURE%\r\n" - f'powershell -Command "echo ProcessArchitecture=({cmdlet})"\r\n' + f'powershell -Command "\'ProcessArchitecture=\' + {cmdlet}"\r\n' ) testing_metadata.config.arch = "arm64" testing_metadata.config.variant["target_platform"] = "win-arm64" @@ -528,4 +528,4 @@ def test_win_arm64_build_on_emulated_win_64( ).exists(): print(wrapper.read_text()) assert "PROCESSOR_ARCHITECTURE=ARM64" in out - assert "ProcessArchitecture=ARM64" in out + assert "ProcessArchitecture=Arm64" in out From c7c01d93d616fb1acb7aa3b212700af1cfde7e5a Mon Sep 17 00:00:00 2001 From: jaimergp Date: Fri, 17 Jul 2026 14:28:01 +0200 Subject: [PATCH 18/32] add link to python issue --- conda_build/windows.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/conda_build/windows.py b/conda_build/windows.py index 8bbb0a176c..771ec64711 100644 --- a/conda_build/windows.py +++ b/conda_build/windows.py @@ -407,6 +407,9 @@ def _running_subdir(): Python platform.machine() (on which conda.base.context._native_subdir() relies) reports ARM64 even when running from a win-64 installation. This is different from what one can observe on macOS/Rosetta, where it reports x86_64. + + More context at https://github.com/python/cpython/issues/98962. + The most obvious way is to check %PROCESSOR_ARCHITECTURE%, if available, but we need to hope it was not overridden. """ From 88e257d369133a33e5846c9c719fea6c153c08e8 Mon Sep 17 00:00:00 2001 From: jaimergp Date: Fri, 17 Jul 2026 14:28:11 +0200 Subject: [PATCH 19/32] fix test --- tests/test_build.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/tests/test_build.py b/tests/test_build.py index ac6db23060..d0d6ce1eb7 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -438,7 +438,7 @@ def _minimal_meta_with_reqs(requirements: dict) -> defaultdict: @pytest.mark.parametrize( - "build_subdir,native_subdir,wrapped", + "build_subdir,running_subdir,wrapped", [ ("win-arm64", "win-64", True), ("win-64", "win-arm64", True), @@ -451,17 +451,15 @@ def test_build_command_win_arm64_wrapper( monkeypatch, tmp_path, build_subdir, - native_subdir, + running_subdir, wrapped, ): - testing_metadata.config.arch = build_subdir.split("-")[1] - testing_metadata.config.host_subdir = build_subdir - testing_metadata.config.target_subdir = build_subdir - monkeypatch.setattr(context, "_native_subdir", lambda: native_subdir) - patched_native = "AMD64" if native_subdir == "win-64" else "ARM64" - monkeypatch.setattr( - windows, "get_native_windows_architecture", lambda: patched_native - ) + platform, arch = build_subdir.split("-") + testing_metadata.config.platform = platform + testing_metadata.config.arch = arch + testing_metadata.config.variant["target_platform"] = build_subdir + monkeypatch.setattr(windows, "_running_subdir", lambda: running_subdir) + work_script = tmp_path / "conda_build.bat" work_script.write_text("@echo off\r\n") wrapper = tmp_path / "_win_native_wrapper.bat" From 0dd56ed2adf770bf485bc37abcd753c732d0ab8b Mon Sep 17 00:00:00 2001 From: jaimergp Date: Fri, 17 Jul 2026 14:29:00 +0200 Subject: [PATCH 20/32] prek --- tests/test_build.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_build.py b/tests/test_build.py index d0d6ce1eb7..40c5f6c74e 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -17,7 +17,7 @@ from typing import TYPE_CHECKING import pytest -from conda.base.context import context, reset_context +from conda.base.context import reset_context from conda.common.compat import on_win from conda_build import api, build, windows @@ -506,7 +506,7 @@ def test_win_arm64_build_on_emulated_win_64( cmdlet = "[System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture" (tmp_path / "bld.bat").write_text( f"echo PROCESSOR_ARCHITECTURE=%PROCESSOR_ARCHITECTURE%\r\n" - f'powershell -Command "\'ProcessArchitecture=\' + {cmdlet}"\r\n' + f"powershell -Command \"'ProcessArchitecture=' + {cmdlet}\"\r\n" ) testing_metadata.config.arch = "arm64" testing_metadata.config.variant["target_platform"] = "win-arm64" From 2bca65ad8fbe3b126fb80063d0b3782894bcf1a1 Mon Sep 17 00:00:00 2001 From: jaimergp Date: Fri, 17 Jul 2026 14:38:44 +0200 Subject: [PATCH 21/32] switch to sysconfig --- conda_build/windows.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/conda_build/windows.py b/conda_build/windows.py index 771ec64711..30d6c6d04c 100644 --- a/conda_build/windows.py +++ b/conda_build/windows.py @@ -3,9 +3,9 @@ from __future__ import annotations import os -import platform import pprint import sys +import sysconfig from functools import cache from itertools import product from os.path import dirname, isdir, isfile, join @@ -411,12 +411,11 @@ def _running_subdir(): More context at https://github.com/python/cpython/issues/98962. The most obvious way is to check %PROCESSOR_ARCHITECTURE%, if available, but we need to hope - it was not overridden. + it was not overridden. `sysconfig.get_platform()` seems to be accurate enough. """ if os.name == "nt": - arch = os.environ.get("PROCESSOR_ARCHITECTURE", platform.machine()).upper() - arch = {"AMD64": "64", "X86": "32"}.get(arch, arch).lower() - return f"win-{arch}" + arch = sysconfig.get_platform().lower() + return {"win-amd64": "win-64", "win32": "win-32"}.get(arch, arch) return context._native_subdir() From 6f1c4c2f89e573399835db6e0b468837e21805ee Mon Sep 17 00:00:00 2001 From: jaimergp Date: Fri, 17 Jul 2026 14:58:33 +0200 Subject: [PATCH 22/32] cleanup a bit --- conda_build/windows.py | 42 ++++++++++++++++++++++++++---------------- tests/test_build.py | 22 +++------------------- 2 files changed, 29 insertions(+), 35 deletions(-) diff --git a/conda_build/windows.py b/conda_build/windows.py index 30d6c6d04c..4302b39098 100644 --- a/conda_build/windows.py +++ b/conda_build/windows.py @@ -3,6 +3,7 @@ from __future__ import annotations import os +import platform import pprint import sys import sysconfig @@ -85,15 +86,21 @@ def fix_staged_scripts(scripts_dir, config): @cache -def get_native_windows_architecture() -> Literal["AMD64", "ARM64", "X86"] | None: +def get_native_windows_architecture() -> Literal["AMD64", "ARM64", "x86"] | str | None: """ - Queries the Windows registry to determine the native machine architecture. - This works reliably even if the Python process is running under emulation - (e.g. win-64 environment on a Windows ARM laptop). + Python 3.12+ `platform.machine()` on Windows reports the actual machine, + not the running interpreter. So we can just use that. + + For prior versions, query the registry. + + Returns None if it value cannot be obtained. """ if sys.platform != "win32": raise OSError("This function is only supported on Windows.") + if sys.version_info >= (3, 12): + return platform.machine() or None + import winreg registry_path = r"SYSTEM\CurrentControlSet\Control\Session Manager\Environment" @@ -106,7 +113,7 @@ def get_native_windows_architecture() -> Literal["AMD64", "ARM64", "X86"] | None return native_arch.upper() except Exception as e: log = get_logger(__name__) - log.debug("Could not detect native architecture via Registry", exc_info=e) + log.debug("Could not detect native architecture via registry", exc_info=e) return None @@ -331,7 +338,7 @@ def write_build_scripts(m, env, bld_bat): if m.config.build_subdir != _running_subdir(): # Can't trust the parent environment under these conditions - build_arch = _build_arch_from_metadata(m) + build_arch = _build_arch(m) env["PROCESSOR_ARCHITECTURE"] = build_arch if build_arch == get_native_windows_architecture(): env.pop("PROCESSOR_ARCHITEW6432", None) @@ -390,23 +397,24 @@ def write_build_scripts(m, env, bld_bat): return work_script, env_script -def _build_arch_from_metadata(m) -> Literal["AMD64", "ARM64", "X86"]: +def _build_arch(m) -> Literal["AMD64", "ARM64", "x86"]: """ - If conda-build is run from e.f. a win-64 environment on a win-arm64 machine + If conda-build is run from e.g. a win-64 environment on a win-arm64 machine users may want to build natively by setting build_platform="win-arm64". In those cases, we need to ensure that the CMD process is native ARM64 via this `start` wrapper. Otherwise Windows picks the AMD64 slice! This gives you the adequate /machine flag value for `start`. """ - build_arch = m.config.build_subdir.split("-")[1] - return {"64": "AMD64", "32": "X86"}.get(build_arch, build_arch).upper() + build_arch = m.config.build_subdir.split("-")[1].upper() + return {"64": "AMD64", "32": "x86"}.get(build_arch, build_arch) def _running_subdir(): """ - Python platform.machine() (on which conda.base.context._native_subdir() relies) - reports ARM64 even when running from a win-64 installation. - This is different from what one can observe on macOS/Rosetta, where it reports x86_64. + On Python 3.12+, `platform.machine()` (on which conda.base.context._native_subdir() relies) + may report ARM64 even when running from a win-64 installation. + This is different from what one can observe on macOS/Rosetta, where an emulated osx-64 Python + on Apple Silicon reports x86_64. More context at https://github.com/python/cpython/issues/98962. @@ -414,7 +422,9 @@ def _running_subdir(): it was not overridden. `sysconfig.get_platform()` seems to be accurate enough. """ if os.name == "nt": - arch = sysconfig.get_platform().lower() + arch = ( + sysconfig.get_platform().lower() + ) # -> Literal['win-amd64', 'win-arm64', 'win32'] return {"win-amd64": "win-64", "win32": "win-32"}.get(arch, arch) return context._native_subdir() @@ -422,11 +432,11 @@ def _running_subdir(): def build_command_arguments(m, script: str) -> list[str]: if m.config.build_subdir != _running_subdir(): # See docstring of _cmd_machine_flag() - wrapper = os.path.join(os.path.dirname(script), "_win_native_wrapper.bat") + wrapper = os.path.join(os.path.dirname(script), "_conda_build_wrapper.bat") with open(wrapper, "w") as f: f.write( "@echo off\r\n" - f'start /b /wait /machine {_build_arch_from_metadata(m)} cmd.exe /d /c "{script}"\r\n' + f'start /b /wait /machine {_build_arch(m)} cmd.exe /d /c "{script}"\r\n' "exit /b %ERRORLEVEL%\r\n" ) return ["cmd.exe", "/d", "/c", os.path.basename(wrapper)] diff --git a/tests/test_build.py b/tests/test_build.py index 40c5f6c74e..0e4939d102 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -17,7 +17,6 @@ from typing import TYPE_CHECKING import pytest -from conda.base.context import reset_context from conda.common.compat import on_win from conda_build import api, build, windows @@ -471,7 +470,7 @@ def test_build_command_win_arm64_wrapper( assert not wrapper.exists() return - assert cmd == ["cmd.exe", "/d", "/c", "_win_native_wrapper.bat"] + assert cmd == ["cmd.exe", "/d", "/c", "_conda_build_wrapper.bat"] contents = wrapper.read_text() contents_bytes = wrapper.read_bytes() @@ -485,12 +484,6 @@ def test_build_command_win_arm64_wrapper( assert contents_bytes.count(b"\n") == contents_bytes.count(b"\r\n") -@pytest.fixture -def force_win_arm64(monkeypatch): - monkeypatch.setenv("CONDA_SUBDIR", "win-arm64") - reset_context() - - @pytest.mark.skipif( not (on_win and windows.get_native_windows_architecture() == "ARM64"), reason="Windows ARM only test", @@ -514,16 +507,7 @@ def test_win_arm64_build_on_emulated_win_64( out, err = capsys.readouterr() print(out) print("---") - print(*os.listdir(testing_metadata.config.work_dir), sep="\n") - print("---") - print("build_env_setup.bat:") - print(Path(testing_metadata.config.work_dir, "build_env_setup.bat").read_text()) - print("---") - print("conda_build.bat:") - print(Path(testing_metadata.config.work_dir, "conda_build.bat").read_text()) - if ( - wrapper := Path(testing_metadata.config.work_dir, "_win_native_wrapper.bat") - ).exists(): - print(wrapper.read_text()) + print("Directory contents:") + print(*sorted(os.listdir(testing_metadata.config.work_dir), sep="\n")) assert "PROCESSOR_ARCHITECTURE=ARM64" in out assert "ProcessArchitecture=Arm64" in out From affb171aee71f9a3e7321c1ce0e05dc2e346db92 Mon Sep 17 00:00:00 2001 From: jaimergp Date: Fri, 17 Jul 2026 15:07:29 +0200 Subject: [PATCH 23/32] add news --- news/6047-win-arm64-native.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 news/6047-win-arm64-native.md diff --git a/news/6047-win-arm64-native.md b/news/6047-win-arm64-native.md new file mode 100644 index 0000000000..5c12a1fab0 --- /dev/null +++ b/news/6047-win-arm64-native.md @@ -0,0 +1,19 @@ +### Enhancements + +* + +### Bug fixes + +* Make CMD subprocesses match `build_platform` architecture when running emulated Python processes. (#6048 via #6047) + +### Deprecations + +* + +### Docs + +* + +### Other + +* From 5440d142b2334b077a32ba09708e62c88bf4ac58 Mon Sep 17 00:00:00 2001 From: jaimergp Date: Fri, 17 Jul 2026 15:08:28 +0200 Subject: [PATCH 24/32] fix test --- tests/test_build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_build.py b/tests/test_build.py index 0e4939d102..c1bb7444a3 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -461,7 +461,7 @@ def test_build_command_win_arm64_wrapper( work_script = tmp_path / "conda_build.bat" work_script.write_text("@echo off\r\n") - wrapper = tmp_path / "_win_native_wrapper.bat" + wrapper = tmp_path / "_conda_build_wrapper.bat" cmd = windows.build_command_arguments(testing_metadata, str(work_script)) From c6a8193acf71b1a6db43cc7b0cf1226156628622 Mon Sep 17 00:00:00 2001 From: jaimergp Date: Fri, 17 Jul 2026 15:15:23 +0200 Subject: [PATCH 25/32] test exit code --- tests/test_build.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test_build.py b/tests/test_build.py index c1bb7444a3..26a2c456bf 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -10,6 +10,7 @@ import json import logging import os +import subprocess import sys from collections import defaultdict from contextlib import nullcontext @@ -500,10 +501,13 @@ def test_win_arm64_build_on_emulated_win_64( (tmp_path / "bld.bat").write_text( f"echo PROCESSOR_ARCHITECTURE=%PROCESSOR_ARCHITECTURE%\r\n" f"powershell -Command \"'ProcessArchitecture=' + {cmdlet}\"\r\n" + f"exit /b 42\r\n" ) testing_metadata.config.arch = "arm64" testing_metadata.config.variant["target_platform"] = "win-arm64" - windows.build(testing_metadata, str(tmp_path / "bld.bat"), {}) + with pytest.raises(subprocess.CalledProcessError) as exc: + windows.build(testing_metadata, str(tmp_path / "bld.bat"), {}) + assert exc.returncode == 42 out, err = capsys.readouterr() print(out) print("---") From 8ffa8dc6249d51dded53ae26432f8d19680f1d95 Mon Sep 17 00:00:00 2001 From: jaimergp Date: Fri, 17 Jul 2026 15:17:08 +0200 Subject: [PATCH 26/32] fix parens --- tests/test_build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_build.py b/tests/test_build.py index 26a2c456bf..0dfe3cc867 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -512,6 +512,6 @@ def test_win_arm64_build_on_emulated_win_64( print(out) print("---") print("Directory contents:") - print(*sorted(os.listdir(testing_metadata.config.work_dir), sep="\n")) + print(*sorted(os.listdir(testing_metadata.config.work_dir)), sep="\n") assert "PROCESSOR_ARCHITECTURE=ARM64" in out assert "ProcessArchitecture=Arm64" in out From 03c704619d0500856ddef1f55414650adefd2ca2 Mon Sep 17 00:00:00 2001 From: jaimergp Date: Fri, 17 Jul 2026 15:27:11 +0200 Subject: [PATCH 27/32] look inside value --- tests/test_build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_build.py b/tests/test_build.py index 0dfe3cc867..e2171c0794 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -507,7 +507,7 @@ def test_win_arm64_build_on_emulated_win_64( testing_metadata.config.variant["target_platform"] = "win-arm64" with pytest.raises(subprocess.CalledProcessError) as exc: windows.build(testing_metadata, str(tmp_path / "bld.bat"), {}) - assert exc.returncode == 42 + assert exc.value.returncode == 42 out, err = capsys.readouterr() print(out) print("---") From 5c6b4e177738f16e9ec8e840ec2473b5aabb129b Mon Sep 17 00:00:00 2001 From: jaimergp Date: Fri, 17 Jul 2026 15:48:54 +0200 Subject: [PATCH 28/32] Re-enable CI fully --- .github/workflows/tests.yml | 43 ++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c3ce66dbc3..109eef57e7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -69,8 +69,7 @@ jobs: linux: # only run test suite if there are code changes needs: changes - if: false - # github.event_name == 'schedule' || needs.changes.outputs.code == 'true' + if: github.event_name == 'schedule' || needs.changes.outputs.code == 'true' runs-on: ubuntu-latest defaults: @@ -218,8 +217,7 @@ jobs: linux-benchmarks: # only run test suite if there are code changes needs: changes - if: false - #needs.changes.outputs.code == 'true' + if: needs.changes.outputs.code == 'true' runs-on: ubuntu-latest defaults: @@ -293,29 +291,25 @@ jobs: needs: changes if: github.event_name == 'schedule' || needs.changes.outputs.code == 'true' - runs-on: ${{ matrix.runs-on }} + runs-on: ${{ matrix.runs-on && matrix.runs-on || 'windows-2022' }} strategy: fail-fast: false matrix: include: - # # test lower version (w/ stable conda) - # - python-version: '3.10' - # conda-version: release - # test-type: serial - # runs-on: windows-2022 - # - python-version: '3.10' - # conda-version: release - # test-type: parallel - # runs-on: windows-2022 - # # upper version (w/ canary conda) - # - python-version: '3.14' - # conda-version: canary - # test-type: serial - # runs-on: windows-2022 - # - python-version: '3.14' - # conda-version: canary - # test-type: parallel - # runs-on: windows-2022 + # test lower version (w/ stable conda) + - python-version: '3.10' + conda-version: release + test-type: serial + - python-version: '3.10' + conda-version: release + test-type: parallel + # upper version (w/ canary conda) + - python-version: '3.14' + conda-version: canary + test-type: serial + - python-version: '3.14' + conda-version: canary + test-type: parallel - python-version: '3.14' conda-version: release test-type: custom @@ -420,8 +414,7 @@ jobs: macos: # only run test suite if there are code changes needs: changes - if: false - # github.event_name == 'schedule' || needs.changes.outputs.code == 'true' + if: github.event_name == 'schedule' || needs.changes.outputs.code == 'true' runs-on: ${{ matrix.runs-on }} defaults: From 3b98c483cfd6e32377ee199ed12982d07926fa1d Mon Sep 17 00:00:00 2001 From: jaimergp Date: Fri, 17 Jul 2026 18:08:51 +0200 Subject: [PATCH 29/32] Update docs --- docs/source/resources/define-metadata.rst | 2 +- docs/source/user-guide/environment-variables.rst | 2 +- tests/test_build.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/resources/define-metadata.rst b/docs/source/resources/define-metadata.rst index d1735fa2e0..fa4c4bc20a 100644 --- a/docs/source/resources/define-metadata.rst +++ b/docs/source/resources/define-metadata.rst @@ -2106,7 +2106,7 @@ variables are booleans. - The NumPy version as an integer such as ``111``. See the CONDA_NPY :ref:`environment variable `. * - build_platform - - The native subdir of the conda executable + - The native subdir of the conda executable. Override with ``CONDA_SUBDIR`` env var when calling ``conda-build``. The use of the Python version selectors, `py27`, `py34`, etc. is discouraged in favor of the more general comparison operators. Additional selectors in this diff --git a/docs/source/user-guide/environment-variables.rst b/docs/source/user-guide/environment-variables.rst index 1689b9106b..849dba7f66 100644 --- a/docs/source/user-guide/environment-variables.rst +++ b/docs/source/user-guide/environment-variables.rst @@ -130,7 +130,7 @@ inherited from the shell environment in which you invoke * - STDLIB_DIR - Python standard library location. * - build_platform - - The native subdir of the conda executable + - The native subdir of the conda executable. Override with ``CONDA_SUBDIR`` env var when calling ``conda-build``. Unix-style packages on Windows, which are usually statically linked to executables, are built in a special ``Library`` diff --git a/tests/test_build.py b/tests/test_build.py index e2171c0794..a3a4fef273 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -503,7 +503,7 @@ def test_win_arm64_build_on_emulated_win_64( f"powershell -Command \"'ProcessArchitecture=' + {cmdlet}\"\r\n" f"exit /b 42\r\n" ) - testing_metadata.config.arch = "arm64" + testing_metadata.config.arch = "arm64" # this is for build_platform testing_metadata.config.variant["target_platform"] = "win-arm64" with pytest.raises(subprocess.CalledProcessError) as exc: windows.build(testing_metadata, str(tmp_path / "bld.bat"), {}) From 79b3f1b75d336242d99fb62c93ad6590cb1ec88c Mon Sep 17 00:00:00 2001 From: jaimergp Date: Fri, 17 Jul 2026 18:09:41 +0200 Subject: [PATCH 30/32] Amend platform handling in rattler-build --- conda_build/_rattler_build/compat.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/conda_build/_rattler_build/compat.py b/conda_build/_rattler_build/compat.py index 3ead49e3c2..139e0f69c0 100644 --- a/conda_build/_rattler_build/compat.py +++ b/conda_build/_rattler_build/compat.py @@ -439,8 +439,8 @@ def run_rattler( for variant in config_files: variant_config = variant_config.merge(VariantConfig.from_file(variant)) - def get_config_value(name): - value = variant_config.get(name, config.subdir) + def get_config_value(name, fallback=None): + value = variant_config.get(name, fallback) if isinstance(value, list): if len(value) != 1: @@ -450,9 +450,9 @@ def get_config_value(name): return value[0] return value - build_platform = get_config_value("build_platform") - host_platform = get_config_value("host_platform") - target_platform = get_config_value("target_platform") + build_platform = config.build_subdir # Overridable via CONDA_SUBDIR env var + host_platform = config.host_subdir + target_platform = get_config_value("target_platform", config.target_subdir) noarch_build_platform = get_config_value("noarch_build_platform") # common tool / platform / render configuration From 0558ea00c34a7ca55a24a5bbcbe31c3d8734f1ef Mon Sep 17 00:00:00 2001 From: jaimergp Date: Fri, 17 Jul 2026 18:14:15 +0200 Subject: [PATCH 31/32] amend host --- conda_build/_rattler_build/compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conda_build/_rattler_build/compat.py b/conda_build/_rattler_build/compat.py index 139e0f69c0..f0de34fa83 100644 --- a/conda_build/_rattler_build/compat.py +++ b/conda_build/_rattler_build/compat.py @@ -451,7 +451,7 @@ def get_config_value(name, fallback=None): return value build_platform = config.build_subdir # Overridable via CONDA_SUBDIR env var - host_platform = config.host_subdir + host_platform = get_config_value("target_platform", config.host_subdir) target_platform = get_config_value("target_platform", config.target_subdir) noarch_build_platform = get_config_value("noarch_build_platform") From 2ce83aff761723846d06ab5eadca1ed159f9e7f4 Mon Sep 17 00:00:00 2001 From: jaimergp Date: Fri, 17 Jul 2026 18:19:09 +0200 Subject: [PATCH 32/32] amend news --- news/6047-win-arm64-native.md | 1 + 1 file changed, 1 insertion(+) diff --git a/news/6047-win-arm64-native.md b/news/6047-win-arm64-native.md index 5c12a1fab0..c260e274af 100644 --- a/news/6047-win-arm64-native.md +++ b/news/6047-win-arm64-native.md @@ -5,6 +5,7 @@ ### Bug fixes * Make CMD subprocesses match `build_platform` architecture when running emulated Python processes. (#6048 via #6047) +* `recipe.yaml` build/host platform handling now behaves in the same as it did in `meta.yaml`. Accidentally allowed `{build,host}_platform` settings in `conda_build_config.yaml` are no longer valid; instead, users can export `CONDA_SUBDIR` and define the `target_platform` setting, respectively. (#6047) ### Deprecations