Skip to content

fix(macos): wrap before_build with arch -x86_64 under Rosetta - #2921

Closed
henryiii wants to merge 1 commit into
pypa:mainfrom
henryiii:fix/macos-before-build-rosetta-wrap
Closed

fix(macos): wrap before_build with arch -x86_64 under Rosetta#2921
henryiii wants to merge 1 commit into
pypa:mainfrom
henryiii:fix/macos-before-build-rosetta-wrap

Conversation

@henryiii

Copy link
Copy Markdown
Contributor

🤖 AI text below 🤖

Problem

On a macOS arm64 host building x86_64 wheels via Rosetta (common now that GHA deprecated macos-13), the command hooks were wrapped with arch -x86_64 inconsistently:

  • before_test and the test command run under arch -x86_64 (test loop: shell_with_arch = functools.partial(call, *arch_prefix, "/bin/sh", "-c") with arch_prefix = ["arch", "-x86_64"]).
  • before_build ran via a plain shell(before_build_prepared, env=env)no prefix.

The wheel itself still cross-compiles via ARCHFLAGS, but hooks that probe the architecture or invoke arch-specific tooling (platform.machine(), uname -m, compilers, etc.) behaved differently in before_build vs before_test. The NumPy maintainers hit this (#2592).

Fix

  • Extract the prefix logic into a small shared helper rosetta_arch_prefix(machine_arch, target_arch) so the build and test paths stay in sync.
  • Wrap before_build with arch -x86_64 when the host is arm64 and the build target is x86_64-only, mirroring the test loop.
  • Refactor the test loop to use the same helper (no behavior change there — it returns [] for native and the previously-handled cases).

The native paths (arm64-on-arm64, x86_64-on-x86_64) are unchanged: the helper returns an empty prefix, so before_build still goes through plain shell().

Design decisions

  • universal2: A universal2 config compiles both arches in a single invocation via ARCHFLAGS="-arch arm64 -arch x86_64". There is no single arch prefix that is correct for that hook, so I left universal2 unwrapped (runs natively as arm64). Forcing arch -x86_64 would be wrong for the arm64 slice. This matches how the build itself is invoked (one process, both arches).
  • before_all: Left unwrapped for now. It runs once before all builds, keyed off python_configurations[0], which does not necessarily represent the arch of every build in a mixed selection. Wrapping it based on the first config could be misleading. Tradeoff noted below as an open question.

Open questions for maintainers

  1. Confirm the desired semantics: wrap before_build for x86_64-only configs on arm64 hosts, but not universal2. Is leaving universal2 native correct, or would you prefer wrapping it as the native arch explicitly (a no-op today) for clarity?
  2. Should before_all also be wrapped? If so, based on what — the first config's arch, or only when all selected configs target x86_64? Or leave it native (current behavior)?
  3. The fix is verified by a unit test on the pure helper. Full end-to-end behavior needs a real arm64-host / x86_64-target (Rosetta) environment, which isn't available in unit tests — see limitation below.

Tests

Added unit_test/macos_rosetta_test.py covering rosetta_arch_prefix for the arm64->x86_64 (Rosetta), native, and unsupported-direction cases. The wrapping wiring inside build() itself isn't unit-testable without a real cross-arch build (it requires real Python.org installs and Rosetta); this is noted as a limitation.

uv run pytest unit_test -> 795 passed. Ruff + mypy (strict) clean on the changed file.

Closes #2592

When building x86_64 wheels on an arm64 host, the test loop already runs
before_test (and the test command) under an `arch -x86_64` prefix so that
arch-probing tooling sees x86_64, but before_build was run via a plain
`shell()` with no prefix. This made before_build/before_all behave
differently from before_test on arm64-on-x86_64 (Rosetta) builds, which
NumPy hit after the macos-13 runner deprecation.

Extract the prefix computation into a shared `rosetta_arch_prefix` helper
and use it for both the build and test paths. before_build is now wrapped
for x86_64-only configs on an arm64 host; universal2 builds compile both
arches in one ARCHFLAGS invocation and have no single correct prefix, so
they remain unwrapped. before_all is left unwrapped for now (open question
for maintainers).

Closes pypa#2592

Assisted-by: ClaudeCode:claude-opus-4.8
@henryiii
henryiii force-pushed the fix/macos-before-build-rosetta-wrap branch from 8e38c4d to 68a7b3f Compare June 17, 2026 02:25
@joerick

joerick commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

I'm not sure the conclusion of that issue thread #2592 was to emulate the before-build calls. There is indeed an inconsistency between build and test phases (cross-compile vs emulation) but isn't it worse to run before-build under emulation and then the build under the native arch?

@henryiii henryiii closed this Jun 22, 2026
@henryiii

Copy link
Copy Markdown
Contributor Author

Okay, I wasn't sure.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Inconsistent wrapping of before-build/before-test for Rosetta on macOS

2 participants