Skip to content

[android-sdk] strip cross-arch swiftmodule leakage from per-arch overlays#548

Open
ken0nek wants to merge 1 commit into
swiftlang:mainfrom
ken0nek:fix/android-sdk-symmetric-overlays
Open

[android-sdk] strip cross-arch swiftmodule leakage from per-arch overlays#548
ken0nek wants to merge 1 commit into
swiftlang:mainfrom
ken0nek:fix/android-sdk-symmetric-overlays

Conversation

@ken0nek
Copy link
Copy Markdown

@ken0nek ken0nek commented Apr 27, 2026

Summary

The Android Swift SDK artifactbundle produced by swift-ci/sdks/android/scripts/build.sh ships non-monotonic per-arch overlays for 16 stdlib + sdk-overlay swiftmodule directories. Each per-arch resource dir (swift-<arch>/android/<Module>.swiftmodule/ and swift_static-<arch>/android/<Module>.swiftmodule/) accumulates .swiftinterface / .swiftmodule / .swiftdoc / .abi.json / .private.swiftinterface files from every cross-compile host that built before it, instead of carrying only its own arch's triple. This patch prunes the leaked entries during the bundling stage so each arch's overlay matches the per-arch layout already produced by Foundation, Dispatch, XCTest, and swift-testing.

Concrete asymmetry in the current bundle

Inspection of swift-6.3.1-RELEASE_android.artifactbundle (Apple-built, SBOM dated 2026-04-14, default TARGET_ARCHS=aarch64,x86_64,armv7):

Resource dir Android.swiftmodule/ entries aarch64 files armv7 files x86_64 files
swift-aarch64/android/ 5 yes
swift-x86_64/android/ 10 yes yes
swift-armv7/android/ 15 yes yes yes

The same accumulation hits 16 modules: Swift, SwiftOnoneSupport, Android, _Concurrency, _Differentiation, Distributed, Synchronization, Observation, Cxx, CxxStdlib, _Builtin_float, _math, _Volatile, _RegexParser, _StringProcessing, RegexBuilder — under both swift-<arch>/ and swift_static-<arch>/.

Foundation, FoundationEssentials, FoundationInternationalization, FoundationNetworking, FoundationXML, Dispatch, XCTest, and Testing are correctly partitioned (each arch's dir contains only its own triple).

Root cause

The discriminator matches the install path used in build.sh:

  • --swift-install-components='compiler;clang-resource-dir-symlink;license;stdlib;sdk-overlay' runs inside the Swift build-script orchestrator from a shared SWIFT_BUILD_ROOT=${build_dir}/swift-project across every --cross-compile-hosts=android-${arch} iteration. The install step copies per-host swiftmodule output from every host that built before it. Leaky.
  • --install-libdispatch --install-foundation --xctest --install-xctest --swift-testing --install-swift-testing are separate per-arch CMake invocations earlier in the arch loop, with strictly per-arch install dirs. Clean.

By the time bundling reaches the mv lib/swift lib/swift-${arch} rename (line 666 of build.sh), ${sdk_staging}/${arch}/usr/lib/swift/android/*.swiftmodule/ already contains cross-arch artifacts. The rename + subsequent rsync propagates the leakage to the final published swift-${arch}/android/ directory.

Why this matters downstream

swift-sdk.json declares 27 targetTriples (3 arches × 9 API levels). When SwiftPM's targetTriples lookup picks the wrong swiftResourcesPath for the requested --triple (a non-determinism in SwiftSDKBundleStore.selectBundle's artifact-ID match path, fixed in SwiftPM trunk by 2f506071ad; regression test in swiftlang/swift-package-manager#9988), the wrong-arch .swiftinterface load succeeds because of the leaked files. The failure surfaces one step later as a Clang modulemap miss:

error: missing required module 'SwiftAndroid'
  import Android
         ^

instead of the cleaner

error: could not find module 'Foundation' for target 'aarch64-unknown-linux-android';
  found: x86_64-unknown-linux-android, at: …/swift-x86_64/android/Foundation.swiftmodule/aarch64-unknown-linux-android

Symmetric per-arch overlays don't eliminate the SwiftPM lottery, but they make its failure mode transparent and consistent with how Foundation already behaves.

The fix

Single 24-line addition to swift-ci/sdks/android/scripts/build.sh, inside the bundling-stage arch loop, after the lib/swift → lib/swift-${arch} rename and before the rsync into swift_res_root:

keep_triple_prefix="${arch}-unknown-linux-android."
for swiftmod_root in lib/swift-$arch/android lib/swift_static-$arch/android; do
    for swiftmod_dir in ${swiftmod_root}/*.swiftmodule; do
        [ -d "${swiftmod_dir}" ] || continue
        find "${swiftmod_dir}" -mindepth 1 -maxdepth 1 -type f \
            ! -name "${keep_triple_prefix}*" -delete
    done
done

The triple-prefix <arch>-unknown-linux-android. is correct for all three arches: filenames in the bundle use armv7-unknown-linux-android.<ext> even though the linker triple is arm-linux-androideabi. Already-clean directories (Foundation et al.) match the keep pattern entirely, so the prune is a no-op there.

Verification (no Linux container, NDK, or build-script rebuild required)

Paste-and-run against any installed swift-6.3.1-RELEASE_android.artifactbundle. The script copies the worst-case arch (armv7, last under default order) to a tmp dir, applies the prune, and asserts symmetry:

#!/usr/bin/env bash
set -euo pipefail
src="${1:-$HOME/Library/org.swift.swiftpm/swift-sdks/swift-6.3.1-RELEASE_android.artifactbundle/swift-android/swift-resources/usr/lib}"
[ -d "$src/swift-armv7" ] || { echo "Missing $src/swift-armv7 — install the Android SDK bundle first." >&2; exit 1; }

dst=$(mktemp -d -t swift-android-prune-XXXX)
trap 'rm -rf "$dst"' EXIT
cp -a "$src/swift-armv7"        "$dst/swift-armv7"
cp -a "$src/swift_static-armv7" "$dst/swift_static-armv7"

cd "$dst"
arch=armv7
keep_triple_prefix="${arch}-unknown-linux-android."
for swiftmod_root in swift-$arch/android swift_static-$arch/android; do
    for swiftmod_dir in ${swiftmod_root}/*.swiftmodule; do
        [ -d "${swiftmod_dir}" ] || continue
        find "${swiftmod_dir}" -mindepth 1 -maxdepth 1 -type f \
            ! -name "${keep_triple_prefix}*" -delete
    done
done

leaky=0
for dir in swift-$arch/android/*.swiftmodule swift_static-$arch/android/*.swiftmodule; do
    [ -d "$dir" ] || continue
    cross=$(ls "$dir" | grep -cE '^(aarch64|x86_64)-' || true)
    (( cross == 0 )) || leaky=$((leaky+1))
done

echo "Leaky swiftmodule directories after prune: $leaky  (expect 0)"
echo
echo "swift-armv7/android/Android.swiftmodule/ after prune (expect 5 armv7-only files):"
ls swift-armv7/android/Android.swiftmodule

Expected output:

Leaky swiftmodule directories after prune: 0  (expect 0)

swift-armv7/android/Android.swiftmodule/ after prune (expect 5 armv7-only files):
armv7-unknown-linux-android.abi.json
armv7-unknown-linux-android.private.swiftinterface
armv7-unknown-linux-android.swiftdoc
armv7-unknown-linux-android.swiftinterface
armv7-unknown-linux-android.swiftmodule

Locally observed before/after counts:

Module class Before prune After prune
16 leaky swift-armv7/android/*.swiftmodule/ 15 entries each 5 entries each
16 leaky swift_static-armv7/android/*.swiftmodule/ 12 entries each 4 entries each
Foundation.swiftmodule/ (already clean) 2 2 (unchanged)
Non-swiftmodule files (lib*.so, lib*.a, lib*.modulemap, etc.) n/a unchanged

Scope

  • The packaging stage is the right scope: each arch's overlay should reflect that arch alone, regardless of whatever the upstream install step happens to dump into the staging tree. This matches the per-arch contract Foundation/Dispatch/XCTest/Testing already meet.
  • The underlying shared-SWIFT_BUILD_ROOT accumulation in swiftlang/swift's utils/build-script-impl install path is not touched here; addressing it properly risks regressions for other consumers and is out of scope for this packaging-level fix.
  • No change to info.json, swift-sdk.json, the targetTriples map, or the swift-resources/usr/lib/swift-<arch> directory layout. Reviewers can compare bundle directory listings before/after to confirm — only file contents of the 32 leaky .swiftmodule/ directories change.

Related

  • swiftlang/swift-package-manager#9988 — regression test locking in the fix for the targetTriples selection lottery. SwiftSDKBundleStore.selectBundle's artifact-ID match path was iterating over the 27-entry triple map in hash-randomized order, producing a non-deterministic swiftResourcesPath that sometimes pointed at the wrong arch (the -target aarch64-…-android33 paired with -resource-dir …/swift-armv7 mismatch observable in description.json). Fixed in SwiftPM trunk by 2f506071ad; the new test synthesizes a multi-triple Android-shaped bundle and asserts selection across all (arch, API-level) combos. This swift-docker PR is independent — symmetric overlays improve bundle correctness whether SwiftPM's selection logic regresses again or not.
  • swiftlang/swift-package-manager#9229 — broader in-flight fix (open since 2025-10-08) that covers the same swift sdk configure --swift-static-resources-path typo in SwiftSDK.PathsConfiguration.merge(with:relativeTo:) (assigning to self.swiftResourcesPath instead of self.swiftStaticResourcesPath), a sibling typo in Sources/SwiftSDKCommand/Configuration/SetConfiguration.swift:97, and a rework of selectSwiftSDK to surface ambiguous matches. Adjacent multi-resource-path SDK territory, but unrelated to the leakage fixed here. (A parallel single-typo fix was filed as #9987 and closed 2026-04-27 as duplicate of #9229.)

…lays

The Swift stdlib and sdk-overlay install steps run from a shared
SWIFT_BUILD_ROOT across all cross-compile-host iterations of build.sh.
Each iteration's install pulls in .swiftinterface / .swiftmodule files
from every host that built before it, leaving non-monotonic per-arch
overlays in the bundle:

  lib/swift-aarch64/android/Android.swiftmodule/   - aarch64 only
  lib/swift-x86_64/android/Android.swiftmodule/    - aarch64 + x86_64
  lib/swift-armv7/android/Android.swiftmodule/     - all three

Foundation, Dispatch, XCTest, and swift-testing are built per-arch via
separate CMake invocations and are correctly partitioned, so the leak
is confined to the 16 stdlib + sdk-overlay swiftmodule dirs (Swift,
Android, _Concurrency, Cxx, CxxStdlib, _Builtin_float, _math,
_RegexParser, _StringProcessing, _Volatile, _Differentiation,
Distributed, Observation, RegexBuilder, SwiftOnoneSupport,
Synchronization).

The asymmetry interacts badly with SwiftPM's targetTriples lookup:
when SwiftPM picks the wrong swiftResourcesPath for the requested
--triple, the wrong-arch .swiftinterface load succeeds because of the
leaked files, and the failure surfaces one step later as a Clang
modulemap miss ("missing required module 'SwiftAndroid'") instead of
the cleaner "could not find module 'Foundation' for target ...".

Strip leaked entries during the bundling stage, after the per-arch
rename and before the rsync into swift_res_root, so each
lib/swift{,_static}-<arch>/android/*.swiftmodule/ contains only this
arch's triple - matching the layout already produced by Foundation
and friends.
@ken0nek ken0nek marked this pull request as ready for review April 27, 2026 04:36
@finagolfin
Copy link
Copy Markdown
Member

Sorry to break it to you after this long exegesis, 😉 but the extra modules issue has long been known, #510, and I fixed it weeks ago:

> ls ~/.swiftpm/swift-sdks/swift-DEVELOPMENT-SNAPSHOT-2026-04-27-a_android.artifactbundle/swift-android/swift-resources/usr/lib/swift*/android/Android.swiftmodule
/Users/finagolfin/.swiftpm/swift-sdks/swift-DEVELOPMENT-SNAPSHOT-2026-04-27-a_android.artifactbundle/swift-android/swift-resources/usr/lib/swift_static-aarch64/android/Android.swiftmodule:
aarch64-unknown-linux-android.private.swiftinterface
aarch64-unknown-linux-android.swiftdoc
aarch64-unknown-linux-android.swiftinterface
aarch64-unknown-linux-android.swiftmodule
aarch64-unknown-linux-android.swiftsourceinfo

/Users/finagolfin/.swiftpm/swift-sdks/swift-DEVELOPMENT-SNAPSHOT-2026-04-27-a_android.artifactbundle/swift-android/swift-resources/usr/lib/swift_static-armv7/android/Android.swiftmodule:
armv7-unknown-linux-android.private.swiftinterface
armv7-unknown-linux-android.swiftdoc
armv7-unknown-linux-android.swiftinterface
armv7-unknown-linux-android.swiftmodule
armv7-unknown-linux-android.swiftsourceinfo

/Users/finagolfin/.swiftpm/swift-sdks/swift-DEVELOPMENT-SNAPSHOT-2026-04-27-a_android.artifactbundle/swift-android/swift-resources/usr/lib/swift_static-x86_64/android/Android.swiftmodule:
x86_64-unknown-linux-android.private.swiftinterface
x86_64-unknown-linux-android.swiftdoc
x86_64-unknown-linux-android.swiftinterface
x86_64-unknown-linux-android.swiftmodule
x86_64-unknown-linux-android.swiftsourceinfo

/Users/finagolfin/.swiftpm/swift-sdks/swift-DEVELOPMENT-SNAPSHOT-2026-04-27-a_android.artifactbundle/swift-android/swift-resources/usr/lib/swift-aarch64/android/Android.swiftmodule:
aarch64-unknown-linux-android.abi.json
aarch64-unknown-linux-android.private.swiftinterface
aarch64-unknown-linux-android.swiftdoc
aarch64-unknown-linux-android.swiftinterface
aarch64-unknown-linux-android.swiftmodule
aarch64-unknown-linux-android.swiftsourceinfo

/Users/finagolfin/.swiftpm/swift-sdks/swift-DEVELOPMENT-SNAPSHOT-2026-04-27-a_android.artifactbundle/swift-android/swift-resources/usr/lib/swift-armv7/android/Android.swiftmodule:
armv7-unknown-linux-android.abi.json
armv7-unknown-linux-android.private.swiftinterface
armv7-unknown-linux-android.swiftdoc
armv7-unknown-linux-android.swiftinterface
armv7-unknown-linux-android.swiftmodule
armv7-unknown-linux-android.swiftsourceinfo

/Users/finagolfin/.swiftpm/swift-sdks/swift-DEVELOPMENT-SNAPSHOT-2026-04-27-a_android.artifactbundle/swift-android/swift-resources/usr/lib/swift-x86_64/android/Android.swiftmodule:
x86_64-unknown-linux-android.abi.json
x86_64-unknown-linux-android.private.swiftinterface
x86_64-unknown-linux-android.swiftdoc
x86_64-unknown-linux-android.swiftinterface
x86_64-unknown-linux-android.swiftmodule
x86_64-unknown-linux-android.swiftsourceinfo

> ls ~/.swiftpm/swift-sdks/swift-6.3-DEVELOPMENT-SNAPSHOT-2026-04-25-a_android.artifactbundle/swift-android/swift-resources/usr/lib/swift*/android/Android.swiftmodule
/Users/finagolfin/.swiftpm/swift-sdks/swift-6.3-DEVELOPMENT-SNAPSHOT-2026-04-25-a_android.artifactbundle/swift-android/swift-resources/usr/lib/swift_static-aarch64/android/Android.swiftmodule:
aarch64-unknown-linux-android.private.swiftinterface
aarch64-unknown-linux-android.swiftdoc
aarch64-unknown-linux-android.swiftinterface
aarch64-unknown-linux-android.swiftmodule

/Users/finagolfin/.swiftpm/swift-sdks/swift-6.3-DEVELOPMENT-SNAPSHOT-2026-04-25-a_android.artifactbundle/swift-android/swift-resources/usr/lib/swift_static-armv7/android/Android.swiftmodule:
armv7-unknown-linux-android.private.swiftinterface
armv7-unknown-linux-android.swiftdoc
armv7-unknown-linux-android.swiftinterface
armv7-unknown-linux-android.swiftmodule

/Users/finagolfin/.swiftpm/swift-sdks/swift-6.3-DEVELOPMENT-SNAPSHOT-2026-04-25-a_android.artifactbundle/swift-android/swift-resources/usr/lib/swift_static-x86_64/android/Android.swiftmodule:
x86_64-unknown-linux-android.private.swiftinterface
x86_64-unknown-linux-android.swiftdoc
x86_64-unknown-linux-android.swiftinterface
x86_64-unknown-linux-android.swiftmodule

/Users/finagolfin/.swiftpm/swift-sdks/swift-6.3-DEVELOPMENT-SNAPSHOT-2026-04-25-a_android.artifactbundle/swift-android/swift-resources/usr/lib/swift-aarch64/android/Android.swiftmodule:
aarch64-unknown-linux-android.abi.json
aarch64-unknown-linux-android.private.swiftinterface
aarch64-unknown-linux-android.swiftdoc
aarch64-unknown-linux-android.swiftinterface
aarch64-unknown-linux-android.swiftmodule

/Users/finagolfin/.swiftpm/swift-sdks/swift-6.3-DEVELOPMENT-SNAPSHOT-2026-04-25-a_android.artifactbundle/swift-android/swift-resources/usr/lib/swift-armv7/android/Android.swiftmodule:
armv7-unknown-linux-android.abi.json
armv7-unknown-linux-android.private.swiftinterface
armv7-unknown-linux-android.swiftdoc
armv7-unknown-linux-android.swiftinterface
armv7-unknown-linux-android.swiftmodule

/Users/finagolfin/.swiftpm/swift-sdks/swift-6.3-DEVELOPMENT-SNAPSHOT-2026-04-25-a_android.artifactbundle/swift-android/swift-resources/usr/lib/swift-x86_64/android/Android.swiftmodule:
x86_64-unknown-linux-android.abi.json
x86_64-unknown-linux-android.private.swiftinterface
x86_64-unknown-linux-android.swiftdoc
x86_64-unknown-linux-android.swiftinterface
x86_64-unknown-linux-android.swiftmodule

It just didn't make it into the first two 6.3 tags, but will be in the next patch release.

@finagolfin
Copy link
Copy Markdown
Member

This is fixed in the 6.3.2 release, as I said it would be:

> ls .swiftpm/swift-sdks/swift-6.3.2-RELEASE_android.artifactbundle/swift-android/swift-resources/usr/lib/swift*/android/Android.swiftmodule/
.swiftpm/swift-sdks/swift-6.3.2-RELEASE_android.artifactbundle/swift-android/swift-resources/usr/lib/swift-aarch64/android/Android.swiftmodule/:
aarch64-unknown-linux-android.abi.json                aarch64-unknown-linux-android.swiftinterface
aarch64-unknown-linux-android.private.swiftinterface  aarch64-unknown-linux-android.swiftmodule
aarch64-unknown-linux-android.swiftdoc

.swiftpm/swift-sdks/swift-6.3.2-RELEASE_android.artifactbundle/swift-android/swift-resources/usr/lib/swift-armv7/android/Android.swiftmodule/:
armv7-unknown-linux-android.abi.json                armv7-unknown-linux-android.swiftinterface
armv7-unknown-linux-android.private.swiftinterface  armv7-unknown-linux-android.swiftmodule
armv7-unknown-linux-android.swiftdoc

.swiftpm/swift-sdks/swift-6.3.2-RELEASE_android.artifactbundle/swift-android/swift-resources/usr/lib/swift_static-aarch64/android/Android.swiftmodule/:
aarch64-unknown-linux-android.private.swiftinterface  aarch64-unknown-linux-android.swiftinterface
aarch64-unknown-linux-android.swiftdoc                aarch64-unknown-linux-android.swiftmodule

.swiftpm/swift-sdks/swift-6.3.2-RELEASE_android.artifactbundle/swift-android/swift-resources/usr/lib/swift_static-armv7/android/Android.swiftmodule/:
armv7-unknown-linux-android.private.swiftinterface  armv7-unknown-linux-android.swiftinterface
armv7-unknown-linux-android.swiftdoc                armv7-unknown-linux-android.swiftmodule

.swiftpm/swift-sdks/swift-6.3.2-RELEASE_android.artifactbundle/swift-android/swift-resources/usr/lib/swift_static-x86_64/android/Android.swiftmodule/:
x86_64-unknown-linux-android.private.swiftinterface  x86_64-unknown-linux-android.swiftinterface
x86_64-unknown-linux-android.swiftdoc                x86_64-unknown-linux-android.swiftmodule

.swiftpm/swift-sdks/swift-6.3.2-RELEASE_android.artifactbundle/swift-android/swift-resources/usr/lib/swift-x86_64/android/Android.swiftmodule/:
x86_64-unknown-linux-android.abi.json                x86_64-unknown-linux-android.swiftinterface
x86_64-unknown-linux-android.private.swiftinterface  x86_64-unknown-linux-android.swiftmodule
x86_64-unknown-linux-android.swiftdoc

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