diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f250fb6a..aa68ac63 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,6 +2,8 @@ kotlin = "2.3.20" pluginPublish = "2.1.0" versionCheck = "0.53.0" +kotlinx-coroutines = "1.11.0" +junit = "4.13.2" [plugins] kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } @@ -10,6 +12,10 @@ pluginPublish = { id = "com.gradle.plugin-publish", version.ref = "pluginPublish versionCheck = { id = "com.github.ben-manes.versions", version.ref = "versionCheck" } [libraries] -junit = "junit:junit:4.13.2" +junit = { module = "junit:junit", version.ref = "junit" } kotlin-gradle-plugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } kotlin-compiler-embeddable = { group = "org.jetbrains.kotlin", name = "kotlin-compiler-embeddable", version.ref = "kotlin" } +# coroutines +kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } +kotlinx-coroutines-core-jvm = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm", version.ref = "kotlinx-coroutines" } +kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" } diff --git a/plugin-build/plugin/build.gradle.kts b/plugin-build/plugin/build.gradle.kts index 935ea5b7..8f412e1d 100644 --- a/plugin-build/plugin/build.gradle.kts +++ b/plugin-build/plugin/build.gradle.kts @@ -1,5 +1,3 @@ -import org.gradle.api.publish.maven.MavenPublication - plugins { kotlin("jvm") `java-gradle-plugin` diff --git a/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/KotlinNativeExportPlugin.kt b/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/KotlinNativeExportPlugin.kt index 4b19199c..1f66e5fa 100644 --- a/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/KotlinNativeExportPlugin.kt +++ b/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/KotlinNativeExportPlugin.kt @@ -1,12 +1,18 @@ package io.github.kdroidfilter.nucleusnativeaccess.plugin +import io.github.kdroidfilter.nucleusnativeaccess.plugin.catalog.kotlinxCoroutineDependency +import io.github.kdroidfilter.nucleusnativeaccess.plugin.catalog.kotlinxCoroutineJvmDependency +import io.github.kdroidfilter.nucleusnativeaccess.plugin.catalog.kotlinxCoroutineTestDependency import io.github.kdroidfilter.nucleusnativeaccess.plugin.tasks.GenerateNativeBridgesTask +import org.gradle.api.GradleException import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.api.logging.LogLevel import org.gradle.api.tasks.testing.Test import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType +import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget import java.io.File /** @@ -43,27 +49,50 @@ class KotlinNativeExportPlugin : Plugin { private fun configureKmp(project: Project, extension: KotlinNativeExportExtension) { val kotlin = project.extensions.getByType(KotlinMultiplatformExtension::class.java) + val libName = extension.nativeLibName.get() val pkg = extension.nativePackage.get() + // read the jvm target name + // JVM target should exist otherwise the plugin is of no use + val jvmTarget = kotlin.targets.filterIsInstance() + .firstOrNull() ?: throw GradleException("Sorry this plugin required jvm target to work with") + + val jvmMainSourceSetName = jvmTarget.name + "Main" + val jvmTestSourceSetName = jvmTarget.name + "Test" + val jvmMainTaskName = jvmMainSourceSetName.replaceFirstChar { it.titlecase() } + val jvmTaskName = jvmTarget.name.replaceFirstChar { it.titlecase() } + val nativeBridgesDir = project.layout.buildDirectory.dir("generated/kne/nativeBridges") val jvmProxiesDir = project.layout.buildDirectory.dir("generated/kne/jvmProxies") val jvmResourcesDir = project.layout.buildDirectory.dir("generated/kne/jvmResources") - // Detect native target and its source sets. + // Detect the first native target and its source sets. // Convention: use src/nativeMain if it exists (shared native source set), // otherwise fall back to the first native target's main source set (e.g. linuxX64Main). val nativeTarget = kotlin.targets .filterIsInstance() .firstOrNull() - val userNativeSrcDirs = mutableListOf() + + val nativeTargetTaskName = nativeTarget?.name?.replaceFirstChar { it.titlecase() } ?: "Native" + + val userNativeSrcDirs = mutableListOf() + + // check if native main dir present val nativeMainDir = project.projectDir.resolve("src/nativeMain/kotlin") if (nativeMainDir.exists()) userNativeSrcDirs.add(nativeMainDir) + + // if native target present use that too if (nativeTarget != null) { + project.logger.log( + LogLevel.INFO, + "NATIVE TARGET FOUND :${nativeTarget.konanTarget.name} ALIAS:${nativeTarget.name}" + ) val targetMainDir = project.projectDir.resolve("src/${nativeTarget.name}Main/kotlin") if (targetMainDir.exists()) userNativeSrcDirs.add(targetMainDir) } + val userNativeSources = project.files(userNativeSrcDirs) // Collect commonMain sources for data class discovery @@ -101,20 +130,20 @@ class KotlinNativeExportPlugin : Plugin { project.tasks.register("generateKneJvmProxies") { it.dependsOn(generateBridges) } // ── Coroutines dependency (required for suspend function support) ── - val coroutinesVersion = "1.10.2" nativeTarget?.let { target -> kotlin.sourceSets.findByName("${target.name}Main")?.dependencies { - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion") + implementation(project.kotlinxCoroutineDependency) } } kotlin.sourceSets.findByName("nativeMain")?.dependencies { - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion") + implementation(project.kotlinxCoroutineDependency) } - kotlin.sourceSets.findByName("jvmMain")?.dependencies { - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:$coroutinesVersion") + + kotlin.sourceSets.findByName(jvmMainSourceSetName)?.dependencies { + implementation(project.kotlinxCoroutineJvmDependency) } - kotlin.sourceSets.findByName("jvmTest")?.dependencies { - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion") + kotlin.sourceSets.findByName(jvmTestSourceSetName)?.dependencies { + implementation(project.kotlinxCoroutineTestDependency) } // Wire generated bridges into the native source set (try nativeMain, fall back to Main) @@ -123,21 +152,20 @@ class KotlinNativeExportPlugin : Plugin { nativeSourceSet?.kotlin?.srcDir(nativeBridgesDir) // Wire generated JVM proxies into jvmMain - kotlin.sourceSets.findByName("jvmMain")?.kotlin?.srcDir(jvmProxiesDir) + kotlin.sourceSets.findByName(jvmMainSourceSetName)?.kotlin?.srcDir(jvmProxiesDir) // Wire generated GraalVM metadata into JVM resources - kotlin.sourceSets.findByName("jvmMain")?.resources?.srcDir(jvmResourcesDir) + kotlin.sourceSets.findByName(jvmMainSourceSetName)?.resources?.srcDir(jvmResourcesDir) // Ensure compilation waits for generation project.tasks.configureEach { task -> val name = task.name if (name.startsWith("compileKotlin") && - (name.contains("Native", ignoreCase = true) || name.contains("LinuxX64") || - name.contains("MacosArm64") || name.contains("MingwX64")) + (name.contains("Native", ignoreCase = true) || name.contains(nativeTargetTaskName, ignoreCase = true)) ) { task.dependsOn(generateBridges) } - if (name == "compileKotlinJvm" || name == "compileKotlinJvmMain") { + if (name == "compileKotlin$jvmTaskName" || name == "compileKotlin$jvmMainTaskName") { task.dependsOn(generateBridges) } } @@ -156,10 +184,17 @@ class KotlinNativeExportPlugin : Plugin { // ── Bundle native lib into JVM resources (zero-config deployment) ──── if (nativeTarget != null) { - val targetName = nativeTarget.name - val targetCap = targetName.replaceFirstChar { it.uppercaseChar() } + // konan target names are the actual target name but we can have an associated alias + val targetAliasName = nativeTarget.name + val targetName = nativeTarget.konanTarget.name + + project.logger.log(LogLevel.INFO, "TARGET ALIAS :$targetAliasName, TARGET NAME:$targetName") + + val targetCap = targetAliasName.replaceFirstChar { it.uppercaseChar() } val libCap = libName.replaceFirstChar { it.uppercaseChar() } + val platform = mapTargetToPlatform(targetName) + val linkTaskName = "link${libCap}ReleaseShared$targetCap" val nativeLibResourceDir = project.layout.buildDirectory.dir("generated/kne/nativeLib") @@ -170,7 +205,7 @@ class KotlinNativeExportPlugin : Plugin { task.dependsOn(linkTaskName) task.doLast { val releaseDir = buildDir - .dir("bin/$targetName/${libName}ReleaseShared").get().asFile + .dir("bin/$targetAliasName/${libName}ReleaseShared").get().asFile val nativeFile = releaseDir.listFiles()?.firstOrNull { f -> f.extension in listOf("so", "dylib", "dll") } @@ -184,11 +219,11 @@ class KotlinNativeExportPlugin : Plugin { } // Wire native lib resource dir into JVM resources - kotlin.sourceSets.findByName("jvmMain")?.resources?.srcDir(nativeLibResourceDir) + kotlin.sourceSets.findByName(jvmMainSourceSetName)?.resources?.srcDir(nativeLibResourceDir) // Ensure processResources waits for the native lib copy project.tasks.configureEach { task -> - if (task.name == "jvmProcessResources" || task.name == "processJvmMainResources") { + if (task.name == "${jvmTarget.name}ProcessResources" || task.name == "process${jvmMainTaskName}Resources") { task.dependsOn(copyNativeLib) } } @@ -200,11 +235,18 @@ class KotlinNativeExportPlugin : Plugin { } /** Map Kotlin/Native target name to platform directory name. */ - private fun mapTargetToPlatform(targetName: String): String = when { - targetName.startsWith("linux") -> if (targetName.contains("Arm") || targetName.contains("aarch")) "linux-aarch64" else "linux-x64" - targetName.startsWith("macos") -> if (targetName.contains("Arm") || targetName.contains("arm64") || targetName.contains("Arm64")) "darwin-aarch64" else "darwin-x64" - targetName.startsWith("mingw") -> if (targetName.contains("Arm")) "win32-arm64" else "win32-x64" - else -> "unknown" + private fun mapTargetToPlatform(targetName: String): String { + + val isArm = targetName.contains("Arm") + val isArch = targetName.contains("aarch") + val isArm64 = targetName.contains("arm64") || targetName.contains("Arm64") + + return when { + targetName.startsWith("linux") -> if (isArm || isArch) "linux-aarch64" else "linux-x64" + targetName.startsWith("macos") -> if (isArm || isArm64) "darwin-aarch64" else "darwin-x64" + targetName.startsWith("mingw") -> if (isArm) "win32-arm64" else "win32-x64" + else -> "unknown" + } } /** diff --git a/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/catalog/Libraries.kt b/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/catalog/Libraries.kt new file mode 100644 index 00000000..a5ae5b38 --- /dev/null +++ b/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/catalog/Libraries.kt @@ -0,0 +1,20 @@ +package io.github.kdroidfilter.nucleusnativeaccess.plugin.catalog + +import org.gradle.api.Project + + +val Project.kotlinxCoroutineDependency + get() = catalog.findLibrary("kotlinx.coroutines.core").get() + .get() + +val Project.kotlinxCoroutineJvmDependency + get() = catalog.findLibrary("kotlinx.coroutines.core.jvm").get() + .get() + +val Project.kotlinxCoroutineTestDependency + get() = catalog.findLibrary("kotlinx.coroutines.test").get() + .get() + +val Project.kotlinEmbeddedCompiler + get() = catalog.findLibrary("kotlin.compiler.embeddable").get() + .get() diff --git a/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/catalog/VersionCatalog.kt b/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/catalog/VersionCatalog.kt new file mode 100644 index 00000000..23f67b9d --- /dev/null +++ b/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/catalog/VersionCatalog.kt @@ -0,0 +1,8 @@ +package io.github.kdroidfilter.nucleusnativeaccess.plugin.catalog + +import org.gradle.api.Project +import org.gradle.api.artifacts.VersionCatalog +import org.gradle.api.artifacts.VersionCatalogsExtension + +val Project.catalog: VersionCatalog + get() = extensions.getByType(VersionCatalogsExtension::class.java).named("libs")