Skip to content
113 changes: 106 additions & 7 deletions docs/configuration/merging/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,20 +77,117 @@ Different strategies will lead to different results for `foo/bar` files in the J
```

The [`ResourceTransformer`][ResourceTransformer]s like [`ServiceFileTransformer`][ServiceFileTransformer] will not work
as expected as the duplicate resource files fed for them are excluded beforehand. However, this behavior might be what you expected for duplicate `foo/bar` files, preventing them from being included.
as expected as the duplicate resource files fed for them are excluded beforehand. However, this behavior might be what
you expected for duplicate `foo/bar` files, preventing them from being included.

Want [`ResourceTransformer`][ResourceTransformer]s and `duplicatesStrategy` to work together? There are several steps
to take:
Want [`ResourceTransformer`][ResourceTransformer]s and `duplicatesStrategy` to work together? There are several common
steps to take:

1. Set the strategy to `INCLUDE` or `WARN`.
1. Set the default strategy to `INCLUDE` or `WARN`.
2. Apply your [`ResourceTransformer`][ResourceTransformer]s.
3. Remove duplicate entries by
- overriding the default strategy for specific files using [`filesMatching`][Jar.filesMatching]
- overriding the default strategy for specific files to `EXCLUDE` or `FAIL` using
[`filesMatching`][Jar.filesMatching], [`filesNotMatching`][Jar.filesNotMatching], or [`eachFile`][Jar.eachFile] functions
- or applying [`PreserveFirstFoundResourceTransformer`][PreserveFirstFoundResourceTransformer] for specific files
- or write your own [`ResourceTransformer`][ResourceTransformer] to handle duplicates
- or mechanism similar.
4. Optionally, enable [`ShadowJar.failOnDuplicateEntries`][ShadowJar.failOnDuplicateEntries] to check duplicate entries in the final JAR.
5. Optionally, use [Diffuse](https://github.com/JakeWharton/diffuse) to diff the JARs.

Alternatively, you can follow these steps:

1. Set the default strategy to `EXCLUDE` or `FAIL`.
2. Apply your [`ResourceTransformer`][ResourceTransformer]s.
3. Bypass the duplicate entries which should be handled by the [`ResourceTransformer`][ResourceTransformer]s using
[`filesMatching`][Jar.filesMatching], [`filesNotMatching`][Jar.filesNotMatching], or [`eachFile`][Jar.eachFile] functions
to set their `duplicatesStrategy` to `INCLUDE` or `WARN`.

Optional steps:

- Enable [`ShadowJar.failOnDuplicateEntries`][ShadowJar.failOnDuplicateEntries] to check duplicate entries in the final JAR.
- Use [Diffuse](https://github.com/JakeWharton/diffuse) to diff the JARs.

Here are some examples:

=== "Kotlin"

```kotlin
tasks.shadowJar {
// Step 1.
duplicatesStrategy = DuplicatesStrategy.INCLUDE // Or WARN.
// Step 2.
mergeServiceFiles()
// Step 3. Using `filesNotMatching`:
filesNotMatching("META-INF/services/**") {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE // Or FAIL.
}
// Step 3. Using `PreserveFirstFoundResourceTransformer`:
transform<com.github.jengelman.gradle.plugins.shadow.transformers.PreserveFirstFoundResourceTransformer>() {
resources.add("META-INF/foo/**") // Or something else where the first occurrence should be preserved.
}
}

tasks.shadowJar {
// Step 1.
duplicatesStrategy = DuplicatesStrategy.EXCLUDE // Or FAIL.
// Step 2.
mergeServiceFiles()
// Step 3. Using `filesMatching`:
filesMatching("META-INF/services/**") {
duplicatesStrategy = DuplicatesStrategy.INCLUDE // Or WARN.
}
// Step 3. Using `eachFile`:
eachFile {
if (path.startsWith("META-INF/services/")) {
duplicatesStrategy = DuplicatesStrategy.INCLUDE // Or WARN.
}
}
}

tasks.shadowJar {
// Optional step.
failOnDuplicateEntries = true
}
```

=== "Groovy"

```groovy
tasks.named('shadowJar', com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) {
// Step 1.
duplicatesStrategy = DuplicatesStrategy.INCLUDE // Or WARN.
// Step 2.
mergeServiceFiles()
// Step 3. Using `filesNotMatching`:
filesNotMatching('META-INF/services/**') {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE // Or FAIL.
}
// Step 3. Using `PreserveFirstFoundResourceTransformer`:
transform(com.github.jengelman.gradle.plugins.shadow.transformers.PreserveFirstFoundResourceTransformer) {
resources.add('META-INF/foo/**') // Or something else where the first occurrence should be preserved.
}
}

tasks.named('shadowJar', com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) {
// Step 1.
duplicatesStrategy = DuplicatesStrategy.EXCLUDE // Or FAIL.
// Step 2.
mergeServiceFiles()
// Step 3. Using `filesMatching`:
filesMatching('META-INF/services/**') {
duplicatesStrategy = DuplicatesStrategy.INCLUDE // Or WARN.
}
// Step 3. Using `eachFile`:
eachFile {
if (it.path.startsWith('META-INF/services/')) {
it.duplicatesStrategy = DuplicatesStrategy.INCLUDE // Or WARN.
}
}
}

tasks.named('shadowJar', com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) {
// Optional step.
failOnDuplicateEntries = true
}
```

## Basic ResourceTransformer Usage

Expand Down Expand Up @@ -451,7 +548,9 @@ It must be added using the [`transform`][ShadowJar.transform] methods.


[AbstractCopyTask]: https://docs.gradle.org/current/dsl/org.gradle.api.tasks.AbstractCopyTask.html
[Jar.eachFile]: https://docs.gradle.org/current/dsl/org.gradle.jvm.tasks.Jar.html#org.gradle.jvm.tasks.Jar:eachFile(org.gradle.api.Action)
[Jar.filesMatching]: https://docs.gradle.org/current/dsl/org.gradle.jvm.tasks.Jar.html#org.gradle.jvm.tasks.Jar:filesMatching(java.lang.Iterable,%20org.gradle.api.Action)
[Jar.filesNotMatching]: https://docs.gradle.org/current/dsl/org.gradle.jvm.tasks.Jar.html#org.gradle.jvm.tasks.Jar:filesNotMatching(java.lang.Iterable,%20org.gradle.api.Action)
[AppendingTransformer]: ../../api/shadow/com.github.jengelman.gradle.plugins.shadow.transformers/-appending-transformer/index.html
[DuplicatesStrategy]: https://docs.gradle.org/current/javadoc/org/gradle/api/file/DuplicatesStrategy.html
[GroovyExtensionModuleTransformer]: ../../api/shadow/com.github.jengelman.gradle.plugins.shadow.transformers/-groovy-extension-module-transformer/index.html
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ import com.github.jengelman.gradle.plugins.shadow.util.getContent
import kotlin.io.path.appendText
import kotlin.io.path.writeText
import org.gradle.api.file.DuplicatesStrategy
import org.gradle.api.file.DuplicatesStrategy.EXCLUDE
import org.gradle.api.file.DuplicatesStrategy.FAIL
import org.gradle.api.file.DuplicatesStrategy.INCLUDE
import org.gradle.api.file.DuplicatesStrategy.INHERIT
import org.gradle.api.file.DuplicatesStrategy.WARN
import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments
Expand Down Expand Up @@ -221,8 +226,8 @@ class ServiceFileTransformerTest : BaseTransformerTest() {
}

@Test
fun strategyExcludeCanBeOverriddenByFilesMatching() {
writeDuplicatesStrategy(DuplicatesStrategy.EXCLUDE)
fun strategyCanBeOverriddenByFilesMatching() {
writeDuplicatesStrategy(EXCLUDE)
projectScript.appendText(
"""
$shadowJarTask {
Expand All @@ -242,8 +247,8 @@ class ServiceFileTransformerTest : BaseTransformerTest() {
}

@Test
fun strategyIncludeCanBeOverriddenByFilesNotMatching() {
writeDuplicatesStrategy(DuplicatesStrategy.INCLUDE)
fun strategyCanBeOverriddenByFilesNotMatching() {
writeDuplicatesStrategy(INCLUDE)
projectScript.appendText(
"""
$shadowJarTask {
Expand All @@ -262,6 +267,34 @@ class ServiceFileTransformerTest : BaseTransformerTest() {
}
}

@ParameterizedTest
@MethodSource("eachFileStrategyProvider")
fun strategyCanBeOverriddenByEachFile(
default: DuplicatesStrategy,
override: DuplicatesStrategy,
matchPath: String,
) {
writeDuplicatesStrategy(default)
projectScript.appendText(
"""
$shadowJarTask {
eachFile {
if (path == '$matchPath') {
duplicatesStrategy = DuplicatesStrategy.$override
}
}
}
""".trimIndent(),
)

run(shadowJarPath)

assertThat(outputShadowedJar).useAll {
getContent(ENTRY_SERVICES_SHADE).isEqualTo(CONTENT_ONE_TWO)
getContent(ENTRY_SERVICES_FOO).isEqualTo("one")
}
}

private fun writeDuplicatesStrategy(strategy: DuplicatesStrategy) {
projectScript.appendText(
"""
Expand All @@ -279,21 +312,21 @@ class ServiceFileTransformerTest : BaseTransformerTest() {
private companion object {
@JvmStatic
fun withThrowingProvider() = listOf(
Arguments.of(
DuplicatesStrategy.FAIL,
"Cannot copy zip entry .* to .* because zip entry .* has already been copied there",
),
Arguments.of(
DuplicatesStrategy.INHERIT,
"Entry .* is a duplicate but no duplicate handling strategy has been set",
),
Arguments.of(FAIL, "Cannot copy zip entry .* to .* because zip entry .* has already been copied there"),
Arguments.of(INHERIT, "Entry .* is a duplicate but no duplicate handling strategy has been set"),
)

@JvmStatic
fun withoutThrowingProvider() = listOf(
Arguments.of(DuplicatesStrategy.EXCLUDE, CONTENT_ONE, "one"),
Arguments.of(DuplicatesStrategy.INCLUDE, CONTENT_ONE_TWO, "one\ntwo"),
Arguments.of(DuplicatesStrategy.WARN, CONTENT_ONE_TWO, "one\ntwo"),
Arguments.of(EXCLUDE, CONTENT_ONE, "one"),
Arguments.of(INCLUDE, CONTENT_ONE_TWO, "one\ntwo"),
Arguments.of(WARN, CONTENT_ONE_TWO, "one\ntwo"),
)

@JvmStatic
fun eachFileStrategyProvider() = listOf(
Arguments.of(EXCLUDE, INCLUDE, ENTRY_SERVICES_SHADE),
Arguments.of(INCLUDE, EXCLUDE, ENTRY_SERVICES_FOO),
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -219,19 +219,12 @@ public abstract class ShadowJar : Jar() {
* **NOTE:** The strategy takes precedence over transforming and relocating.
* Some [ResourceTransformer]s like [ServiceFileTransformer] will not work as expected with setting the strategy to
* [EXCLUDE] (the default), as the duplicate resource files fed for them are excluded beforehand.
* Want [ResourceTransformer]s and the strategy to work together? There are several steps to take:
*
* 1. Set the strategy to [INCLUDE] or [WARN].
* 2. Apply your [ResourceTransformer]s.
* 3. Remove duplicate entries by
* - overriding the default strategy for specific files using [filesMatching]
* - or applying `PreserveFirstFoundResourceTransformer` for specific files
* - or write your own `ResourceTransformer`s to handle duplicates
* - or mechanism similar.
* 4. Optionally, enable [failOnDuplicateEntries] to check duplicate entries in the final JAR.
* 5. Optionally, use [Diffuse](https://github.com/JakeWharton/diffuse) to diff the JARs.
* Want [ResourceTransformer]s and the strategy to work together? See more details in the
* [Handling Duplicates Strategy](https://gradleup.com/shadow/configuration/merging/#handling-duplicates-strategy) section.
*
* @see [eachFile]
* @see [filesMatching]
* @see [filesNotMatching]
* @see [DuplicatesStrategy]
* @see [CopySpec.duplicatesStrategy]
*/
Expand Down