Improving Swift support and interoperability experience for Android

Hey everyone,

Over the past several weeks @compnerd and I have been working on improving Swift support for Android. We now can build Android standard library on Windows (Linux still works as well!), and we now can also build Swift test programs for Android that use C++ interoperability and depend on NDK’s libc++.

As part of this work I have added a new ‘Android’ module to Swift’s standard library. This module imports Android’s C standard library (Bionic) and some other posix and Android APIs into Swift. Additionally the ‘Bionic’ clang module can also be imported into Swift to access the C standard library APIs without pulling in the other Android and posix APIs. The support for the ‘Glibc’ module for Android is removed. These overlay changes are not yet merged, so please provide any feedback that you might have here:

I would like to merge these changes in the next week or two.

Now that these changes are ready, we will be starting to explore how to let Java/Kotlin use Swift APIs. We would like to have a level of interoperability that lets Java/Kotlin invoke a subset of Swift APIs in an effective and ergonomic manner. Please let us know if you have ideas for how such interoperability should look like and/or work!

We would like to see Swift for Android succeed, and we think that as part of that Android should start to move in the direction of being a supported platform that Swift can target. More specifically, it would be great if there was a way to set up a pre-commit CI for Android, in addition to currently existing post-commit CI (https://ci-external.swift.org/). What do other community members think of that idea?

52 Likes

Yes please! This would be absolutely wonderful.
I'm all for Android being a supported platform. It deserves some resources and would strengthen Swift as a language choice similar to how Windows did.

2 Likes

This is great to see, Alex!

I tried playing around a bit with the Android build a couple months ago but struggled to get a compatible working toolchain. Is there a good reference for how to set up an environment from scratch to compile a sort of “hello wold” and run it in an emulator? Maybe in a docker container or something like that?

1 Like

Great, good to see more people working on Android support again.

This would require some small changes to the few current Android users' packages, but is the right move for the future. Pinging @drodriguez, @Geordie_J, @mstokercricut, and @johnburkey, as they've all tried building for Android in the past. Let us know if you are okay with making this change with Swift 6.

That would be great. I discussed moving Android from the community CI to the official CI, but keeping it post-commit, with some members of the core team last November. They OK'ed the move, but I've since been bogged down with some non-tech work at home since December and have done nothing for that move to the official CI, using my lessened time for smaller items like upstreaming the nullability annotation changes for NDK 26.

If you want to set up a pre-commit official CI instead, that would be even better, and I'd be happy to discuss the ins and outs of setting that up, if you'd like.

Have you tried the official Android doc? I wrote a lot of that, let me know if it doesn't work in some way.

3 Likes

I built a swift 5.8 GitHub Action change using a lot of @Finagolfin 's excellent work for examples, and added it to alamofire for android. I need to update my tool chains since it's been a bit but I would love to do this with newer tool chains.

I've built entire apps for android with swift doing rendering and business logic with minimal interoperability surfaced up to the JVM side. I plan to continue doing that once my need for android arises again. I know it's possible and a joy once you get it working well.

Would love to see this process continue to improve

6 Likes

We are not planning to cherry-pick these changes to Swift 6, so they would be aligned with the release after Swift 6.

I think improving the state of the post-commit CI is the right first step, so it's great to hear you're already working on it! I don't have much insight into setting up a pre-commit CI yet, but I'll do some digging into the current post-commit CI setup to understand it better before we propose a concrete action plan for pre-commit CI.

Why not? Your changes are limited to Android and so far no Android users have complained.

The current post-commit community CI simply compiles the Android stdlib on linux and then runs the compiler validation suite in host test mode, ie skipping the 1k executable tests that would need to run on Android.

It has been broken for more than a month because it doesn't have a prebuilt Swift toolchain installed, and Swift trunk is currently broken when bootstrapping on linux, pretty much requiring a prebuilt host toolchain at this point.

I plan to get that running on the official CI by installing a prebuilt Swift toolchain for linux, then expanding it to run the executable tests in the Android emulator, before getting it to cross-compile the corelibs like libdispatch and foundation for Android also, the last of which my daily Android CI has been doing for years.

We can use that post-commit CI config for a pre-commit CI when we feel it's ready.

3 Likes

This all sounds great! I plan to add Android support to FlutterSwift, perhaps I'll wait for this to land.

1 Like

Good point! We don't need it in Swift 6 ourselves, but it sounds like the community would benefit from this change being in Swift 6. It does make sense to switch from Glibc to the Android module now in Swift 6, since it will be a major release, and thus anyone who depends on Glibc for Android now will know that there's a clear boundary that denotes when the Android module is introduced.

I will put up the PR to cherry-pick the Android overlay changes to the Swift 6 branch this week, and will ask the release owners to sign off on it.

8 Likes

I’m all for improving correctness. It’d be simple to update our code accordingly!

I do feel like there are multiple layers that could be targeted here. At the bottom, improving Swift’s interoperation with JNI would be one direction to go: declaring native functions, or at least a standard JNI_OnLoad; non-copyable and non-escaping wrappers for low-level JNI types; support for the JVM’s Modified UTF-8; providing a convenient way to spell JVM type signatures; and perhaps even generating APIs from class files. A level up from that might be direct support for Kotlin coroutines in some way, and other Kotlin-isms that work better in Swift than they do in Java. Finally, you also have everything in the Android SDK—how much of that can be nicely wrapped in a way that makes it more idiomatic for Swift? (Without trying to make it a UIKit clone.) Any such effort is necessarily incomplete, i.e. an app developer will probably have to drop back to raw Java calls at some point, but that doesn’t mean it’s not useful to have it in one place instead of everyone building their own wrappers.

(I don’t work day-to-day on Android specifically, but I am on a team that maintains a Rust library with JNI-based bridging. The Rust jni crate is much nicer to work with than the raw C API.)

6 Likes

As a junior dev I built a somewhat incomplete and admittedly fairly inconsistent JNI wrapper in the era of Swift 2/3. We’re still using 8+ years later in production GitHub - SwiftAndroid/swift-jni: Wrap JNI functions (WIP)

I’m in the process of starting a new app project at the moment, so 90% of my energy is going into that. I would love to revisit Swift JNI someday though now that I have more experience and now we have more Swift features available to us. In the meantime it might be interesting for someone to play around with improving it.

Since you mention it in passing, we also built a large chunk of UIKit for Android that we’re also still using for cross platform purposes. We are a small company so never had a lot of resources to dedicate to these projects full time, but they do what they’re supposed to quite well :slight_smile:

4 Likes

Excited to see Android support improvement. Currently is building android standard library only supported Linux and Windows or is it supported for macOS as well?

Great, good to know. :slight_smile:

People have been building it on macOS for a long time, but the official doc instructions I linked a couple weeks ago have only been validated on linux. If you want to try them on macOS, I recommend using the Homebrew OSS toolchain.

3 Likes

We've been working with the excellent toolchain from @Finagolfin for a little while now to build several large-ish Swift libraries for Android (in addition to Windows, Linux, macOS) and integrating them with Kotlin projects. We read the Swift code, identify types, generate analogous Kotlin type interfaces, and generate code that uses the JNI to hook the Kotlin types to the Swift types. We then package all that up and use Maven for distribution of the libraries. It has worked great for us, but is currently closed source. I'm super excited to see work on a similar system that is open source!

I completely agree that supporting Android as a supported platform that Swift can target will strengthen Swift as a language choice as @STREGA suggests. Even if Kotlin <=> Swift interop is not part of the initial support, being able to call interfaces exposed using @_cdecl using the JNI & NDK would be a huge boon.

Much of the pushback I've gotten about using Swift as the language choice for shared code is the lack of official Android support on Swift.org - Platform Support. Getting the OS on the officially supported list would quiet much of that opposition, giving confidence that Android support won't just disappear in future Swift versions. I bet many other organizations have had similar conversations and decided against Swift for that reason.

If official Android support is the goal, I'm in! How can I help?

7 Likes

This shouldn't cause trouble for us. Go for it.

1 Like

I am currently porting the 6.1 trunk portion of my daily Android CI to this new Android overlay, you can get an idea of the "small changes" I mentioned before from that patch. Once @Alex_L's pulls are in, my CI will automatically produce a 6.1 trunk Android SDK that you can use with an official 6.1 trunk snapshot toolchain, hopefully on Windows too, to port your own packages and make sure everything is still working fine with this new Android overlay.

:+1:

These changes are now merged, so the main-based Swift toolchains now support the 'Android' Swift module instead of 'Glibc' for Android. We have been able to use the new NDK overlay to build some fairly complex downstream projects for Android that use C++ interoperability as well, so it's working out well so far.
I put up a cherry-pick PR to cherry-pick these changes to Swift 6 as the community requested: [swift 6] cherry-pick Android NDK overlay into Swift 6 by hyp · Pull Request #74758 · swiftlang/swift · GitHub .

9 Likes

Will these instructions work with building main or Swift 6 plus NDK overlay? And/or are there any binary toolchains available for macOS or Linux (both arm64)? :)

1 Like

I have never touched that toolchain, so I can't say for sure, but probably not, as they seem to lag behind, still using Swift 5.7.

You'd have to use trunk Swift 6.1, at least until the linked Swift 6 pull gets merged, and on macOS, Apple's proprietary builds are not known to work well with outside SDKs, while nobody builds the pure OSS trunk toolchain for macOS that I know of.

However, that may be changing, with Apple now putting out the static Musl SDK for linux. You could try that first with the latest official trunk Xcode snapshot and if it works well, try out one of my trunk Android SDKs next, say sdk-trunk-aarch64 at the bottom of that page, by using the accompanying instructions.

If that doesn't work, my trunk SDK should work with one of the linux x86_64 snapshot toolchains. Unfortunately, google does not provide an Android NDK for AArch64 yet, so their lightly-patched NDK clang that we use to link Swift binaries for Android is not available on linux AArch64 (shouldn't be a problem on macOS because of Rosetta 2). You could probably just use the stock clang from your AArch64 distro instead, but it will require some extra configuration.

1 Like