Skip to content

ci: widen test-distribution macos-test, rebuild cpr on arm64 only#6138

Open
Fedr wants to merge 14 commits into
masterfrom
test-distribution-more-macos-runners
Open

ci: widen test-distribution macos-test, rebuild cpr on arm64 only#6138
Fedr wants to merge 14 commits into
masterfrom
test-distribution-more-macos-runners

Conversation

@Fedr
Copy link
Copy Markdown
Contributor

@Fedr Fedr commented May 21, 2026

Summary

Brings test-distribution / macos-test up to parity with macos-pip-test (#6137) and fixes the dyld-symbol-mismatch failures the wider matrix surfaced on older macOS runners.

Three coordinated changes:

1. macos-test matrix expansion (.github/workflows/test-distribution.yml)

Matches the 7-runner image set already used by macos-pip-test:

  • arm64: macos-14, macos-15, macos-26 (GitHub-hosted) + macos-arm-build-12 (self-hosted)
  • x64: macos-15-intel, macos-26-intel (GitHub-hosted) + macos-x64-build (self-hosted)

Before this PR the matrix had a single GitHub-hosted runner per arch, so the .pkg distributables were never exercised on macos-14/15/26 even though the wheels were.

2. fastmcpp submodule bump

thirdparty/fastmcpp → fastmcpp c5271bc (MeshInspector/fastmcpp#2 merged into main). That PR widens the _LIBCPP_AVAILABILITY_HAS_INIT_PRIMARY_EXCEPTION 0 workaround to also fire under AppleClang (both at the #if in clang.hpp and at the CMAKE_CXX_COMPILER_ID gate in CMakeLists.txt). Without this, libMRMcp.dylib shipped a reference to __from_native_exception_pointer — absent from /usr/lib/libc++.1.dylib on macOS 12 — and the x64 .pkg refused to load on macos-x64-build (Mac Pro 2013 / Monterey).

3. arm64-only cpr from-source rebuild (.github/actions/install-macos-thirdparty/action.yml)

Brew's prebuilt arm64_sequoia cpr bottle (minos=15) references the same __from_native_exception_pointer symbol via libc++ inline code, breaking the arm64 .pkg on macos-14 runners. Compiling cpr from source on the arm64 build host with MACOSX_DEPLOYMENT_TARGET=12.7 produces a clean libcpr.1.dylib.

x86_64 keeps the brew sonoma cpr bottle as-is — that bottle was built by Homebrew CI on an older Xcode whose libc++ doesn't emit the troublesome reference, and a from-source rebuild on a current macos-15-intel host with newer Xcode would actually reintroduce the symbol. Hence the explicit if: runner.arch == 'ARM64' scope on the rebuild step.

CI scope

Only macOS distribution testing is affected. Non-mac jobs are disabled via labels. full-ci is set so upload_artifacts is true and test-distribution actually runs.

Test plan

  • All 7 test-distribution / macos-test (arch, runner) combinations pass:
    • (arm64, macos-14) ← canary for the fastmcpp + arm64-only-cpr-rebuild combo
    • (arm64, macos-15)
    • (arm64, macos-26)
    • (arm64, macos-arm-build-12)
    • (x64, macos-15-intel)
    • (x64, macos-26-intel)
    • (x64, macos-x64-build) ← canary for the fastmcpp fix (Mac Pro 2013 / Monterey 12)
  • The "Build cpr from source for older macOS target (arm64 only)" step is skipped on x64 build runners and runs on arm64 build runners.

Related

Verify the macOS .pkg installs on the same image set already used by
`macos-pip-test` in pip-build.yml (#6137):

- arm64 .pkg: macos-14, macos-15, macos-26 (GitHub-hosted) and
  macos-arm-build-12 (self-hosted).
- x64 .pkg: macos-15-intel, macos-26-intel (GitHub-hosted) and
  macos-x64-build (self-hosted).

Previously the matrix only ran one runner per arch (macos-15-intel and
macos-latest), so wheel-test coverage was wider than pkg-test coverage.
Fedr added 8 commits May 21, 2026 21:47
fastmcpp's clang.hpp guarded the
`_LIBCPP_AVAILABILITY_HAS_INIT_PRIMARY_EXCEPTION 0` override with
`!defined(__apple_build_version__)`, so the workaround only fired under
upstream LLVM clang (brew-llvm18) and not under AppleClang. The x64 .pkg
is built with AppleClang on macos-15-intel; its libMRMcp.dylib therefore
referenced __from_native_exception_pointer, which is absent from
/usr/lib/libc++.1.dylib on Monterey (macos-x64-build / Mac Pro 2013):

    dyld: Symbol not found: __ZNSt13exception_ptr31__from_native_exception_pointerEPv
      Referenced from: libMRMcp.dylib
      Expected in:     /usr/lib/libc++.1.dylib

The bumped commit (MeshInspector/fastmcpp#2) drops the
__apple_build_version__ guard so the override also applies under
AppleClang, keeping the helper out of libMRMcp.dylib and restoring
.pkg compatibility with the 12.7 deployment target the rest of the
build already targets.
… fix

The previous bump (#6138 commit 653d095) brought in only the clang.hpp
guard removal, but fastmcpp's own CMakeLists.txt still gated its
target_precompile_headers on `CMAKE_CXX_COMPILER_ID STREQUAL "Clang"`,
which excludes AppleClang. So fastmcpp_core (a STATIC library) was
still being compiled without the workaround under AppleClang, and its
.o files dragged the unguarded `__from_native_exception_pointer`
reference into libMRMcp.dylib at link time. Same dyld failure as
before.

This bump picks up MeshInspector/fastmcpp@ecfe6be, which switches the
gate to `MATCHES "^(Apple)?Clang$"` so fastmcpp_core's own
translation units also see clang.hpp under AppleClang.
The bundled libcpr.1.dylib's LC_BUILD_VERSION minos was tracking the
build host's OS via brew's normal per-host bottle selection. When the
arm64 .pkg build landed on a Sequoia 15 self-hosted host, brew pulled
the arm64_sequoia bottle whose libc++ references
__from_native_exception_pointer -- a symbol absent from
/usr/lib/libc++.1.dylib on macOS < 15. The .pkg then failed dyld on
every macos-14 test runner.

Set HOMEBREW_MACOS_VERSION=14.0 and `brew reinstall cpr` after the
normal install_brew_requirements.sh step, so the bundled libcpr is
always the macOS 14 bottle (which targets minos=14 and doesn't emit
the new symbol). The .pkg's effective cpr floor becomes macOS 14
deterministically, regardless of which self-hosted arm64 host CI
picks.

HOMEBREW_MACOS_VERSION is an undocumented Homebrew internal. If brew
removes it, swap for `--build-from-source` with MACOSX_DEPLOYMENT_TARGET=12.7
(slower but officially supported).
The previous attempt used `HOMEBREW_MACOS_VERSION=14.0` to coax brew
into picking the macOS 14 bottle. Current brew ignores it in the bottle
selection path -- the CI logs show the env was set but the step still
poured arm64_sequoia (minos=15). The bundled libcpr.1.dylib's
__from_native_exception_pointer reference then failed to resolve on
macos-14 hosts as before.

Switch to the officially supported `--build-from-source` path with
MACOSX_DEPLOYMENT_TARGET=12.7. The libc++ headers seen by the compiler
gate _LIBCPP_AVAILABILITY_HAS_INIT_PRIMARY_EXCEPTION on the deployment
target, so under 12.7 the inline std::exception_ptr machinery skips the
helper and no reference to it gets emitted into libcpr.dylib.

Cost: a few minutes of compile time per build (cpr is small; deps stay
as bottles since their dylibs are linked at use sites, not compiled
into libcpr).
Latest test-distribution run showed the from-source cpr fixed all four
arm64 runners (incl. macos-14) but broke the previously-passing x64
MACPRO2013 test: building cpr from source on a current macos-15-intel
host with newer Xcode's libc++ re-introduces the
__from_native_exception_pointer reference that the Homebrew CI-built
`sonoma` Intel bottle didn't have. On arm64, the situation is reversed --
brew's `arm64_sequoia` bottle has the symbol, but our from-source build
doesn't.

Gate the rebuild on `runner.arch == 'ARM64'` so:
  - arm64 hosts use the freshly built libcpr (no symbol)
  - Intel hosts keep the brew sonoma bottle (no symbol)
Repoint thirdparty/fastmcpp from the now-orphaned PR branch tip
ecfe6be to the squash-merge commit c5271bc on fastmcpp's main branch
(MeshInspector/fastmcpp#2). Same source content, but the commit is
now reachable from main and survives any future garbage collection
of the deleted branch.
@Fedr Fedr changed the title ci: widen test-distribution macos-test to match macos-pip-test runners ci: widen test-distribution macos-test matrix to match macos-pip-test runners May 22, 2026
@Fedr Fedr changed the title ci: widen test-distribution macos-test matrix to match macos-pip-test runners ci: widen test-distribution macos-test, rebuild cpr on arm64 only May 22, 2026
strategy:
fail-fast: false
# Matches the GitHub-hosted + self-hosted macOS coverage used by
# `macos-pip-test` in pip-build.yml (see #6137), so .pkg installs
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does it refer a merged PR?

Comment on lines +146 to +154
# arm64 only: brew ships an `arm64_sequoia` cpr bottle (minos=15) that
# references libc++ symbols absent from /usr/lib/libc++.1.dylib on macOS
# < 15, breaking .pkg loads on macos-14. Compiling from source under a
# 12.7 deployment target produces a clean libcpr.1.dylib.
# x86_64 keeps the brew `sonoma` cpr bottle (the only Intel bottle
# Homebrew ships) -- it was built on an older Xcode whose libc++ doesn't
# emit the troublesome reference, so it loads on macos-12+ as-is. A
# from-source rebuild on a current macos-15-intel runner would
# *re*introduce the symbol via newer Xcode's libc++.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update the comment to indicate the possible problems with Xcode 16+ and libc++.

The arm64 cpr from-source rebuild successfully drops LC_BUILD_VERSION
minos to 12.7 but doesn't suppress the inline libc++ references to
__from_native_exception_pointer, because Apple's libc++ keys symbol
emission on a feature-test macro the deployment target alone doesn't
flip. The macro is `_LIBCPP_AVAILABILITY_HAS_INIT_PRIMARY_EXCEPTION`
and the fastmcpp clang.hpp workaround forces it to 0 before
<exception> is parsed.

This experiment tries to push the same define into cpr's compile via
brew's env, attempting four candidate carriers (CXXFLAGS, CPPFLAGS,
HOMEBREW_CFLAGS, HOMEBREW_CXXFLAGS) and grepping the build log for
the define landing on an actual compiler invocation. The step prints
a clear DEFINE REACHED THE COMPILER / STRIPPED line so the next
iteration can pick option B (brew tap with patched formula) or option
C (build cpr in MeshLib's own source tree) on a confirmed signal.

The grep is also a backstop -- even if the dyld test on macos-14
passes by coincidence, an absent define in the log means the
workaround is not actually in effect and we'd be back to coin-flipping
build hosts.
Fedr added a commit that referenced this pull request May 22, 2026
…6148)

Two coordinated changes, both Intel-side; arm64 build and test
configuration stays exactly as in master.

1. macos-test matrix gains two x64 rows alongside the existing
   macos-15-intel:
     - macos-26-intel  (GitHub-hosted, newer)
     - macos-x64-build (self-hosted, MACPRO2013 / Monterey 12)
   The arm64 row stays at the single `macos-latest` runner.

2. thirdparty/fastmcpp bumped to c5271bc (MeshInspector/fastmcpp#2),
   which widens fastmcpp's libc++
   `_LIBCPP_AVAILABILITY_HAS_INIT_PRIMARY_EXCEPTION 0` workaround to
   also fire under AppleClang. Without this, libMRMcp.dylib (built
   with AppleClang for the x64 .pkg) references
   `__from_native_exception_pointer`, which is absent from
   /usr/lib/libc++.1.dylib on Monterey 12 and aborts MeshViewer on
   macos-x64-build at load time.

Sibling arm64-side changes from #6138 (additional arm64 test runners
and the arm64-scoped cpr from-source rebuild) are intentionally not
included here -- arm64 self-hosted build hosts run an older Xcode and
the matter is intertwined with brew bottle SDK churn that's worth
landing separately.
Fedr added 4 commits May 22, 2026 17:45
…ore-macos-runners

# Conflicts:
#	.github/workflows/test-distribution.yml
Option A confirmed that brew's superenv scrubs CXXFLAGS/CPPFLAGS/
HOMEBREW_C{,XX}FLAGS before reaching the formula's compile commands,
so we cannot push -D_LIBCPP_AVAILABILITY_HAS_INIT_PRIMARY_EXCEPTION=0
via step env. The formula DSL's `ENV.append "CXXFLAGS", ...` does
survive superenv -- it appends to the curated value from inside the
formula. Use that escape hatch via a private tap:

  1. `brew tap-new --no-git local/patched` creates a fresh tap (idempotent).
  2. `brew cat cpr` prints the canonical homebrew/core formula text.
  3. awk inserts the `ENV.append` line right after `def install`.
  4. Write the patched formula into the tap as `cpr.rb`.
  5. Uninstall the bottle-installed cpr so the keg path frees up.
  6. `brew install --build-from-source local/patched/cpr` builds the
     patched variant; brew exposes it at $BREW_PREFIX/opt/cpr/ just
     like the original, so macos_bundle_dylibs.py finds it without
     any further plumbing.

The verification step greps the build log for the define landing on
an actual compiler invocation and `exit 1`s otherwise -- if the
formula DSL ever stops honoring ENV.append we'll see a clean negative
signal instead of another silent coin-flip green.
The strict `/^  def install$/` anchor failed to match on
MacBook-Pro-Daniil's `brew cat cpr` output. Two refinements:

  - Drop the `^` and `$` anchors and use a `!done` guard so we still
    inject exactly once. Tolerates differing indentation, trailing
    whitespace, or line-ending quirks between brew's API-cached
    formula and homebrew/core's source.
  - On miss, `cat -A` the first 60 lines of brew cat output so the
    next iteration sees the literal bytes brew is feeding us (control
    chars, trailing $, etc.) instead of having to guess again.
Two fixes after option B v2 failed on MacBook-Pro-Daniil:

1. `cat -A` is a GNU extension; BSD cat (the macOS default) only has
   `cat -v` for similar control-char visualisation. Replace.

2. Use `brew formula cpr` (which prints the path of the formula .rb
   file on disk) and read the file directly with awk, instead of
   piping `brew cat cpr`. On hosts running brew in API mode the
   `brew cat` command can produce a stripped or JSON-derived variant
   without the literal `def install` line; the on-disk file is the
   canonical Ruby source either way.

If both still miss, the diagnostic dumps the first 60 lines of each
input through `cat -v` so we can see whether the formula was empty,
encoded oddly, or simply different from upstream.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants