From 6829b92e192c37f5824b00e6a28bd60433f29101 Mon Sep 17 00:00:00 2001 From: Marc Prud'hommeaux Date: Fri, 27 Feb 2026 23:33:11 -0500 Subject: [PATCH 01/17] Run on ubuntu-latest rather than macos --- .github/workflows/swift.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index ea175a30..feeff38d 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -10,12 +10,16 @@ jobs: swift: ['6.2.3', 'nightly-6.3'] arch: ['aarch64', 'x86_64', 'armv7'] sdk: ['28', '29', '31', '33'] - runs-on: macos-15 + runs-on: ubuntu-latest timeout-minutes: 30 steps: - uses: actions/checkout@v4 + - uses: skiptools/actions/setup-skip@v1 + with: + install-swift-android-sdk: true + gradle-version: 'none' + swift-version: ${{ matrix.swift }} + swift-android-sdk-version: ${{ matrix.swift }} - name: "Build Swift Package for Android" run: | - brew install skiptools/skip/skip || (brew update && brew install skiptools/skip/skip) - skip android sdk install --version ${{ matrix.swift }} ANDROID_NDK_ROOT="" ANDROID_SDK_VERSION=${{ matrix.sdk }} skip android build --arch ${{ matrix.arch }} --android-api-level ${{ matrix.sdk }} From dc2c65ba5bd40b24d0e7847d8e93e21f37e01452 Mon Sep 17 00:00:00 2001 From: Marc Prud'hommeaux Date: Tue, 10 Mar 2026 12:11:27 -0400 Subject: [PATCH 02/17] Add test cases and have CI test within an apk --- .github/workflows/swift.yml | 21 ++++- Package.swift | 6 ++ Tests/AndroidAppTests/AndroidAppTests.swift | 86 +++++++++++++++++++++ 3 files changed, 109 insertions(+), 4 deletions(-) create mode 100644 Tests/AndroidAppTests/AndroidAppTests.swift diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index feeff38d..a50450c4 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -1,19 +1,24 @@ name: Swift -on: [push] -jobs: +on: + push: + branches: [master] + workflow_dispatch: + pull_request: +jobs: android: name: Android strategy: fail-fast: false matrix: + os: ['ubuntu-24.04', 'macos-15-intel'] swift: ['6.2.3', 'nightly-6.3'] arch: ['aarch64', 'x86_64', 'armv7'] sdk: ['28', '29', '31', '33'] - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} timeout-minutes: 30 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: skiptools/actions/setup-skip@v1 with: install-swift-android-sdk: true @@ -23,3 +28,11 @@ jobs: - name: "Build Swift Package for Android" run: | ANDROID_NDK_ROOT="" ANDROID_SDK_VERSION=${{ matrix.sdk }} skip android build --arch ${{ matrix.arch }} --android-api-level ${{ matrix.sdk }} + - name: "Install and launch emulator" + run: | + skip android emulator create --android-api-level ${{ matrix.sdk }} + skip android emulator launch --background + - name: "Test Swift package on emulator" + run: | + skip android test --apk + diff --git a/Package.swift b/Package.swift index 699b2763..605799b0 100644 --- a/Package.swift +++ b/Package.swift @@ -303,6 +303,12 @@ var package = Package( sdkVersionDefine ] ), + .testTarget( + name: "AndroidAppTests", + dependencies: [ + "AndroidApp", + ] + ), .target( name: "AndroidContent", dependencies: [ diff --git a/Tests/AndroidAppTests/AndroidAppTests.swift b/Tests/AndroidAppTests/AndroidAppTests.swift new file mode 100644 index 00000000..224bed3d --- /dev/null +++ b/Tests/AndroidAppTests/AndroidAppTests.swift @@ -0,0 +1,86 @@ +import Testing +import CSwiftJavaJNI +import JavaTypes +import SwiftJava +import Android +import AndroidOS +import AndroidApp +import AndroidContent + +@Test func testAndroidAppContext() throws { + #expect(AndroidContent.Context.fullJavaClassName == "android.content.Context", "unexpected class name") + #expect(AndroidApp.Application.fullJavaClassName == "android.app.Application", "unexpected class name") + + let jvm: JavaVirtualMachine = try #require(JavaVirtualMachine.findCreatedJavaVM()) + let env: JNIEnvironment = try jvm.environment() + let app: AndroidApp.Application = try #require(AndroidApp.Application.getApplicationContext(env: env)) + let ctx: AndroidContent.Context = AndroidContent.Context(javaHolder: app.javaHolder) // cast android.app.Application to android.content.Context + + let name = ctx.getPackageName() + #expect(name == "org.swift.test.swiftandroid", "test harness named by skip android test") + + let info: AndroidContent.ApplicationInfo = ctx.getApplicationInfo() + #expect(info.processName == "org.swift.test.swiftandroid") + #expect(info.minSdkVersion == 28) + + let looper: AndroidOS.Looper = ctx.getMainLooper() + #expect(looper.isCurrentThread() == false, "tests should not be running on main thread") + + let activityManagerOb: JavaObject = ctx.getSystemService(ctx.javaClass.ACTIVITY_SERVICE) + let activityManager: ActivityManager = ActivityManager(javaHolder: activityManagerOb.javaHolder) + #expect(activityManager.javaClass.isUserAMonkey() == false, "aren't we all monkeys, though?") +} + +extension AndroidApp.Application { + /// There's no public global way of accessing the Android Context, so we use `android.app.ActivityThread.currentActivityThread().getApplication()` + public static func getApplicationContext(env: JNIEnvironment) -> AndroidApp.Application? { + let jni: JNINativeInterface = env.pointee!.pointee + + guard let activityThreadClass: jclass = jni.FindClass(env, "android/app/ActivityThread") else { return nil } + + // lookup `currentActivityThread()` and call it + guard let currentActivityThread: jmethodID = jni.GetStaticMethodID(env, activityThreadClass, "currentActivityThread", "()Landroid/app/ActivityThread;") else { return nil } + guard let activityThread: jobject = jni.CallStaticObjectMethodA(env, activityThreadClass, currentActivityThread, []) else { return nil } + + // lookup `getApplication()` and call it + guard let getApplication: jmethodID = jni.GetMethodID(env, activityThreadClass, "getApplication", "()Landroid/app/Application;") else { return nil } + guard let application: jobject = jni.CallObjectMethodA(env, activityThread, getApplication, []) else { return nil } + + let contextHolder = JavaObjectHolder(object: application, environment: env) + // this could also be AndroidContent.Context (i.e., android.content.Context) + let ctx = AndroidApp.Application(javaHolder: contextHolder) + return ctx + } +} + +extension JavaVirtualMachine { + public static func findCreatedJavaVM() -> JavaVirtualMachine? { + if let sharedJVM = try? JavaVirtualMachine.shared() { + return sharedJVM + } + + // manual method: Get the ambient JavaVM by invoking `JNI_GetCreatedJavaVMs` by hook or by crook + typealias JavaVMPtr = UnsafeMutablePointer + typealias GetCreatedJavaVMs = @convention(c) (_ pvm: UnsafeMutablePointer, _ count: Int32, _ num: UnsafeMutablePointer) -> jint + + // we need to get the host JVM using JNI_GetCreatedJavaVMs, but it is not exported in jni.h, + // so we need to dlsym it from some library, which has changed over various Android APIs + // libnativehelper.so added in API 31 (https://github.com/android/ndk/issues/1320) to work around "libart.so" no longer being allowed to load + // FIXME: this might not work on API 29 and 30 specifically, since it is after Android started restricting access to the APIs but before they exposed JNI_GetCreatedJavaVMs publicly + for libname in [nil, "libnativehelper.so", "libart.so", "libdvm.so"] { + let lib = dlopen(libname, RTLD_NOW) + guard let getCreatedJavaVMs = dlsym(lib, "JNI_GetCreatedJavaVMs").map({ unsafeBitCast($0, to: (GetCreatedJavaVMs).self) }) else { + continue + } + + var runningCount: Int32 = 0 + var jvm: JavaVMPtr? + if getCreatedJavaVMs(&jvm, 1, &runningCount) == JNI_OK, let jvm = jvm { + return JavaVirtualMachine(adoptingJVM: jvm) + } + } + + return nil + } +} + From 05e36711f135a13abb7c0612e969b613ddf5409f Mon Sep 17 00:00:00 2001 From: Marc Prud'hommeaux Date: Tue, 24 Mar 2026 09:32:39 -0400 Subject: [PATCH 03/17] Merge upstream --- Package.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Package.swift b/Package.swift index c580d798..0fdba030 100644 --- a/Package.swift +++ b/Package.swift @@ -288,6 +288,12 @@ var package = Package( sdkVersionDefine, ] ), + .testTarget( + name: "AndroidAppTests", + dependencies: [ + "AndroidApp", + ] + ), .target( name: "AndroidContent", dependencies: [ From 0c1e4d4180a180e4d0accfe69a69b6e8f59cd1c9 Mon Sep 17 00:00:00 2001 From: Marc Prud'hommeaux Date: Tue, 24 Mar 2026 11:18:50 -0400 Subject: [PATCH 04/17] Update test and AndroidApp.Application.currentApplication() example to use the swift-android-native accessor --- .github/workflows/swift.yml | 11 ++- Package.swift | 9 ++ Tests/AndroidAppTests/AndroidAppTests.swift | 95 +++++++-------------- 3 files changed, 48 insertions(+), 67 deletions(-) diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index a50450c4..cafc50b4 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -11,10 +11,12 @@ jobs: strategy: fail-fast: false matrix: - os: ['ubuntu-24.04', 'macos-15-intel'] - swift: ['6.2.3', 'nightly-6.3'] + # os: ['ubuntu-24.04', 'macos-15-intel'] + os: ['ubuntu-24.04'] + swift: ['nightly-6.3'] arch: ['aarch64', 'x86_64', 'armv7'] sdk: ['28', '29', '31', '33'] + ndk: ['r27d'] runs-on: ${{ matrix.os }} timeout-minutes: 30 steps: @@ -25,14 +27,19 @@ jobs: gradle-version: 'none' swift-version: ${{ matrix.swift }} swift-android-sdk-version: ${{ matrix.swift }} + swift-android-ndk-version: ${{ matrix.ndk }} - name: "Build Swift Package for Android" run: | ANDROID_NDK_ROOT="" ANDROID_SDK_VERSION=${{ matrix.sdk }} skip android build --arch ${{ matrix.arch }} --android-api-level ${{ matrix.sdk }} - name: "Install and launch emulator" + # can only lookup the current JavaVirtualMachine on SDK >= 31 + # https://github.com/swiftlang/swift-java/issues/419 + if: ${{ matrix.sdk >= 31 }} run: | skip android emulator create --android-api-level ${{ matrix.sdk }} skip android emulator launch --background - name: "Test Swift package on emulator" + if: ${{ matrix.sdk >= 31 }} run: | skip android test --apk diff --git a/Package.swift b/Package.swift index 0fdba030..83b6e289 100644 --- a/Package.swift +++ b/Package.swift @@ -291,7 +291,16 @@ var package = Package( .testTarget( name: "AndroidAppTests", dependencies: [ + "AndroidJava", "AndroidApp", + .product( + name: "AndroidContext", + package: "swift-android-native" + ), + .product( + name: "SwiftJava", + package: "swift-java" + ), ] ), .target( diff --git a/Tests/AndroidAppTests/AndroidAppTests.swift b/Tests/AndroidAppTests/AndroidAppTests.swift index 224bed3d..0d6617d2 100644 --- a/Tests/AndroidAppTests/AndroidAppTests.swift +++ b/Tests/AndroidAppTests/AndroidAppTests.swift @@ -1,20 +1,38 @@ import Testing -import CSwiftJavaJNI -import JavaTypes +import SwiftJavaJNICore import SwiftJava -import Android import AndroidOS import AndroidApp -import AndroidContent +import AndroidContext +@preconcurrency import AndroidContent + +extension AndroidApp.Application { + /// The shared `android.app.Application` + public static func currentApplication() throws -> Application { + let jvm = try JavaVirtualMachine.shared() + let env = try jvm.environment() + + // get the low-level application context wrapper from swift-android-native + let lowLevelContext = try AndroidContext.application + + // wrap it in the @JavaClass Context + return Application(javaHolder: JavaObjectHolder(object: lowLevelContext.pointer, environment: env)) + } +} + +extension AndroidContent.Context { + /// The shared `android.content.Context` for the current application + public static func applicationContext() throws -> Context { + // simply cast the Application to a Context + return Context(javaHolder: try Application.currentApplication().javaHolder) + } +} @Test func testAndroidAppContext() throws { - #expect(AndroidContent.Context.fullJavaClassName == "android.content.Context", "unexpected class name") - #expect(AndroidApp.Application.fullJavaClassName == "android.app.Application", "unexpected class name") + let jvm = try JavaVirtualMachine.shared() + let env = try jvm.environment() - let jvm: JavaVirtualMachine = try #require(JavaVirtualMachine.findCreatedJavaVM()) - let env: JNIEnvironment = try jvm.environment() - let app: AndroidApp.Application = try #require(AndroidApp.Application.getApplicationContext(env: env)) - let ctx: AndroidContent.Context = AndroidContent.Context(javaHolder: app.javaHolder) // cast android.app.Application to android.content.Context + let ctx = try Context.applicationContext() let name = ctx.getPackageName() #expect(name == "org.swift.test.swiftandroid", "test harness named by skip android test") @@ -26,61 +44,8 @@ import AndroidContent let looper: AndroidOS.Looper = ctx.getMainLooper() #expect(looper.isCurrentThread() == false, "tests should not be running on main thread") - let activityManagerOb: JavaObject = ctx.getSystemService(ctx.javaClass.ACTIVITY_SERVICE) + let activityManagerOb = try #require(ctx.getSystemService(ctx.javaClass.ACTIVITY_SERVICE)) let activityManager: ActivityManager = ActivityManager(javaHolder: activityManagerOb.javaHolder) - #expect(activityManager.javaClass.isUserAMonkey() == false, "aren't we all monkeys, though?") -} - -extension AndroidApp.Application { - /// There's no public global way of accessing the Android Context, so we use `android.app.ActivityThread.currentActivityThread().getApplication()` - public static func getApplicationContext(env: JNIEnvironment) -> AndroidApp.Application? { - let jni: JNINativeInterface = env.pointee!.pointee - guard let activityThreadClass: jclass = jni.FindClass(env, "android/app/ActivityThread") else { return nil } - - // lookup `currentActivityThread()` and call it - guard let currentActivityThread: jmethodID = jni.GetStaticMethodID(env, activityThreadClass, "currentActivityThread", "()Landroid/app/ActivityThread;") else { return nil } - guard let activityThread: jobject = jni.CallStaticObjectMethodA(env, activityThreadClass, currentActivityThread, []) else { return nil } - - // lookup `getApplication()` and call it - guard let getApplication: jmethodID = jni.GetMethodID(env, activityThreadClass, "getApplication", "()Landroid/app/Application;") else { return nil } - guard let application: jobject = jni.CallObjectMethodA(env, activityThread, getApplication, []) else { return nil } - - let contextHolder = JavaObjectHolder(object: application, environment: env) - // this could also be AndroidContent.Context (i.e., android.content.Context) - let ctx = AndroidApp.Application(javaHolder: contextHolder) - return ctx - } -} - -extension JavaVirtualMachine { - public static func findCreatedJavaVM() -> JavaVirtualMachine? { - if let sharedJVM = try? JavaVirtualMachine.shared() { - return sharedJVM - } - - // manual method: Get the ambient JavaVM by invoking `JNI_GetCreatedJavaVMs` by hook or by crook - typealias JavaVMPtr = UnsafeMutablePointer - typealias GetCreatedJavaVMs = @convention(c) (_ pvm: UnsafeMutablePointer, _ count: Int32, _ num: UnsafeMutablePointer) -> jint - - // we need to get the host JVM using JNI_GetCreatedJavaVMs, but it is not exported in jni.h, - // so we need to dlsym it from some library, which has changed over various Android APIs - // libnativehelper.so added in API 31 (https://github.com/android/ndk/issues/1320) to work around "libart.so" no longer being allowed to load - // FIXME: this might not work on API 29 and 30 specifically, since it is after Android started restricting access to the APIs but before they exposed JNI_GetCreatedJavaVMs publicly - for libname in [nil, "libnativehelper.so", "libart.so", "libdvm.so"] { - let lib = dlopen(libname, RTLD_NOW) - guard let getCreatedJavaVMs = dlsym(lib, "JNI_GetCreatedJavaVMs").map({ unsafeBitCast($0, to: (GetCreatedJavaVMs).self) }) else { - continue - } - - var runningCount: Int32 = 0 - var jvm: JavaVMPtr? - if getCreatedJavaVMs(&jvm, 1, &runningCount) == JNI_OK, let jvm = jvm { - return JavaVirtualMachine(adoptingJVM: jvm) - } - } - - return nil - } + #expect(activityManager.javaClass.isUserAMonkey() == false, "aren't we all monkeys, though?") } - From 5d7c43dd7c400c4d62607a2e346aef8604c86cf1 Mon Sep 17 00:00:00 2001 From: Marc Prud'hommeaux Date: Tue, 24 Mar 2026 11:26:33 -0400 Subject: [PATCH 05/17] Fix CI --- .github/workflows/swift.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index cafc50b4..b2c96c1b 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -13,10 +13,10 @@ jobs: matrix: # os: ['ubuntu-24.04', 'macos-15-intel'] os: ['ubuntu-24.04'] - swift: ['nightly-6.3'] arch: ['aarch64', 'x86_64', 'armv7'] - sdk: ['28', '29', '31', '33'] - ndk: ['r27d'] + android-sdk: ['nightly-6.3'] + android-api: ['28', '29', '31', '33'] + android-ndk: ['r27d'] runs-on: ${{ matrix.os }} timeout-minutes: 30 steps: @@ -25,21 +25,21 @@ jobs: with: install-swift-android-sdk: true gradle-version: 'none' - swift-version: ${{ matrix.swift }} - swift-android-sdk-version: ${{ matrix.swift }} - swift-android-ndk-version: ${{ matrix.ndk }} + swift-version: '6.2.4' + swift-android-sdk-version: ${{ matrix.android-sdk }} + swift-android-ndk-version: ${{ matrix.android-ndk }} - name: "Build Swift Package for Android" run: | - ANDROID_NDK_ROOT="" ANDROID_SDK_VERSION=${{ matrix.sdk }} skip android build --arch ${{ matrix.arch }} --android-api-level ${{ matrix.sdk }} + ANDROID_NDK_ROOT="" ANDROID_SDK_VERSION=${{ matrix.android-api }} skip android build --arch ${{ matrix.arch }} --android-api-level ${{ matrix.android-api }} - name: "Install and launch emulator" # can only lookup the current JavaVirtualMachine on SDK >= 31 # https://github.com/swiftlang/swift-java/issues/419 - if: ${{ matrix.sdk >= 31 }} + if: ${{ matrix.android-api >= 31 }} run: | - skip android emulator create --android-api-level ${{ matrix.sdk }} + skip android emulator create --android-api-level ${{ matrix.android-api }} skip android emulator launch --background - name: "Test Swift package on emulator" - if: ${{ matrix.sdk >= 31 }} + if: ${{ matrix.android-api >= 31 }} run: | skip android test --apk From 9e0a395f6e1a09f8e0e93588194725d3791c432f Mon Sep 17 00:00:00 2001 From: Marc Prud'hommeaux Date: Tue, 24 Mar 2026 11:27:12 -0400 Subject: [PATCH 06/17] Fix CI --- .github/workflows/swift.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index b2c96c1b..149994e6 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -25,7 +25,6 @@ jobs: with: install-swift-android-sdk: true gradle-version: 'none' - swift-version: '6.2.4' swift-android-sdk-version: ${{ matrix.android-sdk }} swift-android-ndk-version: ${{ matrix.android-ndk }} - name: "Build Swift Package for Android" From 2a27d98c3a30cf581da4d2895bf1c614ccd70439 Mon Sep 17 00:00:00 2001 From: Marc Prud'hommeaux Date: Tue, 24 Mar 2026 11:44:19 -0400 Subject: [PATCH 07/17] Fix CI --- .github/workflows/swift.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index 149994e6..f72fab7b 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -23,8 +23,8 @@ jobs: - uses: actions/checkout@v6 - uses: skiptools/actions/setup-skip@v1 with: - install-swift-android-sdk: true gradle-version: 'none' + install-swift-android-sdk: true swift-android-sdk-version: ${{ matrix.android-sdk }} swift-android-ndk-version: ${{ matrix.android-ndk }} - name: "Build Swift Package for Android" From df89a122a411cecd92f22f89379c69fe6fac2381 Mon Sep 17 00:00:00 2001 From: Marc Prud'hommeaux Date: Tue, 24 Mar 2026 12:00:03 -0400 Subject: [PATCH 08/17] Fix CI to use reactivecircus/android-emulator-runner to run emulator tests --- .github/workflows/swift.yml | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index f72fab7b..0ba54f68 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -30,15 +30,13 @@ jobs: - name: "Build Swift Package for Android" run: | ANDROID_NDK_ROOT="" ANDROID_SDK_VERSION=${{ matrix.android-api }} skip android build --arch ${{ matrix.arch }} --android-api-level ${{ matrix.android-api }} - - name: "Install and launch emulator" + - name: "Test Swift Package on Android" # can only lookup the current JavaVirtualMachine on SDK >= 31 # https://github.com/swiftlang/swift-java/issues/419 if: ${{ matrix.android-api >= 31 }} - run: | - skip android emulator create --android-api-level ${{ matrix.android-api }} - skip android emulator launch --background - - name: "Test Swift package on emulator" - if: ${{ matrix.android-api >= 31 }} - run: | - skip android test --apk + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{ matrix.android-api }} + arch: x86_64 + script: skip android test --android-api-level ${{ matrix.android-api }} --apk --verbose From 19c54c1b069acc0d0a2cec7ed4b0392e50ee412b Mon Sep 17 00:00:00 2001 From: Marc Prud'hommeaux Date: Tue, 24 Mar 2026 13:52:51 -0400 Subject: [PATCH 09/17] Fix test for AndroidContent.ApplicationInfo.minSdkVersion --- Tests/AndroidAppTests/AndroidAppTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/AndroidAppTests/AndroidAppTests.swift b/Tests/AndroidAppTests/AndroidAppTests.swift index 0d6617d2..276ad3fb 100644 --- a/Tests/AndroidAppTests/AndroidAppTests.swift +++ b/Tests/AndroidAppTests/AndroidAppTests.swift @@ -39,7 +39,7 @@ extension AndroidContent.Context { let info: AndroidContent.ApplicationInfo = ctx.getApplicationInfo() #expect(info.processName == "org.swift.test.swiftandroid") - #expect(info.minSdkVersion == 28) + #expect(info.minSdkVersion >= 28) let looper: AndroidOS.Looper = ctx.getMainLooper() #expect(looper.isCurrentThread() == false, "tests should not be running on main thread") From 6aa97b001ab3a401bf398f054b2775923d173924 Mon Sep 17 00:00:00 2001 From: Marc Prud'hommeaux Date: Tue, 24 Mar 2026 14:35:19 -0400 Subject: [PATCH 10/17] Update CI to build for all archs in a single matrix --- .github/workflows/swift.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index 0ba54f68..3743ca9b 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -13,7 +13,6 @@ jobs: matrix: # os: ['ubuntu-24.04', 'macos-15-intel'] os: ['ubuntu-24.04'] - arch: ['aarch64', 'x86_64', 'armv7'] android-sdk: ['nightly-6.3'] android-api: ['28', '29', '31', '33'] android-ndk: ['r27d'] @@ -29,7 +28,8 @@ jobs: swift-android-ndk-version: ${{ matrix.android-ndk }} - name: "Build Swift Package for Android" run: | - ANDROID_NDK_ROOT="" ANDROID_SDK_VERSION=${{ matrix.android-api }} skip android build --arch ${{ matrix.arch }} --android-api-level ${{ matrix.android-api }} + ANDROID_SDK_VERSION=${{ matrix.android-api }} skip android build --arch aarch64 --arch armv7 --arch x86_64 --android-api-level ${{ matrix.android-api }} + - name: "Test Swift Package on Android" # can only lookup the current JavaVirtualMachine on SDK >= 31 # https://github.com/swiftlang/swift-java/issues/419 @@ -38,5 +38,6 @@ jobs: with: api-level: ${{ matrix.android-api }} arch: x86_64 - script: skip android test --android-api-level ${{ matrix.android-api }} --apk --verbose + script: | + ANDROID_SDK_VERSION=${{ matrix.android-api }} skip android test --android-api-level ${{ matrix.android-api }} --apk --verbose From 3622071a19c6516ce3acd8dc113b2a5ca99639f9 Mon Sep 17 00:00:00 2001 From: Marc Prud'hommeaux Date: Tue, 24 Mar 2026 15:12:39 -0400 Subject: [PATCH 11/17] Increase build timeout --- .github/workflows/swift.yml | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index 3743ca9b..c66e4e4d 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -17,7 +17,7 @@ jobs: android-api: ['28', '29', '31', '33'] android-ndk: ['r27d'] runs-on: ${{ matrix.os }} - timeout-minutes: 30 + timeout-minutes: 90 steps: - uses: actions/checkout@v6 - uses: skiptools/actions/setup-skip@v1 @@ -26,12 +26,21 @@ jobs: install-swift-android-sdk: true swift-android-sdk-version: ${{ matrix.android-sdk }} swift-android-ndk-version: ${{ matrix.android-ndk }} - - name: "Build Swift Package for Android" + + - name: "Build Swift Package for Android (aarch64)" + run: | + ANDROID_SDK_VERSION=${{ matrix.android-api }} skip android build --arch aarch64 --android-api-level ${{ matrix.android-api }} + + - name: "Build Swift Package for Android (armv7)" + run: | + ANDROID_SDK_VERSION=${{ matrix.android-api }} skip android build --arch armv7--android-api-level ${{ matrix.android-api }} + + - name: "Build Swift Package for Android (x86_64)" run: | - ANDROID_SDK_VERSION=${{ matrix.android-api }} skip android build --arch aarch64 --arch armv7 --arch x86_64 --android-api-level ${{ matrix.android-api }} + ANDROID_SDK_VERSION=${{ matrix.android-api }} skip android build --arch x86_64 --android-api-level ${{ matrix.android-api }} - name: "Test Swift Package on Android" - # can only lookup the current JavaVirtualMachine on SDK >= 31 + # can only lookup the current JavaVirtualMachine on API >= 31 # https://github.com/swiftlang/swift-java/issues/419 if: ${{ matrix.android-api >= 31 }} uses: reactivecircus/android-emulator-runner@v2 From a42cc07b97da6226f8c506040c90d3e5173fee3c Mon Sep 17 00:00:00 2001 From: Marc Prud'hommeaux Date: Tue, 24 Mar 2026 15:34:16 -0400 Subject: [PATCH 12/17] Fix arch flag in CI --- .github/workflows/swift.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index c66e4e4d..2c24b0d0 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -33,7 +33,7 @@ jobs: - name: "Build Swift Package for Android (armv7)" run: | - ANDROID_SDK_VERSION=${{ matrix.android-api }} skip android build --arch armv7--android-api-level ${{ matrix.android-api }} + ANDROID_SDK_VERSION=${{ matrix.android-api }} skip android build --arch armv7 --android-api-level ${{ matrix.android-api }} - name: "Build Swift Package for Android (x86_64)" run: | From 3877bbde3cb4ea87e26c320605dbeb09a204facb Mon Sep 17 00:00:00 2001 From: Marc Prud'hommeaux Date: Tue, 24 Mar 2026 16:29:59 -0400 Subject: [PATCH 13/17] Update to use swift-android-native weak-binder-symbol branch (https://github.com/swift-android-sdk/swift-android-native/pull/24) --- Package.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 83b6e289..971394e1 100644 --- a/Package.swift +++ b/Package.swift @@ -94,7 +94,9 @@ var package = Package( ), .package( url: "https://github.com/swift-android-sdk/swift-android-native.git", - branch: "main" + // TODO: restore main after https://github.com/swift-android-sdk/swift-android-native/pull/24 is resolved + // branch: "main" + branch: "weak-binder-symbol" ), ], targets: [ From e0c00e01ca5e1a889ce7f12591fa90c8cd2738c2 Mon Sep 17 00:00:00 2001 From: Marc Prud'hommeaux Date: Tue, 24 Mar 2026 17:22:43 -0400 Subject: [PATCH 14/17] Update to cast Application to Context --- Tests/AndroidAppTests/AndroidAppTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/AndroidAppTests/AndroidAppTests.swift b/Tests/AndroidAppTests/AndroidAppTests.swift index 276ad3fb..b90ef0f9 100644 --- a/Tests/AndroidAppTests/AndroidAppTests.swift +++ b/Tests/AndroidAppTests/AndroidAppTests.swift @@ -24,7 +24,7 @@ extension AndroidContent.Context { /// The shared `android.content.Context` for the current application public static func applicationContext() throws -> Context { // simply cast the Application to a Context - return Context(javaHolder: try Application.currentApplication().javaHolder) + try Application.currentApplication().as(AndroidContent.Context.self)! } } From 0bb59898978f259cf42e3296a9270bb913c85d39 Mon Sep 17 00:00:00 2001 From: Marc Prud'hommeaux Date: Tue, 24 Mar 2026 18:17:03 -0400 Subject: [PATCH 15/17] Restore main branch for swift-android-native --- Package.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Package.swift b/Package.swift index 971394e1..83b6e289 100644 --- a/Package.swift +++ b/Package.swift @@ -94,9 +94,7 @@ var package = Package( ), .package( url: "https://github.com/swift-android-sdk/swift-android-native.git", - // TODO: restore main after https://github.com/swift-android-sdk/swift-android-native/pull/24 is resolved - // branch: "main" - branch: "weak-binder-symbol" + branch: "main" ), ], targets: [ From b0c597838c7e1e3f5bb10b6ca483fcc299a347e1 Mon Sep 17 00:00:00 2001 From: Marc Prud'hommeaux Date: Tue, 24 Mar 2026 20:06:38 -0400 Subject: [PATCH 16/17] CI From 72a0d916af1b9557d5b61a49ab1b954de1fc51b7 Mon Sep 17 00:00:00 2001 From: Marc Prud'hommeaux Date: Tue, 24 Mar 2026 21:38:41 -0400 Subject: [PATCH 17/17] Remove unnecessary call to JavaVirtualMachine.shared() --- Tests/AndroidAppTests/AndroidAppTests.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/Tests/AndroidAppTests/AndroidAppTests.swift b/Tests/AndroidAppTests/AndroidAppTests.swift index b90ef0f9..2c465463 100644 --- a/Tests/AndroidAppTests/AndroidAppTests.swift +++ b/Tests/AndroidAppTests/AndroidAppTests.swift @@ -29,9 +29,6 @@ extension AndroidContent.Context { } @Test func testAndroidAppContext() throws { - let jvm = try JavaVirtualMachine.shared() - let env = try jvm.environment() - let ctx = try Context.applicationContext() let name = ctx.getPackageName()