From 12cfe629bd05082973e9febbf6a4539bec324071 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 12:28:29 +0000 Subject: [PATCH 1/4] Initial plan From 6331f986e7a0e7313ec4181332e61a653e277280 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 12:30:19 +0000 Subject: [PATCH 2/4] docs: add section on making shadowed jar the default output in multi-project builds (#1893) Co-authored-by: Goooler <10363352+Goooler@users.noreply.github.com> --- docs/multi-project/README.md | 106 +++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/docs/multi-project/README.md b/docs/multi-project/README.md index f961f44d3..1062e2c64 100644 --- a/docs/multi-project/README.md +++ b/docs/multi-project/README.md @@ -28,7 +28,113 @@ configuration of the shadowed project. } ``` +## Making the Shadowed JAR the Default Artifact +When a project needs to expose the shadowed JAR as its default output — so that consumers can +depend on it without specifying the `shadow` configuration explicitly — you can reconfigure the +[consumable configurations][gradle-consumable-configs] `apiElements` and `runtimeElements` to +publish the shadowed JAR instead of the regular JAR. +In the `foo` project (that produces the shadowed JAR): + +=== "Kotlin" + + ```kotlin + plugins { + `java-library` + id("com.gradleup.shadow") + } + + configurations { + named("apiElements") { + outgoing.artifacts.clear() + outgoing.artifact(tasks.shadowJar) + } + named("runtimeElements") { + outgoing.artifacts.clear() + outgoing.artifact(tasks.shadowJar) + } + } + ``` + +=== "Groovy" + + ```groovy + plugins { + id 'java-library' + id 'com.gradleup.shadow' + } + + configurations { + apiElements { + outgoing.artifacts.clear() + outgoing.artifact(tasks.named('shadowJar')) + } + runtimeElements { + outgoing.artifacts.clear() + outgoing.artifact(tasks.named('shadowJar')) + } + } + ``` + +Consuming projects can then depend on `:foo` without specifying the `shadow` configuration: + +=== "Kotlin" + + ```kotlin + dependencies { + implementation(project(":foo")) + } + ``` + +=== "Groovy" + + ```groovy + dependencies { + implementation project(':foo') + } + ``` + +If you want to exclude transitive dependencies that were bundled into the shadow JAR, you can add +[`exclude` rules][gradle-exclude-rules] to the configurations as well: + +=== "Kotlin" + + ```kotlin + configurations { + named("apiElements") { + outgoing.artifacts.clear() + outgoing.artifact(tasks.shadowJar) + exclude(group = "com.example", module = "bundled-library") + } + named("runtimeElements") { + outgoing.artifacts.clear() + outgoing.artifact(tasks.shadowJar) + exclude(group = "com.example", module = "bundled-library") + } + } + ``` + +=== "Groovy" + + ```groovy + configurations { + apiElements { + outgoing.artifacts.clear() + outgoing.artifact(tasks.named('shadowJar')) + exclude group: 'com.example', module: 'bundled-library' + } + runtimeElements { + outgoing.artifacts.clear() + outgoing.artifact(tasks.named('shadowJar')) + exclude group: 'com.example', module: 'bundled-library' + } + } + ``` + + + +[gradle-consumable-configs]: https://docs.gradle.org/current/userguide/declaring_configurations.html#3_consumable_configurations +[gradle-exclude-rules]: https://docs.gradle.org/current/userguide/dependency_downgrade_and_exclude.html#sec:excluding-transitive-deps [Jar]: https://docs.gradle.org/current/dsl/org.gradle.api.tasks.bundling.Jar.html [ShadowJar]: ../api/shadow/com.github.jengelman.gradle.plugins.shadow.tasks/-shadow-jar/index.html From 37538f0fefa99cda961503e7d3fe757b612615c4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 12:45:48 +0000 Subject: [PATCH 3/4] test: add integration tests for shadow JAR as default project artifact (#1893) Co-authored-by: Goooler <10363352+Goooler@users.noreply.github.com> --- .../gradle/plugins/shadow/JavaPluginsTest.kt | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/JavaPluginsTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/JavaPluginsTest.kt index f2d39283b..fd833a8d8 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/JavaPluginsTest.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/JavaPluginsTest.kt @@ -171,6 +171,100 @@ class JavaPluginsTest : BasePluginTest() { } } + @Issue("https://github.com/GradleUp/shadow/issues/1893") + @Test + fun shadowJarIsDefaultArtifactInMultiProjectBuild() { + settingsScript.appendText("include 'foo', 'consumer'$lineSeparator") + projectScript.writeText("") + + path("foo/build.gradle").writeText( + """ + ${getDefaultProjectBuildScript("java-library")} + dependencies { + implementation 'my:a:1.0' + } + configurations { + named('apiElements') { + outgoing.artifacts.clear() + outgoing.artifact(tasks.named('shadowJar')) + } + named('runtimeElements') { + outgoing.artifacts.clear() + outgoing.artifact(tasks.named('shadowJar')) + } + } + """.trimIndent() + lineSeparator, + ) + + path("consumer/build.gradle").writeText( + """ + ${getDefaultProjectBuildScript("java")} + dependencies { + implementation project(':foo') + } + tasks.register('printClasspathFiles') { + doLast { + configurations.runtimeClasspath.files.each { println it.name } + } + } + """.trimIndent() + lineSeparator, + ) + + val result = runWithSuccess(":consumer:printClasspathFiles") + assertThat(result.output).all { + contains("foo-1.0-all.jar") + doesNotContain("foo-1.0.jar") + } + } + + @Issue("https://github.com/GradleUp/shadow/issues/1893") + @Test + fun excludeRulesPreventBundledDepsOnConsumerClasspath() { + settingsScript.appendText("include 'foo', 'consumer'$lineSeparator") + projectScript.writeText("") + + path("foo/build.gradle").writeText( + """ + ${getDefaultProjectBuildScript("java-library")} + dependencies { + implementation 'my:a:1.0' + } + configurations { + named('apiElements') { + outgoing.artifacts.clear() + outgoing.artifact(tasks.named('shadowJar')) + exclude(group: 'my', module: 'a') + } + named('runtimeElements') { + outgoing.artifacts.clear() + outgoing.artifact(tasks.named('shadowJar')) + exclude(group: 'my', module: 'a') + } + } + """.trimIndent() + lineSeparator, + ) + + path("consumer/build.gradle").writeText( + """ + ${getDefaultProjectBuildScript("java")} + dependencies { + implementation project(':foo') + } + tasks.register('printClasspathFiles') { + doLast { + configurations.runtimeClasspath.files.each { println it.name } + } + } + """.trimIndent() + lineSeparator, + ) + + val result = runWithSuccess(":consumer:printClasspathFiles") + assertThat(result.output).all { + contains("foo-1.0-all.jar") + doesNotContain("a-1.0.jar") + } + } + @Issue("https://github.com/GradleUp/shadow/issues/1606") @Test fun shadowExposedCustomSourceSetOutput() { From 7a915581720e8c11f16732e2a29911638ee29af6 Mon Sep 17 00:00:00 2001 From: Goooler Date: Fri, 27 Feb 2026 20:54:26 +0800 Subject: [PATCH 4/4] Fix formats --- .../gradle/plugins/shadow/JavaPluginsTest.kt | 40 +++++++++++-------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/JavaPluginsTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/JavaPluginsTest.kt index fd833a8d8..98dad41c1 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/JavaPluginsTest.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/JavaPluginsTest.kt @@ -177,8 +177,9 @@ class JavaPluginsTest : BasePluginTest() { settingsScript.appendText("include 'foo', 'consumer'$lineSeparator") projectScript.writeText("") - path("foo/build.gradle").writeText( - """ + path("foo/build.gradle") + .writeText( + """ ${getDefaultProjectBuildScript("java-library")} dependencies { implementation 'my:a:1.0' @@ -193,11 +194,13 @@ class JavaPluginsTest : BasePluginTest() { outgoing.artifact(tasks.named('shadowJar')) } } - """.trimIndent() + lineSeparator, - ) - - path("consumer/build.gradle").writeText( """ + .trimIndent() + lineSeparator + ) + + path("consumer/build.gradle") + .writeText( + """ ${getDefaultProjectBuildScript("java")} dependencies { implementation project(':foo') @@ -207,8 +210,9 @@ class JavaPluginsTest : BasePluginTest() { configurations.runtimeClasspath.files.each { println it.name } } } - """.trimIndent() + lineSeparator, - ) + """ + .trimIndent() + lineSeparator + ) val result = runWithSuccess(":consumer:printClasspathFiles") assertThat(result.output).all { @@ -223,8 +227,9 @@ class JavaPluginsTest : BasePluginTest() { settingsScript.appendText("include 'foo', 'consumer'$lineSeparator") projectScript.writeText("") - path("foo/build.gradle").writeText( - """ + path("foo/build.gradle") + .writeText( + """ ${getDefaultProjectBuildScript("java-library")} dependencies { implementation 'my:a:1.0' @@ -241,11 +246,13 @@ class JavaPluginsTest : BasePluginTest() { exclude(group: 'my', module: 'a') } } - """.trimIndent() + lineSeparator, - ) - - path("consumer/build.gradle").writeText( """ + .trimIndent() + lineSeparator + ) + + path("consumer/build.gradle") + .writeText( + """ ${getDefaultProjectBuildScript("java")} dependencies { implementation project(':foo') @@ -255,8 +262,9 @@ class JavaPluginsTest : BasePluginTest() { configurations.runtimeClasspath.files.each { println it.name } } } - """.trimIndent() + lineSeparator, - ) + """ + .trimIndent() + lineSeparator + ) val result = runWithSuccess(":consumer:printClasspathFiles") assertThat(result.output).all {