diff --git a/docs/changes/README.md b/docs/changes/README.md index fe4105901..702487abe 100644 --- a/docs/changes/README.md +++ b/docs/changes/README.md @@ -7,6 +7,7 @@ - Add Kotlin DSL examples in docs. ([#1306](https://github.com/GradleUp/shadow/pull/1306)) - Support using type-safe dependency accessors in `ShadowJar.dependencies`. ([#1322](https://github.com/GradleUp/shadow/pull/1322)) +- Set `Main-Class` attr for KMP 2.1.0 or above. ([#1337](https://github.com/GradleUp/shadow/pull/1337)) **Changed** diff --git a/docs/kmp-plugin/README.md b/docs/kmp-plugin/README.md index 8c7c682e2..3bcdbc106 100644 --- a/docs/kmp-plugin/README.md +++ b/docs/kmp-plugin/README.md @@ -15,7 +15,10 @@ configure additional tasks for bundling the shadowed JAR for its `jvm` target. val ktorVersion = "3.1.0" kotlin { - jvm() + jvm().mainRun { + // Optionally, set the main class for `runJvm`, it's available from Kotlin 2.1.0 + mainClass = "myapp.MainKt" + } sourceSets { val commonMain by getting { dependencies { @@ -49,7 +52,10 @@ configure additional tasks for bundling the shadowed JAR for its `jvm` target. def ktorVersion = "3.1.0" kotlin { - jvm() + jvm().mainRun { + // Optionally, set the main class for `runJvm`, it's available from Kotlin 2.1.0 + it.mainClass.set('myapp.MainKt') + } sourceSets { commonMain { dependencies { diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/KmpPluginTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/KmpPluginTest.kt index 4b014c370..f00924258 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/KmpPluginTest.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/KmpPluginTest.kt @@ -1,11 +1,16 @@ package com.github.jengelman.gradle.plugins.shadow import assertk.assertThat +import assertk.assertions.isEqualTo +import com.github.jengelman.gradle.plugins.shadow.internal.mainClassAttributeKey import com.github.jengelman.gradle.plugins.shadow.util.containsEntries +import com.github.jengelman.gradle.plugins.shadow.util.getMainAttr import kotlin.io.path.appendText import kotlin.io.path.writeText import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource class KmpPluginTest : BasePluginTest() { @BeforeEach @@ -51,4 +56,42 @@ class KmpPluginTest : BasePluginTest() { ) } } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun canSetMainClassAttribute(useShadowAttr: Boolean) { + val mainClassEntry = writeClass(sourceSet = "jvmMain", isJava = false) + val main2ClassEntry = writeClass(sourceSet = "jvmMain", isJava = false, className = "Main2") + val mainClassName = "my.Main" + val main2ClassName = "my.Main2" + val mainAttr = if (useShadowAttr) "attributes '$mainClassAttributeKey': '$main2ClassName'" else "" + projectScriptPath.appendText( + """ + kotlin { + jvm().mainRun { + it.mainClass.set('$mainClassName') + } + } + $shadowJar { + manifest { + $mainAttr + } + } + """.trimIndent(), + ) + + run(shadowJarTask) + + assertThat(outputShadowJar).useAll { + containsEntries( + mainClassEntry, + main2ClassEntry, + ) + if (useShadowAttr) { + getMainAttr(mainClassAttributeKey).isEqualTo(main2ClassName) + } else { + getMainAttr("Main-Class").isEqualTo(mainClassName) + } + } + } } diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowKmpPlugin.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowKmpPlugin.kt index c0c443041..b2204a1fe 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowKmpPlugin.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowKmpPlugin.kt @@ -1,15 +1,21 @@ package com.github.jengelman.gradle.plugins.shadow import com.github.jengelman.gradle.plugins.shadow.ShadowJavaPlugin.Companion.registerShadowJarCommon +import com.github.jengelman.gradle.plugins.shadow.internal.mainClassAttributeKey +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import kotlin.collections.contains import org.gradle.api.Plugin import org.gradle.api.Project +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension +import org.jetbrains.kotlin.gradle.dsl.KotlinVersion as KgpVersion public abstract class ShadowKmpPlugin : Plugin { + private lateinit var kmpExtension: KotlinMultiplatformExtension override fun apply(project: Project) { with(project) { - val kmpExtension = extensions.getByType(KotlinMultiplatformExtension::class.java) + kmpExtension = extensions.getByType(KotlinMultiplatformExtension::class.java) val kotlinJvmMain = kmpExtension.jvm().compilations.named("main") registerShadowJarCommon { task -> task.from(kotlinJvmMain.map { it.output.allOutputs }) @@ -18,6 +24,24 @@ public abstract class ShadowKmpPlugin : Plugin { listOf(configurations.getByName(kotlinJvmMain.get().runtimeDependencyConfigurationName)) }, ) + configureMainClass(task) + } + } + } + + private fun Project.configureMainClass(task: ShadowJar) { + if (KgpVersion.DEFAULT < KgpVersion.KOTLIN_2_1) return + + @OptIn(ExperimentalKotlinGradlePluginApi::class) + kmpExtension.jvm().mainRun { + // Fix cannot serialize object of type 'org.jetbrains.kotlin.gradle.targets.jvm.tasks.KotlinJvmRun'. + val mainClassName = provider { mainClass } + task.inputs.property("mainClassName", mainClassName) + task.doFirst { + val realClass = mainClassName.get().orNull + if (!task.manifest.attributes.contains(mainClassAttributeKey) && !realClass.isNullOrEmpty()) { + task.manifest.attributes[mainClassAttributeKey] = realClass + } } } }