Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/changes/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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<String> = 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<String> = 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<String> = 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<String> = 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<Boolean> = objectFactory.property(false)

/**
* The file encoding of the `LICENSE` file.
*/
@get:Input
public open val charsetName: Property<String> = objectFactory.property(Charsets.UTF_8.name())

/**
* The separator placed between two licenses.
*/
@get:Input
public open val separator: Property<String> = 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()
}
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handled by #1850.

Original file line number Diff line number Diff line change
Expand Up @@ -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

/**
Expand All @@ -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<String> = 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<String> = 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<String> = objectFactory.property("META-INF/NOTICE")
Comment on lines +54 to +60
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be a separate PR.


@get:Input
public open val projectName: Property<String> = objectFactory.property("")

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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"
}
}
Original file line number Diff line number Diff line change
@@ -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

/**
Expand All @@ -23,4 +28,34 @@ class ApacheLicenseResourceTransformerTest : BaseTransformerTest<ApacheLicenseRe
assertThat(transformer.canTransformResource("META-INF/License.md")).isTrue()
assertThat(transformer.canTransformResource("META-INF/MANIFEST.MF")).isFalse()
}

@Test
fun licensesAreIncluded() {
val baos = ByteArrayOutputStream()
val zos = ZipOutputStream(baos)

transformer.separator.set("\n---\n")
transformer.transform(TransformerContext("META-INF/LICENSE", "License 1".byteInputStream()))
transformer.transform(TransformerContext("META-INF/LICENSE.txt", "License 2".byteInputStream()))
transformer.transform(TransformerContext("META-INF/LICENSE.md", "License 3".byteInputStream()))
transformer.transform(TransformerContext("META-INF/LICENSE.txt", "License 1".byteInputStream()))
transformer.transform(TransformerContext("META-INF/LICENSE.txt", "\n\r\nLicense 2".byteInputStream()))
transformer.transform(TransformerContext("META-INF/LICENSE.txt", "\n\r\nLicense 2\n\r\n".byteInputStream()))
transformer.modifyOutputStream(zos, false)
zos.close()

val zis = ZipInputStream(baos.toByteArray().inputStream())
zis.nextEntry
val output = zis.readAllBytes().toString(Charset.forName(transformer.charsetName.get()))

assertThat(output).isEqualTo(
"""
License 1
---
License 2
---
License 3
""".trimIndent(),
)
}
}