Skip to content

Commit f750305

Browse files
kotlin(d2+d3): move android natives to AAR module + wire android-smoke
D2 — Android native staging path moves from kotlin/lib/src/main/resources/lib/<abi>/ (JVM JAR's lib/<abi>/) to kotlin/android/src/main/jniLibs/<abi>/ (AAR source set; AGP packages into the AAR's jni/<abi>/, then merged into consumer APKs). Updated: - cargo make kotlin-android task (writes to jniLibs path). - release.yml stage-natives step (writes both JVM resources + AAR jniLibs from the cdylib + android-jniLibs artifacts). - ci_kotlin.yml kotlin-android-consumer-smoke job (same). - verify-kotlin-artifact (validates both source trees instead of just kotlin/lib/src/main/resources). - publish step: ./gradlew publishAndReleaseToMavenCentral now (no :lib: prefix) so both subprojects publish. - .gitignore: kotlin/android/src/main/jniLibs/ is CI-generated. D3 — kotlin/android-smoke consumes the new AAR coordinate. Composite build substitutes computer.iroh:iroh-android → :android (was iroh → :lib). app/build.gradle.kts depends on iroh-android (which transitively brings in :lib + JNA AAR correctly). Also: make :android subproject conditionally included (skip on hosts without an Android SDK, e.g. the self-hosted linux-aarch64 runner where setup-android@v3 fails). Lets :lib build + test on that runner without the SDK. JVM smoke + JVM test jobs skip setup-android on linux-aarch64.
1 parent 0f1ad32 commit f750305

7 files changed

Lines changed: 88 additions & 51 deletions

File tree

.github/workflows/ci_kotlin.yml

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,12 @@ jobs:
4141
distribution: "temurin"
4242
java-version: "21"
4343
- uses: gradle/actions/setup-gradle@v4
44-
# AGP requires the Android SDK at configuration time. Even though
45-
# test-kotlin only runs :lib:test, Gradle still has to configure the
46-
# :android subproject in the same kotlin/ root build.
44+
# AGP requires the Android SDK at configuration time. Skip on
45+
# linux-aarch64 (setup-android@v3 fails on that runner) — the :android
46+
# subproject is conditionally excluded by settings.gradle.kts when no
47+
# SDK is detected, so :lib still builds + tests fine without it.
4748
- uses: android-actions/setup-android@v3
49+
if: matrix.name != 'linux-aarch64'
4850
- name: cargo make test-kotlin
4951
run: cargo make test-kotlin
5052

@@ -120,10 +122,12 @@ jobs:
120122
distribution: "temurin"
121123
java-version: "21"
122124
- uses: gradle/actions/setup-gradle@v4
123-
# AGP requires the Android SDK at configuration time; even though this
124-
# job only runs :lib:test, Gradle still configures the :android
125-
# subproject in the same root build.
125+
# AGP requires the Android SDK at configuration time. Skip on
126+
# linux-aarch64 (setup-android@v3 fails on that runner) — the :android
127+
# subproject is conditionally excluded by settings.gradle.kts when no
128+
# SDK is detected, so :lib still builds + tests fine without it.
126129
- uses: android-actions/setup-android@v3
130+
if: matrix.name != 'linux-aarch64'
127131
- name: cargo make verify-kotlin-consumer
128132
run: cargo make verify-kotlin-consumer
129133

@@ -194,16 +198,16 @@ jobs:
194198
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
195199
run: |
196200
set -eu
197-
# Stage at lib/<abi>/ — where AGP's mergeNativeLibsTask scans JAR
198-
# deps to pull natives into a consumer APK.
201+
# Stage at kotlin/android/src/main/jniLibs/<abi>/ — AAR source-set
202+
# path, AGP packages these into the AAR's jni/<abi>/.
199203
cargo ndk -o ./_android_libs \
200204
-t armeabi-v7a -t arm64-v8a -t x86 -t x86_64 \
201205
build --release --lib -p iroh-ffi
202-
RES=kotlin/lib/src/main/resources
206+
JNI=kotlin/android/src/main/jniLibs
203207
for abi in armeabi-v7a arm64-v8a x86 x86_64; do
204-
install -D -m644 "./_android_libs/$abi/libiroh_ffi.so" "$RES/lib/$abi/libiroh_ffi.so"
208+
install -D -m644 "./_android_libs/$abi/libiroh_ffi.so" "$JNI/$abi/libiroh_ffi.so"
205209
done
206-
# Composite-build :lib:jar needs the bindings file.
210+
# Composite-build :android:assemble needs iroh_ffi.kt for :lib.
207211
cargo make bindgen-kotlin
208212
- name: Enable KVM
209213
run: |

.github/workflows/release.yml

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -252,11 +252,13 @@ permissions:
252252
- name: Stage Android natives + generate Kotlin bindings
253253
run: |
254254
set -eu
255-
RES=kotlin/lib/src/main/resources
255+
# AAR source-set path — AGP packages files here into the AAR's
256+
# jni/<abi>/, which consumers' APKs then merge.
257+
JNI=kotlin/android/src/main/jniLibs
256258
for abi in armeabi-v7a arm64-v8a x86 x86_64; do
257-
install -D -m644 "android-jniLibs/$abi/libiroh_ffi.so" "$RES/lib/$abi/libiroh_ffi.so"
259+
install -D -m644 "android-jniLibs/$abi/libiroh_ffi.so" "$JNI/$abi/libiroh_ffi.so"
258260
done
259-
# Composite-build `:lib:jar` from android-smoke needs iroh_ffi.kt.
261+
# Composite-build :android:assemble needs iroh_ffi.kt for :lib.
260262
cargo make bindgen-kotlin
261263
- name: Enable KVM
262264
# reactivecircus/android-emulator-runner needs HW accel on
@@ -311,26 +313,28 @@ permissions:
311313
with:
312314
name: kotlin-android-jniLibs
313315
path: android-jniLibs/
314-
- name: Stage cdylibs under jna-platform dirs
316+
- name: Stage natives into both module trees
315317
shell: bash
316318
run: |
317319
set -eu
320+
# :lib (JVM JAR) — desktop cdylibs at JNA-platform paths.
318321
RES=kotlin/lib/src/main/resources
319322
rm -f "$RES"/libiroh_ffi.so "$RES"/libiroh_ffi.dylib "$RES"/iroh_ffi.dll
320323
install -D -m644 cdylibs/kotlin-cdylib-linux-x86_64/libiroh_ffi.so "$RES/linux-x86-64/libiroh_ffi.so"
321324
install -D -m644 cdylibs/kotlin-cdylib-linux-aarch64/libiroh_ffi.so "$RES/linux-aarch64/libiroh_ffi.so"
322325
install -D -m644 cdylibs/kotlin-cdylib-macos-aarch64/libiroh_ffi.dylib "$RES/darwin-aarch64/libiroh_ffi.dylib"
323326
install -D -m644 cdylibs/kotlin-cdylib-windows-x86_64/iroh_ffi.dll "$RES/win32-x86-64/iroh_ffi.dll"
324-
# Android ABIs: cargo-ndk emits <abi>/libiroh_ffi.so; ship them at
325-
# lib/<abi>/ so AGP packages them into consumer APKs.
327+
# :android (AAR) — per-ABI .so under jniLibs/, AGP packages these
328+
# into the AAR's jni/<abi>/ which consumer APKs then merge.
329+
JNI=kotlin/android/src/main/jniLibs
326330
for abi in armeabi-v7a arm64-v8a x86 x86_64; do
327-
install -D -m644 "android-jniLibs/$abi/libiroh_ffi.so" "$RES/lib/$abi/libiroh_ffi.so"
331+
install -D -m644 "android-jniLibs/$abi/libiroh_ffi.so" "$JNI/$abi/libiroh_ffi.so"
328332
done
329-
find "$RES" -type f
333+
find "$RES" "$JNI" -type f
330334
- name: Verify Kotlin artifact contents
331335
# Gate publish on every required native lib being present at the
332336
# right path with the right architecture (catches iroh-ffi#246-class
333-
# bugs — jar uploaded missing a platform slice, app crashes at
337+
# bugs — artifact uploaded missing a platform slice, app crashes at
334338
# runtime with UnsatisfiedLinkError).
335339
run: cargo make verify-kotlin-artifact
336340
- name: Publish to Maven Central
@@ -339,5 +343,9 @@ permissions:
339343
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_USERNAME }}
340344
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_PASSWORD }}
341345
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_KEY }}
342-
run: ./gradlew :lib:publishAndReleaseToMavenCentral --no-daemon --console=plain
346+
# Both modules in one Vanniktech invocation — :lib publishes
347+
# computer.iroh:iroh (JAR), :android publishes computer.iroh:iroh-
348+
# android (AAR). Each subproject's mavenPublishing block configures
349+
# its own coordinates + publication shape.
350+
run: ./gradlew publishAndReleaseToMavenCentral --no-daemon --console=plain
343351

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,10 @@ package-lock.json
108108

109109
kotlin/*.jar
110110
kotlin/**/*.dylib
111+
# Android native libs are CI-staged (release.yml / kotlin-android job); the
112+
# AAR build pulls them into the publication but the .so files themselves are
113+
# generated artifacts, not source.
114+
kotlin/android/src/main/jniLibs/
111115

112116
# Transient xcodebuild derived data for the Swift test task
113117
/.xcode-ddata

Makefile.toml

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ command -v cargo-ndk >/dev/null 2>&1 || cargo install --version 3.5.4 cargo-ndk
165165
# Host lib only needed so uniffi-bindgen can read metadata.
166166
cargo build --lib
167167
TARGET_DIR=$(cargo metadata --format-version 1 --no-deps | python3 -c 'import json,sys;print(json.load(sys.stdin)["target_directory"])')
168-
cargo ndk -o ./kotlin/lib/src/main/jniLibs \
168+
cargo ndk -o ./kotlin/android/src/main/jniLibs \
169169
--manifest-path ./Cargo.toml \
170170
-t armeabi-v7a -t arm64-v8a -t x86 -t x86_64 \
171171
build --release
@@ -265,42 +265,48 @@ echo "verify-swift-xcframework: OK"
265265
'''
266266

267267
[tasks.verify-kotlin-artifact]
268-
description = "Verify kotlin/lib/src/main/resources/ has every required native lib (4 desktop JNA paths + 4 Android ABIs under lib/) with the correct architecture, before Maven publish."
268+
description = "Verify the two Kotlin artifact source trees have every required native lib with the correct architecture, before Maven publish. JVM JAR: 4 desktop JNA paths under kotlin/lib/src/main/resources/. AAR: 4 Android ABIs under kotlin/android/src/main/jniLibs/."
269269
script_runner = "@shell"
270270
script = '''
271271
set -eu
272-
RES="${IROH_KOTLIN_RES:-kotlin/lib/src/main/resources}"
273-
[ -d "$RES" ] || { echo "ERROR: $RES not found" >&2; exit 1; }
274272
275273
fail=0
276274
err() { printf 'FAIL: %s\n' "$*" >&2; fail=1; }
277275
278-
# JNA-platform paths (loaded by JNA from classpath at runtime) + lib/<abi>/
279-
# paths (merged into APKs by AGP from JAR dependencies). Patterns are file(1)
280-
# substring regexes; distinguish 32/64-bit, ARM-vs-x86, and Mach-O/PE/ELF.
276+
# Two source trees, one per published artifact:
277+
# - kotlin/lib/src/main/resources/<jna-platform>/<lib> → JVM JAR; JNA
278+
# discovers libs at <jna-platform>/<libname>.<ext> from classpath.
279+
# - kotlin/android/src/main/jniLibs/<abi>/libiroh_ffi.so → AAR; AGP packages
280+
# these into consumer APKs at lib/<abi>/.
281+
# Patterns are file(1) substring regexes — distinguish 32/64-bit, ARM/x86,
282+
# Mach-O/PE/ELF.
283+
JVM_RES="${IROH_KOTLIN_RES:-kotlin/lib/src/main/resources}"
284+
AND_JNI="${IROH_KOTLIN_JNI:-kotlin/android/src/main/jniLibs}"
285+
[ -d "$JVM_RES" ] || { echo "ERROR: $JVM_RES not found" >&2; exit 1; }
286+
[ -d "$AND_JNI" ] || { echo "ERROR: $AND_JNI not found" >&2; exit 1; }
287+
281288
SPECS="
282-
linux-x86-64/libiroh_ffi.so|ELF 64-bit.*x86-64
283-
linux-aarch64/libiroh_ffi.so|ELF 64-bit.*aarch64
284-
darwin-aarch64/libiroh_ffi.dylib|Mach-O 64-bit.*arm64
285-
win32-x86-64/iroh_ffi.dll|PE32\\+.*x86-64
286-
lib/armeabi-v7a/libiroh_ffi.so|ELF 32-bit.*ARM, EABI
287-
lib/arm64-v8a/libiroh_ffi.so|ELF 64-bit.*aarch64
288-
lib/x86/libiroh_ffi.so|ELF 32-bit.*Intel 80386
289-
lib/x86_64/libiroh_ffi.so|ELF 64-bit.*x86-64
289+
$JVM_RES/linux-x86-64/libiroh_ffi.so|ELF 64-bit.*x86-64
290+
$JVM_RES/linux-aarch64/libiroh_ffi.so|ELF 64-bit.*aarch64
291+
$JVM_RES/darwin-aarch64/libiroh_ffi.dylib|Mach-O 64-bit.*arm64
292+
$JVM_RES/win32-x86-64/iroh_ffi.dll|PE32\\+.*x86-64
293+
$AND_JNI/armeabi-v7a/libiroh_ffi.so|ELF 32-bit.*ARM, EABI
294+
$AND_JNI/arm64-v8a/libiroh_ffi.so|ELF 64-bit.*aarch64
295+
$AND_JNI/x86/libiroh_ffi.so|ELF 32-bit.*Intel 80386
296+
$AND_JNI/x86_64/libiroh_ffi.so|ELF 64-bit.*x86-64
290297
"
291298
292299
while IFS='|' read -r path pat; do
293300
[ -n "$path" ] || continue
294-
full="$RES/$path"
295-
if [ ! -f "$full" ]; then
301+
if [ ! -f "$path" ]; then
296302
err "missing $path"
297303
continue
298304
fi
299-
desc=$(file -b "$full")
305+
desc=$(file -b "$path")
300306
if ! printf '%s' "$desc" | grep -Eq "$pat"; then
301307
err "$path: arch mismatch (want /$pat/, got: $desc)"
302308
else
303-
printf ' ok %-40s %s\n' "$path" "$desc"
309+
printf ' ok %-60s %s\n' "$path" "$desc"
304310
fi
305311
done <<EOF
306312
$SPECS

kotlin/android-smoke/app/build.gradle.kts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,12 @@ android {
3131
}
3232

3333
dependencies {
34-
// The artifact under test. Exclude its JNA transitive — kotlin/lib
35-
// declares net.java.dev.jna:jna:5.15.0 (the JAR variant, fine for JVM
36-
// consumers), but Android needs the AAR variant which ships
37-
// libjnidispatch.so for each ABI. AGP errors on the duplicate-class
38-
// collision if both flavors hit the classpath.
39-
implementation("computer.iroh:iroh:$irohVersion") {
40-
exclude(group = "net.java.dev.jna", module = "jna")
41-
}
42-
implementation("net.java.dev.jna:jna:5.15.0@aar")
34+
// The artifact under test. Android consumers depend on iroh-android (the
35+
// AAR), which already handles the JNA AAR-vs-JAR exclusion internally —
36+
// the transitive computer.iroh:iroh (the JVM JAR) is what carries the
37+
// Kotlin API surface, and iroh-android adds IrohAndroid + the per-ABI
38+
// libiroh_ffi.so files at jni/<abi>/.
39+
implementation("computer.iroh:iroh-android:$irohVersion")
4340
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0")
4441

4542
androidTestImplementation("androidx.test.ext:junit:1.2.1")

kotlin/android-smoke/settings.gradle.kts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ dependencyResolutionManagement {
2727

2828
includeBuild("..") {
2929
dependencySubstitution {
30-
substitute(module("computer.iroh:iroh")).using(project(":lib"))
30+
// Android consumers depend on computer.iroh:iroh-android (the AAR);
31+
// computer.iroh:iroh is the JVM-only JAR sibling, not what gets
32+
// pulled into APKs.
33+
substitute(module("computer.iroh:iroh-android")).using(project(":android"))
3134
}
3235
}
3336

kotlin/settings.gradle.kts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,19 @@ dependencyResolutionManagement {
2424
}
2525

2626
rootProject.name = "iroh-kotlin"
27-
include("lib", "android")
27+
include("lib")
28+
29+
// `:android` requires an Android SDK at Gradle configuration time. On hosts
30+
// without one (e.g. the self-hosted linux-aarch64 runner where
31+
// android-actions/setup-android fails), skip the subproject so the rest of
32+
// the build still works. Jobs that need :android (publish, Android consumer
33+
// smoke) run setup-android before invoking Gradle.
34+
val androidSdkAvailable = listOf(
35+
System.getenv("ANDROID_HOME"),
36+
System.getenv("ANDROID_SDK_ROOT"),
37+
).any { !it.isNullOrEmpty() } || file("local.properties").exists()
38+
if (androidSdkAvailable) {
39+
include("android")
40+
} else {
41+
logger.lifecycle("[settings] No Android SDK detected (ANDROID_HOME / ANDROID_SDK_ROOT unset, no local.properties) — skipping :android subproject")
42+
}

0 commit comments

Comments
 (0)