Skip to content

Fix BCR presubmit: declare apple_support and pin host deployment target#295

Merged
dfed merged 7 commits intomainfrom
fix-bcr-bazel-build
May 6, 2026
Merged

Fix BCR presubmit: declare apple_support and pin host deployment target#295
dfed merged 7 commits intomainfrom
fix-bcr-bazel-build

Conversation

@dfed
Copy link
Copy Markdown
Owner

@dfed dfed commented May 5, 2026

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)

ERROR: ... in xcode_swift_toolchain rule ...
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's xcode_swift_toolchain falls through to cc_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 root MODULE.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=1 in .bcr/presubmit.yml build_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 failed: ...
dyld: Library not loaded: /usr/lib/swift/libswift_DarwinFoundation2.dylib
  Referenced from: ... SafeDITool (built for macOS 26.0 which is newer than running OS)

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.yml build_flags and the corresponding .bazelrc files. Values match the actual support requirements rather than picking a single number for everything:

Flag Where Value Why
--host_macos_minimum_os everywhere 11.0 SafeDITool only needs to run, not exercise newer APIs. Lowest reasonable value — matches Package.swift's .macOS(.v11). Ensures the binary loads on whatever older macOS BCR's runner happens to be on.
--macos_minimum_os (root SafeDI) root .bazelrc, verify_safedi_build BCR task 11.0 Matches Package.swift — what SafeDI as a library claims to support.
--macos_minimum_os (example app) example .bazelrc, bcr_test_module.build_example BCR task 14.0 The example app's source requires it: 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 locally: otool -l SafeDITool reports minos 11.0 in LC_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

Module BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN --macos_minimum_os apple_support in MODULE.bazel Tool runs during BCR test?
swift-syntax 603.0.1 13.0 No
swift-index-store 1.9.2 12.0 No
swift-filename-matcher 2.0.1 No
asc_swift 1.6.0 No
rules_xcodeproj 4.0.1 No

Every flag we use has prior art — except --host_macos_minimum_os in .bcr/presubmit.yml build_flags. None of the surveyed modules execute their tool during the BCR test (they only build it). SafeDI is genuinely uncommon: our bcr_test_module.build_example task uses safedi_compile, which actually invokes SafeDITool, so dyld fires there. The --host_macos_minimum_os flag is the principled exec-config analogue of --macos_minimum_os — same flag family, same Bazel concept — but its specific use in BCR build_flags is our derivation, not directly observed elsewhere.

Confidence

Claim Confidence Why
apple_support + BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN fixes Failure 1 High Unanimous across five surveyed BCR Swift modules.
--host_macos_minimum_os keeps SafeDITool runnable on older OS Verified otool -l confirmed minos 11.0 in LC_BUILD_VERSION, independent of host.
Deployment target values match actual support requirements High 11.0 matches Package.swift; 14.0 for the example matches its actual API usage.
--host_macos_minimum_os belongs in BCR build_flags Medium Principled extension of --macos_minimum_os to 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.
Local CI passing implies BCR will pass Low Established earlier — local CI can't reproduce BCR's environment. BCR remains canonical.

Why we didn't catch this in our CI, and why we're not going to chase it

Initially tried switching the bazel job to macos-15 to reproduce BCR's SDK-vs-runner-OS gap. The right way to actually mirror BCR is .bazelci/presubmit.yml on BuildKite — same infrastructure rules_swift / rules_apple use — but BazelCI is closed to non-bazelbuild projects 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-26 as a general sanity gate, with build_targets aligned to .bcr/presubmit.yml so 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 explicit bazelisk build command. That broke .github/actions/retry/action.yml, which splices ${{ inputs.command }} straight into a bash loop and tacks && break onto the same source line — a multi-line command leaves && break orphaned. Fixed by keeping the command on a single line, with a comment in ci.yml explaining 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:SafeDITool from repo root — passes locally
  • (cd Examples/ExampleBazelIntegration && bazelisk build //Subproject:Subproject //ExampleBazelIntegration:ExampleBazelIntegration) — passes locally
  • otool -l <SafeDITool> shows minos 11.0 — deployment-target flag verified in LC_BUILD_VERSION
  • ./CLI/lint.sh — clean
  • CI green
  • After merge + 2.0.0-rc-3 release, replace BCR PR #8711 with one targeting rc-3 and confirm BCR presubmit passes

🤖 Generated with Claude Code

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
Copy link
Copy Markdown

codecov Bot commented May 5, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (dc5ab3f) to head (8f16e1d).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff            @@
##              main      #295   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           41        41           
  Lines         7043      7043           
=========================================
  Hits          7043      7043           
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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>
@dfed dfed marked this pull request as ready for review May 5, 2026 22:05
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>
@dfed dfed marked this pull request as draft May 5, 2026 22:42
dfed and others added 4 commits May 5, 2026 15:43
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>
@dfed dfed marked this pull request as ready for review May 6, 2026 05:34
@dfed dfed merged commit 67c6cbf into main May 6, 2026
20 checks passed
@dfed dfed deleted the fix-bcr-bazel-build branch May 6, 2026 05:37
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.

1 participant