From c358d28c89c8706278f5ee412a9c39c9a43970c9 Mon Sep 17 00:00:00 2001 From: Goooler Date: Sun, 25 Oct 2026 19:08:34 +0800 Subject: [PATCH] Document that excluded dependencies are not added to shadow configuration Explain why dependencies excluded by the ShadowJar dependency filter are not exposed to consumers, and how to declare them in the `shadow` configuration explicitly. --- docs/configuration/dependencies/README.md | 41 +++++++++++++++++++ docs/publishing/README.md | 4 +- .../gradle/plugins/shadow/PublishingTest.kt | 29 +++++++++++++ 3 files changed, 73 insertions(+), 1 deletion(-) diff --git a/docs/configuration/dependencies/README.md b/docs/configuration/dependencies/README.md index 6ff120ecf..60e584562 100644 --- a/docs/configuration/dependencies/README.md +++ b/docs/configuration/dependencies/README.md @@ -483,6 +483,46 @@ block provides a method that accepts a `Closure` for selecting dependencies. } ``` +### Declaring Excluded Dependencies for Consumers + +Dependency filtering only controls the content of the shadowed JAR. Excluded dependencies are dropped entirely, they are +**not** automatically added to the [`shadow` configuration][shadow-configuration], so they won't appear in the +published POM file, Gradle Module Metadata, or the `Class-Path` attribute of the manifest. + +This is intentional. A dependency is often excluded because the target environment already provides it (e.g. the +Gradle API for a Gradle plugin), so exposing it to consumers would be wrong. Moreover, the filter only sees resolved +dependencies, so declared versions, scopes, and exclusions can't be restored from them. + +If some dependencies should not be bundled but still be required at runtime, declare them in the `shadow` configuration +explicitly. For example, to bundle `log4j-core` only, while keeping its transitive dependency `log4j-api` as a runtime +dependency: + +=== ":material-language-kotlin: build.gradle.kts" + + ```kotlin + dependencies { + // Bundled into the shadowed JAR without its transitive dependencies. + implementation("org.apache.logging.log4j:log4j-core:2.11.1") { + isTransitive = false + } + // Not bundled, but declared as a runtime dependency of the shadowed JAR. + shadow("org.apache.logging.log4j:log4j-api:2.11.1") + } + ``` + +=== ":simple-apachegroovy: build.gradle" + + ```groovy + dependencies { + // Bundled into the shadowed JAR without its transitive dependencies. + implementation('org.apache.logging.log4j:log4j-core:2.11.1') { + transitive = false + } + // Not bundled, but declared as a runtime dependency of the shadowed JAR. + shadow 'org.apache.logging.log4j:log4j-api:2.11.1' + } + ``` + [Jar.from]: https://docs.gradle.org/current/dsl/org.gradle.jvm.tasks.Jar.html#org.gradle.jvm.tasks.Jar:from(java.lang.Object,%20org.gradle.api.Action) [Jar]: https://docs.gradle.org/current/dsl/org.gradle.api.tasks.bundling.Jar.html @@ -495,3 +535,4 @@ block provides a method that accepts a `Closure` for selecting dependencies. [pom-files]: https://repo1.maven.org/maven2/org/graalvm/polyglot/js-community/24.2.2/ [so-files]: https://repo1.maven.org/maven2/io/github/ganadist/sqlite4java/libsqlite4java-osx-aarch64/1.0.392/ [minimizing]: ../minimizing/README.md +[shadow-configuration]: ../../publishing/README.md#shadow-configuration-and-publishing diff --git a/docs/publishing/README.md b/docs/publishing/README.md index e6f91aba1..cc756a35f 100644 --- a/docs/publishing/README.md +++ b/docs/publishing/README.md @@ -155,7 +155,8 @@ The Shadow plugin provides a custom configuration (`configurations.shadow`) to s `shadow` configuration, are translated to become `RUNTIME` scoped dependencies of the published artifact. No other dependencies are automatically configured for inclusion in the POM file. For example, excluded dependencies are -**not** automatically added to the POM file or if the configuration for merging are modified by specifying +**not** automatically added to the POM file (see [Declaring Excluded Dependencies for Consumers][excluded-dependencies]) +or if the configuration for merging are modified by specifying `shadowJar.configurations = [configurations.myConfiguration]`, there is no automatic configuration of the POM file. This automatic configuration occurs _only_ when using the above methods for configuring publishing. If this behavior is @@ -1000,3 +1001,4 @@ If using [Dokka][dokka] for Kotlin projects, you can extract the shadowed source [gradle-plugin-publish-docs]: https://docs.gradle.org/current/userguide/publishing_gradle_plugins.html#shadow_dependencies [configuring-output-name]: ../configuration/README.md#configuring-output-name [dokka]: https://kotlinlang.org/docs/dokka-introduction.html +[excluded-dependencies]: ../configuration/dependencies/README.md#declaring-excluded-dependencies-for-consumers diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt index 4298ae2e4..bd7c5a002 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt @@ -277,6 +277,35 @@ class PublishingTest : BasePluginTest() { assertShadowVariantCommon("$artifactRoot/maven-1.0.module") } + @Test // #265 + fun publishBundledDependencyWithTransitiveDependencyInShadowConfiguration() { + projectScript.appendText( + publishConfiguration( + dependenciesBlock = + """ + |implementation('my:d:1.0') { + | transitive = false + |} + |shadow 'my:c:1.0' + """ + .trimMargin(), + ), + ) + + publish() + + val artifactRoot = "my/maven-all/1.0" + assertThat(repoJarPath("$artifactRoot/maven-all-1.0-all.jar")).useAll { + containsOnly("d.properties", "META-INF/", "META-INF/MANIFEST.MF") + getMainAttr(classPathAttributeKey).isEqualTo("c-1.0.jar") + } + assertPomCommon("$artifactRoot/maven-all-1.0.pom", coordinates = arrayOf("my:c:1.0")) + assertShadowVariantCommon( + "$artifactRoot/maven-all-1.0.module", + coordinates = arrayOf("my:c:1.0"), + ) + } + @Test fun publishShadowJarWithSourcesWhenWithSourcesJarEnabled() { projectScript.appendText(