Skip to content

Put together an Android SDK bundle #80788

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
5 tasks
finagolfin opened this issue Apr 13, 2025 · 12 comments
Open
5 tasks

Put together an Android SDK bundle #80788

finagolfin opened this issue Apr 13, 2025 · 12 comments
Assignees
Labels
Android Platform: Android cross-compilation Area → utils: Cross-compilation of project sources task

Comments

@finagolfin
Copy link
Member

Description

Mishal recently asked the Android workgroup to submit a Dockerfile which builds an Android SDK for the 6.2 and trunk branches, so @marcprux and I will be putting one together in the coming weeks. Here's a checklist of the decisions that have to be made:

  • What Android API should the Swift runtime be built against? We've tentatively decided on API 28, as that is when most of the posix_spawn APIs were added to the Bionic libc, which some parts of Foundation depend on.
  • Do we bundle the Android C/C++ sysroot from the NDK with the Swift SDK? Probably safest not to, which means we'll have to try out the existing SDK bundle support for using an external C/C++ sysroot now.
  • Do we support FoundationXML and FoundationNetworking, which will require cross-compiling their libxml2, libcurl, and BoringSSL dependencies also? We plan to look into cross-compiling all those libraries from source for Android.
  • The new Testing library and a few other toolchain product repos have shifted to using CMake Toolchain files when configuring their build for linux and mac. We need to investigate doing the same for Android.
  • Testing calls the backtrace() API, which wasn't added to Bionic till API 33. Investigate if that can be disabled for now, or if we want to add a backport library.

Additional information

Much of this already works in build-script and the corelibs, but a few tweaks will be needed.

@finagolfin finagolfin added Android Platform: Android cross-compilation Area → utils: Cross-compilation of project sources task labels Apr 13, 2025
@finagolfin finagolfin self-assigned this Apr 13, 2025
@finagolfin finagolfin moved this to Todo in Swift on Android Apr 13, 2025
@marcprux
Copy link
Contributor

My work-in-progress PR on this is at swift-android-sdk/swift-docker#1. It is modeled on the Musl (static Linux) build script (swiftlang/swift-docker#389).

My current assumptions – all of which are open to discussion – are:

What Android API should the Swift runtime be built against? We've tentatively decided on API 28, as that is when most of the posix_spawn APIs were added to the Bionic libc, which some parts of Foundation depend on.

28 supposedly covers 92% of Android devices, so I feel it is a good level.

Do we bundle the Android C/C++ sysroot from the NDK with the Swift SDK? Probably safest not to, which means we'll have to try out the existing SDK bundle support for using an external C/C++ sysroot now.

When we discussed this at finagolfin/swift-android-sdk#163 (comment), I think we determined that the subset of the NDK that would need to be bundled with the Swift SDK is redistributable.

If we do decide to exclude it, are we expecting that people will build with a command like:

~/Library/Developer/Toolchains/swift-6.1-RELEASE.xctoolchain/usr/bin/swift build --swift-sdk aarch64-unknown-linux-android24 -Xswiftc -sysroot -Xswiftc ~/Library/Android/sdk/ndk/27.0.12077973/prebuilt/darwin-x86_64

Has this been tested? I tried a quick experiment where I edit swift-sdk.json to remove the sdkRootPath and swiftResourcesPath for aarch64-unknown-linux-android24 and instead specify it with the command above, and it fails when those keys are missing.

Do we support FoundationXML and FoundationNetworking, which will require cross-compiling their libxml2, libcurl, and BoringSSL dependencies also? We plan to look into cross-compiling all those libraries from source for Android.

I've got those all building and linking, so I think we will be good for including FoundationXML and FoundationNetworking (notwithstanding issues like swiftlang/swift-corelibs-foundation#5163).

The new Testing library and a few other toolchain product repos have shifted to using CMake Toolchain files when configuring their build for linux and mac. We need to investigate doing the same for Android.

Testing seems to be building fine with build-script for me. Can you elaborate on this?

Testing calls the backtrace() API, which wasn't added to Bionic till API 33. Investigate if that can be disabled for now, or if we want to add a backport library.

I just patch out the call to backtrace() and it builds, but I don't know the consequences of this. Would it just be lacking cosmetic assistance with test failures? As I mentioned at finagolfin/swift-android-sdk#174 (comment), we could probably just pull execinfo.c into Testing and bring back support for it.

@finagolfin
Copy link
Member Author

My work-in-progress PR on this is at swift-android-sdk/swift-docker#1. It is modeled on the Musl (static Linux) build script

Thanks for working on this, 😄 I don't like that it's a bunch of bash scripts so far though. I understand why Alastair did that for the static linux SDK, as he didn't use build-script to build much of it, but you're only building a few libraries outside of build-script here.

Also, I just checked and it looks like @etcwilde added support for building curl and libxml2 from build-script a couple years ago, both using CMake toolchain files, would be better if we just modified that to cross-compile for Android instead.

we determined that the subset of the NDK that would need to be bundled with the Swift SDK is redistributable.

I said that I think it's open source and was willing to redistribute it, but I don't think that's a risk worth taking for the official SDK, especially since some have expressed that they'd like to use it with older NDKs too.

are we expecting that people will build with a command like

No, the SDK bundle spec explicitly discusses that since platform C/C++ SDKs are often proprietary and cannot be redistributed, that it supports configuring external C/C++ SDKs like the NDK too.

Has this been tested? I tried a quick experiment where I edit swift-sdk.json to remove the sdkRootPath and swiftResourcesPath for aarch64-unknown-linux-android24

No, I mentioned to you privately last week that I intend to try out "using an external NDK instead" later this week. I fully expect that we will find bugs in that external SDK logic that will need to be fixed too.

Testing seems to be building fine with build-script for me. Can you elaborate on this?

It only works because you're using my patches to pass those cross-compilation flags in, but we definitely need to switch over to the favored CMake toolchain file approach instead.

I just patch out the call to backtrace() and it builds, but I don't know the consequences of this. Would it just be lacking cosmetic assistance with test failures?

I don't know, that's what we need to check.

we could probably just pull execinfo.c into Testing and bring back support for it.

Since that file uses a BSD license instead, I doubt that they'd accept it, and I'm not sure if we should use it in this official SDK bundle either.

@xavgru12
Copy link

Thanks for working on this, 😄 I don't like that it's a bunch of bash scripts so far though. I understand why Alastair did that for the static linux SDK, as he didn't use build-script to build much of it, but you're only building a few libraries outside of build-script here.

What is the reason for using bash scripts for static Linux SDK?
Standalone and better maintainable?

I would love to see both the static Linux SDK as well as Android support inside the build scripts without using the bash scripts.

I asked here
and he mentioned that build scripts are being worked on. Hopefully the cmake toolchain file will be used for all components/libraries. Maybe ask for the state of the new build scripts/investigate them, so we combine forces here and work out a plan?

@etcwilde
Copy link
Contributor

I believe the other half of the reason that @al45tair did that for the static SDK was to avoid building the compiler as part of the SDK build. The static SDK builds are downstream from the nightly toolchain and use the nightly toolchain to build to ensure that the swift modules produced would work with a pre-downloaded nightly since the modules are tied to the compiler that built them.

This is both for performance and for convenience of folks using the SDK.

@marcprux
Copy link
Contributor

I've got the scripts building and packaging the SDK. You can see a successful run at https://github.com/swift-android-sdk/swift-docker/actions/runs/14585532437, which builds and tests both a full SDK (swift-6.1-RELEASE_android-0.1.artifactbundle.tar.gz) as well as a limited x86_64-only one (swift-6.1-RELEASE_android-0.1.artifactbundle-x86_64.tar.gz), which just exists to speed up the CI testing (since it builds in about half the time as the full 3-arch build).

I don't like that it's a bunch of bash scripts so far though

Me neither, but that's where we are at right now. The various moving parts are:

  1. The master build script that invokes fetch-source.sh, applies @finagolfin's patches, and then invokes build.sh
  2. fetch-source.sh which gets libxml2, curl, and boringssl, and swift (for the specific tag).
  3. build.sh which performs the build for each specified architecture and assembles it into an artifactbundle.
  4. The pull_request.yml CI which runs build, uploads the artifacts, installs the freshly-built SDK and build and tests swift-algorithms against an Android emulator.

The structure of the SDK is basically identical to @finagolfin's (https://github.com/finagolfin/swift-android-sdk/releases). I expect we will be changing this over time, but it was a quick and proven way to get started. However, the scripts only handle building the 6.1-release tag. Supporting devel and trunk would likely just require conditional patching (or hopefully no patching at all!)

One wart is that we need to manually un-set the ANDROID_NDK_ROOT environment variable in order to build anything (swiftlang/swift-driver#1879).

Sundry responses:

I just checked and it looks like @etcwilde added support for building curl and libxml2 from build-script a couple years ago, both using CMake toolchain files, would be better if we just modified that to cross-compile for Android instead.

I noticed, but the curl is built without SSL support, and there's no build script for OpenSSL or BoringSSL. I expect it could be added, I'd just need to dig into it. libxml2 should be usable as-is, though – it hadn't occurred to me to try it, just because the static-linux script was building its own libxml2, and I assumed there was some good reason for it.

I intend to try out "using an external NDK instead" later this week. I fully expect that we will find bugs in that external SDK logic that will need to be fixed too.

Great! Let me know if I can help with this in any way.

@finagolfin
Copy link
Member Author

What is the reason for using bash scripts for static Linux SDK?
Standalone and better maintainable?

My guess is that he is not that familiar with build-script and found it easier to script the static SDK build himself.

the other half of the reason that @al45tair did that for the static SDK was to avoid building the compiler as part of the SDK build. The static SDK builds are downstream from the nightly toolchain and use the nightly toolchain to build

No, the static SDK script builds a fresh Swift compiler each time, then uses that freshly-built compiler to build the stdlib and corelibs. What you describe is possible- it is how my Android builds and Marc's pull work- but not the way the static Musl SDK is currently built.

there's no build script for OpenSSL or BoringSSL. I expect it could be added, I'd just need to dig into it.

Please do.

Great! Let me know if I can help with this in any way.

I've privately detailed a particular config you can try out, let me know how that works.

@marcprux
Copy link
Contributor

Following up on @finagolfin's comment in the Android workflow PR (swiftlang/github-workflows#106 (comment)) here, since it relates directly to the SDK bundle:

I agree that we should get your SDK bundle pull in once it builds 6.2 and 6.3 in a linux Docker and we've tried using it with an external NDK instead.

I've updated the PR swiftlang/swift-docker#467 to build for each of release/devel/trunk, so the 6.2 nightly snapshots are being created. No Docker yet, and Testing is not yet being built with CMake, but the NDK and SDK are no longer intermingled. However, the NDK sysroot is still included in the .artifactbundle under its own root directory because it needs swiftrt.o, but the NDK (sdkRootPath) and resource (swiftResourcesPath) are independent of each other. I.e., with the swift-sdk.json:

{
  "schemaVersion": "4.0",
  "targetTriples": { 
    "x86_64-unknown-linux-android28": {
      "sdkRootPath": "ndk-sysroot",
      "swiftResourcesPath": "swift-resources/usr/lib/swift-x86_64",
      "swiftStaticResourcesPath": "swift-resources/usr/lib/swift_static-x86_64",
      "toolsetPaths": [ "swift-toolset.json" ]
    },

an abridged yet representative view of the new .artifactbundle layout is:

+ tree /home/runner/work/_temp/swift-android-sdk/build/swift-6.2-DEVELOPMENT-SNAPSHOT-2025-04-24-a-android-0.1.artifactbundle --filesfirst --prune -P Android.swiftmodule -P 'libswiftAndroid.*' -P 'libFoundation.*' -P swiftrt.o -P 'swift*.json' -P info.json -P api-level.h -P android.modulemap -P SwiftAndroidNDK.h -P bridging.modulemap
/home/runner/work/_temp/swift-android-sdk/build/swift-6.2-DEVELOPMENT-SNAPSHOT-2025-04-24-a-android-0.1.artifactbundle
├── info.json
└── swift-android
    ├── swift-sdk.json
    ├── swift-toolset.json
    ├── ndk-sysroot
    │   └── usr
    │       ├── include
    │       │   └── android
    │       │       └── api-level.h
    │       └── lib
    │           └── swift
    │               └── android
    │                   └── x86_64
    │                       └── swiftrt.o -> ../../../../../../swift-resources/usr/lib/swift-x86_64/android/x86_64/swiftrt.o
    └── swift-resources
        └── usr
            ├── include
            │   └── swift
            │       └── bridging.modulemap
            └── lib
                ├── swift-x86_64
                │   └── android
                │       ├── libFoundation.so
                │       ├── libswiftAndroid.so
                │       └── x86_64
                │           ├── SwiftAndroidNDK.h
                │           ├── android.modulemap
                │           └── swiftrt.o
                └── swift_static-x86_64
                    └── android
                        ├── libFoundation.a
                        ├── libswiftAndroid.a
                        └── x86_64
                            ├── SwiftAndroidNDK.h
                            ├── android.modulemap
                            └── swiftrt.o

There are two blockers for removing the NDK completely from the .artifactbundle and just referencing it externally:

  1. Is there a succinct explanation for why swiftrt.o is being sought in the wrong place? @etcwilde? @compnerd? I know this had been documented at length ([Unix] Go back to only checking the runtime resource path for swiftrt.o swift-driver#1822, Have the frontend and new swift-driver look in an external -sdk for non-Darwin platform runtime libraries and modules too #79621, Stop pulling sources from compiler into runtimes #77462, etc.), but it seems to not have drawn much attention or elicited any action.
  2. I haven't found a way to exclude the sdkRootPath and configure the installed Swift SDK to use a custom location for the NDK. Removing/nulling the field in the swift-sdk.json file raises an error ("sdkRootPath … Cannot get value of type String -- found null value instead"), and setting it to a bogus value "NONE" and attempting to override it with the command swift sdk configure --sdk-root-path …/android-ndk-r27c/toolchains/llvm/prebuilt/linux-x86_64/sysroot swift-6.2-DEVELOPMENT-SNAPSHOT-2025-04-24-a-android-0.1 x86_64-unknown-linux-android28 reports: "info: These properties of Swift SDK … for target triple … were successfully updated: sdkRootPath" but then doesn't seem to have any effect: swift sdk configure --show-configuration … still just shows the file's sdkRootPath: …/NONE and attempting to build fails. @MaxDesiatov, would you expect this to work, or do you know of anyone who has gotten this to work? It seems that sdkRoot was originally intended to be optional, but then got changed: swiftlang/swift-evolution@a9a77c1

If we can tackle these two issues, I think we will be in good shape for a candidate official Android SDK bundle for Swift 6.2 (outstanding patches notwithstanding).

@MaxDesiatov
Copy link
Contributor

attempting to override it with the command swift sdk configure --sdk-root-path …/android-ndk-r27c/toolchains/llvm/prebuilt/linux-x86_64/sysroot swift-6.2-DEVELOPMENT-SNAPSHOT-2025-04-24-a-android-0.1 x86_64-unknown-linux-android28 reports: "info: These properties of Swift SDK … for target triple … were successfully updated: sdkRootPath" but then doesn't seem to have any effect: swift sdk configure --show-configuration … still just shows the file's sdkRootPath: …/NONE and attempting to build fails.

This should work. Mind filing an issue on the SwiftPM repo for this?

As for making it optional, I don't quite remember the exact issue that caused it to become non-optional. After all, making it optional is technically not a breaking change, so potentially could be considered if necessary.

@finagolfin
Copy link
Member Author

Is there a succinct explanation for why swiftrt.o is being sought in the wrong place?

I suspect I'm the first to really dig into how the Driver/swift-driver worked with those Swift runtime modules/libraries when cross-compiling for non-Darwin platforms, and the others have presumably been busy and not followed my lead. I'll write up a full explanation with concrete examples of failing commands in the coming week, and push to get that fix in soon.

Removing/nulling the field in the swift-sdk.json file raises an error ("sdkRootPath … Cannot get value of type String -- found null value instead"), and setting it to a bogus value "NONE" and attempting to override it with the command swift sdk configure --sdk-root-path …/android-ndk-r27c/toolchains/llvm/prebuilt/linux-x86_64/sysroot swift-6.2-DEVELOPMENT-SNAPSHOT-2025-04-24-a-android-0.1 x86_64-unknown-linux-android28 reports: "info: These properties of Swift SDK … for target triple … were successfully updated: sdkRootPath" but then doesn't seem to have any effect: swift sdk configure --show-configuration … still just shows the file's sdkRootPath: …/NONE and attempting to build fails.

As I said last week, "I fully expect that we will find bugs in that external SDK logic that will need to be fixed too," looks like you found a pretty big one! 😄

@marcprux
Copy link
Contributor

marcprux commented Apr 30, 2025

This should work. Mind filing an issue on the SwiftPM repo for this?

Done: swiftlang/swift-package-manager#8584. The issue is that the configure command is updating some random target triple rather than the one that was specified.

@finagolfin, even if this were working, would the developer who installs the SDK be expected to run swift sdk configure --sdk-root-path $ANDROID_NDK_HOME ARCH-unknown-linux-androidAPILEVEL for every single ARCH and APILEVEL combination (36, in this case)?

I'm wondering if we should seek an alternative solution to using swift sdk configure, such as having the tool paths in swift-toolset.json point to a script that adds the right flag to the various tools. E.g., something like:

{
  "cCompiler": { "extraCLIOptions": ["-fPIC"] },
  "swiftCompiler": { "path": "swift-android-toolset/bin/swift-android-frontend", "extraCLIOptions": ["-Xclang-linker", "-fuse-ld=lld"] },
  "schemaVersion": "1.0"
}

And then making scripts like swift-android-frontend:

#!/bin/sh
swift-frontend --sdkroot ${ANDROID_NDK_HOME} "${@}"

I haven't thought this through, but it could in theory enable us to have nicer error messages when the local NDK is not installed or cannot be located.

@al45tair
Copy link
Contributor

al45tair commented May 1, 2025

What is the reason for using bash scripts for static Linux SDK?
Standalone and better maintainable?

My guess is that he is not that familiar with build-script and found it easier to script the static SDK build himself.

The reason is indeed that it's standalone and more easily maintained. In an ideal world after @etcwilde and @edymtt are done, we might be able to replace it with a CMake script.

No, the static SDK script builds a fresh Swift compiler each time, then uses that freshly-built compiler to build the stdlib and corelibs. What you describe is possible- it is how my Android builds and Marc's pull work- but not the way the static Musl SDK is currently built.

Indeed it does. I forget exactly why that was necessary — obviously I would much rather have just built the runtimes, but there was some obstacle that prevented that.

@finagolfin
Copy link
Member Author

would the developer who installs the SDK be expected to run swift sdk configure --sdk-root-path $ANDROID_NDK_HOME ARCH-unknown-linux-androidAPILEVEL for every single ARCH and APILEVEL combination (36, in this case)?

No idea, I never tried running that command, which is why you found the bug and not me! 😄 I think we should modify that SwiftPM command to be able to set the same external C/C++ SDK for all triples in a Swift SDK bundle also, as the Android NDK supports many target triples.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Android Platform: Android cross-compilation Area → utils: Cross-compilation of project sources task
Projects
Status: In Progress
Development

No branches or pull requests

6 participants