Repository: GradleUp/shadow (kotlin)

Issue:
Eagerly create jvm target for KMP
> Please don't call `jvm()`. Because this method declares a jvm target in KMP project. And users should do that explicitly. 
> More over, they can specify a custom name for their jvm target. 
> The correct API is to lazily iterate over targets collection. And check for KotlinJvmTarget instance.

```kt
kmpExtension.targets.configureEach { target ->
   if (target is KotlinJvmTarget) // do configuration here
}
```

_Originally posted by @antohaby in https://github.com/GradleUp/shadow/pull/1280#discussion_r2018890845_

Pull request description:
Closes #1377.

---

- [x] [CHANGELOG](https://github.com/GradleUp/shadow/blob/main/docs/changes/README.md)'s "Unreleased" section has been updated, if applicable.

Patch:
diff --git a/docs/changes/README.md b/docs/changes/README.md
index af01c77f0..ccde7b443 100644
--- a/docs/changes/README.md
+++ b/docs/changes/README.md
@@ -4,6 +4,10 @@
 ## [Unreleased]
 [Unreleased]: https://github.com/GradleUp/shadow/compare/9.0.0-beta12...HEAD
 
+**Fixed**
+
+- Avoid creating jvm targets eagerly for KMP. ([#1378](https://github.com/GradleUp/shadow/pull/1378))
+
 
 ## [v9.0.0-beta12] (2025-04-01)
 [v9.0.0-beta12]: https://github.com/GradleUp/shadow/releases/tag/9.0.0-beta12
diff --git a/docs/kotlin-plugins/README.md b/docs/kotlin-plugins/README.md
index 92f027ed6..753b5cb25 100644
--- a/docs/kotlin-plugins/README.md
+++ b/docs/kotlin-plugins/README.md
@@ -80,6 +80,7 @@ configure additional tasks for bundling the shadowed JAR for its `jvm` target.
     val ktorVersion = "3.1.0"
 
     kotlin {
+      @Suppress("OPT_IN_USAGE")
       jvm().mainRun {
         // Optionally, set the main class for `runJvm`, it's available from Kotlin 2.1.0
         mainClass = "myapp.MainKt"
@@ -98,7 +99,8 @@ configure additional tasks for bundling the shadowed JAR for its `jvm` target.
       }
     }
 
-    tasks.shadowJar {
+    // TODO: we can't use `tasks.shadowJar` here like the other examples, something wrong with Gradle or Kotlin plugin?
+    tasks.named<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>("shadowJar") {
       manifest {
         // Optionally, set the main class for the shadowed JAR.
         attributes["Main-Class"] = "com.example.MainKt"
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 b2204a1fe..6d7666192 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
@@ -9,6 +9,7 @@ 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
+import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget
 
 public abstract class ShadowKmpPlugin : Plugin<Project> {
   private lateinit var kmpExtension: KotlinMultiplatformExtension
@@ -16,24 +17,28 @@ public abstract class ShadowKmpPlugin : Plugin<Project> {
   override fun apply(project: Project) {
     with(project) {
       kmpExtension = extensions.getByType(KotlinMultiplatformExtension::class.java)
-      val kotlinJvmMain = kmpExtension.jvm().compilations.named("main")
-      registerShadowJarCommon { task ->
-        task.from(kotlinJvmMain.map { it.output.allOutputs })
-        task.configurations.convention(
-          provider {
-            listOf(configurations.getByName(kotlinJvmMain.get().runtimeDependencyConfigurationName))
-          },
-        )
-        configureMainClass(task)
+      kmpExtension.targets.configureEach { target ->
+        if (target !is KotlinJvmTarget) return@configureEach
+
+        val kotlinJvmMain = target.compilations.named("main")
+        registerShadowJarCommon { task ->
+          task.from(kotlinJvmMain.map { it.output.allOutputs })
+          task.configurations.convention(
+            provider {
+              listOf(configurations.getByName(kotlinJvmMain.get().runtimeDependencyConfigurationName))
+            },
+          )
+          configureMainClass(target.name, task)
+        }
       }
     }
   }
 
-  private fun Project.configureMainClass(task: ShadowJar) {
+  private fun Project.configureMainClass(targetName: String, task: ShadowJar) {
     if (KgpVersion.DEFAULT < KgpVersion.KOTLIN_2_1) return
 
     @OptIn(ExperimentalKotlinGradlePluginApi::class)
-    kmpExtension.jvm().mainRun {
+    kmpExtension.jvm(targetName).mainRun {
       // Fix cannot serialize object of type 'org.jetbrains.kotlin.gradle.targets.jvm.tasks.KotlinJvmRun'.
       val mainClassName = provider { mainClass }
       task.inputs.property("mainClassName", mainClassName)


Test patch:
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 41a92ef88..9ae2e0b0d 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
@@ -1,8 +1,10 @@
 package com.github.jengelman.gradle.plugins.shadow
 
 import assertk.assertThat
+import assertk.assertions.contains
 import assertk.assertions.isEqualTo
 import com.github.jengelman.gradle.plugins.shadow.internal.mainClassAttributeKey
+import com.github.jengelman.gradle.plugins.shadow.util.Issue
 import com.github.jengelman.gradle.plugins.shadow.util.JvmLang
 import com.github.jengelman.gradle.plugins.shadow.util.containsAtLeast
 import com.github.jengelman.gradle.plugins.shadow.util.containsOnly
@@ -10,6 +12,7 @@ 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
 
@@ -104,6 +107,70 @@ class KotlinPluginsTest : BasePluginTest() {
     }
   }
 
+  @Issue(
+    "https://github.com/GradleUp/shadow/issues/1377",
+  )
+  @Test
+  fun compatKmpForOtherNamedJvmTarget() {
+    val jvmTargetName = "newJvm"
+    val jvmTargetMain = "${jvmTargetName}Main"
+    val stdlib = compileOnlyStdlib(true)
+    val mainClassEntry = writeClass(sourceSet = jvmTargetMain, jvmLang = JvmLang.Kotlin)
+    projectScriptPath.appendText(
+      """
+        kotlin {
+          jvm("$jvmTargetName")
+          sourceSets {
+            commonMain {
+              dependencies {
+                implementation 'my:b:1.0'
+                $stdlib
+              }
+            }
+            $jvmTargetMain {
+              dependencies {
+                implementation 'my:a:1.0'
+              }
+            }
+          }
+        }
+      """.trimIndent(),
+    )
+
+    run(shadowJarTask)
+
+    assertThat(outputShadowJar).useAll {
+      val entries = arrayOf(
+        "my/",
+        "META-INF/my.kotlin_module",
+        mainClassEntry,
+        *entriesInAB,
+        *manifestEntries,
+      )
+      containsAtLeast(*entries)
+    }
+  }
+
+  @Issue(
+    "https://github.com/GradleUp/shadow/issues/1377",
+  )
+  @Test
+  fun doNotCreateJvmTargetEagerly() {
+    projectScriptPath.appendText(
+      """
+        kotlin {
+          mingwX64()
+        }
+      """.trimIndent(),
+    )
+
+    val result = runWithFailure(shadowJarTask)
+
+    assertThat(result.output).contains(
+      "Cannot locate tasks that match ':shadowJar' as task 'shadowJar' not found in root project",
+    )
+  }
+
   @ParameterizedTest
   @ValueSource(booleans = [false, true])
   fun canSetMainClassAttribute(useShadowAttr: Boolean) {