Fix BCR presubmit: declare apple_support and pin host deployment target#295
Merged
Fix BCR presubmit: declare apple_support and pin host deployment target#295
Conversation
BCR presubmit (PR #8711) failed in two distinct ways that didn't surface in our GitHub Actions CI: 1. `Build SafeDI targets` failed analysis with `Invalid target triple: local, this likely means you're using the wrong CC toolchain, make sure you include apple_support in your project`. rules_swift 3.x on Bazel 9 needs `apple_support` declared at the *root* MODULE.bazel so the proper Apple-aware CC toolchain is registered. Without it, Bazel falls back to auto-config that returns the literal string `"local"` for `cc_toolchain.target_gnu_system_name`. Our example's MODULE.bazel had `apple_support`; SafeDI's didn't. The auto-config path happened to produce a parseable triple on `macos-26`, so the issue was invisible on our runner but reliably broken on BCR's. 2. `Build downstream example` failed at runtime with `dyld: Library not loaded ... built for macOS 26.0 which is newer than running OS`. SafeDITool got compiled fresh under exec config with Xcode 26's SDK, pinning macOS 26 as the deployment target. BCR's runner has Xcode 26 installed but runs an older OS. Our `macos-26` runner actually runs macOS 26, so SDK and runner aligned and the issue couldn't surface. swift-syntax solves the same problem with `--host_macos_minimum_os` in its `.bazelrc`. Fixes: - Add `apple_support` `bazel_dep` to root MODULE.bazel. - Set `--host_macos_minimum_os=14.0` in both root and example `.bazelrc` so tools (exec config) get a backwards-compatible deployment target. The example's `.bazelrc` matters because it becomes the root when the BCR `bcr_test_module` task runs and downstream consumers don't inherit our root `.bazelrc`. - Mirror BCR's `verify_safedi_build` target list explicitly in ci.yml (was `//Sources/...`) and add a sync pointer at the top of the `bazel` job so we keep the two configs aligned. These fixes need to ship in 2.0.0-rc-3 — the BCR PR is built from the published 2.0.0-rc-2 tarball and can't pick them up retroactively. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #295 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 41 41
Lines 7043 7043
=========================================
Hits 7043 7043 🚀 New features to boost your workflow:
|
The retry composite action splices `${{ inputs.command }}` directly
into a bash `for` loop and appends ` && break` on the same source
line. A multi-line YAML command (via `|`) leaves `&& break` orphaned,
producing a syntax error at line 9. Inline the target list so the
command stays on a single line.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
BCR's `macos_arm64` runner has Xcode 26 installed but its OS is older than macOS 26, which is exactly why a SafeDITool binary built with macOS 26 deployment target fails dyld at execution time on BCR. Running our bazel job on `macos-26` aligned SDK and runner OS, so a deployment-target regression couldn't fail here. Switch to `macos-15` (Sequoia) and keep `xcode-select` pointed at Xcode 26.0 — the runner image ships it at the same path as before (/Applications/Xcode_26.0.app, a symlink to 26.0.1). This reproduces BCR's SDK-vs-runner gap locally, so a future regression on `--host_macos_minimum_os` (or any other host-deployment-target lever) fails our CI before it fails BCR. Also flag the `9.x` vs `.bazelversion` drift risk in the comment, so a future maintainer knows to bump `.bazelversion` when BCR's `9.x` matrix selector starts resolving to something newer than ours. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Switching to macos-15 to reproduce BCR's SDK-vs-runner-OS gap was reinventing a wheel we can't actually finish: BazelCI's BuildKite infrastructure (the right way to mirror BCR exactly) is closed to non-`bazelbuild` projects, and any GH Actions runner we pick will still differ from BCR's image in ways we can't fully control — CC toolchain auto-detection, exact Bazel point release, etc. Treat BCR's presubmit as the canonical macOS-on-older-OS check instead. This job runs on macos-26 as a general bazel sanity gate; target list stays aligned with `.bcr/presubmit.yml` so divergence is visible in review. The two real fixes — `apple_support` and `--host_macos_minimum_os=14.0` — still ship and address the underlying BCR failures. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
swift-syntax (modules/swift-syntax/603.0.1/presubmit.yml in BCR) is
the closest prior art for our setup — same dep shape (swift-syntax +
rules_swift on Bazel 9.x with Xcode 26 SDK on an older runner OS).
Their BCR `presubmit.yml` carries three flags we were missing:
--repo_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1
Stops Bazel from auto-detecting the host CC toolchain. This is
a more direct fix for Failure 1 than relying on `apple_support`
to register the right toolchain — without auto-detection,
`xcode_swift_toolchain` never falls through to the broken
`target_gnu_system_name == "local"` path. Belt-and-suspenders
with the apple_support bazel_dep we already have.
--host_macos_minimum_os=14.0
The dyld fix for Failure 2 (matches what we already had in
.bazelrc).
--macos_minimum_os=14.0
Same idea for target-config builds. Matches swift-syntax.
Carrying the flags in `.bcr/presubmit.yml`'s `build_flags` field is
the cleaner channel because BCR's test runner passes them directly,
no `.bazelrc` propagation guesswork — sidesteps "whose .bazelrc is
root?" entirely.
Also mirrored both flags in our and the example's `.bazelrc` for
local dev consistency.
Verified on a local build: `otool -l SafeDITool` reports
`minos 14.0, sdk 26.4` — the flag actually takes effect in the
binary's LC_BUILD_VERSION, so SafeDITool is loadable on macOS 14+
regardless of which SDK built it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Surveyed five BCR-published Swift modules for prior art on the exact deployment-target value: swift-syntax 603.0.1: --macos_minimum_os=13.0 swift-index-store 1.9.2: --macos_minimum_os=12.0 swift-filename-matcher: not pinned asc_swift 1.6.0: not pinned rules_xcodeproj 4.0.1: not pinned The previous 14.0 was a guess. Lowering to 12.0 (swift-index-store's value, since it's also a Swift binary tool) is strictly more permissive — anything that runs on 12.0 runs on 14.0+ — and matches the lowest empirically-validated BCR value in the survey. Eliminates the "what if BCR's runner OS is older than 14?" risk. Verified: `otool -l SafeDITool` after rebuild reports `minos 12.0`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously pinned 12.0 (then 14.0) everywhere — both higher than
SafeDI's declared platform support (.macOS(.v11) in Package.swift).
The library would have shipped tagged macOS 12+ via the Bazel path,
inconsistent with what we publish for SPM consumers.
Split host vs. target deployment targets per real requirements:
Host (--host_macos_minimum_os=11.0): SafeDITool only needs to
*run* on the build machine, not exercise newer APIs. Lowest
reasonable value; ensures the binary loads on whatever older
macOS BCR's runner happens to be on. Matches Package.swift.
Target (--macos_minimum_os):
* SafeDI library: 11.0 — matches Package.swift, what we claim
to support.
* ExampleBazelIntegration: 14.0 — driven by the example app's
own source. LoggedInView's onChange(of:initial:_:) is macOS
14+, NameEntryView's TextField init is macOS 12+. The example
app's deployment requirements are independent of SafeDI's.
Verified `otool -l SafeDITool` reports `minos 11.0` after rebuild.
Both root SafeDI build and example build pass locally.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
BCR presubmit (bazelbuild/bazel-central-registry#8711) failed in two ways. Both are well-trodden ground for BCR-published Swift modules — surveyed five of them and adopted the unanimously-used flags. This PR carries the substantive config fixes; the surrounding meta-changes (CI mirror attempts, retry-action workaround) are documented at the bottom.
Substantive fixes
Failure 1 —
Build SafeDI targets(CC toolchain analysis error)rules_swift'sxcode_swift_toolchainfalls through tocc_toolchain.target_gnu_system_name, which returns the literal string"local"on BCR's runner because Bazel auto-detects a non-Apple-aware CC toolchain.Fix (both belt and suspenders, both have prior art):
bazel_dep(name = "apple_support", version = "1.24.2", ...)in rootMODULE.bazel. Matches what swift-syntax, swift-index-store, and rules_xcodeproj all do — apple_support's bazel_dep registers a proper Apple-aware CC toolchain so the auto-detect fallback isn't reached.--repo_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1in.bcr/presubmit.ymlbuild_flags. This is unanimous across all five BCR Swift modules I surveyed — it tells Bazel to skip CC auto-detection entirely, so the broken fallback can't run regardless of toolchain registration.Failure 2 —
Build downstream example(dyld at execution)SafeDITool gets compiled fresh under exec config with Xcode 26's SDK, which pins macOS 26 as the deployment target. BCR's runner has Xcode 26 installed but runs an older OS, so dyld refuses the binary.
Fix: Pin host and target deployment targets in
.bcr/presubmit.ymlbuild_flagsand the corresponding.bazelrcfiles. Values match the actual support requirements rather than picking a single number for everything:--host_macos_minimum_os11.0Package.swift's.macOS(.v11). Ensures the binary loads on whatever older macOS BCR's runner happens to be on.--macos_minimum_os(root SafeDI).bazelrc,verify_safedi_buildBCR task11.0--macos_minimum_os(example app).bazelrc,bcr_test_module.build_exampleBCR task14.0LoggedInView'sonChange(of:initial:_:)is macOS 14+,NameEntryView'sTextFieldinit is macOS 12+. The example app's deployment requirements are independent of SafeDI's.Verified locally:
otool -l SafeDIToolreportsminos 11.0inLC_BUILD_VERSION. The flag actually takes effect in the binary, independent of runner OS. (Earlier iterations of this PR set the host target at 12.0 then 14.0 — both wrong: 12.0 was a guess, 14.0 was higher than Package.swift's claim.)Prior art surveyed
BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN--macos_minimum_os13.012.0Every flag we use has prior art — except
--host_macos_minimum_osin.bcr/presubmit.ymlbuild_flags. None of the surveyed modules execute their tool during the BCR test (they only build it). SafeDI is genuinely uncommon: ourbcr_test_module.build_exampletask usessafedi_compile, which actually invokes SafeDITool, so dyld fires there. The--host_macos_minimum_osflag is the principled exec-config analogue of--macos_minimum_os— same flag family, same Bazel concept — but its specific use in BCRbuild_flagsis our derivation, not directly observed elsewhere.Confidence
apple_support+BAZEL_DO_NOT_DETECT_CPP_TOOLCHAINfixes Failure 1--host_macos_minimum_oskeeps SafeDITool runnable on older OSotool -lconfirmedminos 11.0inLC_BUILD_VERSION, independent of host.--host_macos_minimum_osbelongs in BCRbuild_flags--macos_minimum_osto exec config. No direct prior art because no surveyed module exec's a tool during BCR. Hard to raise without actually running on BCR's runner.Why we didn't catch this in our CI, and why we're not going to chase it
Initially tried switching the
bazeljob tomacos-15to reproduce BCR's SDK-vs-runner-OS gap. The right way to actually mirror BCR is.bazelci/presubmit.ymlon BuildKite — same infrastructure rules_swift / rules_apple use — but BazelCI is closed to non-bazelbuildprojects that aren't rules. Any GH Actions mirror we hand-build will still drift from BCR's image (CC toolchain auto-detection, exact Bazel point release).Reverted that switch. The bazel job stays on
macos-26as a general sanity gate, withbuild_targetsaligned to.bcr/presubmit.ymlso divergence is at least visible in review. BCR's presubmit is treated as the canonical macOS-on-older-OS check.Note on the retry composite action
A first iteration tried to use a multi-line YAML
|block scalar for the new explicitbazelisk buildcommand. That broke.github/actions/retry/action.yml, which splices${{ inputs.command }}straight into a bash loop and tacks&& breakonto the same source line — a multi-line command leaves&& breakorphaned. Fixed by keeping the command on a single line, with a comment inci.ymlexplaining why.Release note
These fixes ship in 2.0.0-rc-3. The existing BCR PR for 2.0.0-rc-2 is built from the published tarball and can't pick them up retroactively — it'll need to be replaced with a new submission targeting rc-3.
Test plan
bazelisk build //Sources/SafeDI:SafeDI //Sources/SafeDICore:SafeDICore //Sources/SafeDIMacros:SafeDIMacros //Sources/SafeDITool:SafeDIToolfrom repo root — passes locally(cd Examples/ExampleBazelIntegration && bazelisk build //Subproject:Subproject //ExampleBazelIntegration:ExampleBazelIntegration)— passes locallyotool -l <SafeDITool>showsminos 11.0— deployment-target flag verified inLC_BUILD_VERSION./CLI/lint.sh— clean🤖 Generated with Claude Code