(Partial) Nightlies for android SDK

@Vogel - there is no reason to do that on CI, but that doesn't mean that you can't do macOS to android ARM64 cross-compilation for development with what I have set up.

The same artifacts that are on the azure instance for Windows can be downloaded and extracted on macOS (its just a zip file). Make sure that you grab the correct component: the android SDK for the architecture that you want (armeabi = armv7, arm64 = aarch64). (I would like to unify them into a single SDK distributable, but that is future work.) You will need to find the Glibc.modulemap in the SDK and alter the contents such that C:/Microsoft/AndroidNDK64/android-ndk-r16b/ is replaced with the path to the NDK that you installed on macOS (e.g. /opt/AndroidNDK64/android-ndk-r16b).

After that, the same set of instructions would apply to developing on macOS or Windows. Note that the same could also be applied for Linux.

That is the reason that I have setup the builds as I have. There are three components:

  • toolchain
    • the tools that you use to build
  • SDK
    • the resources needed to develop for the target, in the future will also contain host dependent tools
  • runtime
    • the resources that need to be distributed to end users to run the applications themselves

In order to write programs for android, you will need all three components. The first component is shared across all the various target that you may be interested in working on. For running previously built programs you would need only the last component.

Eventually, the only thing that you will gain by using these builds on Windows will be a much smoother experience as I intend to have a proper installer to make the workflow simply be download and run the installer.

If there really is interest in using a prebuilt macOS toolchain from the same sources (probably a good idea), I can look into setting up a macOS build as well. If someone else wants to help set that up, that would definitely be welcome help (as well as a toolchain for Linux).

1 Like

Okay, as of now, ARMv7 and AArch64 are both live!

Future work that remains to be done is integrating XCTest into the image. But, this still should manage to get us actual signal on whether or not Foundation is in a good state or not.

3 Likes

The android ARM64 and ARMv7 SDKs are complete at this point. They include the standard library, libdispatch, foundation, and XCTest. The Windows x64 SDK is building as well which provides llbuild artifacts. I will probably look into adding a x86_64 variant for the android SDK to enable easier testing, but building for android at this point should be easy on Windows. For Linux, you should be able to use the swift toolchain snapshots along with the SDK that you can download from azure for building code for android on Linux or Darwin.

3 Likes

Until official version will be ready you can look on non-official toolchain: Releases · vgorloff/swift-everywhere-toolchain · GitHub. Otherwise you can build toolchain by yourself.

1 Like

I’m not totally understanding what this is. Is the idea here to use Swift to develop an Android app? If so, is there sample code or a tutorial for using this SDK?

Yes, the Swift SDK for android enables you to write applications for android using Swift. I don't think that I would be really be able to write a tutorial for this (or even sample code). If someone was to help with that, I can explain how to accomplish this, but, I feel like my knowledge of how all the pieces work together make me blind to how to most effectively disseminate the knowledge.

Is it feasible to build and test SwiftPM packages on Android (and Windows) in a CI service like Travis CI?

So has anyone ever written an app for Android using this SDK? I’d like to look at the code used to do so.

No, s-p-m is not really in a state where you can really port it to other platforms. I've looked at trying to get this working, but there is a significant amount of work still needed. I had a number of patches to remove the custom implementation which causes problems for portability and the idea is to move more of it to Foundation.

Currently, the most effective way that I have found for building cross-platform packages has been CMake.

2 Likes

This SDK contains the swift standard library, libdispatch, and Foundation (and XCTest). It entirely depends on what you are attempting to build. I suspect that those who have built applications with this will not be willing to share the source code, but a command line hello world program I could easily recreate.

1 Like

Maybe you may want to take a look at https://scade.io. I’ve not used it, but its been brewing for a while.

1 Like

Hi @toph42 we (flowkey.com) have an app in production using our port of UIKit for Android (GitHub - flowkey/UIKit-cross-platform: Cross-platform Swift implementation of UIKit, mostly for Android). It's not SwiftUI (yet) but interestingly we're using a similar data and layouting model for our custom views.

3 Likes

Here is a "hello world" OpenGL app https://github.com/sakrist/Swift_OpenGL_Example

2 Likes

A few weeks ago, we were looking at some fixes in Foundation re: Android; I think we should document more clearly somewhere that, on Android, any use of Foundation really requires the Java side of things to invoke Os.setenv("CFFIXED_USER_HOME", this.getFilesDir().getPath()); before any Swift code is invoked.

Another obvious improvement there would be a similar environment variable setup for getCacheDir(), so that FileManager returns a system-cleaned cache subdirectory; and there could be a shim for these calls as a community contribution that takes a Context and sets up these variables appropriately. There's a lot we as a community could do to make the Android experience better from this point of view.

1 Like

Also, a word of warning. I have self-built artifacts from similar invocations to the ones that Saleem uses to build the artifacts, and, currently, Foundation and XCTest are not really that usable. One detail, that Saleem is aware and trying to fix, is that there's no ICU data, so Foundation cannot print dates, and XCTest tries to print a date almost at the start. There's also more details that make it difficult to start using the artifacts, like Foundation using cURL using zlib, but not linking against it, or the usage of pthread_getname_np in Foundation (depending on your Android version, the function might or not be available). Those two has to be figure out before anyone can link against Foundation. As far as I know the Swift stdlib and Glibc (Bionic in Android), are working, and one can code against them. And from Swift - Android (arm64) (Tools RA, Stdlib RD) (main) [Jenkins] we know that some of the tests are passing (some because the test that run on the device are not easy to run in the continous integration machine).

I want to repeat that this happen in my self-built pieces, but I checked against the symbols of some of the published artifacts and they match my findings. We are trying to work out the roughness, and help is very much appreciated, either in the form of work, or in the form of bug reports.

A very good "Getting started" is the documentation in the Swift repository, swift/Android.md at main · apple/swift · GitHub, but it is a little dated, and not all in there might be needed and some pieces might be missing. If one wants to drill into the test configuration file (swift/lit.cfg at main · apple/swift · GitHub), it contains the pieces needed to compile all of the test programs for Android. The scripts in swift/utils/android at main · apple/swift · GitHub can be used to deploy simple programs to a device (through USB). One can see how the tests use those scripts in swift/CMakeLists.txt at main · apple/swift · GitHub.

With a pre-release CMake 3.15, I think the easiest way to have a more complicated program can be using GitHub - compnerd/swift-cmake-examples: Swift example projects as an example, and for Android, it will need to be mixed with Android NDK CMake support (CMake  |  Android NDK  |  Android Developers).

Making a proper APK is not as easy as that. I haven't look in depth, but the project linked above by Vlad seems like a good example. The trick is compiling all the Swift code into one or several dynamic libraries, which are loaded from the Java side, and invoke them with JNI. One probably want to code the UI in standard Java, and maybe implement the business logic in Swift, but depending on how complex things are, one might need to define a lot bridging code to make things work.

As Lily says, one need to setup a couple of environment variables. In my testing I put HOME and TMP, and I trying to code everything in Foundation to rely on those variables. Because of the Android sandboxing, the values can only be set by the app once you have a Context, and before Foundation tries to use those values.

As anyone can see, there's still a lot of steps that are difficult to figure out, but we are getting there. I can say that it is lot better state than it was one year ago.

3 Likes

(Maybe I misread, but as a note: please do not rely on HOME and TMP when you're writing code that ends up inside Foundation. Use NSHomeDirectory() and NSTemporaryDirectory(); CFFIXED_USER_HOME should take care of the former, and the latter will source from TMPDIR/TMP.)

Sorry, what I meant is that I set HOME and TMPDIR (not TMP, sorry about that mistake) and expect Foundation to use those. If interacting with Foundation I use those functions pointed above to access the values (they are really convenient). I, however, try not to set CFFIXED_USER_HOME, but that's a personal preference. The variable is completely related to Foundation, and it doesn't seem to be used everywhere in the code where HOME is used. Normally HOME and CFFIXED_USER_HOME will have the same value, because otherwise, pieces of code that do not depend on Foundation will not pick up on the value of the variable, and they might try to read/write files in some place that Foundation doesn't expect, but since that's not enforced, I try to avoid setting the Foundation specific one. Since all usages of CFFIXED_USER_HOME seems to fallback to HOME, and everybody knows about that environment variable, I prefer to use it, and remove complexity.

Since you were interested, I suppose I should mention that macOS toolchain builds are now available on Azure as well. You should be able to use that with the android SDK to do macOS x64 cross-compile to android ARM64.

FYI: It is possible to build sources with SPM (from Xcode 11 or manually built) with custom compiler (passed via SWIFT_EXEC) in order to get executables or so-files targeted Android.

Here is an example:

SWIFT_EXEC=/Users/[EDITED]/swift-android-toolchain/bin/swiftc-arm-linux-androideabi swift build \
-Xswiftc -target -Xswiftc armv7-none-linux-androideabi \
-Xswiftc -swift-version -Xswiftc 5 \
-Xswiftc -sdk -Xswiftc /ndk/platforms/android-24/arch-arm \
-Xlinker -L -Xlinker /ndk-bundle/toolchains/llvm/prebuilt/darwin-x86_64/sysroot/usr/lib/arm-linux-androideabi/24 \
-v

Example package:

// swift-tools-version:5.0

import PackageDescription

let package = Package(
   name: "HelloJNI",
   products: [
      // See: https://theswiftdev.com/2019/01/14/all-about-the-swift-package-manager-and-the-swift-toolchain/
      .library(name: "Lib", type: .dynamic, targets: ["Lib"])
   ],
   targets: [
      .target(name: "Lib"),
      .target(name: "Exe")
   ]
)

Note that after build you have to rename file /.build/x86_64-apple-macosx/debug/libLib.dylib to libLib.so.

$ file /HelloJNI/.build/x86_64-apple-macosx/debug/Exe 
/HelloJNI/.build/x86_64-apple-macosx/debug/Exe: ELF 32-bit LSB pie executable ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /system/, with debug_info, not stripped

$ file /HelloJNI/.build/x86_64-apple-macosx/debug/libLib.dylib 
/HelloJNI/.build/x86_64-apple-macosx/debug/libLib.dylib: ELF 32-bit LSB pie executable ARM, EABI5 version 1 (SYSV), dynamically linked, with debug_info, not stripped

And ignore the directory name x86_64-apple-macosx. SPM seems not handling target triples.

3 Likes

I made a sample iOS / Android project which uses Swift Package Manager to build cross-platform code.

That project performs typical Network layer routines, such as: Network request, JSON Encoding / Decoding, Dispatch queues and Operations.

Most routines seems working, but when I am making request with URLSession.dataTask(with: url) { ... } the error happens:

I/SwiftAndroid: Error Domain=NSURLErrorDomain Code=-1 "(null)"
I/SwiftAndroid: UserInfo: ["NSErrorFailingURLKey": https://www.google.com, "NSErrorFailingURLStringKey": "https://www.google.com", "NSUnderlyingError": Error Domain=NSURLErrorDomain Code=-1 "(null)", "NSLocalizedDescription": "SSL certificate problem: unable to get local issuer certificate"]

The file AndroidManifest.xml already contains settings like below:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

How to fix that SSL certificate problem: unable to get local issuer certificate error?

Thank you!

3 Likes