From 7eadad4e327f9c680fae0f5107af444e66a3d3db Mon Sep 17 00:00:00 2001 From: Michal Harakal Date: Mon, 31 Aug 2026 15:43:41 +0200 Subject: [PATCH 1/2] feat(build): publish androidNativeArm32/Arm64 from the six downstream modules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit skainet-io-core, skainet-lang-core, skainet-compile-core, skainet-io-safetensors and skainet-lang-ksp-annotations already target Android native. The rest of the chain a device consumer needs does not, so SKaiNET-transformers' gemma-iree fails to resolve at configuration time: Could not determine the dependencies of task ':llm-runtime:gemma-iree:compileKotlinAndroidNativeArm32'. > Could not resolve sk.ainet.core:skainet-compile-dag / -compile-opt / -io-gguf / -backend-api. Adds the two targets to skainet-io-gguf, skainet-lang-dag, skainet-compile-dag, skainet-compile-opt, skainet-backend-api and skainet-backend-cpu. None of the five hand-declared modules carries posix-typed code, so arm32's Int-width ssize_t/size_t needs no 64-bit split source set — the reason skainet-io-core has one does not apply. skainet-backend-api wires the new source sets onto nativeMain by hand, matching how it declares the rest of its hierarchy. skainet-backend-cpu opts in through the convention plugin's existing skainet.targets property and gets a platformDefaultCpuOpsFactory actual. That actual is a copy of the Linux one rather than a dependsOn(linuxMain): Android native is Linux-based but bionic, not glibc, and linuxMain is free to grow code that assumes the latter. Supersedes most of #990, whose io-core half develop has since landed independently and whose io-gguf half conflicts with the RandomAccessSourceFactory consolidation. Its exportMain split is no longer needed either: that existed to keep export tooling off arm32 because it needs compile-dag, which now has the target. Verified: compileKotlinAndroidNativeArm32 and Arm64 green for all six; jvmTest and repo-wide apiCheck unchanged; and via composite build, transformers' :llm-runtime:gemma-iree:compileKotlinAndroidNativeArm32 — the task that fails on SKaiNET-transformers#315 — now succeeds. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01YKeDSK4JF295y53Uvez954 --- .../skainet-backend-api/build.gradle.kts | 18 +++++++++++++++ .../skainet-backend-cpu/build.gradle.kts | 6 +++++ .../skainet-backend-cpu/gradle.properties | 6 ++++- .../PlatformCpuOpsFactory.androidNative.kt | 22 +++++++++++++++++++ .../skainet-compile-dag/build.gradle.kts | 7 ++++++ .../skainet-compile-opt/build.gradle.kts | 7 ++++++ skainet-io/skainet-io-gguf/build.gradle.kts | 7 ++++++ .../skainet-lang-dag/build.gradle.kts | 7 ++++++ 8 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 skainet-backends/skainet-backend-cpu/src/androidNativeMain/kotlin/sk/ainet/exec/tensor/ops/PlatformCpuOpsFactory.androidNative.kt diff --git a/skainet-backends/skainet-backend-api/build.gradle.kts b/skainet-backends/skainet-backend-api/build.gradle.kts index e37a2ea34..5a0fcadc5 100644 --- a/skainet-backends/skainet-backend-api/build.gradle.kts +++ b/skainet-backends/skainet-backend-api/build.gradle.kts @@ -25,6 +25,13 @@ kotlin { linuxX64() linuxArm64() + // Android-native targets, for on-device consumers that link the engine directly + // (SKaiNET-transformers builds gemma-iree for androidNativeArm32). Unlike + // skainet-io-core this module has no posix-typed code, so arm32's Int-width + // ssize_t/size_t needs no separate 64-bit source set here. + androidNativeArm64() + androidNativeArm32() + jvm() js { @@ -102,5 +109,16 @@ kotlin { val linuxArm64Main by getting { dependsOn(linuxMain) } + + // Straight onto nativeMain rather than linuxMain: Android's native targets are + // Linux-based, but linuxMain is free to grow glibc-specific code that bionic would + // not carry. nativeMain holds what actually applies to both. + val androidNativeArm64Main by getting { + dependsOn(nativeMain) + } + + val androidNativeArm32Main by getting { + dependsOn(nativeMain) + } } } diff --git a/skainet-backends/skainet-backend-cpu/build.gradle.kts b/skainet-backends/skainet-backend-cpu/build.gradle.kts index d3309296f..7b56fbdec 100644 --- a/skainet-backends/skainet-backend-cpu/build.gradle.kts +++ b/skainet-backends/skainet-backend-cpu/build.gradle.kts @@ -44,6 +44,12 @@ kotlin { linuxX64Main { dependsOn(linuxMain.get()) } linuxArm64Main { dependsOn(linuxMain.get()) } + // See PlatformCpuOpsFactory.androidNative.kt for why these hang off nativeMain and + // not linuxMain. + androidNativeMain { dependsOn(nativeMain.get()) } + androidNativeArm32Main { dependsOn(androidNativeMain.get()) } + androidNativeArm64Main { dependsOn(androidNativeMain.get()) } + // Golden (bit-identical) regression tests for the packed encodings — SKEEP-003 gate. // Shared by the JVM and Kotlin/Native test targets only: JS/Wasm compute Float in // double precision and are not expected to be bit-identical (they are covered by the diff --git a/skainet-backends/skainet-backend-cpu/gradle.properties b/skainet-backends/skainet-backend-cpu/gradle.properties index 8380ea84c..2c148ba07 100644 --- a/skainet-backends/skainet-backend-cpu/gradle.properties +++ b/skainet-backends/skainet-backend-cpu/gradle.properties @@ -1,4 +1,8 @@ POM_ARTIFACT_ID=skainet-backend-cpu POM_NAME=skainet neural network scripting API -kotlin.mpp.applyDefaultHierarchyTemplate=false \ No newline at end of file +kotlin.mpp.applyDefaultHierarchyTemplate=false + +# Adds androidNativeArm32/Arm64. On-device consumers link the eager CPU backend directly, +# so it has to publish the same targets skainet-lang-core and skainet-io-core already do. +skainet.targets=jvm,js,wasmJs,wasmWasi,apple,linux,androidNative \ No newline at end of file diff --git a/skainet-backends/skainet-backend-cpu/src/androidNativeMain/kotlin/sk/ainet/exec/tensor/ops/PlatformCpuOpsFactory.androidNative.kt b/skainet-backends/skainet-backend-cpu/src/androidNativeMain/kotlin/sk/ainet/exec/tensor/ops/PlatformCpuOpsFactory.androidNative.kt new file mode 100644 index 000000000..0d2b0fb1b --- /dev/null +++ b/skainet-backends/skainet-backend-cpu/src/androidNativeMain/kotlin/sk/ainet/exec/tensor/ops/PlatformCpuOpsFactory.androidNative.kt @@ -0,0 +1,22 @@ +package sk.ainet.exec.tensor.ops + +import sk.ainet.backend.api.kernel.KernelRegistry +import sk.ainet.exec.kernel.ScalarKernelProvider +import sk.ainet.lang.tensor.data.TensorDataFactory +import sk.ainet.lang.tensor.ops.TensorOps + +/** + * Android's native targets get the same portable factory the Linux one uses, and for the same + * reason: `DirectCpuExecutionContext` is pure Kotlin here, with no cinterop of its own. + * + * It is a copy rather than a `dependsOn(linuxMain)` on purpose. Android native is Linux-based, + * but it is bionic, not glibc — `linuxMain` is free to grow code that assumes the latter, and + * inheriting that silently is worse than the six duplicated lines below. The accelerated + * kernels for these targets live in `skainet-backend-native-cpu`, not here. + */ +internal actual fun platformDefaultCpuOpsFactory(): (TensorDataFactory) -> TensorOps { + // Non-JVM has no ServiceLoader; register the scalar packed-quant kernels + // (Q4_K/Q6_K/Q5_1/Q5_0/Q8_0/Q4_0) so DefaultCpuOpsBase can dispatch them. + KernelRegistry.register(ScalarKernelProvider) + return { factory -> DefaultCpuOps(factory) } +} diff --git a/skainet-compile/skainet-compile-dag/build.gradle.kts b/skainet-compile/skainet-compile-dag/build.gradle.kts index 4281ec714..0562de4c5 100644 --- a/skainet-compile/skainet-compile-dag/build.gradle.kts +++ b/skainet-compile/skainet-compile-dag/build.gradle.kts @@ -27,6 +27,13 @@ kotlin { linuxX64 () linuxArm64 () + // Android-native targets, for on-device consumers that link the engine directly + // (SKaiNET-transformers builds gemma-iree for androidNativeArm32). Unlike + // skainet-io-core this module has no posix-typed code, so arm32's Int-width + // ssize_t/size_t needs no separate 64-bit source set here. + androidNativeArm64() + androidNativeArm32() + jvm() js { diff --git a/skainet-compile/skainet-compile-opt/build.gradle.kts b/skainet-compile/skainet-compile-opt/build.gradle.kts index 0315845fa..2b4893464 100644 --- a/skainet-compile/skainet-compile-opt/build.gradle.kts +++ b/skainet-compile/skainet-compile-opt/build.gradle.kts @@ -26,6 +26,13 @@ kotlin { linuxX64 () linuxArm64 () + // Android-native targets, for on-device consumers that link the engine directly + // (SKaiNET-transformers builds gemma-iree for androidNativeArm32). Unlike + // skainet-io-core this module has no posix-typed code, so arm32's Int-width + // ssize_t/size_t needs no separate 64-bit source set here. + androidNativeArm64() + androidNativeArm32() + jvm() js { diff --git a/skainet-io/skainet-io-gguf/build.gradle.kts b/skainet-io/skainet-io-gguf/build.gradle.kts index 854c04052..c7819e045 100644 --- a/skainet-io/skainet-io-gguf/build.gradle.kts +++ b/skainet-io/skainet-io-gguf/build.gradle.kts @@ -36,6 +36,13 @@ kotlin { linuxX64 () linuxArm64 () + // Android-native targets, for on-device consumers that link the engine directly + // (SKaiNET-transformers builds gemma-iree for androidNativeArm32). Unlike + // skainet-io-core this module has no posix-typed code, so arm32's Int-width + // ssize_t/size_t needs no separate 64-bit source set here. + androidNativeArm64() + androidNativeArm32() + js { browser() } diff --git a/skainet-lang/skainet-lang-dag/build.gradle.kts b/skainet-lang/skainet-lang-dag/build.gradle.kts index 448bc18c4..d00d95f42 100644 --- a/skainet-lang/skainet-lang-dag/build.gradle.kts +++ b/skainet-lang/skainet-lang-dag/build.gradle.kts @@ -28,6 +28,13 @@ kotlin { linuxX64() linuxArm64() + // Android-native targets, for on-device consumers that link the engine directly + // (SKaiNET-transformers builds gemma-iree for androidNativeArm32). Unlike + // skainet-io-core this module has no posix-typed code, so arm32's Int-width + // ssize_t/size_t needs no separate 64-bit source set here. + androidNativeArm64() + androidNativeArm32() + jvm() js { From 4109afdbae85998ce99a1816aca0c73cdb4971b8 Mon Sep 17 00:00:00 2001 From: Michal Harakal Date: Mon, 31 Aug 2026 16:07:20 +0200 Subject: [PATCH 2/2] fix(build): close the androidNative target set over test compilations too The first pass added the targets to the six modules a device consumer needs, but only ever compiled their MAIN source sets locally. CI's assemble also configures the test compilations, and those pull in modules the target set did not reach: Could not determine the dependencies of task ':skainet-backend-cpu:compileTestKotlinAndroidNativeArm32'. > Could not resolve project ':skainet-lang:skainet-lang-models'. A target set has to close over test dependencies, not just main ones. Adding androidNative to skainet-lang-models pulls in skainet-compile-json (its own commonTest needs it), and compile-json's tests need lang-models straight back, so the two of them close the cycle: main deps of both were already covered. compile-json also needed a writeExportToFile actual. Its native actuals are all the same NotImplementedError stub duplicated per target; this one covers both androidNative targets in a single androidNativeMain file, which the default hierarchy template groups for us. Verified with the task CI actually failed on this time: assemble is green, as are compileTestKotlinAndroidNativeArm32 across the affected modules, apiCheck, and their jvm suites. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01YKeDSK4JF295y53Uvez954 --- .../skainet-compile-json/build.gradle.kts | 5 +++++ .../compile/json/Serialization.androidNative.kt | 12 ++++++++++++ skainet-lang/skainet-lang-models/build.gradle.kts | 5 +++++ 3 files changed, 22 insertions(+) create mode 100644 skainet-compile/skainet-compile-json/src/androidNativeMain/kotlin/sk/ainet/compile/json/Serialization.androidNative.kt diff --git a/skainet-compile/skainet-compile-json/build.gradle.kts b/skainet-compile/skainet-compile-json/build.gradle.kts index 08d01b7d6..26c46f722 100644 --- a/skainet-compile/skainet-compile-json/build.gradle.kts +++ b/skainet-compile/skainet-compile-json/build.gradle.kts @@ -27,6 +27,11 @@ kotlin { linuxX64() linuxArm64() + // Android-native targets, to match the modules this one is compiled against: skainet-backend-cpu + // publishes them, and its commonTest depends on this module, so the target set has to close. + androidNativeArm64() + androidNativeArm32() + jvm() js { diff --git a/skainet-compile/skainet-compile-json/src/androidNativeMain/kotlin/sk/ainet/compile/json/Serialization.androidNative.kt b/skainet-compile/skainet-compile-json/src/androidNativeMain/kotlin/sk/ainet/compile/json/Serialization.androidNative.kt new file mode 100644 index 000000000..ac3ab828b --- /dev/null +++ b/skainet-compile/skainet-compile-json/src/androidNativeMain/kotlin/sk/ainet/compile/json/Serialization.androidNative.kt @@ -0,0 +1,12 @@ +package sk.ainet.compile.json + +import sk.ainet.compile.json.model.SkJsonExport + +/** + * One file for both `androidNativeArm32` and `androidNativeArm64` — the default hierarchy + * template groups them under `androidNativeMain`, so this does not need the per-target + * duplication the other native actuals here carry. + */ +public actual suspend fun writeExportToFile(export: SkJsonExport, path: String, pretty: Boolean) { + throw NotImplementedError("writeExportToFile is not implemented for Android native in this module. Use toJsonString() and handle file I/O in the host app.") +} diff --git a/skainet-lang/skainet-lang-models/build.gradle.kts b/skainet-lang/skainet-lang-models/build.gradle.kts index 0021c82a8..7d3211bd0 100644 --- a/skainet-lang/skainet-lang-models/build.gradle.kts +++ b/skainet-lang/skainet-lang-models/build.gradle.kts @@ -28,6 +28,11 @@ kotlin { linuxX64() linuxArm64() + // Android-native targets, to match the modules this one is compiled against: skainet-backend-cpu + // publishes them, and its commonTest depends on this module, so the target set has to close. + androidNativeArm64() + androidNativeArm32() + jvm() js {