android: drive the Android Swift bridge link with the unwrapped swift-driver (unbreak Build (Android)) #37

Merged
founder merged 2 commits from claude/android-bridge-unwrapped-swift-driver into main 2026-08-16 06:24:23 +00:00
Owner

Problem

Build (Android) has failed on every executed run since 2026-06-19 (last green: run 10734, 2026-05-28) with:

ld.lld: error: --fix-cortex-a53-843419 is only supported on AArch64 targets

Root cause (not runner/toolchain drift)

Reproduced byte-for-byte on the forge with the exact CI store paths (skip-swift-toolchain-6.2.3 vyqr501…, skip-swift-sdk-home-6.2.3 ixwjfap5…, NDK 27.0.12077973). The direct bridge builder from 4d9e989a (2026-06-14) calls the Nix toolchain's swiftc, which is the cc-wrapper-derived shell wrapper from Packages/swift-flake/build/wrapper.sh. On Linux it unconditionally injects host glibc/gcc flags into every non-wasm invocation (-L<glibc>/lib, -B<glibc>/lib, -idirafter <glibc-dev>/include, -nostdlibinc, -resource-dir=<clang-wrapper>, -Xlinker -dynamic-linker=<x86_64 ld.so>). swift-driver forwards those -L dirs to the NDK clang ahead of the Android sysroot, so -lc/-ldl resolve to x86_64 glibc's libc.so linker script; its OUTPUT_FORMAT(elf64-x86-64) flips lld's target machine to x86_64, and lld then rejects the (correct) --fix-cortex-a53-843419 the NDK clang added for aarch64-android. On macOS the wrapper clears all NIX_* flags, which is why the 06-14 rewrite looked fine locally and never worked on Linux CI.

Proof (forge, NDK lld): ld.lld -m aarch64linux --fix-cortex-a53-843419 -shared … AndroidJNIShim.o → OK; add -L<glibc>/lib … -lc → the exact error.

Fix

  • Scripts/build-android-bridge.sh: invoke the unwrapped swift-driver (--driver-mode=swiftc, SWIFT_DRIVER_SWIFT_FRONTEND_EXEC → real frontend), i.e. what the wrapper execs after its host-flag injection and what the nix cc-wrapper's multi-target warning itself recommends. Falls back to swiftc if the toolchain has no resolvable driver.
  • Verify the produced .so's ELF machine per ABI so host-flag leakage fails loudly.
  • Link the C++ JNI shim against libc++_shared explicitly and stage libc++_shared.so from the NDK sysroot next to the bridge. libswiftCore.so already NEEDS it, but runtime deps were only copied from the Swift resource dir, so the packaged bridge could never dlopen on device (SwiftHardwareKeyStoreHost swallows the UnsatisfiedLinkError, so it was silent). Bionic libs in the same sysroot dir stay excluded.
  • .forgejo/workflows/build-android.yml: bazel_target input, default build_release_stamp, so the manual workflow exercises the same target as the release lane (unsigned APK+AAB incl. the bridge) instead of a bridge-less debug APK.

Verification

  • Forge (x86_64-linux, CI store paths): Scripts/build-android-bridge.sh links both ABIs (AArch64 / X86-64, all LOADs 16 KiB-aligned, libc++_shared.so in NEEDED and staged); Scripts/android/prepare-skip-core.sh --print-env exports KEYSTORE_SKIP_BRIDGE_JNI_DIR in ~11s.
  • Namespace runner, Build: Android on this branch with bazel_target=build_release_stamp: run 17026 got past the bridge (both ABIs linked on the real runner) and then hit compileReleaseKotlin: KeyStoreWebDAVClient.kt:114 Unresolved reference 'chunkedStreamingMode' — a Kotlin bug from 1edba7a6 (2026-06-14) that was masked all along because the bridge failed first in the same genrule. Fixed in the second commit (setChunkedStreamingMode(...)); re-probe = run 17049.

Follow-up (not in this PR): Packages/swift-flake/build/wrapper.sh still contaminates any non-wasm cross target on Linux; generalizing its wasm exemption to "any non-host -target" is the principled fix but rebuilds every Swift toolchain derivation (incl. the web/wasm lane), so it is deliberately left out of this unbreak.

## Problem Build (Android) has failed on every executed run since 2026-06-19 (last green: run 10734, 2026-05-28) with: ``` ld.lld: error: --fix-cortex-a53-843419 is only supported on AArch64 targets ``` ## Root cause (not runner/toolchain drift) Reproduced byte-for-byte on the forge with the exact CI store paths (`skip-swift-toolchain-6.2.3` `vyqr501…`, `skip-swift-sdk-home-6.2.3` `ixwjfap5…`, NDK 27.0.12077973). The direct bridge builder from 4d9e989a (2026-06-14) calls the Nix toolchain's `swiftc`, which is the cc-wrapper-derived shell wrapper from `Packages/swift-flake/build/wrapper.sh`. On Linux it unconditionally injects host glibc/gcc flags into every non-wasm invocation (`-L<glibc>/lib`, `-B<glibc>/lib`, `-idirafter <glibc-dev>/include`, `-nostdlibinc`, `-resource-dir=<clang-wrapper>`, `-Xlinker -dynamic-linker=<x86_64 ld.so>`). swift-driver forwards those `-L` dirs to the NDK clang **ahead of the Android sysroot**, so `-lc`/`-ldl` resolve to x86_64 glibc's `libc.so` linker script; its `OUTPUT_FORMAT(elf64-x86-64)` flips lld's target machine to x86_64, and lld then rejects the (correct) `--fix-cortex-a53-843419` the NDK clang added for aarch64-android. On macOS the wrapper clears all `NIX_*` flags, which is why the 06-14 rewrite looked fine locally and never worked on Linux CI. Proof (forge, NDK lld): `ld.lld -m aarch64linux --fix-cortex-a53-843419 -shared … AndroidJNIShim.o` → OK; add `-L<glibc>/lib … -lc` → the exact error. ## Fix - `Scripts/build-android-bridge.sh`: invoke the **unwrapped** `swift-driver` (`--driver-mode=swiftc`, `SWIFT_DRIVER_SWIFT_FRONTEND_EXEC` → real frontend), i.e. what the wrapper execs after its host-flag injection and what the nix cc-wrapper's multi-target warning itself recommends. Falls back to `swiftc` if the toolchain has no resolvable driver. - Verify the produced `.so`'s ELF machine per ABI so host-flag leakage fails loudly. - Link the C++ JNI shim against `libc++_shared` explicitly and stage `libc++_shared.so` from the NDK sysroot next to the bridge. `libswiftCore.so` already NEEDS it, but runtime deps were only copied from the Swift resource dir, so the packaged bridge could never `dlopen` on device (`SwiftHardwareKeyStoreHost` swallows the `UnsatisfiedLinkError`, so it was silent). Bionic libs in the same sysroot dir stay excluded. - `.forgejo/workflows/build-android.yml`: `bazel_target` input, default `build_release_stamp`, so the manual workflow exercises the same target as the release lane (unsigned APK+AAB incl. the bridge) instead of a bridge-less debug APK. ## Verification - Forge (x86_64-linux, CI store paths): `Scripts/build-android-bridge.sh` links both ABIs (`AArch64` / `X86-64`, all LOADs 16 KiB-aligned, `libc++_shared.so` in NEEDED and staged); `Scripts/android/prepare-skip-core.sh --print-env` exports `KEYSTORE_SKIP_BRIDGE_JNI_DIR` in ~11s. - Namespace runner, `Build: Android` on this branch with `bazel_target=build_release_stamp`: run 17026 got past the bridge (both ABIs linked on the real runner) and then hit `compileReleaseKotlin`: `KeyStoreWebDAVClient.kt:114 Unresolved reference 'chunkedStreamingMode'` — a Kotlin bug from 1edba7a6 (2026-06-14) that was masked all along because the bridge failed first in the same genrule. Fixed in the second commit (`setChunkedStreamingMode(...)`); re-probe = run 17049. Follow-up (not in this PR): `Packages/swift-flake/build/wrapper.sh` still contaminates any non-wasm cross target on Linux; generalizing its wasm exemption to "any non-host `-target`" is the principled fix but rebuilds every Swift toolchain derivation (incl. the web/wasm lane), so it is deliberately left out of this unbreak.
android: drive the Android Swift bridge link with the unwrapped swift-driver
Some checks failed
Build: SQLite SEE / Redacted SEE compile (pull_request) Successful in 7s
Build: Apple / Check (SwiftLint) (pull_request) Successful in 16s
Build: Apple / Build (iOS) (pull_request) Successful in 29s
Build: Apple / Build (visionOS) (pull_request) Successful in 24s
Build: KaiOS / Build (KaiOS) (pull_request) Has been cancelled
Build: Linux / Build (Linux x86_64) (pull_request) Has been cancelled
Build: Linux / Build (keystore-forge x86_64) (pull_request) Has been cancelled
Build: Web / Build (Web) (pull_request) Has been cancelled
Build: Apple / Build (macOS) (pull_request) Has been cancelled
51db5733c5
Build (Android) has failed on every executed run since 2026-06-19 with

  ld.lld: error: --fix-cortex-a53-843419 is only supported on AArch64 targets

That message is a red herring. The direct bridge builder introduced in
4d9e989a calls the Nix toolchain's `swiftc`, which is the cc-wrapper-derived
shell wrapper from Packages/swift-flake/build/wrapper.sh. On Linux (not on
macOS, where the wrapper clears every NIX_* flag, which is why this looked
fine locally) it unconditionally injects host-glibc/gcc flags into any
non-wasm invocation: `-L<glibc>/lib`, `-B<glibc>/lib`, `-idirafter
<glibc-dev>/include`, `-nostdlibinc`, `-resource-dir=<clang-wrapper>`,
`-Xlinker -dynamic-linker=<x86_64 ld.so>`. swift-driver forwards the host
`-L` dirs to the NDK clang ahead of the Android sysroot, so `-lc`/`-ldl`
resolve to x86_64 glibc's `libc.so` linker script whose
`OUTPUT_FORMAT(elf64-x86-64)` flips lld's target to x86_64, at which point
lld rejects the (correct) `--fix-cortex-a53-843419` that the NDK clang added
for aarch64-android. Reproduced byte-for-byte on the forge with the same
skip-swift-toolchain / skip-swift-sdk-home / NDK 27 store paths as CI, so
this is not runner or toolchain drift.

Fix: invoke the unwrapped swift-driver directly (`--driver-mode=swiftc`,
`SWIFT_DRIVER_SWIFT_FRONTEND_EXEC` pointed at the real frontend), which is
exactly what the wrapper execs after its host-flag injection and what the
nix cc-wrapper's own multi-target warning recommends. Also:

- verify the produced .so's ELF machine per ABI so host-flag leakage fails
  loudly instead of surfacing as an unrelated lld diagnostic;
- link the C++ JNI shim against libc++_shared explicitly and stage
  libc++_shared.so from the NDK sysroot next to the bridge. libswiftCore.so
  already needs it, but the runtime deps were only copied from the Swift
  resource dir, so the packaged bridge could never dlopen on device
  (SwiftHardwareKeyStoreHost swallows the UnsatisfiedLinkError, so this was
  silent). Bionic libs in the same sysroot dir stay excluded.

Both ABIs now link (AArch64 / X86-64, 16 KiB page-aligned) and
prepare-skip-core.sh --print-env exports KEYSTORE_SKIP_BRIDGE_JNI_DIR on
x86_64-linux.

The manual "Build: Android" workflow gains a bazel_target input defaulting
to build_release_stamp so it exercises the same target as the release lane
(unsigned APK+AAB including the bridge) instead of a bridge-less debug APK.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
android: fix KeyStoreWebDAVClient chunked streaming setter (unbreak compileReleaseKotlin)
Some checks failed
Build: Linux / Build (Linux x86_64) (pull_request) Waiting to run
Build: SQLite SEE / Redacted SEE compile (pull_request) Successful in 7s
Build: Apple / Check (SwiftLint) (pull_request) Successful in 30s
Build: Apple / Build (visionOS) (pull_request) Successful in 23s
Build: Apple / Build (iOS) (pull_request) Successful in 29s
Build: Apple / Build (macOS) (pull_request) Successful in 1m52s
Build: Web / Build (Web) (pull_request) Has started running
Build: KaiOS / Build (KaiOS) (pull_request) Successful in 14m37s
Build: Linux / Build (keystore-forge x86_64) (pull_request) Has been cancelled
fde3c057c3
HttpURLConnection only has setChunkedStreamingMode(int) (no getter), so Kotlin
cannot synthesize a chunkedStreamingMode property; compileReleaseKotlin has
failed with "Unresolved reference: chunkedStreamingMode" since 1edba7a6
(2026-06-14). It was invisible until now because the Swift bridge link
failed earlier in the same Bazel genrule (run 17026 on the fix branch is the
first Android build to reach the Kotlin compiler since June).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
founder force-pushed claude/android-bridge-unwrapped-swift-driver from fde3c057c3
Some checks failed
Build: Linux / Build (Linux x86_64) (pull_request) Waiting to run
Build: SQLite SEE / Redacted SEE compile (pull_request) Successful in 7s
Build: Apple / Check (SwiftLint) (pull_request) Successful in 30s
Build: Apple / Build (visionOS) (pull_request) Successful in 23s
Build: Apple / Build (iOS) (pull_request) Successful in 29s
Build: Apple / Build (macOS) (pull_request) Successful in 1m52s
Build: Web / Build (Web) (pull_request) Has started running
Build: KaiOS / Build (KaiOS) (pull_request) Successful in 14m37s
Build: Linux / Build (keystore-forge x86_64) (pull_request) Has been cancelled
to ea760c4b4a
All checks were successful
Release: If Needed / Check (Release Needed) (push) Successful in 6s
Deploy: API / Deploy (API) (push) Successful in 3m1s
Deploy: Web / Deploy (Web) (push) Successful in 11m12s
2026-08-16 06:24:22 +00:00
Compare
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
key-store/key.store!37
No description provided.