Skip to content

Commit 06c6ae4

Browse files
GooolerCopilot
andauthored
Compat Kotlin Multiplatform plugin (#1280)
* Introduce KMP dependency * Apply `ShadowJavaPlugin` for org.jetbrains.kotlin.multiplatform * Configure `from` and `configurations` for org.jetbrains.kotlin.multiplatform * Configure kmp related logic in `ShadowKmpPlugin` * Test `compatKmpJvmTarget` * Don't combine java plugin with kmp plugin * Extract `registerShadowJarCommon` * Check main class as well * Add `friends` configuration https://www.liutikas.net/2025/01/12/Kotlin-Library-Friends.html * Revert "Add `friends` configuration" This reverts commit 48a84b4. * Update changelog * New doc for "Integrating with Kotlin Multiplatform Plugin" * Update src/docs/kmp-plugin/README.md Co-authored-by: Copilot <[email protected]> * Refine * Tweak style * Improve `registerShadowJarCommon`'s interoperability for Groovy --------- Co-authored-by: Copilot <[email protected]>
1 parent f0fbbc7 commit 06c6ae4

File tree

11 files changed

+198
-34
lines changed

11 files changed

+198
-34
lines changed

api/shadow.api

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,19 @@ public abstract class com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugi
5151
protected fun configureConfigurations (Lorg/gradle/api/Project;)V
5252
protected fun configureJavaGradlePlugin (Lorg/gradle/api/Project;)V
5353
protected fun configureShadowJar (Lorg/gradle/api/Project;)V
54+
public static final fun registerShadowJarCommon (Lorg/gradle/api/Project;Lorg/gradle/api/Action;)Lorg/gradle/api/tasks/TaskProvider;
5455
}
5556

5657
public final class com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin$Companion {
5758
public final fun getShadowJar (Lorg/gradle/api/tasks/TaskContainer;)Lorg/gradle/api/tasks/TaskProvider;
5859
public final fun getShadowRuntimeElements (Lorg/gradle/api/artifacts/ConfigurationContainer;)Lorg/gradle/api/NamedDomainObjectProvider;
60+
public final fun registerShadowJarCommon (Lorg/gradle/api/Project;Lorg/gradle/api/Action;)Lorg/gradle/api/tasks/TaskProvider;
61+
}
62+
63+
public abstract class com/github/jengelman/gradle/plugins/shadow/ShadowKmpPlugin : org/gradle/api/Plugin {
64+
public fun <init> ()V
65+
public synthetic fun apply (Ljava/lang/Object;)V
66+
public fun apply (Lorg/gradle/api/Project;)V
5967
}
6068

6169
public abstract class com/github/jengelman/gradle/plugins/shadow/ShadowPlugin : org/gradle/api/Plugin {

build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ val testPluginClasspath by configurations.registering {
5151
}
5252

5353
dependencies {
54+
compileOnly(libs.kotlin.kmp)
5455
implementation(libs.apache.ant)
5556
implementation(libs.apache.commonsIo)
5657
implementation(libs.apache.log4j)
@@ -62,6 +63,7 @@ dependencies {
6263

6364
testPluginClasspath(libs.foojayResolver)
6465
testPluginClasspath(libs.pluginPublish)
66+
testPluginClasspath(libs.kotlin.kmp)
6567

6668
lintChecks(libs.androidx.gradlePluginLints)
6769
}

gradle/libs.versions.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
[versions]
2+
kotlin = "2.1.10"
23
moshi = "1.15.2"
34

45
[libraries]
@@ -15,13 +16,14 @@ plexus-xml = "org.codehaus.plexus:plexus-xml:4.0.4"
1516
xmlunit = "org.xmlunit:xmlunit-legacy:2.10.0"
1617
moshi = { module = "com.squareup.moshi:moshi", version.ref = "moshi" }
1718
moshi-kotlin = { module = "com.squareup.moshi:moshi-kotlin", version.ref = "moshi" }
18-
foojayResolver = "org.gradle.toolchains.foojay-resolver-convention:org.gradle.toolchains.foojay-resolver-convention.gradle.plugin:0.9.0"
1919

2020
pluginPublish = "com.gradle.publish:plugin-publish-plugin:1.3.1"
2121
mavenPublish = "com.vanniktech:gradle-maven-publish-plugin:0.30.0"
2222
gitPublish = "org.ajoberstar.git-publish:gradle-git-publish:5.1.0"
2323
jetbrains-dokka = "org.jetbrains.dokka:dokka-gradle-plugin:2.0.0"
2424
node = "com.github.node-gradle:gradle-node-plugin:7.1.0"
25+
foojayResolver = "org.gradle.toolchains.foojay-resolver-convention:org.gradle.toolchains.foojay-resolver-convention.gradle.plugin:0.9.0"
26+
kotlin-kmp = { module = "org.jetbrains.kotlin.multiplatform:org.jetbrains.kotlin.multiplatform.gradle.plugin", version.ref = "kotlin" }
2527

2628
androidx-gradlePluginLints = "androidx.lint:lint-gradle:1.0.0-alpha03"
2729
# Dummy to get renovate updates, the version is used in rootProject build.gradle with spotless.
@@ -31,7 +33,7 @@ junit-bom = "org.junit:junit-bom:5.12.0"
3133
assertk = "com.willowtreeapps.assertk:assertk:0.28.1"
3234

3335
[plugins]
34-
kotlin-jvm = "org.jetbrains.kotlin.jvm:2.1.10"
36+
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
3537
android-lint = "com.android.lint:8.8.1"
3638
jetbrains-bcv = "org.jetbrains.kotlinx.binary-compatibility-validator:0.17.0"
3739
spotless = "com.diffplug.spotless:7.0.2"

src/docs/.vuepress/config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ module.exports = {
2626
'/configuration/reproducible-builds/',
2727
'/custom-tasks/',
2828
'/application-plugin/',
29+
'/kmp-plugin/',
2930
'/publishing/',
3031
'/multi-project/',
3132
'/plugins/',

src/docs/changes/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33

44
## [Unreleased]
55

6+
**Added**
7+
8+
- Compat Kotlin Multiplatform plugin. ([#1280](https://github.com/GradleUp/shadow/pull/1280))
9+
You still need to manually configure `manifest.attributes` (e.g. `Main-Class` attr) in the `shadowJar` task if necessary.
10+
611
**Fixed**
712

813
- Fix the last modified time of shadowed directories. ([#1277](https://github.com/GradleUp/shadow/pull/1277))

src/docs/kmp-plugin/README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Integrating with Kotlin Multiplatform Plugin
2+
3+
Shadow honors Kotlin's
4+
[`org.jetbrains.kotlin.multiplatform`](https://kotlinlang.org/docs/multiplatform-intro.html) plugin and will automatically
5+
configure additional tasks for bundling the shadowed JAR for its `jvm` target.
6+
```groovy
7+
// Using Shadow with KMP Plugin
8+
plugins {
9+
id 'org.jetbrains.kotlin.multiplatform'
10+
id 'com.gradleup.shadow'
11+
}
12+
13+
def ktorVersion = "3.1.0"
14+
15+
kotlin {
16+
jvm()
17+
sourceSets {
18+
commonMain {
19+
dependencies {
20+
implementation "io.ktor:ktor-client-core$ktorVersion"
21+
}
22+
}
23+
jvmMain {
24+
dependencies {
25+
implementation "io.ktor:ktor-client-okhttp$ktorVersion"
26+
}
27+
}
28+
}
29+
}
30+
31+
tasks.named('shadowJar', com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) {
32+
manifest {
33+
// Optionally, set the main class for the shadowed JAR.
34+
attributes 'Main-Class': 'com.example.MainKt'
35+
}
36+
}
37+
```

src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/BasePluginTest.kt

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -103,15 +103,15 @@ abstract class BasePluginTest {
103103
val outputServerShadowJar: JarPath get() = jarPath("server/build/libs/server-1.0-all.jar")
104104

105105
fun getDefaultProjectBuildScript(
106-
javaPlugin: String = "java",
106+
plugin: String = "java",
107107
withGroup: Boolean = false,
108108
withVersion: Boolean = false,
109109
): String {
110110
val groupInfo = if (withGroup) "group = 'my'" else ""
111111
val versionInfo = if (withVersion) "version = '1.0'" else ""
112112
return """
113113
plugins {
114-
id('$javaPlugin')
114+
id('$plugin')
115115
id('com.gradleup.shadow')
116116
}
117117
$groupInfo
@@ -194,26 +194,43 @@ abstract class BasePluginTest {
194194
packageName: String = "my",
195195
withImports: Boolean = false,
196196
className: String = "Main",
197+
isJava: Boolean = true,
197198
): String {
198-
val imports = if (withImports) "import junit.framework.Test;" else ""
199-
val classRef = if (withImports) "\"Refs: \" + Test.class.getName()" else "\"Refs: null\""
200-
path("src/$sourceSet/java/$packageName/$className.java").writeText(
201-
"""
202-
package $packageName;
203-
$imports
204-
public class $className {
205-
public static void main(String[] args) {
206-
if (args.length == 0) {
207-
throw new IllegalArgumentException("No arguments provided.");
199+
if (isJava) {
200+
val imports = if (withImports) "import junit.framework.Test;" else ""
201+
val classRef = if (withImports) "\"Refs: \" + Test.class.getName()" else "\"Refs: null\""
202+
path("src/$sourceSet/java/$packageName/$className.java").writeText(
203+
"""
204+
package $packageName;
205+
$imports
206+
public class $className {
207+
public static void main(String[] args) {
208+
if (args.length == 0) throw new IllegalArgumentException("No arguments provided.");
209+
String content = String.format("Hello, World! (%s) from $className", (Object[]) args);
210+
System.out.println(content);
211+
System.out.println($classRef);
208212
}
209-
String content = String.format("Hello, World! (%s) from $className", (Object[]) args);
210-
System.out.println(content);
211-
System.out.println($classRef);
212213
}
213-
}
214-
""".trimIndent(),
215-
)
216-
return packageName.replace('.', '/') + "/$className.class"
214+
""".trimIndent(),
215+
)
216+
} else {
217+
val imports = if (withImports) "import junit.framework.Test;" else ""
218+
val classRef = if (withImports) "\"Refs: \" + Test.class.getName()" else "\"Refs: null\""
219+
path("src/$sourceSet/kotlin/$packageName/$className.kt").writeText(
220+
"""
221+
package $packageName
222+
$imports
223+
fun main(vararg args: String) {
224+
if (args.isEmpty()) throw IllegalArgumentException("No arguments provided.")
225+
val content ="Hello, World! (%s) from $className".format(*args)
226+
println(content)
227+
println($classRef)
228+
}
229+
""".trimIndent(),
230+
)
231+
}
232+
val baseClassPath = packageName.replace('.', '/') + "/$className"
233+
return if (isJava) "$baseClassPath.class" else "${baseClassPath}Kt.class"
217234
}
218235

219236
fun writeClientAndServerModules(
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package com.github.jengelman.gradle.plugins.shadow
2+
3+
import assertk.assertThat
4+
import com.github.jengelman.gradle.plugins.shadow.util.containsEntries
5+
import kotlin.io.path.appendText
6+
import kotlin.io.path.writeText
7+
import org.junit.jupiter.api.BeforeEach
8+
import org.junit.jupiter.api.Test
9+
10+
class KmpPluginTest : BasePluginTest() {
11+
@BeforeEach
12+
override fun setup() {
13+
super.setup()
14+
val projectBuildScript = getDefaultProjectBuildScript(
15+
plugin = "org.jetbrains.kotlin.multiplatform",
16+
withGroup = true,
17+
withVersion = true,
18+
)
19+
projectScriptPath.writeText(projectBuildScript)
20+
}
21+
22+
@Test
23+
fun compatKmpJvmTarget() {
24+
val mainClass = writeMainClass(sourceSet = "jvmMain", isJava = false)
25+
projectScriptPath.appendText(
26+
"""
27+
kotlin {
28+
jvm()
29+
sourceSets {
30+
commonMain {
31+
dependencies {
32+
implementation 'my:b:1.0'
33+
}
34+
}
35+
jvmMain {
36+
dependencies {
37+
implementation 'my:a:1.0'
38+
}
39+
}
40+
}
41+
}
42+
""".trimIndent(),
43+
)
44+
45+
run(shadowJarTask)
46+
47+
assertThat(outputShadowJar).useAll {
48+
containsEntries(
49+
mainClass,
50+
*entriesInAB,
51+
)
52+
}
53+
}
54+
}

src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.kt

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import com.github.jengelman.gradle.plugins.shadow.internal.runtimeConfiguration
88
import com.github.jengelman.gradle.plugins.shadow.internal.sourceSets
99
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
1010
import javax.inject.Inject
11+
import org.gradle.api.Action
1112
import org.gradle.api.NamedDomainObjectProvider
1213
import org.gradle.api.Plugin
1314
import org.gradle.api.Project
@@ -38,10 +39,7 @@ public abstract class ShadowJavaPlugin @Inject constructor(
3839

3940
protected open fun Project.configureShadowJar() {
4041
val jarTask = tasks.jar
41-
val taskProvider = tasks.register(SHADOW_JAR_TASK_NAME, ShadowJar::class.java) { task ->
42-
task.group = ShadowBasePlugin.GROUP_NAME
43-
task.description = "Create a combined JAR of project and runtime dependencies"
44-
task.archiveClassifier.set("all")
42+
val taskProvider = registerShadowJarCommon { task ->
4543
@Suppress("EagerGradleConfiguration")
4644
task.manifest.inheritFrom(jarTask.get().manifest)
4745
val attrProvider = jarTask.map { it.manifest.attributes[classPathAttributeKey]?.toString().orEmpty() }
@@ -54,15 +52,6 @@ public abstract class ShadowJavaPlugin @Inject constructor(
5452
}
5553
task.from(sourceSets.named("main").map { it.output })
5654
task.configurations.convention(provider { listOf(runtimeConfiguration) })
57-
task.exclude(
58-
"META-INF/INDEX.LIST",
59-
"META-INF/*.SF",
60-
"META-INF/*.DSA",
61-
"META-INF/*.RSA",
62-
// module-info.class in Multi-Release folders.
63-
"META-INF/versions/**/module-info.class",
64-
"module-info.class",
65-
)
6655
}
6756
artifacts.add(configurations.shadow.name, taskProvider)
6857
}
@@ -132,5 +121,26 @@ public abstract class ShadowJavaPlugin @Inject constructor(
132121

133122
public inline val ConfigurationContainer.shadowRuntimeElements: NamedDomainObjectProvider<Configuration>
134123
get() = named(SHADOW_RUNTIME_ELEMENTS_CONFIGURATION_NAME)
124+
125+
@JvmStatic
126+
public fun Project.registerShadowJarCommon(
127+
action: Action<ShadowJar>,
128+
): TaskProvider<ShadowJar> {
129+
return tasks.register(SHADOW_JAR_TASK_NAME, ShadowJar::class.java) { task ->
130+
task.group = ShadowBasePlugin.GROUP_NAME
131+
task.description = "Create a combined JAR of project and runtime dependencies"
132+
task.archiveClassifier.set("all")
133+
task.exclude(
134+
"META-INF/INDEX.LIST",
135+
"META-INF/*.SF",
136+
"META-INF/*.DSA",
137+
"META-INF/*.RSA",
138+
// module-info.class in Multi-Release folders.
139+
"META-INF/versions/**/module-info.class",
140+
"module-info.class",
141+
)
142+
action.execute(task)
143+
}
144+
}
135145
}
136146
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.github.jengelman.gradle.plugins.shadow
2+
3+
import com.github.jengelman.gradle.plugins.shadow.ShadowJavaPlugin.Companion.registerShadowJarCommon
4+
import org.gradle.api.Plugin
5+
import org.gradle.api.Project
6+
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
7+
8+
public abstract class ShadowKmpPlugin : Plugin<Project> {
9+
10+
override fun apply(project: Project) {
11+
with(project) {
12+
val kmpExtension = extensions.getByType(KotlinMultiplatformExtension::class.java)
13+
val kotlinJvmMain = kmpExtension.jvm().compilations.named("main")
14+
registerShadowJarCommon { task ->
15+
task.from(kotlinJvmMain.map { it.output.allOutputs })
16+
task.configurations.convention(
17+
provider {
18+
listOf(configurations.getByName(kotlinJvmMain.get().runtimeDependencyConfigurationName))
19+
},
20+
)
21+
}
22+
}
23+
}
24+
}

0 commit comments

Comments
 (0)