From be794bd1d5754957ced08d072fc264e26c196c6c Mon Sep 17 00:00:00 2001 From: Goooler Date: Fri, 15 May 2026 17:54:12 +0800 Subject: [PATCH 1/2] Check DuplicatesStrategy for merging transformers --- docs/changes/README.md | 2 ++ .../ServiceFileTransformerTest.kt | 11 ++++++- .../internal/DuplicatesStrategyChecker.kt | 29 +++++++++++++++++++ .../transformers/AppendingTransformer.kt | 5 +++- .../DeduplicatingResourceTransformer.kt | 7 +++-- .../PatternFilterableResourceTransformer.kt | 3 +- .../PreserveFirstFoundResourceTransformer.kt | 5 +++- .../transformers/PropertiesFileTransformer.kt | 3 +- .../transformers/XmlAppendingTransformer.kt | 5 +++- 9 files changed, 62 insertions(+), 8 deletions(-) create mode 100644 src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/DuplicatesStrategyChecker.kt diff --git a/docs/changes/README.md b/docs/changes/README.md index e2da68391..ebc352beb 100644 --- a/docs/changes/README.md +++ b/docs/changes/README.md @@ -5,6 +5,8 @@ ### Added +- Check `DuplicatesStrategy` for merging transformers. ([#2026](https://github.com/GradleUp/shadow/pull/2026)) + This will log warnings if the wrong `DuplicatesStrategy`s are applied in the Gradle configurations for built-in `ResourceTransformer`s. - Expose `patternSet` of `ComponentsXmlResourceTransformer` as `public`. ([#2028](https://github.com/GradleUp/shadow/pull/2028)) - Expose `patternSet` of `GroovyExtensionModuleTransformer` as `public`. ([#2028](https://github.com/GradleUp/shadow/pull/2028)) - Expose `patternSet` of `Log4j2PluginsCacheFileTransformer` as `public`. ([#2028](https://github.com/GradleUp/shadow/pull/2028)) diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ServiceFileTransformerTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ServiceFileTransformerTest.kt index f1902d590..5b2972d87 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ServiceFileTransformerTest.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ServiceFileTransformerTest.kt @@ -1,6 +1,7 @@ package com.github.jengelman.gradle.plugins.shadow.transformers import assertk.assertThat +import assertk.assertions.contains import assertk.assertions.containsMatch import assertk.assertions.isEqualTo import com.github.jengelman.gradle.plugins.shadow.testkit.getContent @@ -221,7 +222,15 @@ class ServiceFileTransformerTest : BaseTransformerTest() { ) { writeDuplicatesStrategy(strategy) - runWithSuccess(shadowJarPath) + val result = runWithSuccess(shadowJarPath) + + if (strategy == EXCLUDE) { + assertThat(result.output) + .contains( + "META-INF/services/com.acme.Foo' is matched by com.github.jengelman.gradle.plugins.shadow.transformers.ServiceFileTransformer (a merging transformer) but its DuplicatesStrategy is EXCLUDE — duplicates may be silently dropped before merging.", + "META-INF/services/org.apache.maven.Shade' is matched by com.github.jengelman.gradle.plugins.shadow.transformers.ServiceFileTransformer (a merging transformer) but its DuplicatesStrategy is EXCLUDE — duplicates may be silently dropped before merging.", + ) + } assertThat(outputShadowedJar).useAll { getContent(ENTRY_SERVICES_SHADE).isEqualTo(firstValue) diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/DuplicatesStrategyChecker.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/DuplicatesStrategyChecker.kt new file mode 100644 index 000000000..cad251195 --- /dev/null +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/DuplicatesStrategyChecker.kt @@ -0,0 +1,29 @@ +package com.github.jengelman.gradle.plugins.shadow.internal + +import com.github.jengelman.gradle.plugins.shadow.transformers.ResourceTransformer +import org.gradle.api.file.DuplicatesStrategy +import org.gradle.api.file.FileCopyDetails +import org.gradle.api.file.FileTreeElement +import org.gradle.api.logging.Logging + +internal fun ResourceTransformer.checkDupStrategy( + canTransformResource: Boolean, + element: FileTreeElement, +) { + when { + !canTransformResource -> return + element !is FileCopyDetails -> return + element.duplicatesStrategy == DuplicatesStrategy.EXCLUDE -> { + val logger = Logging.getLogger(this::class.java) + logger.warn( + """ + ${element.path}' is matched by ${this::class.qualifiedName} (a merging transformer) but its DuplicatesStrategy is ${element.duplicatesStrategy} — duplicates may be silently dropped before merging. + Set it to INCLUDE or WARN to ensure all duplicates are processed by the transformer. + See https://gradleup.com/shadow/configuration/merging/#handling-duplicates-strategy for more details. + """ + .trimIndent() + ) + } + else -> Unit + } +} diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/AppendingTransformer.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/AppendingTransformer.kt index 7e29c4ceb..ce7b080b3 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/AppendingTransformer.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/AppendingTransformer.kt @@ -1,5 +1,6 @@ package com.github.jengelman.gradle.plugins.shadow.transformers +import com.github.jengelman.gradle.plugins.shadow.internal.checkDupStrategy import com.github.jengelman.gradle.plugins.shadow.internal.property import com.github.jengelman.gradle.plugins.shadow.internal.zipEntry import java.io.ByteArrayOutputStream @@ -32,7 +33,9 @@ constructor(final override val objectFactory: ObjectFactory) : ResourceTransform @get:Input public open val separator: Property = objectFactory.property(DEFAULT_SEPARATOR) override fun canTransformResource(element: FileTreeElement): Boolean { - return resource.get().equals(element.path, ignoreCase = true) + return resource.get().equals(element.path, ignoreCase = true).also { flag -> + checkDupStrategy(flag, element) + } } override fun transform(context: TransformerContext) { diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/DeduplicatingResourceTransformer.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/DeduplicatingResourceTransformer.kt index 61d95e66d..c2cc23985 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/DeduplicatingResourceTransformer.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/DeduplicatingResourceTransformer.kt @@ -1,5 +1,6 @@ package com.github.jengelman.gradle.plugins.shadow.transformers +import com.github.jengelman.gradle.plugins.shadow.internal.checkDupStrategy import com.github.jengelman.gradle.plugins.shadow.tasks.FindResourceInClasspath import java.io.File import javax.inject.Inject @@ -66,8 +67,10 @@ public open class DeduplicatingResourceTransformer( val file = element.file val hash = file.sha256Hex() - val pathInfos = - sources.computeIfAbsent(element.path) { PathInfos(patternSpec.isSatisfiedBy(element)) } + val flag = patternSpec.isSatisfiedBy(element) + checkDupStrategy(flag, element) + + val pathInfos = sources.computeIfAbsent(element.path) { PathInfos(flag) } val retainInOutput = pathInfos.addFile(hash, file) return !retainInOutput diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PatternFilterableResourceTransformer.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PatternFilterableResourceTransformer.kt index 5c943d62e..932d95344 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PatternFilterableResourceTransformer.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PatternFilterableResourceTransformer.kt @@ -1,5 +1,6 @@ package com.github.jengelman.gradle.plugins.shadow.transformers +import com.github.jengelman.gradle.plugins.shadow.internal.checkDupStrategy import com.github.jengelman.gradle.plugins.shadow.internal.unsafeLazy import org.gradle.api.file.FileTreeElement import org.gradle.api.specs.Spec @@ -24,7 +25,7 @@ public abstract class PatternFilterableResourceTransformer( } override fun canTransformResource(element: FileTreeElement): Boolean { - return patternSpec.isSatisfiedBy(element) + return patternSpec.isSatisfiedBy(element).also { flag -> checkDupStrategy(flag, element) } } @Input // Trigger task executions after includes changed. diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PreserveFirstFoundResourceTransformer.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PreserveFirstFoundResourceTransformer.kt index 915f2f360..5c86eefc4 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PreserveFirstFoundResourceTransformer.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PreserveFirstFoundResourceTransformer.kt @@ -1,5 +1,6 @@ package com.github.jengelman.gradle.plugins.shadow.transformers +import com.github.jengelman.gradle.plugins.shadow.internal.checkDupStrategy import com.github.jengelman.gradle.plugins.shadow.internal.setProperty import com.github.jengelman.gradle.plugins.shadow.internal.unsafeLazy import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar @@ -48,6 +49,8 @@ public open class PreserveFirstFoundResourceTransformer( override fun canTransformResource(element: FileTreeElement): Boolean { // Init once before patternSpec is accessed. includeResources - return patternSpec.isSatisfiedBy(element) && !found.add(element.path) + val flag = patternSpec.isSatisfiedBy(element) + checkDupStrategy(flag, element) + return flag && !found.add(element.path) } } diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.kt index 7ce7e950a..20e13f7d4 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.kt @@ -1,6 +1,7 @@ package com.github.jengelman.gradle.plugins.shadow.transformers import com.github.jengelman.gradle.plugins.shadow.internal.ReproducibleProperties +import com.github.jengelman.gradle.plugins.shadow.internal.checkDupStrategy import com.github.jengelman.gradle.plugins.shadow.internal.mapProperty import com.github.jengelman.gradle.plugins.shadow.internal.property import com.github.jengelman.gradle.plugins.shadow.internal.setProperty @@ -137,7 +138,7 @@ constructor(final override val objectFactory: ObjectFactory) : ResourceTransform path in paths -> true paths.any { it.toRegex().containsMatchIn(path) } -> true else -> mappings.isEmpty() && paths.isEmpty() && path.endsWith(PROPERTIES_SUFFIX) - } + }.also { checkDupStrategy(it, element) } } override fun transform(context: TransformerContext) { diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/XmlAppendingTransformer.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/XmlAppendingTransformer.kt index 990bb90f2..be30afba0 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/XmlAppendingTransformer.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/XmlAppendingTransformer.kt @@ -1,5 +1,6 @@ package com.github.jengelman.gradle.plugins.shadow.transformers +import com.github.jengelman.gradle.plugins.shadow.internal.checkDupStrategy import com.github.jengelman.gradle.plugins.shadow.internal.property import com.github.jengelman.gradle.plugins.shadow.internal.zipEntry import java.io.IOException @@ -38,7 +39,9 @@ constructor(final override val objectFactory: ObjectFactory) : ResourceTransform @get:Input public open val resource: Property = objectFactory.property("") override fun canTransformResource(element: FileTreeElement): Boolean { - return resource.get().equals(element.path, ignoreCase = true) + return resource.get().equals(element.path, ignoreCase = true).also { flag -> + checkDupStrategy(flag, element) + } } override fun transform(context: TransformerContext) { From f638ce48ce95feeee921f4aef7e90170c4fcfd14 Mon Sep 17 00:00:00 2001 From: Zongle Wang Date: Tue, 23 Jun 2026 13:15:43 +0800 Subject: [PATCH 2/2] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- docs/changes/README.md | 2 +- .../plugins/shadow/transformers/ServiceFileTransformerTest.kt | 4 ++-- .../plugins/shadow/internal/DuplicatesStrategyChecker.kt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/changes/README.md b/docs/changes/README.md index ebc352beb..7f9d6d88e 100644 --- a/docs/changes/README.md +++ b/docs/changes/README.md @@ -6,7 +6,7 @@ ### Added - Check `DuplicatesStrategy` for merging transformers. ([#2026](https://github.com/GradleUp/shadow/pull/2026)) - This will log warnings if the wrong `DuplicatesStrategy`s are applied in the Gradle configurations for built-in `ResourceTransformer`s. + This will log warnings if the wrong `DuplicatesStrategy` values are applied in the Gradle configurations for built-in `ResourceTransformer`s. - Expose `patternSet` of `ComponentsXmlResourceTransformer` as `public`. ([#2028](https://github.com/GradleUp/shadow/pull/2028)) - Expose `patternSet` of `GroovyExtensionModuleTransformer` as `public`. ([#2028](https://github.com/GradleUp/shadow/pull/2028)) - Expose `patternSet` of `Log4j2PluginsCacheFileTransformer` as `public`. ([#2028](https://github.com/GradleUp/shadow/pull/2028)) diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ServiceFileTransformerTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ServiceFileTransformerTest.kt index 5b2972d87..81be856ea 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ServiceFileTransformerTest.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ServiceFileTransformerTest.kt @@ -227,8 +227,8 @@ class ServiceFileTransformerTest : BaseTransformerTest() { if (strategy == EXCLUDE) { assertThat(result.output) .contains( - "META-INF/services/com.acme.Foo' is matched by com.github.jengelman.gradle.plugins.shadow.transformers.ServiceFileTransformer (a merging transformer) but its DuplicatesStrategy is EXCLUDE — duplicates may be silently dropped before merging.", - "META-INF/services/org.apache.maven.Shade' is matched by com.github.jengelman.gradle.plugins.shadow.transformers.ServiceFileTransformer (a merging transformer) but its DuplicatesStrategy is EXCLUDE — duplicates may be silently dropped before merging.", + "'META-INF/services/com.acme.Foo' is matched by com.github.jengelman.gradle.plugins.shadow.transformers.ServiceFileTransformer but its DuplicatesStrategy is EXCLUDE — duplicates may be silently dropped before the transformer processes them.", + "'META-INF/services/org.apache.maven.Shade' is matched by com.github.jengelman.gradle.plugins.shadow.transformers.ServiceFileTransformer but its DuplicatesStrategy is EXCLUDE — duplicates may be silently dropped before the transformer processes them.", ) } diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/DuplicatesStrategyChecker.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/DuplicatesStrategyChecker.kt index cad251195..8cfda4809 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/DuplicatesStrategyChecker.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/DuplicatesStrategyChecker.kt @@ -17,7 +17,7 @@ internal fun ResourceTransformer.checkDupStrategy( val logger = Logging.getLogger(this::class.java) logger.warn( """ - ${element.path}' is matched by ${this::class.qualifiedName} (a merging transformer) but its DuplicatesStrategy is ${element.duplicatesStrategy} — duplicates may be silently dropped before merging. + '${element.path}' is matched by ${this::class.qualifiedName} but its DuplicatesStrategy is ${element.duplicatesStrategy} — duplicates may be silently dropped before the transformer processes them. Set it to INCLUDE or WARN to ensure all duplicates are processed by the transformer. See https://gradleup.com/shadow/configuration/merging/#handling-duplicates-strategy for more details. """