With the official announcement of the Swift SDK for Android, the Android workgroup has fielded some questions about the minimum supported Android version for Swift. Currently we are supporting Android API 28 (a.k.a. Android 9, version code P, codename "Pie"). Released in 2018, it is supported by 91.7% of active Android devices according to the estimates at apilevels.com.
The choice of minimum supported API has been largely pragmatic. Older versions lack some APIs that are needed by various components of the core frameworks. In particular, posix_spawn was added in API 28, and as discussed at https://github.com/swiftlang/github-workflows/issues/83, we settled on that minimum level for expedience.
That being said, there are paths to supporting even older versions of Android, especially now that we will be able to conditionally gate APIs using #available clauses for Android levels (thanks to @madsodgaard for https://github.com/swiftlang/swift/pull/84574!)
We have heard of custom Swift SDKs builds being created for older API levels, and we would like to survey the community about whether they have the need to support older Android versions, along with any details they can share about their target environment. This might be because they would like to use Swift on bespoke Android hardware that is stuck on older API levels, or simply because they want/need to be able to target an even wider swath of Android consumers than the ~92% coverage provided by API 28. Android powers not just phones and tablets, but a huge range of consumer and industrial devices, and we do not presume to know all the scenarios where developers might benefit from being able to develop in Swift for Android.
In any case, we want to hear from you! Feel free to chime in on this thread about your ideal Android minimums, or reach out directly to the team by messaging @android-workgroup on the Swift Forums.
I personally find a worldwide 8.3% to be still pretty relevant, given that this could be much higher in Africa or countries like India, to name one. I noticed this change in the recent releases of the SDK with Swift 6.2, but sticking to 6.1 has allowed me to keep support down to API 24, for others reading.
At the end of the day, it really depends on the nature of the applications, and while I find the % significant for my case, I don’t think most developers will notice.
Still curious to hear other perspectives on the matter.
I do understand the rationale behind API 28 minimum. But, we also need to support API 23+ for our use case. We unfortunately do not have the luxury of upgrading Android versions, as we run it on "custom" hardware, i.e. non-phones, as well as Android phones.
Our current plan is to maintain a fork of finagolf's repo, modified to support API 23+, unless the working group decides to distribute the official SDK with lower versions available. API 23 is old, and there are a lot of APIs that were first added in 24 even, so maybe this is too much to ask... However, maybe the above fork can be an inspiration for what is "missing", and at least just a "fallback" for people requiring legacy support.
However, I do still think it's relevant to talk about API 24 minimum.
You mention that the main issue is probably posix_spawn. However, from what I could find the spawn APIs are only used in the new Subprocess API from Foundation and the old Process. I might be wrong on this one, so please correct me if it is also used in other parts of Swift. I would argue that this API is probably not the most common use case for the Android SDK, and it can now even be safely guarded using the Android availability checking added to Swift.
IMHO this approach is important for Swift evolution in general. Aside from the inherent issue of Android API support, if we're giving up API 24 for something that only a small % of developers use in Swift (first time I hear of the Subprocess API), then I 100% lean towards conservative availability checks. If supporting API 24 increases the complexity of the build system, that's a different story, but I don't know the internal extent of the change to draw any conclusions.
Yes, it is not used much in a Swift SDK, just there and in Testing for exit tests. As you and @etcwilde noted, it wouldn't take much more to integrate Bionic back to API 23 or 24 also, as I'm well aware since my Android SDK bundle has long supported API 24.
The problem is that before you brought up this API 23 support issue elsewhere for the first time a month ago, nobody in the workgroup seemed to really want to go that far back and do the work to fix the remaining holes. Then, there is of course the possible futex issue a workgroup member mentioned, where they saw crashes before API 29 because Swift 6 started using that system call in the stdlib and he said those older linux kernels didn't have it.
Another consideration I just remembered is that Android didn't add native Thread-Local Storage (TLS) till API 29, so we are actually compiling the one TLS variable in the Swift stdlib with emulated TLS, to support that older API.
However, as I pointed out to you then, even those Process-related APIs in Foundation don't really need posix_spawn_* in Bionic, as there are polyfills in Foundation for most of them, for all but these more newly added ones.
If you and whoever you're working with want to push the official SDK back to API 23, by doing that work to fix or avoid the few remaining issues, we can certainly consider it.
I'll point out we don't support exit tests on Android, at least not yet. (But I'm happy to work with you to get them up and running—it shouldn't be hard to support them.)
Thanks to everyone that has chimed in so far. This is very valuable feedback for us, so keep it coming!
As an experiment, I did a quick patching-out of the offending posix_spawn_* calls (in Android API 24 by marcprux · Pull Request #16 · swift-android-sdk/swift-docker · GitHub), and the SDK does indeed build for API 24 and pass some testing. So we might want to consider moving the official supported level down to 24. I'll add it to the agenda of the next workgroup meeting and we can discuss further.
P.S. for completeness, I did also try API 23, but hit some more significant build failures. If this is a compelling enough target for anyone, they might help seek workarounds for the missing symbols like getgrnam_r and getgrgid_r, as well as any subsequent shortcomings this target might encounter.
I'm definitely interested in hearing the results of testing against actual devices with API 23 or 24. I haven't even tested against an emulator at those levels, and don't have any device that goes back before API 28.
Nice! For API 23, you would probably also want to add shims for ifaddrs and change the FILE pointer for it to be actually useable. You check how I do it in repo linked further up. I’d be happy to help out, if the workgroup decides that this is something we officially want to support:)
I've also faced the need for an even lower NDK* version (21). My initial approach was to back-port the missing functions, but if I remember correctly it only worked for ndk 23 and I ran into another problem. I realized that I need a workaround similar to what you made in your fork. I'm also working with a non-phone integrated device that has very limited hardware (it even misses a screen).
Returning to the main topic, for me, the lower the supported version, the better. If an @available check is coming soon, why not simply mark all the relevant APIs - for example, Process from Foundation and Subprocess ?
I'm assuming you mean API level 21, not NDK version.
Yes, that feature will enable us to do that (should we decide to target the lower API levels). But we'd need to build using a higher NDK version (28+, I think).
I'll see what happens when I try building for API 21. Anyone have an even lower Android API they need to target?
I agree that for the vast majority, API 23 is more than sufficient. I actually meant version 23 when I wrote " the lower, the better" - sorry for not specifying. I only mentioned version 21 remembering personal experience.
Just got off the latest Android workgroup call, where we went over all this again in detail.
I think we can go ahead and push this down to API 24 in the coming month, which really won't require much. The main reason we chose API 28 so far is because the Process APIs in Foundation depend on posix_spawn(), which were only added to Bionic in API 28. There was some concern today that the existing polyfills in Foundation may not work as well, but since the current dlsym()-based approach only loads those polyfills at runtime on devices at API level 27 and earlier that don't have posix_spawn() APIs in Bionic, that shouldn't be a problem for devices running API 28 and above, ie we're not statically compiling in the polyfills at compile-time.
If those who want to push this SDK down to earlier than API 28, such as @madsodgaard and @etcwilde so far, are willing to add polyfills for the remaining three APIs that I mentioned earlier, and any other small changes that are needed, I'm fairly certain we could get this SDK down to API 24 with a couple days' work.
That then leaves API 23, which is a bigger question, largely because of the way FILE changed with API 24 in Bionic and how Swift deals with such pointer types. Starting with API 24, it is an opaque struct in Bionic, so FILE* is an OpaquePointer, but earlier, it did have a definition, so while the difference may not matter in C and C++, Swift imports them with different types.
Mads and others don't see a way to use API notes to automatically change all FILE* in code targeting API 23 to OpaquePointer, so the only way they see is to add API notes for every single Bionic API that invokes a FILE* to change its type, which seems unworkable to me.
Perhaps someone who knows these C typing issues better can chime in on whether that is our only option. The other issues with API 23 appear minor.
This might not pan out, but what if you use swift_newtype(struct) for the FILE typedef? I thiiink there’s an apinote form of that. Then you have a consistent wrapper type even though it’ll have a different raw value on 23 vs 24.
And then we just refer to SWTFilePointer instead. For something public-facing, we could make sure the Android module overlay has something equivalent, perhaps? (Actually, frankly, we probably should have something equivalent in all the C stdlib modules, since the FILE * type is imported various different ways across them. But that's beyond the scope of your work.)