Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -35,12 +38,7 @@ public abstract class ShadowApplicationPlugin : Plugin<Project> {
}

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)
Expand All @@ -52,13 +50,8 @@ public abstract class ShadowApplicationPlugin : Plugin<Project> {
}

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)
Expand Down Expand Up @@ -96,7 +89,7 @@ public abstract class ShadowApplicationPlugin : Plugin<Project> {
}

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
Expand All @@ -105,12 +98,6 @@ public abstract class ShadowApplicationPlugin : Plugin<Project> {
},
)
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)
Expand All @@ -131,7 +118,7 @@ public abstract class ShadowApplicationPlugin : Plugin<Project> {
/**
* 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

Expand All @@ -146,5 +133,43 @@ public abstract class ShadowApplicationPlugin : Plugin<Project> {
@get:JvmSynthetic
public inline val TaskContainer.installShadowDist: TaskProvider<Sync>
get() = named(SHADOW_INSTALL_TASK_NAME, Sync::class.java)

internal fun Project.registerRunShadowCommon(
action: Action<JavaExec>,
): TaskProvider<JavaExec> {
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<CreateStartScripts>,
): TaskProvider<CreateStartScripts> {
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<Distribution>,
): Provider<Distribution> {
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)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -13,6 +22,8 @@ import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget
public abstract class ShadowKmpPlugin : Plugin<Project> {

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")
Expand All @@ -23,6 +34,25 @@ public abstract class ShadowKmpPlugin : Plugin<Project> {
}

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()
}
}

Expand All @@ -44,4 +74,47 @@ public abstract class ShadowKmpPlugin : Plugin<Project> {
}
}
}

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)
}
}
}
}