Skip to content

Commit 5b2ad59

Browse files
authored
Merge pull request #22 from tuuhin/ref/kotlin-dsl
Refactor: Migration to Kotlin DSL and newer Gradle APIs
2 parents 566b459 + 4fc0f0f commit 5b2ad59

6 files changed

Lines changed: 119 additions & 112 deletions

File tree

build.gradle.kts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
22

33
plugins {
4-
alias(libs.plugins.kotlin) apply false
4+
alias(libs.plugins.kotlin.jvm) apply false
55
alias(libs.plugins.versionCheck)
66
}
77

@@ -13,7 +13,8 @@ tasks.withType<DependencyUpdatesTask> {
1313

1414
fun String.isNonStable() = "^[0-9,.v-]+(-r)?$".toRegex().matches(this).not()
1515

16-
tasks.register("clean", Delete::class.java) {
16+
tasks.register<Delete>("clean") {
17+
description = "Delete the root project build directory"
1718
delete(rootProject.layout.buildDirectory)
1819
}
1920

gradle/libs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ kotlinx-coroutines = "1.11.0"
66
junit = "4.13.2"
77

88
[plugins]
9-
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
9+
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
1010
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
1111
pluginPublish = { id = "com.gradle.plugin-publish", version.ref = "pluginPublish" }
1212
versionCheck = { id = "com.github.ben-manes.versions", version.ref = "versionCheck" }

plugin-build/build.gradle.kts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,21 @@
11
plugins {
2-
alias(libs.plugins.kotlin) apply false
32
alias(libs.plugins.pluginPublish) apply false
43
alias(libs.plugins.versionCheck)
54
}
65

7-
val resolvedVersion =
8-
providers
9-
.environmentVariable("GITHUB_REF")
10-
.orNull
11-
?.removePrefix("refs/tags/v")
12-
?: "0.1.0"
6+
val resolvedVersion = providers
7+
.environmentVariable("GITHUB_REF")
8+
.orNull
9+
?.removePrefix("refs/tags/v")
10+
?: "0.1.0"
1311

1412
allprojects {
1513
group = property("GROUP").toString()
1614
version = resolvedVersion
1715
}
1816

19-
tasks.register("clean", Delete::class.java) {
17+
tasks.register<Delete>("clean") {
18+
description = "Delete the root project build directory"
2019
delete(rootProject.layout.buildDirectory)
2120
}
2221

plugin-build/plugin/build.gradle.kts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,12 @@
11
plugins {
2-
kotlin("jvm")
3-
`java-gradle-plugin`
2+
`kotlin-dsl`
43
alias(libs.plugins.pluginPublish)
54
}
65

76
dependencies {
8-
implementation(kotlin("stdlib"))
9-
implementation(gradleApi())
10-
117
// kotlin-compiler-embeddable for PSI parsing (compileOnly — loaded at runtime via isolated Worker classloader)
128
compileOnly(libs.kotlin.compiler.embeddable)
13-
149
compileOnly(libs.kotlin.gradle.plugin)
15-
1610
testImplementation(libs.junit)
1711
}
1812

@@ -49,6 +43,8 @@ publishing {
4943
*/
5044

5145
tasks.register("setupPluginUploadFromEnvironment") {
46+
description = "Upload plugin to publish"
47+
5248
doLast {
5349
val key = System.getenv("GRADLE_PUBLISH_KEY")
5450
val secret = System.getenv("GRADLE_PUBLISH_SECRET")

plugin-build/plugin/src/main/kotlin/dev/nucleusframework/nna/plugin/KotlinNativeExportPlugin.kt

Lines changed: 55 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package dev.nucleusframework.nna.plugin
22

3+
import dev.nucleusframework.nna.plugin.catalog.kotlinEmbeddedCompiler
34
import dev.nucleusframework.nna.plugin.catalog.kotlinxCoroutineDependency
45
import dev.nucleusframework.nna.plugin.catalog.kotlinxCoroutineJvmDependency
56
import dev.nucleusframework.nna.plugin.catalog.kotlinxCoroutineTestDependency
@@ -9,6 +10,10 @@ import org.gradle.api.Plugin
910
import org.gradle.api.Project
1011
import org.gradle.api.logging.LogLevel
1112
import org.gradle.api.tasks.testing.Test
13+
import org.gradle.kotlin.dsl.create
14+
import org.gradle.kotlin.dsl.getByType
15+
import org.gradle.kotlin.dsl.register
16+
import org.gradle.kotlin.dsl.withType
1217
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
1318
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
1419
import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType
@@ -33,10 +38,8 @@ import java.io.File
3338
class KotlinNativeExportPlugin : Plugin<Project> {
3439

3540
override fun apply(project: Project) {
36-
val extension = project.extensions.create(
37-
"kotlinNativeExport",
38-
KotlinNativeExportExtension::class.java,
39-
)
41+
42+
val extension = project.extensions.create<KotlinNativeExportExtension>("kotlinNativeExport")
4043

4144
extension.nativeLibName.convention("nativelib")
4245
extension.nativePackage.convention("")
@@ -48,7 +51,7 @@ class KotlinNativeExportPlugin : Plugin<Project> {
4851
}
4952

5053
private fun configureKmp(project: Project, extension: KotlinNativeExportExtension) {
51-
val kotlin = project.extensions.getByType(KotlinMultiplatformExtension::class.java)
54+
val kotlin = project.extensions.getByType<KotlinMultiplatformExtension>()
5255

5356
val libName = extension.nativeLibName.get()
5457
val pkg = extension.nativePackage.get()
@@ -100,34 +103,30 @@ class KotlinNativeExportPlugin : Plugin<Project> {
100103
val commonSources = project.files(if (commonMainDir.exists()) commonMainDir else null)
101104

102105
// ── PSI parser classpath (kotlin-compiler-embeddable for isolated Worker classloader) ──
103-
val kotlinVersion = kotlin.coreLibrariesVersion
104-
val psiClasspath = project.configurations.create("knePsiClasspath") {
105-
it.isCanBeConsumed = false
106-
it.isCanBeResolved = true
107-
it.isVisible = false
106+
val knePsiScope = project.configurations.dependencyScope("knePsiScope").get()
107+
val psiClasspath = project.configurations.resolvable("knePsiClasspath") {
108+
extendsFrom(knePsiScope)
109+
description = "Classpath for KNE PSI resolution"
108110
}
109-
project.dependencies.add("knePsiClasspath", "org.jetbrains.kotlin:kotlin-compiler-embeddable:$kotlinVersion")
111+
project.dependencies.add(knePsiScope.name, project.kotlinEmbeddedCompiler)
110112

111113
// ── Code-generation tasks ────────────────────────────────────────────
112114

113115
// Single task generates both native bridges and JVM proxies (PSI parsing + codegen in isolated worker)
114-
val generateBridges = project.tasks.register(
115-
"generateKneNativeBridges",
116-
GenerateNativeBridgesTask::class.java,
117-
) { task ->
118-
task.group = "kne"
119-
task.description = "Generate Kotlin/Native bridges and JVM FFM proxies"
120-
task.nativeSources.from(userNativeSources)
121-
task.commonSources.from(commonSources)
122-
task.libName.set(libName)
123-
task.jvmPackage.set(pkg)
124-
task.outputDir.set(nativeBridgesDir)
125-
task.jvmOutputDir.set(jvmProxiesDir)
126-
task.jvmResourcesDir.set(jvmResourcesDir)
127-
task.psiClasspath.from(psiClasspath)
116+
val generateBridges = project.tasks.register<GenerateNativeBridgesTask>("generateKneNativeBridges") {
117+
group = "kne"
118+
description = "Generate Kotlin/Native bridges and JVM FFM proxies"
119+
taskNativeSources.from(userNativeSources)
120+
taskCommonSources.from(commonSources)
121+
taskLibName.set(libName)
122+
taskJvmPackage.set(pkg)
123+
taskOutputDir.set(nativeBridgesDir)
124+
taskJvmOutputDir.set(jvmProxiesDir)
125+
taskJvmResourcesDir.set(jvmResourcesDir)
126+
taskPsiClasspath.from(psiClasspath)
128127
}
129128
// Keep old task name as alias
130-
project.tasks.register("generateKneJvmProxies") { it.dependsOn(generateBridges) }
129+
project.tasks.register("generateKneJvmProxies") { dependsOn(generateBridges) }
131130

132131
// ── Coroutines dependency (required for suspend function support) ──
133132
nativeTarget?.let { target ->
@@ -158,28 +157,28 @@ class KotlinNativeExportPlugin : Plugin<Project> {
158157
kotlin.sourceSets.findByName(jvmMainSourceSetName)?.resources?.srcDir(jvmResourcesDir)
159158

160159
// Ensure compilation waits for generation
161-
project.tasks.configureEach { task ->
160+
project.tasks.filter { task ->
162161
val name = task.name
163-
if (name.startsWith("compileKotlin") &&
164-
(name.contains("Native", ignoreCase = true) || name.contains(nativeTargetTaskName, ignoreCase = true))
165-
) {
166-
task.dependsOn(generateBridges)
167-
}
168-
if (name == "compileKotlin$jvmTaskName" || name == "compileKotlin$jvmMainTaskName") {
169-
task.dependsOn(generateBridges)
170-
}
162+
val isCompileKotlinOrNative = task.name.startsWith("compileKotlin") &&
163+
(task.name.contains("Native", ignoreCase = true) || task.name.contains(
164+
nativeTargetTaskName,
165+
ignoreCase = true
166+
))
167+
168+
val isJvmTask = name == "compileKotlin$jvmTaskName" || name == "compileKotlin$jvmMainTaskName"
169+
isJvmTask || isCompileKotlinOrNative
170+
}.forEach { task ->
171+
task.dependsOn(generateBridges)
171172
}
172173

173174
// ── Native binaries (both debug + release, like swift-java) ──────────
174175

175-
kotlin.targets
176-
.filterIsInstance<KotlinNativeTarget>()
177-
.forEach { target ->
178-
target.binaries.sharedLib(
179-
namePrefix = libName,
180-
buildTypes = listOf(NativeBuildType.DEBUG, NativeBuildType.RELEASE),
181-
)
182-
}
176+
kotlin.targets.filterIsInstance<KotlinNativeTarget>().forEach { target ->
177+
target.binaries.sharedLib(
178+
namePrefix = libName,
179+
buildTypes = listOf(NativeBuildType.DEBUG, NativeBuildType.RELEASE),
180+
)
181+
}
183182

184183
// ── Bundle native lib into JVM resources (zero-config deployment) ────
185184

@@ -199,11 +198,11 @@ class KotlinNativeExportPlugin : Plugin<Project> {
199198
val nativeLibResourceDir = project.layout.buildDirectory.dir("generated/kne/nativeLib")
200199

201200
val buildDir = project.layout.buildDirectory
202-
val copyNativeLib = project.tasks.register("copyKneNativeLib") { task ->
203-
task.group = "kne"
204-
task.description = "Copy native shared library into JVM resources for JAR bundling"
205-
task.dependsOn(linkTaskName)
206-
task.doLast {
201+
val copyNativeLib = project.tasks.register("copyKneNativeLib") {
202+
group = "kne"
203+
description = "Copy native shared library into JVM resources for JAR bundling"
204+
dependsOn(linkTaskName)
205+
doLast {
207206
val releaseDir = buildDir
208207
.dir("bin/$targetAliasName/${libName}ReleaseShared").get().asFile
209208
val nativeFile = releaseDir.listFiles()?.firstOrNull { f ->
@@ -213,7 +212,7 @@ class KotlinNativeExportPlugin : Plugin<Project> {
213212
val destDir = nativeLibResourceDir.get().asFile.resolve("kne/native/$platform")
214213
destDir.mkdirs()
215214
nativeFile.copyTo(destDir.resolve(nativeFile.name), overwrite = true)
216-
task.logger.lifecycle("kne: Bundled ${nativeFile.name} → kne/native/$platform/")
215+
logger.lifecycle("kne: Bundled ${nativeFile.name} → kne/native/$platform/")
217216
}
218217
}
219218
}
@@ -222,9 +221,9 @@ class KotlinNativeExportPlugin : Plugin<Project> {
222221
kotlin.sourceSets.findByName(jvmMainSourceSetName)?.resources?.srcDir(nativeLibResourceDir)
223222

224223
// Ensure processResources waits for the native lib copy
225-
project.tasks.configureEach { task ->
226-
if (task.name == "${jvmTarget.name}ProcessResources" || task.name == "process${jvmMainTaskName}Resources") {
227-
task.dependsOn(copyNativeLib)
224+
project.tasks.configureEach {
225+
if (name == "${jvmTarget.name}ProcessResources" || name == "process${jvmMainTaskName}Resources") {
226+
dependsOn(copyNativeLib)
228227
}
229228
}
230229
}
@@ -283,10 +282,10 @@ class KotlinNativeExportPlugin : Plugin<Project> {
283282
val buildType = extension.buildType.get().replaceFirstChar { it.uppercaseChar() }
284283
val linkTaskName = "link${libCap}${buildType}Shared$targetCap"
285284

286-
project.tasks.withType(Test::class.java).configureEach { testTask ->
287-
testTask.dependsOn(project.tasks.matching { it.name == linkTaskName })
288-
testTask.useJUnitPlatform()
289-
testTask.jvmArgs(
285+
project.tasks.withType<Test>().configureEach {
286+
dependsOn(project.tasks.matching { it.name == linkTaskName })
287+
useJUnitPlatform()
288+
jvmArgs(
290289
"-Djava.library.path=$libraryPath",
291290
"--enable-native-access=ALL-UNNAMED",
292291
)

plugin-build/plugin/src/main/kotlin/dev/nucleusframework/nna/plugin/tasks/GenerateNativeBridgesTask.kt

Lines changed: 50 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,68 +4,80 @@ import dev.nucleusframework.nna.plugin.analysis.PsiParseWorkAction
44
import org.gradle.api.DefaultTask
55
import org.gradle.api.file.ConfigurableFileCollection
66
import org.gradle.api.file.DirectoryProperty
7+
import org.gradle.api.model.ObjectFactory
78
import org.gradle.api.provider.Property
8-
import org.gradle.api.tasks.Classpath
9-
import org.gradle.api.tasks.Input
10-
import org.gradle.api.tasks.InputFiles
11-
import org.gradle.api.tasks.OutputDirectory
12-
import org.gradle.api.tasks.PathSensitive
13-
import org.gradle.api.tasks.PathSensitivity
14-
import org.gradle.api.tasks.SkipWhenEmpty
15-
import org.gradle.api.tasks.TaskAction
9+
import org.gradle.api.tasks.*
1610
import org.gradle.work.DisableCachingByDefault
17-
import org.gradle.api.model.ObjectFactory
1811
import org.gradle.workers.WorkerExecutor
1912
import javax.inject.Inject
2013

2114
@DisableCachingByDefault(because = "Bridge generation depends on source analysis that is not yet cacheable")
2215
abstract class GenerateNativeBridgesTask : DefaultTask() {
2316

24-
@get:Inject abstract val workerExecutor: WorkerExecutor
25-
@get:Inject abstract val objectFactory: ObjectFactory
17+
@get:Inject
18+
abstract val taskWorkerExecutor: WorkerExecutor
19+
20+
@get:Inject
21+
abstract val taskObjectFactory: ObjectFactory
22+
23+
@get:InputFiles
24+
@get:SkipWhenEmpty
25+
@get:PathSensitive(PathSensitivity.RELATIVE)
26+
abstract val taskNativeSources: ConfigurableFileCollection
27+
28+
@get:InputFiles
29+
@get:PathSensitive(PathSensitivity.RELATIVE)
30+
abstract val taskCommonSources: ConfigurableFileCollection
2631

27-
@get:InputFiles @get:SkipWhenEmpty @get:PathSensitive(PathSensitivity.RELATIVE)
28-
abstract val nativeSources: ConfigurableFileCollection
32+
@get:Classpath
33+
abstract val taskPsiClasspath: ConfigurableFileCollection
2934

30-
@get:InputFiles @get:PathSensitive(PathSensitivity.RELATIVE)
31-
abstract val commonSources: ConfigurableFileCollection
35+
@get:Input
36+
abstract val taskLibName: Property<String>
3237

33-
@get:Classpath abstract val psiClasspath: ConfigurableFileCollection
34-
@get:Input abstract val libName: Property<String>
35-
@get:Input abstract val jvmPackage: Property<String>
36-
@get:OutputDirectory abstract val outputDir: DirectoryProperty
37-
@get:OutputDirectory abstract val jvmOutputDir: DirectoryProperty
38-
@get:OutputDirectory abstract val jvmResourcesDir: DirectoryProperty
38+
@get:Input
39+
abstract val taskJvmPackage: Property<String>
40+
41+
@get:OutputDirectory
42+
abstract val taskOutputDir: DirectoryProperty
43+
44+
@get:OutputDirectory
45+
abstract val taskJvmOutputDir: DirectoryProperty
46+
47+
@get:OutputDirectory
48+
abstract val taskJvmResourcesDir: DirectoryProperty
3949

4050
@TaskAction
4151
fun generate() {
42-
outputDir.get().asFile.apply { deleteRecursively(); mkdirs() }
43-
jvmOutputDir.get().asFile.apply { deleteRecursively(); mkdirs() }
52+
taskOutputDir.get().asFile.apply { deleteRecursively(); mkdirs() }
53+
taskJvmOutputDir.get().asFile.apply { deleteRecursively(); mkdirs() }
4454

45-
val ktFiles = nativeSources.asFileTree.filter { it.extension == "kt" }.files
46-
if (ktFiles.isEmpty()) { logger.lifecycle("kne: No Kotlin sources found, skipping."); return }
55+
val ktFiles = taskNativeSources.asFileTree.filter { it.extension == "kt" }.files
56+
if (ktFiles.isEmpty()) {
57+
logger.lifecycle("kne: No Kotlin sources found, skipping."); return
58+
}
4759

48-
val commonKtFiles = commonSources.asFileTree.filter { it.extension == "kt" }.files
60+
val commonKtFiles = taskCommonSources.asFileTree.filter { it.extension == "kt" }.files
4961
logger.lifecycle("kne: Parsing ${ktFiles.size} native + ${commonKtFiles.size} common source file(s) [PSI]...")
5062

5163
val pluginJarUrl = PsiParseWorkAction::class.java.protectionDomain?.codeSource?.location
52-
val pluginJar = objectFactory.fileCollection().apply {
64+
val pluginJar = taskObjectFactory.fileCollection().apply {
5365
pluginJarUrl?.let { from(java.io.File(it.toURI())) }
5466
}
5567

56-
val workQueue = workerExecutor.classLoaderIsolation { spec ->
57-
spec.classpath.from(psiClasspath)
58-
spec.classpath.from(pluginJar)
68+
val workQueue = taskWorkerExecutor.classLoaderIsolation {
69+
classpath.from(taskPsiClasspath)
70+
classpath.from(pluginJar)
5971
}
6072

61-
workQueue.submit(PsiParseWorkAction::class.java) { params ->
62-
params.nativeSourceFiles.from(ktFiles)
63-
params.commonSourceFiles.from(commonKtFiles)
64-
params.libName.set(libName)
65-
params.jvmPackage.set(jvmPackage)
66-
params.nativeBridgesDir.set(outputDir)
67-
params.jvmProxiesDir.set(jvmOutputDir)
68-
params.jvmResourcesDir.set(jvmResourcesDir)
73+
workQueue.submit(PsiParseWorkAction::class.java) {
74+
nativeSourceFiles.from(ktFiles)
75+
commonSourceFiles.from(commonKtFiles)
76+
libName.set(taskLibName)
77+
jvmPackage.set(taskJvmPackage)
78+
nativeBridgesDir.set(taskOutputDir)
79+
jvmProxiesDir.set(taskJvmOutputDir)
80+
jvmResourcesDir.set(taskJvmResourcesDir)
6981
}
7082
}
7183
}

0 commit comments

Comments
 (0)