diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/KotlinPluginsTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/KotlinPluginsTest.kt index ad41a3b9d..7fc931548 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/KotlinPluginsTest.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/KotlinPluginsTest.kt @@ -268,6 +268,38 @@ class KotlinPluginsTest : BasePluginTest() { ) } + @Test + fun compatKmpApplicationDsl() { + writeClass(sourceSet = "jvmMain", withImports = true, jvmLang = JvmLang.Kotlin) + projectScript.appendText( + """ + kotlin { + jvm { + binaries { + executable { + it.mainClass.set('my.Main') + } + } + } + sourceSets { + jvmMain { + dependencies { + implementation 'junit:junit:3.8.2' + } + } + } + } + """.trimIndent(), + ) + + val result = run(runShadowPath, "--args='foo'") + + assertThat(result.output).contains( + "Hello, World! (foo) from Main", + "Refs: junit.framework.Test", + ) + } + private fun compileOnlyStdlib(exclude: Boolean): String { return if (exclude) { // Disable the stdlib dependency added via `implementation`. diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowApplicationPlugin.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowApplicationPlugin.kt index f5b05ad69..b21e24d5e 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowApplicationPlugin.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowApplicationPlugin.kt @@ -8,10 +8,13 @@ import com.github.jengelman.gradle.plugins.shadow.internal.javaPluginExtension import com.github.jengelman.gradle.plugins.shadow.internal.javaToolchainService import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar.Companion.shadowJar import java.io.IOException +import org.gradle.api.Action import org.gradle.api.GradleException import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.api.distribution.Distribution import org.gradle.api.plugins.ApplicationPlugin +import org.gradle.api.provider.Provider import org.gradle.api.tasks.JavaExec import org.gradle.api.tasks.Sync import org.gradle.api.tasks.TaskContainer @@ -35,12 +38,7 @@ public abstract class ShadowApplicationPlugin : Plugin { } protected open fun Project.addRunTask() { - tasks.register(SHADOW_RUN_TASK_NAME, JavaExec::class.java) { task -> - task.description = "Runs this project as a JVM application using the shadow jar" - task.group = ApplicationPlugin.APPLICATION_GROUP - - task.classpath = files(tasks.shadowJar) - + registerRunShadowCommon { task -> with(applicationExtension) { task.mainModule.convention(mainModule) task.mainClass.convention(mainClass) @@ -52,13 +50,8 @@ public abstract class ShadowApplicationPlugin : Plugin { } protected open fun Project.addCreateScriptsTask() { - tasks.register(SHADOW_SCRIPTS_TASK_NAME, CreateStartScripts::class.java) { task -> - task.description = "Creates OS specific scripts to run the project as a JVM application using the shadow jar" - task.group = ApplicationPlugin.APPLICATION_GROUP - - task.classpath = files(tasks.shadowJar) - - @Suppress("InternalGradleApiUsage") // Usages of conventionMapping. + registerStartShadowScriptsCommon { task -> + @Suppress("InternalGradleApiUsage", "DuplicatedCode") // Usages of conventionMapping. with(applicationExtension) { task.mainModule.convention(mainModule) task.mainClass.convention(mainClass) @@ -96,7 +89,7 @@ public abstract class ShadowApplicationPlugin : Plugin { } protected open fun Project.configureDistribution() { - distributions.register(DISTRIBUTION_NAME) { dist -> + registerShadowDistributionCommon { dist -> dist.distributionBaseName.convention( provider { // distributionBaseName defaults to `$project.name-$distribution.name`, applicationName defaults to project.name @@ -105,12 +98,6 @@ public abstract class ShadowApplicationPlugin : Plugin { }, ) dist.contents { distSpec -> - distSpec.from(file("src/dist")) - distSpec.into("lib") { lib -> - lib.from(tasks.shadowJar) - // Reflects the value of the `Class-Path` attribute in the JAR manifest. - lib.from(configurations.shadow) - } // Defaults to bin dir. distSpec.into(provider(applicationExtension::getExecutableDir)) { bin -> bin.from(tasks.startShadowScripts) @@ -131,7 +118,7 @@ public abstract class ShadowApplicationPlugin : Plugin { /** * Reflects the number of 755. */ - private const val UNIX_SCRIPT_PERMISSIONS = "rwxr-xr-x" + internal const val UNIX_SCRIPT_PERMISSIONS = "rwxr-xr-x" public const val DISTRIBUTION_NAME: String = SHADOW @@ -146,5 +133,43 @@ public abstract class ShadowApplicationPlugin : Plugin { @get:JvmSynthetic public inline val TaskContainer.installShadowDist: TaskProvider get() = named(SHADOW_INSTALL_TASK_NAME, Sync::class.java) + + internal fun Project.registerRunShadowCommon( + action: Action, + ): TaskProvider { + return tasks.register(SHADOW_RUN_TASK_NAME, JavaExec::class.java) { task -> + task.description = "Runs this project as a JVM application using the shadow jar" + task.group = ApplicationPlugin.APPLICATION_GROUP + task.classpath = files(tasks.shadowJar) + action.execute(task) + } + } + + internal fun Project.registerStartShadowScriptsCommon( + action: Action, + ): TaskProvider { + return tasks.register(SHADOW_SCRIPTS_TASK_NAME, CreateStartScripts::class.java) { task -> + task.description = "Creates OS specific scripts to run the project as a JVM application using the shadow jar" + task.group = ApplicationPlugin.APPLICATION_GROUP + task.classpath = files(tasks.shadowJar) + action.execute(task) + } + } + + internal fun Project.registerShadowDistributionCommon( + action: Action, + ): Provider { + return distributions.register(DISTRIBUTION_NAME) { dist -> + dist.contents { distSpec -> + distSpec.from(file("src/dist")) + distSpec.into("lib") { lib -> + lib.from(tasks.shadowJar) + // Reflects the value of the `Class-Path` attribute in the JAR manifest. + lib.from(configurations.shadow) + } + } + action.execute(dist) + } + } } } 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 6f2e16030..4d072658e 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,10 +1,19 @@ package com.github.jengelman.gradle.plugins.shadow +import com.github.jengelman.gradle.plugins.shadow.ShadowApplicationPlugin.Companion.UNIX_SCRIPT_PERMISSIONS +import com.github.jengelman.gradle.plugins.shadow.ShadowApplicationPlugin.Companion.registerRunShadowCommon +import com.github.jengelman.gradle.plugins.shadow.ShadowApplicationPlugin.Companion.registerShadowDistributionCommon +import com.github.jengelman.gradle.plugins.shadow.ShadowApplicationPlugin.Companion.registerStartShadowScriptsCommon +import com.github.jengelman.gradle.plugins.shadow.ShadowApplicationPlugin.Companion.startShadowScripts import com.github.jengelman.gradle.plugins.shadow.internal.isAtLeastKgpVersion import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar.Companion.SHADOW_JAR_TASK_NAME import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar.Companion.registerShadowJarCommon +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar.Companion.shadowJar +import java.util.Locale import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.api.tasks.JavaExec +import org.gradle.api.tasks.application.CreateStartScripts import org.gradle.api.tasks.bundling.Jar import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension @@ -13,6 +22,8 @@ import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget public abstract class ShadowKmpPlugin : Plugin { override fun apply(project: Project): Unit = with(project) { + var jvmTarget: KotlinJvmTarget? = null + extensions.getByType(KotlinMultiplatformExtension::class.java).targets.configureEach { target -> if (target !is KotlinJvmTarget) return@configureEach @Suppress("EagerGradleConfiguration") @@ -23,6 +34,25 @@ public abstract class ShadowKmpPlugin : Plugin { } configureShadowJar(target) + jvmTarget = target + } + + // TODO: https://youtrack.jetbrains.com/issue/KT-77499 + afterEvaluate { + if (!isAtLeastKgpVersion(2, 1, 20)) return@afterEvaluate + jvmTarget ?: return@afterEvaluate + val targetNameCap = jvmTarget.targetName.replaceFirstChar { it.titlecase(Locale.US) } + + @Suppress("EagerGradleConfiguration") // TODO: https://issuetracker.google.com/issues/444825893 + (tasks.findByName("run$targetNameCap") as? JavaExec)?.let { + addRunTask(it) + } ?: return@afterEvaluate + // This task must exist if the runJvmTask exists. + @Suppress("EagerGradleConfiguration") // TODO: https://issuetracker.google.com/issues/444825893 + (tasks.getByName("startScriptsFor$targetNameCap") as CreateStartScripts).let { + addCreateScriptsTask(it) + } + configureDistribution() } } @@ -44,4 +74,47 @@ public abstract class ShadowKmpPlugin : Plugin { } } } + + private fun Project.addRunTask(runJvmTask: JavaExec) { + tasks.shadowJar.configure { task -> + task.mainClass.convention(runJvmTask.mainClass) + } + registerRunShadowCommon { task -> + with(runJvmTask) { + task.mainModule.convention(mainModule) + task.mainClass.convention(mainClass) + task.jvmArguments.convention(jvmArguments) + task.modularity.inferModulePath.convention(modularity.inferModulePath) + task.javaLauncher.convention(javaLauncher) + } + } + } + + private fun Project.addCreateScriptsTask(startScriptsTask: CreateStartScripts) { + registerStartShadowScriptsCommon { task -> + @Suppress("InternalGradleApiUsage", "DuplicatedCode") // Usages of conventionMapping. + with(startScriptsTask) { + task.mainModule.convention(mainModule) + task.mainClass.convention(mainClass) + task.conventionMapping.map("applicationName", ::getApplicationName) + task.conventionMapping.map("outputDir") { layout.buildDirectory.dir("scriptsShadow").get().asFile } + task.conventionMapping.map("executableDir", ::getExecutableDir) + task.conventionMapping.map("defaultJvmOpts", ::getDefaultJvmOpts) + } + } + } + + private fun Project.configureDistribution() { + registerShadowDistributionCommon { dist -> + dist.contents { distSpec -> + // Should use KotlinJvmBinaryDsl.applicationDistribution instead. + distSpec.into("bin") { bin -> + bin.from(tasks.startShadowScripts) + bin.filePermissions { permissions -> permissions.unix(UNIX_SCRIPT_PERMISSIONS) } + } + // TODO: we can't access KotlinJvmBinaryDsl instance for now. +// distSpec.with(KotlinJvmBinaryDsl.applicationDistribution) + } + } + } }