1- # Swift cross-compilation SDK for Android
1+ # Swift cross-compilation SDK bundle for Android
22
3- The patch used to build this SDK is open source and listed below. I
3+ The patches used to build this SDK bundle are open source and listed below. I
44maintain [ a daily CI on github Actions] ( https://github.com/finagolfin/swift-android-sdk/actions?query=event%3Aschedule )
5- that [ cross-compiles the SDK from the release and development source branches of
5+ that [ cross-compiles the SDK bundle from the release and development source branches of
66the Swift toolchain for AArch64, armv7, and x86_64, builds several Swift
77packages against those SDKs, and then runs their tests in the Android x86_64
88emulator] ( https://github.com/finagolfin/swift-android-sdk/blob/main/.github/workflows/sdks.yml ) .
99
10- Now that Swift 5.10 supports [ the new SDK bundle
11- format] ( https://github.com/apple/swift-evolution/blob/main/proposals/0387-cross-compilation-destinations.md ) ,
12- I plan to distribute an Android SDK bundle in the coming months.
10+ ## Cross-compiling and testing Swift packages with the Android SDK buhdle
1311
14- ## Cross-compiling and testing Swift packages with the Android SDK
15-
16- To build with the Swift 5.10 SDK, first download [ the latest Android LTS NDK
17- 27] ( https://developer.android.com/ndk/downloads ) and [ Swift 5.10.1
18- compiler] ( https://swift.org/download/#releases ) (make sure to install the Swift
19- compiler's dependencies linked there). Unpack these archives and the SDK.
20-
21- Change the symbolic link at ` swift-5.10-android-24-sdk/usr/lib/swift/clang `
22- to point to the clang headers that come with your swift compiler, eg
12+ To build with the Swift 6 SDK bundle, first download [ the official open-source
13+ Swift 6.0.1 toolchain for linux or macOS] ( https://swift.org/download/#releases )
14+ (make sure to install the Swift dependencies linked there). Install the OSS
15+ toolchain on macOS as detailed in [ the instructions for using the static linux
16+ Musl SDK bundle at swift.org] ( https://www.swift.org/documentation/articles/static-linux-getting-started.html ) .
17+ On linux, simply download the toolchain, unpack it, and add it to your ` PATH ` .
2318
19+ Next, install the Android SDK bundle by having the Swift toolchain directly
20+ download it:
2421```
25- ln -sf /home/yourname/swift-5.10.1-RELEASE-ubuntu22.04/usr/lib/clang/15.0.0
26- swift-5.10-android-24-sdk/usr/lib/swift/clang
22+ swift sdk install https://github.com/finagolfin/swift-android-sdk/releases/download/6.0.1/swift-6.0.1-RELEASE-android-24-0.1.artifactbundle.tar.gz --checksum 28d019e91902681e04bf62b9535888441aa7c0cc96902940964013f29020d100
2723```
24+ You can check if it was properly installed by running ` swift sdk list ` .
2825
29- Next, modify the cross-compilation JSON file ` android-aarch64.json ` in this repo
30- similarly:
31-
32- 1 . All paths to the NDK should change from ` /home/finagolfin/android-ndk-r27 `
33- to the path to your NDK, ` /home/yourname/android-ndk-r27 ` .
34-
35- 2 . The path to the compiler should change from ` /home/finagolfin/swift-5.10.1-RELEASE-ubuntu22.04 `
36- to the path to your Swift compiler, ` /home/yourname/swift-5.10.1-RELEASE-ubi9 ` .
37-
38- 3 . The paths to the Android SDK should change from ` /home/finagolfin/swift-5.10-android-24-sdk `
39- to the path where you unpacked the Android SDK, ` /home/yourname/swift-5.10-android-24-sdk ` .
40-
41- Now you're ready to cross-compile a Swift package with the cross-compilation
42- configuration JSON file, ` android-aarch64.json ` , and run its tests on Android.
26+ Now you're ready to cross-compile a Swift package and run its tests on Android.
4327I'll demonstrate with the swift-argument-parser package:
4428```
4529git clone --depth 1 https://github.com/apple/swift-argument-parser.git
4630
4731cd swift-argument-parser/
4832
49- /home/yourname/swift-5.10.1-RELEASE-ubuntu22.04/usr/bin/swift build --build-tests
50- --destination ~/swift-android-sdk/android-aarch64.json
51- -Xlinker -rpath -Xlinker \$ORIGIN/swift-5.10-android-24-sdk/usr/lib/aarch64-linux-android
33+ swift build --build-tests --swift-sdk aarch64-unknown-linux-android24
5234```
53- This will cross-compile the package for Android aarch64 and produce a test
54- runner executable with the ` .xctest ` extension, in this case at
35+ This will cross-compile the package for Android aarch64 at API 24 and produce a
36+ test runner executable with the ` .xctest ` extension, in this case at
5537` .build/aarch64-unknown-linux-android24/debug/swift-argument-parserPackageTests.xctest ` .
56- It adds a rpath for where it expects the SDK libraries to be relative to the
57- test runner when run on Android.
5838
5939Sometimes the test runner will depend on additional files or executables: this
60- one depends on the example executables ` generate-manual ` , ` math ` , ` repeat ` , and
61- ` roll ` in the same build directory. Other packages use ` #file ` to point at test
62- data in the repo: I've had success moving this data with the test runner, after
63- modifying the test source so it has the path to this test data in the Android
64- test environment. See the example of [ swift-crypto on the
65- CI] ( https://github.com/finagolfin/swift-android-sdk/blob/5.10 /.github/workflows/sdks.yml#L317 ) .
66-
67- You can copy these executables and the SDK to [ an emulator or a USB
68- debugging-enabled device with adb] ( https://github.com/apple /swift/blob/release/5.10 /docs/Android.md#3-deploying-the-build-products-to-the-device ) ,
40+ one depends on the example executables ` color ` , ` generate-manual ` , ` math ` ,
41+ ` repeat ` , and ` roll ` in the same build directory. Other packages use ` #file ` to
42+ point at test data in the repo: I've had success moving this data with the test
43+ runner, after modifying the test source so it has the path to this test data in
44+ the Android test environment. See the example of [ swift-crypto on the
45+ CI] ( https://github.com/finagolfin/swift-android-sdk/blob/6.0.1 /.github/workflows/sdks.yml#L492 ) .
46+
47+ You can copy these executables and the Swift runtime libraries to [ an emulator
48+ or a USB debugging-enabled device with adb] ( https://github.com/swiftlang /swift/blob/release/6.0 /docs/Android.md#3-deploying-the-build-products-to-the-device ) ,
6949or put them on an Android device with [ a terminal emulator app like Termux] ( https://termux.dev/en/ ) .
7050I test aarch64 with Termux so I'll show how to run the test runner there, but
71- the process is similar with adb, [ as can be seen on the CI] ( https://github.com/finagolfin/swift-android-sdk/blob/5.10 /.github/workflows/sdks.yml#L355 ) .
51+ the process is similar with adb, [ as can be seen on the CI] ( https://github.com/finagolfin/swift-android-sdk/blob/6.0.1 /.github/workflows/sdks.yml#L440 ) .
7252
73- Copy the test executables to the same directory as the SDK:
53+ Copy the test executables to the same directory as the Swift 6 runtime libraries,
54+ removing a few Android stub libraries that aren't needed:
7455```
75- cp .build/aarch64-unknown-linux-android24/debug/{swift-argument-parserPackageTests.xctest,generate-manual,math,repeat,roll} ..
56+ cp .build/aarch64-unknown-linux-android24/debug/{swift-argument-parserPackageTests.xctest,color,generate-manual,math,repeat,roll} ..
57+ cp ~/.swiftpm/swift-sdks/swift-6.0.1-RELEASE-android-24-0.1.artifactbundle/swift-6.0.1-release-android-24-sdk/android-27b-sysroot/usr/lib/aarch64-linux-android/24/lib*.so ..
58+ rm ../lib{c,dl,log,m,z}.so
7659```
77- You can copy the SDK and test executables to Termux using scp from OpenSSH, run
78- these commands in Termux on the Android device:
60+ You can copy the test executables and Swift 6 runtime libraries to Termux using
61+ scp from OpenSSH, run these commands in Termux on the Android device:
7962```
8063uname -m # check if you're running on the right architecture, should say `aarch64`
8164cd # move to the Termux app's home directory
8265pkg install openssh
8366
84- scp [email protected] :{swift-5.10-android-24-sdk.tar.xz, 85- swift-argument-parserPackageTests.xctest,generate-manual,math,repeat,roll} .
86-
87- tar xf swift-5.10-android-24-sdk.tar.xz
67+ scp [email protected] :{lib*.so,swift-argument-parserPackageTests.xctest,color,generate-manual,math,repeat,roll} . 8868
8969./swift-argument-parserPackageTests.xctest
9070```
9171I've tried several Swift packages, including some mostly written in C or C++,
92- and all the cross-compiled tests passed.
72+ and all the cross-compiled tests passed. Note that while this SDK bundle is
73+ compiled against Android API 24, there was a regression in Swift 6 so that
74+ Foundation can only be run on Android API 29 or later, #175 . I will update the
75+ SDK bundle when I find a fix for that new issue.
9376
9477You can even run armv7 tests on an aarch64 device, though Termux may require
9578running ` unset LD_PRELOAD ` before invoking an armv7 test runner on aarch64.
@@ -99,28 +82,25 @@ mode.
9982
10083## Porting Swift packages to Android
10184
102- The most commonly needed change is to simply import Glibc for Android too (while
103- Bionic is the name of the C library on Android, currently Swift uses the name
104- ` Glibc ` as a placeholder for most non-Darwin, non-Windows C libraries), so add
105- or change these two lines for Android:
85+ The most commonly needed change is to import the new Android overlay, so add
86+ these two lines for Android when calling Android's C APIs:
10687```
107- #if canImport(Glibc )
108- import Glibc
88+ #if canImport(Android )
89+ import Android
10990```
110- For example, that is all I had to do [ to port swift-argument-parser to
111- Android] ( https://github.com/apple/swift-argument-parser/pull/14/files ) .
112-
113- You may also need to add some Android-specific support using ` #if os(Android) ` ,
91+ You may also need to add some Android-specific support using ` #if canImport(Android) ` ,
11492for example, since FILE is an opaque struct since Android 7, you will [ have to
115- refer to any FILE pointers like this] ( https://github.com/apple /swift-tools-support-core/pull/243/files ) :
93+ refer to any FILE pointers like this] ( https://github.com/swiftlang /swift-tools-support-core/pull/243/files ) :
11694```
117- #if os (Android)
95+ #if canImport (Android)
11896typealias FILEPointer = OpaquePointer
11997```
98+ Those changes are all I had to do [ to port swift-argument-parser to
99+ Android] ( https://github.com/apple/swift-argument-parser/pull/651/files ) .
120100
121101## Building an Android app with Swift
122102
123- Some people have reported an issue with using the libraries from this SDK in
103+ Some people have reported an issue with using previous libraries from this SDK in
124104their Android app, that the Android toolchain strips ` libdispatch.so ` and
125105complains that it has an ` empty/missing DT_HASH/DT_GNU_HASH ` . You can [ work
126106around this issue by adding the following to your ` build.gradle ` ] ( https://github.com/finagolfin/swift-android-sdk/issues/67#issuecomment-1227460068 ) :
@@ -132,39 +112,34 @@ packagingOptions {
132112}
133113```
134114
135- Note that the FoundationNetworking and FoundationXML libraries won't actually
136- run on Android with this SDK, as their dependencies libcurl and libxml2 have other
137- library dependencies that aren't included. If you want to use either of these
138- separate Foundation libraries, you will have to track down those other libcurl/xml2
139- dependencies and include them yourself.
140-
141- ## Building the Android SDKs from source
115+ ## Building an Android SDK from source
142116
143- Download the Swift 5.10 .1 compiler and Android NDK 27 as above. Check out this
144- repo and run
145- ` SWIFT_TAG=swift-5.10 .1-RELEASE ANDROID_ARCH=aarch64 swift get-packages-and-swift-source.swift `
146- to get some prebuilt Android libraries and the Swift source to build the SDK. If
147- you pass in a different tag like ` swift-DEVELOPMENT-SNAPSHOT-2024-03-30 -a `
117+ Download the Swift 6.0 .1 compiler as above and Android NDK 27b (only building
118+ the Android SDKs on linux works for now). Check out this repo and run
119+ ` SWIFT_TAG=swift-6.0 .1-RELEASE ANDROID_ARCH=aarch64 swift get-packages-and-swift-source.swift `
120+ to get some prebuilt Android libraries and the Swift source to build an AArch64
121+ SDK. If you pass in a different tag like ` swift-DEVELOPMENT-SNAPSHOT-2024-10-08 -a `
148122for the latest Swift trunk snapshot and pass in the path to the corresponding
149123prebuilt Swift toolchain to ` build-script ` below, you can build a Swift trunk
150124SDK too, as seen on the CI.
151125
152126Next, apply a patch to the Swift source, ` swift-android.patch ` from this repo,
153- which adds a dependency for the Foundation core library in this Android SDK, and
154- four more patches that make modifications for the nullability annotations newly
155- added in NDK 26 :
127+ plus three more patches that make modifications for NDK 27 and [ the Foundation
128+ rewrite in Swift 6 that was merged this summer ] ( https://www.swift.org/blog/foundation-preview-now-available/ )
129+ and substitute a string for NDK 27 :
156130```
157- git apply swift-android.patch swift-android-both-ndks.patch swift-android-foundation-ndk26.patch swift-android-stdlib-ndk26.patch swift-android-stdlib-except-trunk.patch
131+ git apply swift-android.patch swift-android-foundation.patch swift-android-foundation-release.patch swift-android-foundation-except-trunk.patch
132+ perl -pi -e 's%r26%r27%' swift/stdlib/cmake/modules/AddSwiftStdlib.cmake
158133```
159134
160- After making sure [ needed build tools like python 3, CMake, and ninja] ( https://github.com/apple /swift/blob/release/5.10 /docs/HowToGuides/GettingStarted.md#linux )
135+ After making sure [ needed build tools like python 3, CMake, and ninja] ( https://github.com/swiftlang /swift/blob/release/6.0 /docs/HowToGuides/GettingStarted.md#linux )
161136are installed, run the following ` build-script ` command with your local paths
162137substituted instead:
163138```
164139./swift/utils/build-script -RA --skip-build-cmark --build-llvm=0 --android
165- --android-ndk /home/finagolfin/android-ndk-r27 / --android-arch aarch64 --android-api-level 24
166- --build-swift-tools=0 --native-swift-tools-path=/home/finagolfin/swift-5.10 .1-RELEASE-ubuntu22.04/usr/bin/
167- --native-clang-tools-path=/home/finagolfin/swift-5.10 .1-RELEASE-ubuntu22.04/usr/bin/
140+ --android-ndk /home/finagolfin/android-ndk-r27b / --android-arch aarch64 --android-api-level 24
141+ --build-swift-tools=0 --native-swift-tools-path=/home/finagolfin/swift-6.0 .1-RELEASE-ubuntu22.04/usr/bin/
142+ --native-clang-tools-path=/home/finagolfin/swift-6.0 .1-RELEASE-ubuntu22.04/usr/bin/
168143--host-cc=/usr/bin/clang-13 --host-cxx=/usr/bin/clang++-13
169144--cross-compile-hosts=android-aarch64 --cross-compile-deps-path=/home/finagolfin/swift-release-android-aarch64-24-sdk
170145--skip-local-build --xctest --swift-install-components='clang-resource-dir-symlink;license;stdlib;sdk-overlay'
@@ -176,63 +151,61 @@ Make sure you have an up-to-date CMake and not something old like 3.16. The
176151` --host-cc ` and ` --host-cxx ` flags are not needed if you have a ` clang ` and
177152` clang++ ` in your ` PATH ` already, but I don't and they're unused for this build
178153anyway but required by ` build-script ` . Substitute armv7 or x86_64 for aarch64
179- into these commands to build for those architectures instead.
154+ into these commands to build SDKs for those architectures instead.
180155
181156Finally, copy ` libc++_shared.so ` from the NDK and modify the cross-compiled
182- ` libdispatch.so ` and Swift corelibs to include ` $ORIGIN ` and other relative
183- directories in their rpaths:
157+ Swift corelibs to include ` $ORIGIN ` and other relative directories in their rpaths:
184158```
185- cp /home/yourname/android-ndk-r27 /toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/aarch64-linux-android/libc++_shared.so swift-release-android-aarch64-24-sdk/usr/lib
159+ cp /home/yourname/android-ndk-r27b /toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/aarch64-linux-android/libc++_shared.so swift-release-android-aarch64-24-sdk/usr/lib
186160patchelf --set-rpath \$ORIGIN/../..:\$ORIGIN swift-release-android-aarch64-24-sdk/usr/lib/swift/android/lib*.so
187161```
188162
189163Here is a description of what the above Swift script is doing:
190164
191165This prebuilt SDK was compiled against Android API 24, because the Swift
192- stdlib and corelibs require some libraries like libicu , that are pulled from the
166+ Foundation libraries require some libraries like libcurl , that are pulled from the
193167prebuilt library packages used by the Termux app, which are built against Android
194- API 24. Specifically, it downloads the libicu, libicu-static, libandroid-spawn,
195- libcurl, and libxml2 packages from the [ Termux package
168+ API 24. Specifically, it downloads the libandroid-spawn, libcurl, and libxml2
169+ packages and their handful of dependencies from the [ Termux package
196170repository] ( https://packages.termux.dev/apt/termux-main/pool/main/ ) .
197171
198- Each one is unpacked with ` ar x libicu_74.2_aarch64 .deb; tar xf data.tar.xz ` and
172+ Each one is unpacked with ` ar x libcurl_8.10.1-1_aarch64 .deb; tar xf data.tar.xz ` and
199173the resulting files moved to a newly-created Swift release SDK directory:
200174```
201175mkdir swift-release-android-aarch64-24-sdk
202176mv data/data/com.termux/files/usr swift-release-android-aarch64-24-sdk
203177```
204178It removes two config scripts in ` usr/bin ` , runs ` patchelf ` to remove the
205- Termux rpath from all Termux shared libraries, and modifies the ICU libraries
206- to get rid of the versioning and symlinks (three libicu libraries are removed
207- since they're unused by Swift) :
179+ Termux rpath from all Termux shared libraries, removes some unused libraries
180+ and config files, and modifies the libraries to get rid of the versioning and
181+ symlinks, which can't always be used on Android :
208182```
209183rm swift-release-android-aarch64-24-sdk/usr/bin/*-config
210184cd swift-release-android-aarch64-24-sdk/usr/lib
211185
212- rm libicu{io,test,tu}*
213- patchelf --set-rpath \$ORIGIN libandroid-spawn.so libcurl.so libicu*so.74.2 libxml2.so
186+ patchelf --set-rpath \$ORIGIN libandroid-spawn.so libcurl.so libxml2.so
214187
215- # repeat the following for libicui18n.so and libicudata.so , as needed
216- rm libicuuc .so libicuuc .so.74
217- readelf -d libicuuc .so.74.2
218- mv libicuuc .so.74.2 libicuuc .so
219- patchelf --set-soname libicuuc .so libicuuc .so
220- patchelf --replace-needed libicudata .so.74 libicudata .so libicuuc .so
188+ # repeat the following for all versioned Termux libraries , as needed
189+ rm libxml2 .so libxml2 .so.2
190+ readelf -d libxml2 .so.2.13.4
191+ mv libxml2 .so.2.13.4 libxml2 .so
192+ patchelf --set-soname libxml2 .so libxml2 .so
193+ patchelf --replace-needed libz .so.1 libz .so libxml2 .so
221194```
222195The libcurl and libxml2 packages are [ only needed for the FoundationNetworking
223- and FoundationXML libraries respectively] ( https://github.com/apple /swift-corelibs-foundation/blob/release/5.10/Docs/ReleaseNotes_Swift5.md ) ,
196+ and FoundationXML libraries respectively] ( https://github.com/swiftlang /swift-corelibs-foundation/blob/release/5.10/Docs/ReleaseNotes_Swift5.md ) ,
224197so you don't have to deploy them on the Android device if you don't use those
225- extra Foundation libraries. I simply include all four libraries since there's
226- currently no way to disable building them in the CMake configuration.
198+ extra Foundation libraries.
227199
228- The libicu dependency can be [ cross-compiled for Android from scratch using
229- these instructions ] ( https://github.com/apple/swift/blob/release/5.5/docs/ Android.md#1-downloading-or-building- the-swift-android-stdlib-dependencies )
230- instead, so this Swift SDK for Android could be built without using
231- any prebuilt Termux packages, if you're willing to put in the effort to
232- cross-compile them yourself, for example, against a different Android API .
200+ This Swift SDK for Android could be built without using any prebuilt Termux
201+ packages, by compiling against a more recent Android API that doesn't need the
202+ ` libandroid-spawn ` backport, and by cross-compiling libcurl/libxml2 and their
203+ dependencies yourself or not using FoundationNetworking and FoundationXML, by
204+ disabling their build .
233205
234- Finally, it gets [ the 5.10 .1 source] ( https://github.com/apple /swift/releases/tag/swift-5.10 .1-RELEASE )
235- tarballs for seven Swift repos and renames them to ` llvm-project/ ` , ` swift/ ` ,
206+ Finally, it gets [ the 6.0 .1 source] ( https://github.com/swiftlang /swift/releases/tag/swift-6.0 .1-RELEASE )
207+ tarballs for ten Swift repos and renames them to ` llvm-project/ ` , ` swift/ ` ,
236208` swift-syntax ` , ` swift-experimental-string-processing ` , ` swift-corelibs-libdispatch ` ,
237- ` swift-corelibs-foundation ` , and ` swift-corelibs-xctest ` , as required by the Swift
209+ ` swift-corelibs-foundation ` , ` swift-collections ` , ` swift-foundation ` ,
210+ ` swift-foundation-icu ` , and ` swift-corelibs-xctest ` , as required by the Swift
238211` build-script ` .
0 commit comments