(Partial) Nightlies for android SDK

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

I don't know which cURL is being compiled into your Foundation, but it might or not have HTTPS support. For cURL having HTTPS support, you will need OpenSSL. Once that cURL has been compiled correctly, for OpenSSL to work, you can follow the comment at swift-corelibs-foundation/EasyHandle.swift at 84d6a68f05793f55c1a3aecf553c74fe2fae2ae9 · apple/swift-corelibs-foundation · GitHub

That should allow you to request secure HTTPS URLs.

2 Likes

Thank you!

Downloading cacert.pem and setting path into environment variable URLSessionCertificateAuthorityInfoFile seems helps.


File certificatesFile = this.getCachedAsset(this, "cacert.pem");
Os.setenv("URLSessionCertificateAuthorityInfoFile", certificatesFile.getAbsolutePath(), true);

// Or alternative !!!unsafe!!! way.
Os.setenv("URLSessionCertificateAuthorityInfoFile", "INSECURE_SSL_NO_VERIFY", true);

But now I am getting another error:

A/SwiftRuntime: Fatal error: Unexpected ubrk_open failure: file /ToolChain/Sources/swift/stdlib/public/core/ThreadLocalStorage.swift, line 165

The related code in file ThreadLocalStorage.swift:

@_silgen_name("_stdlib_createTLS")
internal func _createThreadLocalStorage()
  -> UnsafeMutablePointer<_ThreadLocalStorage>
{
  // Allocate and initialize a UBreakIterator and UText.
  var err = __swift_stdlib_U_ZERO_ERROR
  let newUBreakIterator = __swift_stdlib_ubrk_open(
      /*type:*/ __swift_stdlib_UBRK_CHARACTER, /*locale:*/ nil,
      /*text:*/ nil, /*textLength:*/ 0, /*status:*/ &err)
  _precondition(err.isSuccess, "Unexpected ubrk_open failure")

  // utext_openUTF8 needs a valid pointer, even though we won't read from it
  var a: Int8 = 0x41
  let newUText = __swift_stdlib_utext_openUTF8(
      /*ut:*/ nil, /*s:*/ &a, /*len:*/ 1, /*status:*/ &err)

  _precondition(err.isSuccess, "Unexpected utext_openUTF8 failure")

  let tlsPtr: UnsafeMutablePointer<_ThreadLocalStorage>
    = UnsafeMutablePointer<_ThreadLocalStorage>.allocate(
      capacity: 1
  )
  tlsPtr.initialize(to: _ThreadLocalStorage(
    _uBreakIterator: newUBreakIterator, _uText: newUText))

  return tlsPtr
}

Seems like it is something addressed UBreakIterator from ICU.

File: stdlib/public/stubs/UnicodeNormalization.cpp

swift::__swift_stdlib_UBreakIterator *swift::__swift_stdlib_ubrk_open(
    swift::__swift_stdlib_UBreakIteratorType type, const char *locale,
    const __swift_stdlib_UChar *text, int32_t textLength,
    __swift_stdlib_UErrorCode *status) {
  return ptr_cast<swift::__swift_stdlib_UBreakIterator>(
      ubrk_open(static_cast<UBreakIteratorType>(type), locale,
                reinterpret_cast<const UChar *>(text), textLength,
                ptr_cast<UErrorCode>(status)));
}

Maybe ICU is not properly configured on runtime (i.e. environment variables)?

Check the size of libicudataswift.so (or similar name, but one that says data). If it is small (some few KiB), the ICU installation is missing the data library, if it is around 20 MiB (IIRC), then you should be fine. In you have a big library, you might want to check that the locale that's being picked up is correct (try to use a simpler locale first, like en-US, if possible).
If you are using GitHub - SwiftAndroid/libiconv-libicu-android: Port of libiconv and libicu to Android to compile those libraries, you probably want to apply Generate the ICU data into a library. by drodriguez · Pull Request #13 · SwiftAndroid/libiconv-libicu-android · GitHub and recompile, to get the library with the data inside.

1 Like