Skip to content

fix(bazel): make the Bazel build work on macOS hosts - #409

Merged
filmil merged 2 commits into
google:mainfrom
clydegerber:fix/bazel-macos-llvm-toolchain
Aug 16, 2026
Merged

fix(bazel): make the Bazel build work on macOS hosts#409
filmil merged 2 commits into
google:mainfrom
clydegerber:fix/bazel-macos-llvm-toolchain

Conversation

@clydegerber

Copy link
Copy Markdown
Contributor

Summary

The Bazel build currently cannot produce a single binary on macOS. This makes
bazel build //... and bazel test //... work there, which is a prerequisite
for #405.

Each change is a place where the build assumed GNU/ELF conventions. Linux
behaviour is unchanged throughout
: every select() keeps the existing value
under //conditions:default, and the ICU patch edits a file only Darwin reads.

The four blockers

1. LLVM toolchain is too old for current macOS SDKs. llvm_version was
pinned to 15.0.6 (2022). Its ld64.lld cannot read the .tbd stub libraries
in a current SDK:

ld64.lld: error: MacOSX26.5.sdk/usr/lib/libSystem.tbd(libSystem.B.dylib)
          is incompatible with arm64 (macOS)

This kills every Rust link, including rules_rust's own process_wrapper
host tool, so the build died before reaching any rust_icu or ICU code. Switched
to llvm_versions, whose "" entry holds 15.0.6 for every platform the
presubmit exercises; only the two Darwin hosts move to 21.1.8.

2. ICU static archiving assumes GNU ar. ICU archives with
$(AR) $(ARFLAGS) $(AR_OUTOPT)$@ $^ but never defines AR_OUTOPT, so the
output name is passed positionally. On Apple platforms rules_foreign_cc supplies
libtool as AR, which requires an explicit -o and fails with
no output file (-o) specified. config/mh-darwin also does ARFLAGS += -c,
appending to make's built-in r; both are GNU ar spellings libtool rejects.
Patched that one line to ARFLAGS := -static -o at fetch time.

3. out_shared_libs named .so files, which do not exist on macOS.
Selected .dylib there.

4. -Wl,--whole-archive in rust_icu_ecma402 is a GNU ld spelling the
Mach-O linker rejects. Selected Apple's -all_load equivalent.

Verification

On macOS 26.5 / arm64 with the default ICU 74 backend:

bazel build //...                                              # succeeds
bazel test --jobs=1 --test_env=RUST_TEST_THREADS=1 //...       # 26 of 26 pass

I have no Linux machine to verify against, so the Linux presubmit is the real
check there. The changes are structured so that path is untouched.

Out of scope

--config=icu_75, icu_76, icu_77 and icu_tot still fail, but for an
unrelated and platform-independent reason: 26 BUILD.bazel files hardcode
RUST_ICU_MAJOR_VERSION_NUMBER to "74", so renamed symbols are looked up with
a _74 suffix against a differently-versioned library
(undefined symbol: utext_clone_74). This fails identically on Linux and is the
subject of #406. ICU 77 itself compiles cleanly on macOS with these changes.

One note for reviewers: bazel run //:buildifier reformats ~30 further
BUILD.bazel files by hoisting load() statements. That churn is unrelated to
this change, so I reverted it and kept the diff to the 8 files above.

Progresses #405.

This commit was created by an automated coding assistant, with human
supervision.

The Bazel build cannot link anything on macOS. `MODULE.bazel` pins
`llvm_version = "15.0.6"`, which predates the current macOS SDKs, and that
LLVM's `ld64.lld` cannot read their text-based `.tbd` stub libraries:

    ld64.lld: error: MacOSX26.5.sdk/usr/lib/libSystem.tbd(libSystem.B.dylib)
              is incompatible with arm64 (macOS)

This fails every Rust link, including `rules_rust`'s own `process_wrapper`
host tool, so the build dies before reaching any rust_icu or ICU code.

Switch from `llvm_version` to `llvm_versions`, which allows per-host
overrides. The `""` entry keeps 15.0.6 for every platform the presubmit
currently exercises, so the Linux CI toolchain is unchanged; only the two
Darwin hosts move to 21.1.8.

This is a prerequisite for google#405, not a complete fix. Two further macOS
blockers remain after this change:

  * ICU's Darwin archiving invokes `libtool -static r -c`, which the
    toolchain's `llvm-libtool-darwin` rejects with "unknown argument '-c'".
  * `third_party/icu_*/icu.BUILD.bazel` hardcode `.so` names in
    `out_shared_libs`; macOS produces `.dylib`.

Verified on macOS 26.5 / arm64: before this change `bazel build //...`
executed a single action before failing; after it, it clears all Rust
linking and proceeds to compiling ICU from source.

This commit was created by an automated coding assistant, with human
supervision.
Follows the LLVM toolchain fix in the previous commit with the remaining
changes needed for `bazel build //...` and `bazel test //...` to work on
macOS. Each is a place where the build assumed GNU/ELF conventions.

* ICU archives static libraries with `$(AR) $(ARFLAGS) $(AR_OUTOPT)$@ $^`
  but never defines `AR_OUTOPT`, so the output name is passed positionally,
  as GNU `ar` expects. On Apple platforms rules_foreign_cc supplies `libtool`
  as AR, which requires an explicit `-o` and fails with "no output file (-o)
  specified". `config/mh-darwin` additionally does `ARFLAGS += -c`, appending
  to make's built-in `r`; both spellings are GNU ar-isms libtool rejects.
  Patch that single line to `ARFLAGS := -static -o` at fetch time. The
  fragment is read only on Darwin, so Linux is unaffected.

* `out_shared_libs` named `.so` files, which do not exist on macOS. Select
  `.dylib` there.

* rust_icu_ecma402 passed `-Wl,--whole-archive`, a GNU ld spelling that the
  Mach-O linker rejects. Select Apple's `-all_load` equivalent.

The Linux behaviour of all three is unchanged: every `select()` keeps the
existing value under `//conditions:default`, and the ICU patch touches a
file only Darwin reads.

Verified on macOS 26.5 / arm64 with the default ICU 74 backend:
`bazel build //...` succeeds and `bazel test --jobs=1
--test_env=RUST_TEST_THREADS=1 //...` reports 26 of 26 tests passing.

Note that `--config=icu_75`, `icu_76`, `icu_77` and `icu_tot` still fail,
but for an unrelated and platform-independent reason: 26 BUILD.bazel files
hardcode `RUST_ICU_MAJOR_VERSION_NUMBER` to "74", so renamed symbols are
looked up with a `_74` suffix against a differently-versioned library. That
is the subject of google#406 and is not addressed here. ICU 77 itself compiles
cleanly on macOS with these changes.

Progresses google#405.

This commit was created by an automated coding assistant, with human
supervision.
@filmil

filmil commented Aug 14, 2026

Copy link
Copy Markdown
Member

hm, I thought we had a macos builder running already, what gives?

@clydegerber

Copy link
Copy Markdown
Contributor Author

@filmil - There is one, but it's on the Cargo path, not the Bazel one.

test.yml's test-static-linking job runs a [ubuntu-latest, macos-latest] matrix and calls make macos-test on the Mac, which is brew install icu4c + cargo test. That's green and has been for a while.

bazel.yml is runs-on: ubuntu-latest, and its matrix only varies bazel_version — there's no macOS entry. So the Bazel build has never actually executed on a Mac, which is how these four issues accumulated:

  • the LLVM 15 pin can't read current macOS SDK .tbd stubs,
  • ICU's archiving assumes GNU ar,
  • out_shared_libs names .so,
  • ecma402 passes -Wl,--whole-archive.

Each one fully blocks the build, and each hid the next.

I can add macos-latest to the bazel.yml matrix here so it stays fixed — that's the CI half of issue #405.

@filmil

filmil commented Aug 16, 2026

Copy link
Copy Markdown
Member

I'm OK with this change, but we'll want to get a MacOS builder in.

I distinctly remember working on the arch matrix for bazel builders recently, not sure where it went. (Still think it's better that someone who actually has a Mac work on MacOS builder.)

@filmil
filmil merged commit d884c35 into google:main Aug 16, 2026
21 checks passed
@clydegerber
clydegerber deleted the fix/bazel-macos-llvm-toolchain branch August 18, 2026 20:45
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.

2 participants