From 7a85e627e446f88af14caf13b5b32f025b6c7d70 Mon Sep 17 00:00:00 2001 From: michal harakal Date: Sun, 9 Aug 2026 18:16:51 +0200 Subject: [PATCH 1/2] build(io): Windows random-access reads for io-core and io-gguf (#911) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit posix pread(2) does not exist on mingw, and mingwX64 is LLP64 — so the Win32 path is a separate leaf implementation instead of joining io-core's native64Main posix source set: - io-core: WindowsRandomAccessSource (CreateFileW + ReadFile with an OVERLAPPED 64-bit offset — positional, thread-safe without locking, >2 GB-safe via Offset/OffsetHigh; GetFileSizeEx for size). Test is a port of PosixPreadRandomAccessSourceTest (kotlinx-io only). - io-gguf: migrated to sk.ainet.multiplatform with mingw enabled. The posix createRandomAccessSource actual moves from nativeMain into a new posixMain intermediate; the module opts out of the default hierarchy template (custom dependsOn edges silently disable it — the compilations collapsed to [leaf, commonMain] until wired by hand, same pattern as backend-cpu) and wires nativeMain/posixMain/leaf edges explicitly, with mingwX64Main off nativeMain carrying the Win32 actual. Fallback contract unchanged: null when the file cannot be opened -> legacy sequential reader. A windows-latest mingwX64Test CI lane follows in a separate workflow change (token pushing this branch lacks the workflow scope). Verified locally: linuxX64/macosArm64/mingwX64 compile green for io-gguf; mingw test klibs compile for io-core and io-gguf. --- .../sk/ainet/io/WindowsRandomAccessSource.kt | 134 ++++++++++++++++++ .../ainet/io/WindowsRandomAccessSourceTest.kt | 126 ++++++++++++++++ skainet-io/skainet-io-gguf/build.gradle.kts | 96 +++++-------- skainet-io/skainet-io-gguf/gradle.properties | 4 +- .../gguf/RandomAccessSourceFactory.mingw.kt | 13 ++ .../gguf/RandomAccessSourceFactory.posix.kt | 14 ++ 6 files changed, 326 insertions(+), 61 deletions(-) create mode 100644 skainet-io/skainet-io-core/src/mingwX64Main/kotlin/sk/ainet/io/WindowsRandomAccessSource.kt create mode 100644 skainet-io/skainet-io-core/src/mingwX64Test/kotlin/sk/ainet/io/WindowsRandomAccessSourceTest.kt create mode 100644 skainet-io/skainet-io-gguf/src/mingwX64Main/kotlin/sk/ainet/io/gguf/RandomAccessSourceFactory.mingw.kt create mode 100644 skainet-io/skainet-io-gguf/src/posixMain/kotlin/sk/ainet/io/gguf/RandomAccessSourceFactory.posix.kt diff --git a/skainet-io/skainet-io-core/src/mingwX64Main/kotlin/sk/ainet/io/WindowsRandomAccessSource.kt b/skainet-io/skainet-io-core/src/mingwX64Main/kotlin/sk/ainet/io/WindowsRandomAccessSource.kt new file mode 100644 index 000000000..1c801d707 --- /dev/null +++ b/skainet-io/skainet-io-core/src/mingwX64Main/kotlin/sk/ainet/io/WindowsRandomAccessSource.kt @@ -0,0 +1,134 @@ +package sk.ainet.io + +import kotlinx.cinterop.ExperimentalForeignApi +import kotlinx.cinterop.addressOf +import kotlinx.cinterop.alloc +import kotlinx.cinterop.convert +import kotlinx.cinterop.memScoped +import kotlinx.cinterop.ptr +import kotlinx.cinterop.usePinned +import kotlinx.cinterop.value +import platform.windows.CloseHandle +import platform.windows.CreateFileW +import platform.windows.DWORDVar +import platform.windows.ERROR_HANDLE_EOF +import platform.windows.FILE_ATTRIBUTE_NORMAL +import platform.windows.FILE_SHARE_READ +import platform.windows.GENERIC_READ +import platform.windows.GetFileSizeEx +import platform.windows.GetLastError +import platform.windows.HANDLE +import platform.windows.INVALID_HANDLE_VALUE +import platform.windows.LARGE_INTEGER +import platform.windows.OPEN_EXISTING +import platform.windows.OVERLAPPED +import platform.windows.ReadFile + +/** + * Windows [RandomAccessSource] backed by `ReadFile` with an `OVERLAPPED` offset. + * + * POSIX `pread(2)` does not exist on mingw; passing an `OVERLAPPED` structure with an + * explicit 64-bit offset to `ReadFile` gives the same positional semantics — every call + * names its own offset, so concurrent reads from different positions are safe without + * locking, and files > 2 GB work (offset is split into `Offset`/`OffsetHigh`). + * + * This is deliberately a separate leaf implementation: `mingwX64Main` must stay out of + * this module's `native64Main` source set (that set is POSIX-`pread`-shaped and LP64; + * mingw is LLP64). [close] is single-shot. See #911. + */ +@OptIn(ExperimentalForeignApi::class) +public class WindowsRandomAccessSource private constructor( + private val handle: HANDLE, + override val size: Long, +) : RandomAccessSource { + + private var closed = false + + override fun readAt(position: Long, length: Int): ByteArray { + require(position >= 0) { "Position must be non-negative: $position" } + require(length >= 0) { "Length must be non-negative: $length" } + require(position + length <= size) { + "Read beyond end of file: position=$position, length=$length, size=$size" + } + if (length == 0) return ByteArray(0) + + val buffer = ByteArray(length) + val bytesRead = readAt(position, buffer, 0, length) + return if (bytesRead < length) buffer.copyOf(bytesRead) else buffer + } + + override fun readAt(position: Long, buffer: ByteArray, offset: Int, length: Int): Int { + require(position >= 0) { "Position must be non-negative: $position" } + require(offset >= 0) { "Offset must be non-negative: $offset" } + require(length >= 0) { "Length must be non-negative: $length" } + require(offset + length <= buffer.size) { + "Buffer overflow: offset=$offset, length=$length, buffer.size=${buffer.size}" + } + check(!closed) { "Source is closed" } + if (length == 0) return 0 + + return buffer.usePinned { pinned -> + var totalRead = 0 + while (totalRead < length) { + val chunk = memScoped { + val pos = position + totalRead + val overlapped = alloc() + overlapped.Offset = (pos and 0xFFFF_FFFFL).toUInt() + overlapped.OffsetHigh = (pos ushr 32).toUInt() + val read = alloc() + val ok = ReadFile( + handle, + pinned.addressOf(offset + totalRead), + (length - totalRead).convert(), + read.ptr, + overlapped.ptr, + ) + if (ok == 0) { + val err = GetLastError() + if (err == ERROR_HANDLE_EOF.toUInt()) return@memScoped 0 + error("ReadFile failed at offset $pos: Win32 error $err") + } + read.value.toInt() + } + if (chunk == 0) break // EOF + totalRead += chunk + } + totalRead + } + } + + override fun close() { + if (closed) return + closed = true + CloseHandle(handle) + } + + public companion object { + /** + * Open [path] for read-only random access. Returns `null` if the file cannot be + * opened or sized — matching the JVM/POSIX implementations, so consumers fall + * back to the legacy sequential reader. + */ + public fun open(path: String): WindowsRandomAccessSource? { + val handle = CreateFileW( + path, + GENERIC_READ.convert(), + FILE_SHARE_READ.convert(), + null, + OPEN_EXISTING.convert(), + FILE_ATTRIBUTE_NORMAL.convert(), + null, + ) + if (handle == null || handle == INVALID_HANDLE_VALUE) return null + return memScoped { + val sizeVar = alloc() + if (GetFileSizeEx(handle, sizeVar.ptr) == 0) { + CloseHandle(handle) + null + } else { + WindowsRandomAccessSource(handle, sizeVar.QuadPart) + } + } + } + } +} diff --git a/skainet-io/skainet-io-core/src/mingwX64Test/kotlin/sk/ainet/io/WindowsRandomAccessSourceTest.kt b/skainet-io/skainet-io-core/src/mingwX64Test/kotlin/sk/ainet/io/WindowsRandomAccessSourceTest.kt new file mode 100644 index 000000000..c7283a5ee --- /dev/null +++ b/skainet-io/skainet-io-core/src/mingwX64Test/kotlin/sk/ainet/io/WindowsRandomAccessSourceTest.kt @@ -0,0 +1,126 @@ +package sk.ainet.io + +import kotlinx.io.buffered +import kotlinx.io.files.Path +import kotlinx.io.files.SystemFileSystem +import kotlinx.io.files.SystemTemporaryDirectory +import kotlinx.io.write +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertNull +import kotlin.test.assertTrue + +class WindowsRandomAccessSourceTest { + + private val expected = ByteArray(8192) { (it and 0xFF).toByte() } // 0..255 repeating + private lateinit var path: Path + + @BeforeTest + fun setUp() { + path = Path(SystemTemporaryDirectory, "win-read-test-${kotlin.random.Random.nextLong()}.bin") + SystemFileSystem.sink(path).buffered().use { it.write(expected) } + } + + @AfterTest + fun tearDown() { + if (SystemFileSystem.exists(path)) SystemFileSystem.delete(path) + } + + @Test + fun open_reports_correct_size() { + val src = WindowsRandomAccessSource.open(path.toString())!! + try { + assertEquals(expected.size.toLong(), src.size) + } finally { + src.close() + } + } + + @Test + fun read_at_zero_returns_prefix() { + WindowsRandomAccessSource.open(path.toString())!!.use { src -> + val got = src.readAt(0, 16) + assertContentEquals(expected.copyOfRange(0, 16), got) + } + } + + @Test + fun read_at_arbitrary_offset_returns_slice() { + WindowsRandomAccessSource.open(path.toString())!!.use { src -> + val got = src.readAt(1234, 256) + assertContentEquals(expected.copyOfRange(1234, 1234 + 256), got) + } + } + + @Test + fun read_at_end_returns_suffix() { + WindowsRandomAccessSource.open(path.toString())!!.use { src -> + val got = src.readAt(expected.size - 32L, 32) + assertContentEquals(expected.copyOfRange(expected.size - 32, expected.size), got) + } + } + + @Test + fun read_into_buffer_reports_bytes_read() { + WindowsRandomAccessSource.open(path.toString())!!.use { src -> + val buf = ByteArray(64) + val n = src.readAt(100L, buf, 0, 64) + assertEquals(64, n) + assertContentEquals(expected.copyOfRange(100, 164), buf) + } + } + + @Test + fun read_into_buffer_with_offset() { + WindowsRandomAccessSource.open(path.toString())!!.use { src -> + val buf = ByteArray(128) + val n = src.readAt(50L, buf, offset = 32, length = 64) + assertEquals(64, n) + assertContentEquals(expected.copyOfRange(50, 114), buf.copyOfRange(32, 96)) + // Bytes outside the requested window must remain zero. + for (i in 0 until 32) assertEquals(0, buf[i]) + for (i in 96 until 128) assertEquals(0, buf[i]) + } + } + + @Test + fun read_past_end_throws() { + WindowsRandomAccessSource.open(path.toString())!!.use { src -> + assertFailsWith { src.readAt(expected.size - 1L, 16) } + } + } + + @Test + fun negative_position_throws() { + WindowsRandomAccessSource.open(path.toString())!!.use { src -> + assertFailsWith { src.readAt(-1L, 4) } + } + } + + @Test + fun read_after_close_throws() { + val src = WindowsRandomAccessSource.open(path.toString())!! + src.close() + assertFailsWith { + src.readAt(0L, ByteArray(4), 0, 4) + } + } + + @Test + fun close_is_idempotent() { + val src = WindowsRandomAccessSource.open(path.toString())!! + src.close() + src.close() // must not throw + assertTrue(true) + } + + @Test + fun open_missing_file_returns_null() { + val missing = Path(SystemTemporaryDirectory, "definitely-does-not-exist-${kotlin.random.Random.nextLong()}.bin") + assertNull(WindowsRandomAccessSource.open(missing.toString())) + } +} diff --git a/skainet-io/skainet-io-gguf/build.gradle.kts b/skainet-io/skainet-io-gguf/build.gradle.kts index 0d6b25956..308b54ccd 100644 --- a/skainet-io/skainet-io-gguf/build.gradle.kts +++ b/skainet-io/skainet-io-gguf/build.gradle.kts @@ -1,74 +1,50 @@ -import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { - alias(libs.plugins.kotlinMultiplatform) + id("sk.ainet.multiplatform") alias(libs.plugins.androidMultiplatformLibrary) alias(libs.plugins.vanniktech.mavenPublish) id("sk.ainet.dokka") } -kotlin { - targets.configureEach { - compilations.configureEach { - compileTaskProvider.get().compilerOptions { - freeCompilerArgs.add("-Xexpect-actual-classes") - } - } - } - - jvm() - android { - namespace = "sk.ainet.io.gguf" - compileSdk = libs.versions.android.compileSdk.get().toInt() - minSdk = libs.versions.android.minSdk.get().toInt() - compilerOptions { - jvmTarget.set(JvmTarget.JVM_1_8) - } - } - - iosArm64() - iosSimulatorArm64() - macosArm64 () - linuxX64 () - linuxArm64 () - - js { - browser() - } - - @OptIn(ExperimentalWasmDsl::class) - wasmJs { - browser() - } - - @OptIn(ExperimentalWasmDsl::class) - wasmWasi { - nodejs() - } +// Targets come from skainet.targets in this module's gradle.properties (mingw per #911: +// its createRandomAccessSource actual routes to io-core's WindowsRandomAccessSource). +// explicitApi(), kotlin-test and -Xexpect-actual-classes come from sk.ainet.multiplatform. +skainet { + namespace = "sk.ainet.io.gguf" + androidJvmTarget = JvmTarget.JVM_1_8 + expectActualClasses = true + // Pre-migration behavior: this module never enabled explicit API mode; turning it on + // is a separate cleanup from the #911 target work. + explicitApi = false +} +kotlin { sourceSets { - val commonMain by getting { - dependencies { - implementation(libs.kotlinx.io.core) - implementation(project(":skainet-lang:skainet-lang-core")) - implementation(project(":skainet-io:skainet-io-core")) - implementation(project(":skainet-compile:skainet-compile-core")) - implementation(project(":skainet-compile:skainet-compile-dag")) - - } - } - val commonTest by getting { - dependencies { - implementation(libs.kotlin.test) - } + // This module opts out of the default hierarchy template + // (kotlin.mpp.applyDefaultHierarchyTemplate=false in gradle.properties) — custom + // dependsOn edges would silently disable it anyway — and wires the native tree by + // hand: the posix `pread`-backed createRandomAccessSource actual is shared by the + // Apple and Linux targets via `posixMain`, while mingwX64 hangs off `nativeMain` + // directly with its own Win32-backed leaf actual (posix pread does not exist + // there). No apple/linux intermediates: no sources live at that level. + nativeMain { dependsOn(commonMain.get()) } + val posixMain by creating { dependsOn(nativeMain.get()) } + listOf(iosArm64Main, iosSimulatorArm64Main, macosArm64Main, linuxX64Main, linuxArm64Main) + .forEach { it.get().dependsOn(posixMain) } + mingwX64Main { dependsOn(nativeMain.get()) } + + commonMain.dependencies { + implementation(libs.kotlinx.io.core) + implementation(project(":skainet-lang:skainet-lang-core")) + implementation(project(":skainet-io:skainet-io-core")) + implementation(project(":skainet-compile:skainet-compile-core")) + implementation(project(":skainet-compile:skainet-compile-dag")) } - val jvmTest by getting { - dependencies { - implementation(libs.junit) - implementation(libs.kotlinx.coroutines) - implementation(libs.kotlinx.coroutines.test) - } + jvmTest.dependencies { + implementation(libs.junit) + implementation(libs.kotlinx.coroutines) + implementation(libs.kotlinx.coroutines.test) } } } diff --git a/skainet-io/skainet-io-gguf/gradle.properties b/skainet-io/skainet-io-gguf/gradle.properties index a7d40a9a5..ca2741c59 100644 --- a/skainet-io/skainet-io-gguf/gradle.properties +++ b/skainet-io/skainet-io-gguf/gradle.properties @@ -1,2 +1,4 @@ POM_ARTIFACT_ID=skainet-io-gguf -POM_NAME=skainet IO API \ No newline at end of file +POM_NAME=skainet IO API +skainet.targets=jvm,js,wasmJs,wasmWasi,apple,linux,mingw +kotlin.mpp.applyDefaultHierarchyTemplate=false diff --git a/skainet-io/skainet-io-gguf/src/mingwX64Main/kotlin/sk/ainet/io/gguf/RandomAccessSourceFactory.mingw.kt b/skainet-io/skainet-io-gguf/src/mingwX64Main/kotlin/sk/ainet/io/gguf/RandomAccessSourceFactory.mingw.kt new file mode 100644 index 000000000..f32ae9339 --- /dev/null +++ b/skainet-io/skainet-io-gguf/src/mingwX64Main/kotlin/sk/ainet/io/gguf/RandomAccessSourceFactory.mingw.kt @@ -0,0 +1,13 @@ +package sk.ainet.io.gguf + +import sk.ainet.io.RandomAccessSource +import sk.ainet.io.WindowsRandomAccessSource + +/** + * Windows implementation of [createRandomAccessSource]: `ReadFile` with an `OVERLAPPED` + * offset via io-core's [WindowsRandomAccessSource] (posix `pread` does not exist on + * mingw). Returns `null` if the file cannot be opened, matching the JVM/POSIX actuals' + * contract so callers can fall back to the legacy sequential reader. See #911. + */ +public actual fun createRandomAccessSource(filePath: String): RandomAccessSource? = + WindowsRandomAccessSource.open(filePath) diff --git a/skainet-io/skainet-io-gguf/src/posixMain/kotlin/sk/ainet/io/gguf/RandomAccessSourceFactory.posix.kt b/skainet-io/skainet-io-gguf/src/posixMain/kotlin/sk/ainet/io/gguf/RandomAccessSourceFactory.posix.kt new file mode 100644 index 000000000..5cd3b2086 --- /dev/null +++ b/skainet-io/skainet-io-gguf/src/posixMain/kotlin/sk/ainet/io/gguf/RandomAccessSourceFactory.posix.kt @@ -0,0 +1,14 @@ +package sk.ainet.io.gguf + +import sk.ainet.io.PosixPreadRandomAccessSource +import sk.ainet.io.RandomAccessSource + +/** + * Native implementation of [createRandomAccessSource] using POSIX `pread(2)`. + * + * Returns `null` if the file cannot be opened (missing, permission denied, + * etc.), matching the JVM actual's contract so callers can fall back to the + * legacy sequential reader. + */ +public actual fun createRandomAccessSource(filePath: String): RandomAccessSource? = + PosixPreadRandomAccessSource.open(filePath) From 464f96b0d42c8fb879e548b61bcdd044372eeaa2 Mon Sep 17 00:00:00 2001 From: michal harakal Date: Sun, 9 Aug 2026 18:16:01 +0200 Subject: [PATCH 2/2] build: opt-in 'mingw' target group in sk.ainet.multiplatform; enable mingwX64 across lang/compile/backend/data/io modules (#911, #804) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds mingwX64 as an opt-in target group (KNOWN, not DEFAULT) to the convention plugin and migrates skainet-lang-{ksp-annotations,core,dag}, skainet-compile-{core,dag}, skainet-data-api, skainet-backend-api and skainet-io-{core,safetensors} to sk.ainet.multiplatform with explicit skainet.targets (advancing #804). skainet-lang-models keeps its hand-rolled block (custom browser test config) and gains a plain mingwX64() line. backend-cpu wires mingwX64Main into its manual hierarchy and adds the scalar PlatformCpuOpsFactory actual (same body as linux — pure Kotlin; SIMD is #910). io modules keep their pre-migration non-explicit-API mode; io-core's posix-pread native64Main split is untouched and mingw deliberately stays out of it (LLP64, no pread) — the Windows RandomAccessSource lands in the follow-up PR together with io-gguf. A mingw-cross CI leg (repo-wide compileKotlinMingwX64 + scoped test compilation) follows in a separate workflow change (token pushing this branch lacks the workflow scope). Verified locally: mingw main+test klibs cross-compile from Linux; jvmTest green across all migrated modules; linuxX64Test green for io-core (native64 wiring) and backend-cpu. --- .../kmp/SkainetMultiplatformPlugin.kt | 4 + .../sk/ainet/buildlogic/kmp/SkainetTargets.kt | 5 +- .../skainet-backend-api/build.gradle.kts | 90 ++----------------- .../skainet-backend-api/gradle.properties | 2 + .../skainet-backend-cpu/build.gradle.kts | 1 + .../skainet-backend-cpu/gradle.properties | 5 +- .../tensor/ops/PlatformCpuOpsFactory.mingw.kt | 14 +++ .../skainet-compile-core/build.gradle.kts | 56 ++---------- .../skainet-compile-core/gradle.properties | 3 +- .../skainet-compile-dag/build.gradle.kts | 47 ++-------- .../skainet-compile-dag/gradle.properties | 4 +- .../skainet-data-api/build.gradle.kts | 54 +++-------- .../skainet-data-api/gradle.properties | 4 +- skainet-io/skainet-io-core/build.gradle.kts | 80 +++++------------ skainet-io/skainet-io-core/gradle.properties | 3 +- .../skainet-io-safetensors/build.gradle.kts | 90 ++++++------------- .../skainet-io-safetensors/gradle.properties | 2 + .../skainet-lang-core/build.gradle.kts | 55 ++---------- .../skainet-lang-core/gradle.properties | 3 +- .../skainet-lang-dag/build.gradle.kts | 45 ++-------- .../skainet-lang-dag/gradle.properties | 3 +- .../build.gradle.kts | 39 +------- .../gradle.properties | 3 +- .../skainet-lang-models/build.gradle.kts | 1 + 24 files changed, 145 insertions(+), 468 deletions(-) create mode 100644 skainet-backends/skainet-backend-cpu/src/mingwX64Main/kotlin/sk/ainet/exec/tensor/ops/PlatformCpuOpsFactory.mingw.kt diff --git a/build-logic/convention/src/main/kotlin/sk/ainet/buildlogic/kmp/SkainetMultiplatformPlugin.kt b/build-logic/convention/src/main/kotlin/sk/ainet/buildlogic/kmp/SkainetMultiplatformPlugin.kt index 698619826..424d9e46f 100644 --- a/build-logic/convention/src/main/kotlin/sk/ainet/buildlogic/kmp/SkainetMultiplatformPlugin.kt +++ b/build-logic/convention/src/main/kotlin/sk/ainet/buildlogic/kmp/SkainetMultiplatformPlugin.kt @@ -176,6 +176,10 @@ class SkainetMultiplatformPlugin : Plugin { kotlin.androidNativeArm32() kotlin.androidNativeArm64() } + + if (targets.mingw) { + kotlin.mingwX64() + } } /** diff --git a/build-logic/convention/src/main/kotlin/sk/ainet/buildlogic/kmp/SkainetTargets.kt b/build-logic/convention/src/main/kotlin/sk/ainet/buildlogic/kmp/SkainetTargets.kt index b8518a33f..b6cdd5057 100644 --- a/build-logic/convention/src/main/kotlin/sk/ainet/buildlogic/kmp/SkainetTargets.kt +++ b/build-logic/convention/src/main/kotlin/sk/ainet/buildlogic/kmp/SkainetTargets.kt @@ -46,6 +46,8 @@ internal data class SkainetTargets( val linux: Boolean, /** `androidNativeArm32`, `androidNativeArm64` — vendor backends linking device libraries. */ val androidNative: Boolean, + /** `mingwX64` — Windows. Opt-in (not in the default set); see issue #911. */ + val mingw: Boolean, val wasmJsExecutable: Boolean, ) { val web: Boolean get() = js || wasmJs @@ -55,7 +57,7 @@ internal data class SkainetTargets( const val WASM_JS_EXECUTABLE_PROPERTY = "skainet.wasmJs.executable" private val DEFAULT = setOf("jvm", "js", "wasmJs", "wasmWasi", "apple", "linux") - private val KNOWN = DEFAULT + setOf("androidNative") + private val KNOWN = DEFAULT + setOf("androidNative", "mingw") fun from(project: Project): SkainetTargets { // findProperty, not providers.gradleProperty: as of Gradle 9 the provider API @@ -83,6 +85,7 @@ internal data class SkainetTargets( apple = "apple" in selected, linux = "linux" in selected, androidNative = "androidNative" in selected, + mingw = "mingw" in selected, wasmJsExecutable = project.findProperty(WASM_JS_EXECUTABLE_PROPERTY)?.toString().toBoolean(), ) } diff --git a/skainet-backends/skainet-backend-api/build.gradle.kts b/skainet-backends/skainet-backend-api/build.gradle.kts index 601c87edb..9cc582466 100644 --- a/skainet-backends/skainet-backend-api/build.gradle.kts +++ b/skainet-backends/skainet-backend-api/build.gradle.kts @@ -1,46 +1,18 @@ -import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl -import org.jetbrains.kotlin.gradle.dsl.JvmTarget - plugins { - alias(libs.plugins.kotlinMultiplatform) + id("sk.ainet.multiplatform") alias(libs.plugins.androidMultiplatformLibrary) alias(libs.plugins.vanniktech.mavenPublish) id("sk.ainet.dokka") } -kotlin { - explicitApi() - android { - namespace = "sk.ainet.backend.api" - compileSdk = libs.versions.android.compileSdk.get().toInt() - minSdk = libs.versions.android.minSdk.get().toInt() - compilerOptions { - jvmTarget.set(JvmTarget.JVM_11) - } - } - - iosArm64() - iosSimulatorArm64() - macosArm64() - linuxX64() - linuxArm64() - - jvm() - - js { - browser() - } - - @OptIn(ExperimentalWasmDsl::class) - wasmJs { - browser() - } - - @OptIn(ExperimentalWasmDsl::class) - wasmWasi { - nodejs() - } +// Targets come from skainet.targets in this module's gradle.properties. The previous +// hand-wired native source-set tree carried no source files and is gone — the default +// hierarchy template covers this module. +skainet { + namespace = "sk.ainet.backend.api" +} +kotlin { sourceSets { commonMain.dependencies { // Neutral backend API is an `api` re-export of the tensor op and @@ -50,51 +22,5 @@ kotlin { // reach TensorOps / TensorDataFactory / TensorData. api(project(":skainet-lang:skainet-lang-core")) } - - val jvmMain by getting - val androidMain by getting - val wasmJsMain by getting - - val commonMain by getting - - val nativeMain by creating { - dependsOn(commonMain) - } - - val appleMain by creating { - dependsOn(nativeMain) - } - - val linuxMain by creating { - dependsOn(nativeMain) - } - - val iosMain by creating { - dependsOn(appleMain) - } - - val macosMain by creating { - dependsOn(appleMain) - } - - val iosArm64Main by getting { - dependsOn(iosMain) - } - - val iosSimulatorArm64Main by getting { - dependsOn(iosMain) - } - - val macosArm64Main by getting { - dependsOn(macosMain) - } - - val linuxX64Main by getting { - dependsOn(linuxMain) - } - - val linuxArm64Main by getting { - dependsOn(linuxMain) - } } } diff --git a/skainet-backends/skainet-backend-api/gradle.properties b/skainet-backends/skainet-backend-api/gradle.properties index a0f9c0649..c88d93bac 100644 --- a/skainet-backends/skainet-backend-api/gradle.properties +++ b/skainet-backends/skainet-backend-api/gradle.properties @@ -1,2 +1,4 @@ POM_ARTIFACT_ID=skainet-backend-api POM_NAME=skainet backend-neutral API + +skainet.targets=jvm,js,wasmJs,wasmWasi,apple,linux,mingw diff --git a/skainet-backends/skainet-backend-cpu/build.gradle.kts b/skainet-backends/skainet-backend-cpu/build.gradle.kts index f9ba32c0e..98ca1650f 100644 --- a/skainet-backends/skainet-backend-cpu/build.gradle.kts +++ b/skainet-backends/skainet-backend-cpu/build.gradle.kts @@ -43,6 +43,7 @@ kotlin { macosArm64Main { dependsOn(macosMain.get()) } linuxX64Main { dependsOn(linuxMain.get()) } linuxArm64Main { dependsOn(linuxMain.get()) } + mingwX64Main { dependsOn(nativeMain.get()) } } } diff --git a/skainet-backends/skainet-backend-cpu/gradle.properties b/skainet-backends/skainet-backend-cpu/gradle.properties index 8380ea84c..e2bf418aa 100644 --- a/skainet-backends/skainet-backend-cpu/gradle.properties +++ b/skainet-backends/skainet-backend-cpu/gradle.properties @@ -1,4 +1,7 @@ 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 + +# Explicit target list (was the plugin default) + Windows; see #911. +skainet.targets=jvm,js,wasmJs,wasmWasi,apple,linux,mingw diff --git a/skainet-backends/skainet-backend-cpu/src/mingwX64Main/kotlin/sk/ainet/exec/tensor/ops/PlatformCpuOpsFactory.mingw.kt b/skainet-backends/skainet-backend-cpu/src/mingwX64Main/kotlin/sk/ainet/exec/tensor/ops/PlatformCpuOpsFactory.mingw.kt new file mode 100644 index 000000000..c274b89e1 --- /dev/null +++ b/skainet-backends/skainet-backend-cpu/src/mingwX64Main/kotlin/sk/ainet/exec/tensor/ops/PlatformCpuOpsFactory.mingw.kt @@ -0,0 +1,14 @@ +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 + +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. + // Scalar parity with the other Kotlin/Native targets; SIMD is tracked in #910. + KernelRegistry.register(ScalarKernelProvider) + return { factory -> DefaultCpuOps(factory) } +} diff --git a/skainet-compile/skainet-compile-core/build.gradle.kts b/skainet-compile/skainet-compile-core/build.gradle.kts index eaf2d0b46..56ff0ddfd 100644 --- a/skainet-compile/skainet-compile-core/build.gradle.kts +++ b/skainet-compile/skainet-compile-core/build.gradle.kts @@ -1,63 +1,21 @@ -import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl -import org.jetbrains.kotlin.gradle.dsl.JvmTarget - plugins { - alias(libs.plugins.kotlinMultiplatform) + id("sk.ainet.multiplatform") alias(libs.plugins.androidMultiplatformLibrary) alias(libs.plugins.vanniktech.mavenPublish) alias(libs.plugins.binary.compatibility.validator) id("sk.ainet.dokka") } -kotlin { - explicitApi() - - android { - namespace = "sk.ainet.compilie.core" - compileSdk = libs.versions.android.compileSdk.get().toInt() - minSdk = libs.versions.android.minSdk.get().toInt() - compilerOptions { - jvmTarget.set(JvmTarget.JVM_11) - } - } - - iosArm64() - iosSimulatorArm64() - macosArm64() - linuxX64() - linuxArm64() - - // Android Native targets for vendor-specific backends linking native device libs. - androidNativeArm32() - androidNativeArm64() - - jvm() - - js { - browser() - } - - @OptIn(ExperimentalWasmDsl::class) - wasmJs { - browser() - } - - @OptIn(ExperimentalWasmDsl::class) - wasmWasi { - nodejs() - } +// Targets come from skainet.targets in this module's gradle.properties; +// explicitApi() and kotlin-test in commonTest come from sk.ainet.multiplatform. +skainet { + namespace = "sk.ainet.compilie.core" +} +kotlin { sourceSets { commonMain.dependencies { implementation(project(":skainet-lang:skainet-lang-core")) } - - commonTest.dependencies { - implementation(libs.kotlin.test) - } - - jvmTest.dependencies { - implementation(libs.kotlin.test) - } } } diff --git a/skainet-compile/skainet-compile-core/gradle.properties b/skainet-compile/skainet-compile-core/gradle.properties index 22a937d80..f13a2c2f5 100644 --- a/skainet-compile/skainet-compile-core/gradle.properties +++ b/skainet-compile/skainet-compile-core/gradle.properties @@ -1,2 +1,3 @@ POM_ARTIFACT_ID=skainet-compile-core -POM_NAME=skainet neural network compile core \ No newline at end of file +POM_NAME=skainet neural network compile core +skainet.targets=jvm,js,wasmJs,wasmWasi,apple,linux,androidNative,mingw diff --git a/skainet-compile/skainet-compile-dag/build.gradle.kts b/skainet-compile/skainet-compile-dag/build.gradle.kts index 4281ec714..f6d95cbf4 100644 --- a/skainet-compile/skainet-compile-dag/build.gradle.kts +++ b/skainet-compile/skainet-compile-dag/build.gradle.kts @@ -1,49 +1,18 @@ -import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl -import org.jetbrains.kotlin.gradle.dsl.JvmTarget - plugins { - alias(libs.plugins.kotlinMultiplatform) + id("sk.ainet.multiplatform") alias(libs.plugins.androidMultiplatformLibrary) alias(libs.plugins.vanniktech.mavenPublish) alias(libs.plugins.binary.compatibility.validator) id("sk.ainet.dokka") } -kotlin { - explicitApi() - - android { - namespace = "sk.ainet.compilie.dag" - compileSdk = libs.versions.android.compileSdk.get().toInt() - minSdk = libs.versions.android.minSdk.get().toInt() - compilerOptions { - jvmTarget.set(JvmTarget.JVM_11) - } - } - - iosArm64() - iosSimulatorArm64() - macosArm64 () - linuxX64 () - linuxArm64 () - - jvm() - - js { - browser() - } - - @OptIn(ExperimentalWasmDsl::class) - wasmJs { - browser() - binaries.executable() - } - - @OptIn(ExperimentalWasmDsl::class) - wasmWasi { - nodejs() - } +// Targets come from skainet.targets in this module's gradle.properties (incl. the +// wasmJs executable flag); explicitApi() and kotlin-test come from sk.ainet.multiplatform. +skainet { + namespace = "sk.ainet.compilie.dag" +} +kotlin { sourceSets { commonMain.dependencies { api(project(":skainet-lang:skainet-lang-core")) @@ -52,10 +21,8 @@ kotlin { } commonTest.dependencies { - implementation(libs.kotlin.test) implementation(project(":skainet-backends:skainet-backend-cpu")) implementation(project(":skainet-lang:skainet-lang-models")) - } } } diff --git a/skainet-compile/skainet-compile-dag/gradle.properties b/skainet-compile/skainet-compile-dag/gradle.properties index 578933eb1..192d97ec2 100644 --- a/skainet-compile/skainet-compile-dag/gradle.properties +++ b/skainet-compile/skainet-compile-dag/gradle.properties @@ -1,2 +1,4 @@ POM_ARTIFACT_ID=skainet-compile-dag -POM_NAME=skainet neural network compile DAG \ No newline at end of file +POM_NAME=skainet neural network compile DAG +skainet.targets=jvm,js,wasmJs,wasmWasi,apple,linux,mingw +skainet.wasmJs.executable=true diff --git a/skainet-data/skainet-data-api/build.gradle.kts b/skainet-data/skainet-data-api/build.gradle.kts index 511226f9c..de108e76a 100644 --- a/skainet-data/skainet-data-api/build.gradle.kts +++ b/skainet-data/skainet-data-api/build.gradle.kts @@ -1,59 +1,25 @@ -import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl -import org.jetbrains.kotlin.gradle.dsl.JvmTarget - plugins { - alias(libs.plugins.kotlinMultiplatform) + id("sk.ainet.multiplatform") alias(libs.plugins.androidMultiplatformLibrary) alias(libs.plugins.vanniktech.mavenPublish) id("sk.ainet.dokka") } -kotlin { - explicitApi() - - android { - namespace = "sk.ainet.core.api" - compileSdk = libs.versions.android.compileSdk.get().toInt() - minSdk = libs.versions.android.minSdk.get().toInt() - compilerOptions { - jvmTarget.set(JvmTarget.JVM_11) - } - } - - iosArm64() - iosSimulatorArm64() - macosArm64() - linuxX64() - linuxArm64() - - jvm() - - js { - browser() - } - - @OptIn(ExperimentalWasmDsl::class) - wasmJs { - browser() - } - - @OptIn(ExperimentalWasmDsl::class) - wasmWasi { - nodejs() - } +// Targets come from skainet.targets in this module's gradle.properties; +// explicitApi() and kotlin-test in commonTest come from sk.ainet.multiplatform. +skainet { + namespace = "sk.ainet.core.api" +} +kotlin { sourceSets { - val commonMain by getting { - dependencies { - implementation(project(":skainet-lang:skainet-lang-core")) - implementation(libs.kotlinx.coroutines) - } + commonMain.dependencies { + implementation(project(":skainet-lang:skainet-lang-core")) + implementation(libs.kotlinx.coroutines) } commonTest.dependencies { - implementation(libs.kotlin.test) implementation(libs.kotlinx.coroutines.test) - // implementation(project(":skainet-core:skainet-performance")) } } } diff --git a/skainet-data/skainet-data-api/gradle.properties b/skainet-data/skainet-data-api/gradle.properties index b3e89dd84..f07fd0109 100644 --- a/skainet-data/skainet-data-api/gradle.properties +++ b/skainet-data/skainet-data-api/gradle.properties @@ -1,2 +1,4 @@ POM_ARTIFACT_ID=skainet-data-api -POM_NAME=skainet datasets API \ No newline at end of file +POM_NAME=skainet datasets API +# Windows (mingwX64) rides the shared target groups; see #911. +skainet.targets=jvm,js,wasmJs,wasmWasi,apple,linux,mingw diff --git a/skainet-io/skainet-io-core/build.gradle.kts b/skainet-io/skainet-io-core/build.gradle.kts index 13dff0505..8ac4cf257 100644 --- a/skainet-io/skainet-io-core/build.gradle.kts +++ b/skainet-io/skainet-io-core/build.gradle.kts @@ -1,11 +1,8 @@ -@file:OptIn(ExperimentalWasmDsl::class) - -import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl import org.jetbrains.kotlin.gradle.dsl.JvmTarget import java.net.URI plugins { - alias(libs.plugins.kotlinMultiplatform) + id("sk.ainet.multiplatform") alias(libs.plugins.androidMultiplatformLibrary) alias(libs.plugins.kotlinSerialization) @@ -13,57 +10,28 @@ plugins { id("sk.ainet.dokka") } -kotlin { - - targets.configureEach { - compilations.configureEach { - compileTaskProvider.get().compilerOptions { - freeCompilerArgs.add("-Xexpect-actual-classes") - } - } - } - - jvm() - - android { - namespace = "sk.ainet.io.core" - compileSdk = libs.versions.android.compileSdk.get().toInt() - minSdk = libs.versions.android.minSdk.get().toInt() - compilerOptions { - jvmTarget.set(JvmTarget.JVM_1_8) - } - } +// Targets come from skainet.targets in this module's gradle.properties (androidNative for +// on-device consumers; mingw per #911). explicitApi(), kotlin-test in commonTest and +// -Xexpect-actual-classes come from sk.ainet.multiplatform. +skainet { + namespace = "sk.ainet.io.core" + androidJvmTarget = JvmTarget.JVM_1_8 + expectActualClasses = true + // Pre-migration behavior: this module never enabled explicit API mode; turning it on + // is a separate cleanup from the #911 target work. + explicitApi = false +} - iosArm64() - iosSimulatorArm64() - macosArm64 () - linuxX64 () - linuxArm64 () - androidNativeArm64() +kotlin { // androidNativeArm32 is 32-bit: posix ssize_t/size_t are Int here vs Long on every other - // native target. PosixPreadRandomAccessSource therefore lives in the 64-bit-only `native64Main` - // source set (wired below), NOT in the shared `nativeMain` — otherwise the shared native - // metadata compile fails ("numbers with different bit widths"). arm32 gets the rest of - // io-core (tokenizers etc.); on-device file I/O for arm32 is a separate concern. - androidNativeArm32() - - js { - browser() - } - - @OptIn(ExperimentalWasmDsl::class) - wasmJs { - browser() - } - - @OptIn(ExperimentalWasmDsl::class) - wasmWasi { - nodejs() - } - - // 64-bit-only intermediate source sets: hold the posix `pread` RandomAccessSource, whose - // ssize_t/size_t widths are uniform (Long) across every native target EXCEPT androidNativeArm32. - // Keeping it out of the all-native `nativeMain` avoids the mixed-width metadata compile error. + // POSIX native target. PosixPreadRandomAccessSource therefore lives in the 64-bit-only + // `native64Main` source set (wired below), NOT in the shared `nativeMain` — otherwise the + // shared native metadata compile fails ("numbers with different bit widths"). arm32 gets + // the rest of io-core (tokenizers etc.). + // + // mingwX64 is 64-bit but LLP64 and has no posix `pread` — it stays OUT of native64Main + // too and carries its own leaf implementation (WindowsRandomAccessSource, Win32 + // ReadFile+OVERLAPPED) in src/mingwX64Main. See #911. applyDefaultHierarchyTemplate() val native64Targets = listOf("iosArm64", "iosSimulatorArm64", "macosArm64", "linuxX64", "linuxArm64", "androidNativeArm64") @@ -87,12 +55,6 @@ kotlin { } } - val commonTest by getting { - dependencies { - implementation(libs.kotlin.test) - } - } - val jvmTest by getting { dependencies { implementation(libs.kotlinx.coroutines) diff --git a/skainet-io/skainet-io-core/gradle.properties b/skainet-io/skainet-io-core/gradle.properties index d3f9c41f8..24806034e 100644 --- a/skainet-io/skainet-io-core/gradle.properties +++ b/skainet-io/skainet-io-core/gradle.properties @@ -1,2 +1,3 @@ POM_ARTIFACT_ID=skainet-io-core -POM_NAME=skainet IO GGUF implementation \ No newline at end of file +POM_NAME=skainet IO GGUF implementation +skainet.targets=jvm,js,wasmJs,wasmWasi,apple,linux,androidNative,mingw diff --git a/skainet-io/skainet-io-safetensors/build.gradle.kts b/skainet-io/skainet-io-safetensors/build.gradle.kts index 6b20fbf6f..d74fbe40a 100644 --- a/skainet-io/skainet-io-safetensors/build.gradle.kts +++ b/skainet-io/skainet-io-safetensors/build.gradle.kts @@ -1,79 +1,39 @@ -import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { - alias(libs.plugins.kotlinMultiplatform) + id("sk.ainet.multiplatform") alias(libs.plugins.androidMultiplatformLibrary) alias(libs.plugins.vanniktech.mavenPublish) id("sk.ainet.dokka") } -kotlin { - targets.configureEach { - compilations.configureEach { - compileTaskProvider.get().compilerOptions { - freeCompilerArgs.add("-Xexpect-actual-classes") - } - } - } - - jvm() - android { - namespace = "sk.ainet.io.safetensors" - compileSdk = libs.versions.android.compileSdk.get().toInt() - minSdk = libs.versions.android.minSdk.get().toInt() - compilerOptions { - jvmTarget.set(JvmTarget.JVM_1_8) - } - } - - iosArm64() - iosSimulatorArm64() - macosArm64() - linuxX64() - linuxArm64() - // androidNative (edge NPU / phone). io-safetensors has no posix in its own nativeMain — - // createRandomAccessSource / readTextFile are stubs and currentTimeMillis uses a monotonic - // TimeSource — so there is no bit-width metadata issue here (unlike io-core, whose posix pread - // needed the native64Main split). File-backed reads route through io-core's RandomAccessSource. - androidNativeArm32() - androidNativeArm64() - - js { - browser() - } - - @OptIn(ExperimentalWasmDsl::class) - wasmJs { - browser() - } - - @OptIn(ExperimentalWasmDsl::class) - wasmWasi { - nodejs() - } +// Targets come from skainet.targets in this module's gradle.properties. mingw is safe +// here without extra source: io-safetensors has no posix in its own nativeMain — +// createRandomAccessSource / readTextFile are stubs and currentTimeMillis uses a monotonic +// TimeSource — so there is no bit-width metadata issue (unlike io-core, whose posix pread +// needed the native64Main split). File-backed reads route through io-core's RandomAccessSource. +skainet { + namespace = "sk.ainet.io.safetensors" + androidJvmTarget = JvmTarget.JVM_1_8 + expectActualClasses = true + // Pre-migration behavior: this module never enabled explicit API mode; turning it on + // is a separate cleanup from the #911 target work. + explicitApi = false +} +kotlin { sourceSets { - val commonMain by getting { - dependencies { - implementation(libs.kotlinx.io.core) - implementation(libs.kotlinx.coroutines) - implementation(project(":skainet-lang:skainet-lang-core")) - implementation(project(":skainet-io:skainet-io-core")) - } - } - val commonTest by getting { - dependencies { - implementation(libs.kotlin.test) - } + commonMain.dependencies { + implementation(libs.kotlinx.io.core) + implementation(libs.kotlinx.coroutines) + implementation(project(":skainet-lang:skainet-lang-core")) + implementation(project(":skainet-io:skainet-io-core")) } - val jvmTest by getting { - dependencies { - implementation(libs.junit) - implementation(libs.kotlinx.coroutines) - implementation(libs.kotlinx.coroutines.test) - implementation(project(":skainet-backends:skainet-backend-cpu")) - } + jvmTest.dependencies { + implementation(libs.junit) + implementation(libs.kotlinx.coroutines) + implementation(libs.kotlinx.coroutines.test) + implementation(project(":skainet-backends:skainet-backend-cpu")) } } } diff --git a/skainet-io/skainet-io-safetensors/gradle.properties b/skainet-io/skainet-io-safetensors/gradle.properties index ce7da199b..e7f0fc40f 100644 --- a/skainet-io/skainet-io-safetensors/gradle.properties +++ b/skainet-io/skainet-io-safetensors/gradle.properties @@ -1,2 +1,4 @@ POM_ARTIFACT_ID=skainet-io-safetensors POM_NAME=skainet IO ONNX + +skainet.targets=jvm,js,wasmJs,wasmWasi,apple,linux,androidNative,mingw diff --git a/skainet-lang/skainet-lang-core/build.gradle.kts b/skainet-lang/skainet-lang-core/build.gradle.kts index 10a8f9437..e71353fe7 100644 --- a/skainet-lang/skainet-lang-core/build.gradle.kts +++ b/skainet-lang/skainet-lang-core/build.gradle.kts @@ -1,8 +1,5 @@ -import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl -import org.jetbrains.kotlin.gradle.dsl.JvmTarget - plugins { - alias(libs.plugins.kotlinMultiplatform) + id("sk.ainet.multiplatform") alias(libs.plugins.androidMultiplatformLibrary) alias(libs.plugins.vanniktech.mavenPublish) alias(libs.plugins.binary.compatibility.validator) @@ -11,44 +8,14 @@ plugins { id("org.jetbrains.kotlinx.benchmark") } -kotlin { - explicitApi() - - android { - namespace = "sk.ainet.lang.core" - compileSdk = libs.versions.android.compileSdk.get().toInt() - minSdk = libs.versions.android.minSdk.get().toInt() - compilerOptions { - jvmTarget.set(JvmTarget.JVM_11) - } - } - - iosArm64() - iosSimulatorArm64() - macosArm64 () - linuxX64 () - linuxArm64 () - - // Android Native targets for vendor-specific backends linking native device libs. - androidNativeArm32() - androidNativeArm64() - - jvm() - - js { - browser() - } - - @OptIn(ExperimentalWasmDsl::class) - wasmJs { - browser() - } - - @OptIn(ExperimentalWasmDsl::class) - wasmWasi { - nodejs() - } +// Targets come from skainet.targets in this module's gradle.properties (androidNative for +// vendor-specific backends linking native device libs); explicitApi() and kotlin-test in +// commonTest come from sk.ainet.multiplatform. +skainet { + namespace = "sk.ainet.lang.core" +} +kotlin { sourceSets { commonMain { // Include KSP-generated sources in commonMain so downstream modules can access them @@ -61,10 +28,6 @@ kotlin { jvmMain.dependencies { implementation(libs.kotlinx.benchmark.runtime) } - - commonTest.dependencies { - implementation(libs.kotlin.test) - } } } @@ -100,4 +63,4 @@ benchmark { targets { register("jvm") } -} \ No newline at end of file +} diff --git a/skainet-lang/skainet-lang-core/gradle.properties b/skainet-lang/skainet-lang-core/gradle.properties index 239f293a3..6fee44457 100644 --- a/skainet-lang/skainet-lang-core/gradle.properties +++ b/skainet-lang/skainet-lang-core/gradle.properties @@ -1,3 +1,4 @@ POM_ARTIFACT_ID=skainet-lang-core POM_NAME=skainet neural network scripting API -android.enableLegacyVariantApi=true \ No newline at end of file +android.enableLegacyVariantApi=true +skainet.targets=jvm,js,wasmJs,wasmWasi,apple,linux,androidNative,mingw diff --git a/skainet-lang/skainet-lang-dag/build.gradle.kts b/skainet-lang/skainet-lang-dag/build.gradle.kts index 448bc18c4..ca63cbc1d 100644 --- a/skainet-lang/skainet-lang-dag/build.gradle.kts +++ b/skainet-lang/skainet-lang-dag/build.gradle.kts @@ -1,8 +1,5 @@ -import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl -import org.jetbrains.kotlin.gradle.dsl.JvmTarget - plugins { - alias(libs.plugins.kotlinMultiplatform) + id("sk.ainet.multiplatform") alias(libs.plugins.androidMultiplatformLibrary) alias(libs.plugins.vanniktech.mavenPublish) alias(libs.plugins.binary.compatibility.validator) @@ -10,40 +7,13 @@ plugins { id("sk.ainet.dokka") } -kotlin { - explicitApi() - - android { - namespace = "sk.ainet.lang.dag" - compileSdk = libs.versions.android.compileSdk.get().toInt() - minSdk = libs.versions.android.minSdk.get().toInt() - compilerOptions { - jvmTarget.set(JvmTarget.JVM_11) - } - } - - iosArm64() - iosSimulatorArm64() - macosArm64() - linuxX64() - linuxArm64() - - jvm() - - js { - browser() - } - - @OptIn(ExperimentalWasmDsl::class) - wasmJs { - browser() - } - - @OptIn(ExperimentalWasmDsl::class) - wasmWasi { - nodejs() - } +// Targets come from skainet.targets in this module's gradle.properties; +// explicitApi() and kotlin-test in commonTest come from sk.ainet.multiplatform. +skainet { + namespace = "sk.ainet.lang.dag" +} +kotlin { sourceSets { commonMain { kotlin.srcDir("build/generated/ksp/metadata/commonMain/kotlin") @@ -54,7 +24,6 @@ kotlin { } commonTest.dependencies { - implementation(libs.kotlin.test) implementation(project(":skainet-backends:skainet-backend-cpu")) } } diff --git a/skainet-lang/skainet-lang-dag/gradle.properties b/skainet-lang/skainet-lang-dag/gradle.properties index dd496ce48..44949b18d 100644 --- a/skainet-lang/skainet-lang-dag/gradle.properties +++ b/skainet-lang/skainet-lang-dag/gradle.properties @@ -1,2 +1,3 @@ POM_ARTIFACT_ID=skainet-lang-dag -POM_NAME=skainet neural network scripting API \ No newline at end of file +POM_NAME=skainet neural network scripting API +skainet.targets=jvm,js,wasmJs,wasmWasi,apple,linux,mingw diff --git a/skainet-lang/skainet-lang-ksp-annotations/build.gradle.kts b/skainet-lang/skainet-lang-ksp-annotations/build.gradle.kts index eb4e28134..22efa3886 100644 --- a/skainet-lang/skainet-lang-ksp-annotations/build.gradle.kts +++ b/skainet-lang/skainet-lang-ksp-annotations/build.gradle.kts @@ -1,41 +1,8 @@ -import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl - plugins { - alias(libs.plugins.kotlinMultiplatform) + id("sk.ainet.multiplatform") alias(libs.plugins.vanniktech.mavenPublish) id("sk.ainet.dokka") } - -kotlin { - jvm() - explicitApi() - - iosArm64() - iosSimulatorArm64() - macosArm64 () - linuxX64 () - linuxArm64 () - - // Android Native targets for vendor-specific backends linking directly against - // libneuralnetworks.so / libOpenCL.so / etc. (e.g. skainet-backend-nnapi). - androidNativeArm32() - androidNativeArm64() - - jvm() - - js { - browser() - } - - @OptIn(ExperimentalWasmDsl::class) - wasmJs { - browser() - } - - @OptIn(ExperimentalWasmDsl::class) - wasmWasi { - nodejs() - } -} - +// Targets come from skainet.targets in this module's gradle.properties (androidNative for +// vendor-specific backends linking against libneuralnetworks.so / libOpenCL.so / etc.). diff --git a/skainet-lang/skainet-lang-ksp-annotations/gradle.properties b/skainet-lang/skainet-lang-ksp-annotations/gradle.properties index f7d38eff0..2353bd520 100644 --- a/skainet-lang/skainet-lang-ksp-annotations/gradle.properties +++ b/skainet-lang/skainet-lang-ksp-annotations/gradle.properties @@ -1,2 +1,3 @@ POM_ARTIFACT_ID=skainet-lang-ksp-annotations -POM_NAME=miKrograd annotations \ No newline at end of file +POM_NAME=miKrograd annotations +skainet.targets=jvm,js,wasmJs,wasmWasi,apple,linux,androidNative,mingw diff --git a/skainet-lang/skainet-lang-models/build.gradle.kts b/skainet-lang/skainet-lang-models/build.gradle.kts index 0021c82a8..0c8b93c8f 100644 --- a/skainet-lang/skainet-lang-models/build.gradle.kts +++ b/skainet-lang/skainet-lang-models/build.gradle.kts @@ -27,6 +27,7 @@ kotlin { macosArm64() linuxX64() linuxArm64() + mingwX64() jvm()