diff --git a/docs/changes/README.md b/docs/changes/README.md index e6ac914f2..43c08b8d3 100644 --- a/docs/changes/README.md +++ b/docs/changes/README.md @@ -8,6 +8,8 @@ - Change the group of `startShadowScripts` from `application` to `other`. ([#1797](https://github.com/GradleUp/shadow/pull/1797)) - Update ASM and jdependency to support Java 26. ([#1799](https://github.com/GradleUp/shadow/pull/1799)) - Bump min Gradle requirement to 9.0.0. ([#1801](https://github.com/GradleUp/shadow/pull/1801)) +- Fix `ApacheLicenseResourceTransformer` to not remove `META-INF/LICENSE*` files ([#1842](https://github.com/GradleUp/shadow/pull/1842)) +- Configurable match-patterns for `ApacheLicenseResourceTransformer` + `ApacheNoticeResourceTransformer` files ([#1842](https://github.com/GradleUp/shadow/pull/1845)) ## [9.2.2](https://github.com/GradleUp/shadow/compare/9.2.2) - 2025-09-26 diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ApacheLicenseResourceTransformer.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ApacheLicenseResourceTransformer.kt index 71f29c862..b5f2b469a 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ApacheLicenseResourceTransformer.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ApacheLicenseResourceTransformer.kt @@ -1,6 +1,15 @@ package com.github.jengelman.gradle.plugins.shadow.transformers +import com.github.jengelman.gradle.plugins.shadow.internal.property +import com.github.jengelman.gradle.plugins.shadow.internal.zipEntry +import java.nio.charset.StandardCharsets +import javax.inject.Inject +import org.apache.tools.zip.ZipOutputStream import org.gradle.api.file.FileTreeElement +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.Property +import org.gradle.api.provider.SetProperty +import org.gradle.api.tasks.Input /** * Prevents duplicate copies of the license. @@ -10,17 +19,88 @@ import org.gradle.api.file.FileTreeElement * @author John Engelman */ @CacheableTransformer -public open class ApacheLicenseResourceTransformer : ResourceTransformer by ResourceTransformer.Companion { +public open class ApacheLicenseResourceTransformer @Inject constructor( + final override val objectFactory: ObjectFactory, +) : ResourceTransformer by ResourceTransformer.Companion { + + private val elements: MutableSet = LinkedHashSet() + + /** + * Paths to consider as a license files, evaluated using `it.equals(path, ignoreCase = true)`. + * + * Defaults to `META-INF/LICENSE`. + */ + @get:Input + public open val paths: SetProperty = objectFactory.setProperty(String::class.java).value(setOf("META-INF/LICENSE")) + + /** + * Paths to consider as a license files, evaluated using `it.regionMatches(0, path, 0, it.length, ignoreCase = true)`. + * + * Defaults to `META-INF/LICENSE.txt` and `META-INF/LICENSE.md`. + */ + @get:Input + public open val regionMatchPaths: SetProperty = objectFactory.setProperty(String::class.java).value(setOf("META-INF/LICENSE.txt", "META-INF/LICENSE.md")) + + /** + * Path of the resulting output file. + * + * Defaults to `META-INF/LICENSE`. + */ + @get:Input + public open val outputPath: Property = objectFactory.property("META-INF/LICENSE") + + /** + * Whether to include an empty output, if no input file matches. + * + * Defaults to `false`. + */ + @get:Input + public open val writeEmpty: Property = objectFactory.property(false) + + /** + * The file encoding of the `LICENSE` file. + */ + @get:Input + public open val charsetName: Property = objectFactory.property(Charsets.UTF_8.name()) + + /** + * The separator placed between two licenses. + */ + @get:Input + public open val separator: Property = objectFactory.property("\n\n${"-".repeat(120)}\n\n") + override fun canTransformResource(element: FileTreeElement): Boolean { val path = element.path - return LICENSE_PATH.equals(path, ignoreCase = true) || - LICENSE_TXT_PATH.regionMatches(0, path, 0, LICENSE_TXT_PATH.length, ignoreCase = true) || - LICENSE_MD_PATH.regionMatches(0, path, 0, LICENSE_MD_PATH.length, ignoreCase = true) + return paths.get().any { + it.equals(path, ignoreCase = true) + } || regionMatchPaths.get().any { + it.regionMatches(0, path, 0, it.length, ignoreCase = true) + } } - private companion object { - private const val LICENSE_PATH = "META-INF/LICENSE" - private const val LICENSE_TXT_PATH = "META-INF/LICENSE.txt" - private const val LICENSE_MD_PATH = "META-INF/LICENSE.md" + override fun transform(context: TransformerContext) { + val bytes = context.inputStream.readAllBytes() + val content = bytes.toString(StandardCharsets.UTF_8).trim('\n', '\r') + if (!content.isEmpty()) { + elements.add(content) + } + } + + override fun hasTransformedResource(): Boolean = true + + override fun modifyOutputStream(os: ZipOutputStream, preserveFileTimestamps: Boolean) { + if (!elements.isEmpty()) { + os.putNextEntry(zipEntry(outputPath.get(), preserveFileTimestamps)) + var first = true + val separator = separator.get().toByteArray(StandardCharsets.UTF_8) + for (element in elements) { + if (!first) { + os.write(separator) + } + os.write(element.toByteArray(StandardCharsets.UTF_8)) + first = false + } + os.closeEntry() + } } } diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ApacheNoticeResourceTransformer.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ApacheNoticeResourceTransformer.kt index 40109113a..eaf6eacb3 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ApacheNoticeResourceTransformer.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ApacheNoticeResourceTransformer.kt @@ -12,6 +12,7 @@ import org.apache.tools.zip.ZipOutputStream import org.gradle.api.file.FileTreeElement import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.Property +import org.gradle.api.provider.SetProperty import org.gradle.api.tasks.Input /** @@ -34,6 +35,30 @@ public open class ApacheNoticeResourceTransformer @Inject constructor( */ private var fallbackCopyright: String? = null + /** + * Paths to consider as a notice files, evaluated using `it.equals(path, ignoreCase = true)`. + * + * Defaults to `META-INF/NOTICE`. + */ + @get:Input + public open val paths: SetProperty = objectFactory.setProperty(String::class.java).value(setOf("META-INF/NOTICE")) + + /** + * Paths to consider as a notice files, evaluated using `it.regionMatches(0, path, 0, it.length, ignoreCase = true)`. + * + * Defaults to `META-INF/NOTICE.txt` and `META-INF/NOTICE.md`. + */ + @get:Input + public open val regionMatchPaths: SetProperty = objectFactory.setProperty(String::class.java).value(setOf("META-INF/NOTICE.txt", "META-INF/NOTICE.md")) + + /** + * Path of the resulting output file. + * + * Defaults to `META-INF/NOTICE`. + */ + @get:Input + public open val outputPath: Property = objectFactory.property("META-INF/NOTICE") + @get:Input public open val projectName: Property = objectFactory.property("") @@ -77,9 +102,11 @@ public open class ApacheNoticeResourceTransformer @Inject constructor( override fun canTransformResource(element: FileTreeElement): Boolean { val path = element.path - return NOTICE_PATH.equals(path, ignoreCase = true) || - NOTICE_TXT_PATH.equals(path, ignoreCase = true) || - NOTICE_MD_PATH.equals(path, ignoreCase = true) + return paths.get().any { + it.equals(path, ignoreCase = true) + } || regionMatchPaths.get().any { + it.regionMatches(0, path, 0, it.length, ignoreCase = true) + } } override fun transform(context: TransformerContext) { @@ -184,16 +211,10 @@ public open class ApacheNoticeResourceTransformer @Inject constructor( } } - os.putNextEntry(zipEntry(NOTICE_PATH, preserveFileTimestamps)) + os.putNextEntry(zipEntry(outputPath.get(), preserveFileTimestamps)) os.write(sb.toString().trim().toByteArray(charset)) os.closeEntry() entries.clear() } - - private companion object { - private const val NOTICE_PATH = "META-INF/NOTICE" - private const val NOTICE_TXT_PATH = "META-INF/NOTICE.txt" - private const val NOTICE_MD_PATH = "META-INF/NOTICE.md" - } } diff --git a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ApacheLicenseResourceTransformerTest.kt b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ApacheLicenseResourceTransformerTest.kt index 556e9c6bc..f6928eb9d 100644 --- a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ApacheLicenseResourceTransformerTest.kt +++ b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ApacheLicenseResourceTransformerTest.kt @@ -1,8 +1,13 @@ package com.github.jengelman.gradle.plugins.shadow.transformers import assertk.assertThat +import assertk.assertions.isEqualTo import assertk.assertions.isFalse import assertk.assertions.isTrue +import java.io.ByteArrayOutputStream +import java.nio.charset.Charset +import java.util.zip.ZipInputStream +import org.apache.tools.zip.ZipOutputStream import org.junit.jupiter.api.Test /** @@ -23,4 +28,34 @@ class ApacheLicenseResourceTransformerTest : BaseTransformerTest