Skip to content

Commit 961a3c3

Browse files
committed
Merge remote-tracking branch 'origin/main' into g/20260115/JEP-484
# Conflicts: # src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt # src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowCopyAction.kt
2 parents 4c3bd6a + 6e3dfb5 commit 961a3c3

5 files changed

Lines changed: 126 additions & 131 deletions

File tree

docs/changes/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@
1616
addShadowJarToAssembleLifecycle = false
1717
}
1818
```
19+
- Stop catching `ZipException` when writing entries. ([#1970](https://github.com/GradleUp/shadow/pull/1970))
1920

2021
### Fixed
2122

2223
- Fix interaction with Gradle artifact transforms. ([#1345](https://github.com/GradleUp/shadow/pull/1345))
24+
- Fix `skipStringConstants` per-relocator behavior in `mapName`. ([#1968](https://github.com/GradleUp/shadow/pull/1968))
2325

2426
## [9.3.2](https://github.com/GradleUp/shadow/releases/tag/9.3.2) - 2026-02-27
2527

Lines changed: 33 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -1,108 +1,28 @@
11
package com.github.jengelman.gradle.plugins.shadow.internal
22

33
import com.github.jengelman.gradle.plugins.shadow.relocation.Relocator
4-
import com.github.jengelman.gradle.plugins.shadow.relocation.relocateClass
5-
import com.github.jengelman.gradle.plugins.shadow.relocation.relocatePath
6-
import java.util.regex.Pattern
7-
import java.util.zip.ZipException
8-
import org.apache.tools.zip.UnixStat
9-
import org.apache.tools.zip.ZipOutputStream
104
import org.gradle.api.GradleException
115
import org.gradle.api.file.FileCopyDetails
12-
import org.gradle.api.logging.Logger
136
import org.vafer.jdeb.shaded.objectweb.asm.ClassReader
147
import org.vafer.jdeb.shaded.objectweb.asm.ClassWriter
158
import org.vafer.jdeb.shaded.objectweb.asm.Opcodes
169
import org.vafer.jdeb.shaded.objectweb.asm.commons.ClassRemapper
1710
import org.vafer.jdeb.shaded.objectweb.asm.commons.Remapper
1811

1912
/**
20-
* Modified from
21-
* [org.apache.maven.plugins.shade.DefaultShader.RelocatorRemapper](https://github.com/apache/maven-shade-plugin/blob/83c123d1f9c5f6927af2aca12ee322b5168a7c63/src/main/java/org/apache/maven/plugins/shade/DefaultShader.java#L689-L772).
22-
* Modified from
23-
* [org.apache.maven.plugins.shade.DefaultShader.DefaultPackageMapper](https://github.com/apache/maven-shade-plugin/blob/199ffaecd26a912527173ed4edae366e48a00998/src/main/java/org/apache/maven/plugins/shade/DefaultShader.java#L737-L774).
24-
*
25-
* @author John Engelman
26-
*/
27-
internal class RelocatorRemapper(
28-
private val relocators: Set<Relocator>,
29-
private val onModified: () -> Unit = {},
30-
) : Remapper(Opcodes.ASM9) {
31-
32-
override fun mapValue(value: Any): Any {
33-
return if (value is String) {
34-
mapName(value, mapLiterals = true)
35-
} else {
36-
super.mapValue(value)
37-
}
38-
}
39-
40-
override fun map(internalName: String): String = mapName(internalName)
41-
42-
private fun mapName(name: String, mapLiterals: Boolean = false): String {
43-
// Maybe a list of types.
44-
val newName = name.split(';').joinToString(";") { mapNameImpl(it, mapLiterals) }
45-
46-
if (newName != name) {
47-
onModified()
48-
}
49-
return newName
50-
}
51-
52-
private fun mapNameImpl(name: String, mapLiterals: Boolean): String {
53-
var newName = name
54-
var prefix = ""
55-
var suffix = ""
56-
57-
val matcher = classPattern.matcher(newName)
58-
if (matcher.matches()) {
59-
prefix = matcher.group(1) + "L"
60-
suffix = ""
61-
newName = matcher.group(2)
62-
}
63-
64-
for (relocator in relocators) {
65-
if (mapLiterals && relocator.skipStringConstants) {
66-
return name
67-
} else if (relocator.canRelocateClass(newName)) {
68-
return prefix + relocator.relocateClass(newName) + suffix
69-
} else if (relocator.canRelocatePath(newName)) {
70-
return prefix + relocator.relocatePath(newName) + suffix
71-
}
72-
}
73-
74-
return name
75-
}
76-
77-
private companion object {
78-
/** https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html */
79-
val classPattern: Pattern = Pattern.compile("([\\[()BCDFIJSZ]*)?L([^;]+);?")
80-
}
81-
}
82-
83-
/**
84-
* Applies remapping to the given class with the specified relocation path. The remapped class is
85-
* then written to the zip file.
13+
* Applies remapping to the given class file using the provided relocators and returns the
14+
* (possibly) remapped class bytes. If no remapping is required, the original bytes are returned.
8615
*/
87-
internal fun FileCopyDetails.remapClass(
88-
relocators: Set<Relocator>,
89-
zipOutStr: ZipOutputStream,
90-
preserveFileTimestamps: Boolean,
91-
lastModified: Long,
92-
logger: Logger,
93-
) =
16+
internal fun FileCopyDetails.remapClass(relocators: Set<Relocator>): ByteArray =
9417
file.readBytes().let { bytes ->
9518
var modified = false
9619
val remapper = RelocatorRemapper(relocators) { modified = true }
9720

98-
// We don't pass the ClassReader here. This forces the ClassWriter to rebuild the constant
99-
// pool.
100-
// Copying the original constant pool should be avoided because it would keep references
101-
// to the original class names. This is not a problem at runtime (because these entries in
102-
// the
103-
// constant pool are never used), but confuses some tools such as Felix's
104-
// maven-bundle-plugin
105-
// that use the constant pool to determine the dependencies of a class.
21+
// We don't pass the ClassReader here. This forces the ClassWriter to rebuild the constant pool.
22+
// Copying the original constant pool should be avoided because it would keep references to the
23+
// original class names. This is not a problem at runtime (because these entries in the constant
24+
// pool are never used), but confuses some tools such as Felix's maven-bundle-plugin that use
25+
// the constant pool to determine the dependencies of a class.
10626
val cw = ClassWriter(0)
10727
val cr = ClassReader(bytes)
10828
val cv = ClassRemapper(cw, remapper)
@@ -113,28 +33,31 @@ internal fun FileCopyDetails.remapClass(
11333
throw GradleException("Error in ASM processing class $path", t)
11434
}
11535

116-
val newBytes =
117-
if (modified) {
118-
cw.toByteArray()
119-
} else {
120-
// If we didn't need to change anything, keep the original bytes as-is
121-
bytes
122-
}
36+
// If we didn't need to change anything, keep the original bytes as-is.
37+
if (modified) cw.toByteArray() else bytes
38+
}
12339

124-
// Temporarily remove the multi-release prefix.
125-
val multiReleasePrefix = "^META-INF/versions/\\d+/".toRegex().find(path)?.value.orEmpty()
126-
val newPath = path.replace(multiReleasePrefix, "")
127-
val relocatedPath = multiReleasePrefix + relocators.relocatePath(newPath)
128-
try {
129-
val entry =
130-
zipEntry(relocatedPath, preserveFileTimestamps, lastModified) {
131-
unixMode = UnixStat.FILE_FLAG or permissions.toUnixNumeric()
132-
}
133-
// Now we put it back on so the class file is written out with the right extension.
134-
zipOutStr.putNextEntry(entry)
135-
zipOutStr.write(newBytes)
136-
zipOutStr.closeEntry()
137-
} catch (_: ZipException) {
138-
logger.warn("We have a duplicate $relocatedPath in source project")
40+
/**
41+
* Modified from
42+
* [org.apache.maven.plugins.shade.DefaultShader.RelocatorRemapper](https://github.com/apache/maven-shade-plugin/blob/83c123d1f9c5f6927af2aca12ee322b5168a7c63/src/main/java/org/apache/maven/plugins/shade/DefaultShader.java#L689-L772).
43+
* Modified from
44+
* [org.apache.maven.plugins.shade.DefaultShader.DefaultPackageMapper](https://github.com/apache/maven-shade-plugin/blob/199ffaecd26a912527173ed4edae366e48a00998/src/main/java/org/apache/maven/plugins/shade/DefaultShader.java#L737-L774).
45+
*
46+
* @author John Engelman
47+
*/
48+
private class RelocatorRemapper(
49+
private val relocators: Set<Relocator>,
50+
private val onModified: () -> Unit,
51+
) : Remapper(Opcodes.ASM9) {
52+
53+
override fun mapValue(value: Any): Any {
54+
return if (value is String) {
55+
relocators.mapName(name = value, mapLiterals = true, onModified = onModified)
56+
} else {
57+
super.mapValue(value)
13958
}
14059
}
60+
61+
override fun map(internalName: String): String =
62+
relocators.mapName(name = internalName, onModified = onModified)
63+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package com.github.jengelman.gradle.plugins.shadow.internal
2+
3+
import com.github.jengelman.gradle.plugins.shadow.relocation.Relocator
4+
import com.github.jengelman.gradle.plugins.shadow.relocation.relocateClass
5+
import com.github.jengelman.gradle.plugins.shadow.relocation.relocatePath
6+
import java.util.regex.Pattern
7+
8+
/** https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html */
9+
private val classPattern: Pattern = Pattern.compile("([\\[()BCDFIJSZ]*)?L([^;]+);?")
10+
11+
internal fun Set<Relocator>.mapName(
12+
name: String,
13+
mapLiterals: Boolean = false,
14+
onModified: () -> Unit = {},
15+
): String {
16+
// Maybe a list of types.
17+
val newName = name.split(';').joinToString(";") { realMap(it, mapLiterals) }
18+
if (newName != name) {
19+
onModified()
20+
}
21+
return newName
22+
}
23+
24+
private fun Set<Relocator>.realMap(name: String, mapLiterals: Boolean): String {
25+
var newName = name
26+
var prefix = ""
27+
var suffix = ""
28+
29+
val matcher = classPattern.matcher(newName)
30+
if (matcher.matches()) {
31+
prefix = matcher.group(1) + "L"
32+
suffix = ""
33+
newName = matcher.group(2)
34+
}
35+
36+
for (relocator in this) {
37+
if (mapLiterals && relocator.skipStringConstants) {
38+
continue
39+
} else if (relocator.canRelocateClass(newName)) {
40+
return prefix + relocator.relocateClass(newName) + suffix
41+
} else if (relocator.canRelocatePath(newName)) {
42+
return prefix + relocator.relocatePath(newName) + suffix
43+
}
44+
}
45+
46+
return name
47+
}

src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowCopyAction.kt

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -174,13 +174,13 @@ constructor(
174174
if (relocators.isEmpty()) {
175175
fileDetails.writeToZip(path)
176176
} else {
177-
fileDetails.remapClass(
178-
relocators,
179-
zipOutStr,
180-
preserveFileTimestamps,
181-
fileDetails.lastModified,
182-
logger,
183-
)
177+
with(fileDetails) {
178+
// Temporarily remove the multi-release prefix.
179+
val multiReleasePrefix = multiReleaseRegex.find(path)?.value.orEmpty()
180+
val pathSuffix = path.removePrefix(multiReleasePrefix)
181+
val relocatedPath = multiReleasePrefix + relocators.relocatePath(pathSuffix)
182+
writeToZip(entryName = relocatedPath, bytes = remapClass(relocators = relocators))
183+
}
184184
}
185185
}
186186
enableKotlinModuleRemapping && path.endsWith(".kotlin_module") -> {
@@ -247,13 +247,7 @@ constructor(
247247
// matter to the compiler.
248248
else -> path.replace(".kotlin_module", ".shadow.kotlin_module")
249249
}
250-
val entry =
251-
zipEntry(entryName, preserveFileTimestamps, lastModified) {
252-
unixMode = UnixStat.FILE_FLAG or permissions.toUnixNumeric()
253-
}
254-
zipOutStr.putNextEntry(entry)
255-
zipOutStr.write(newBytes)
256-
zipOutStr.closeEntry()
250+
writeToZip(entryName = entryName, bytes = newBytes)
257251
}
258252

259253
private fun transform(fileDetails: FileCopyDetails, path: String): Boolean {
@@ -266,19 +260,24 @@ constructor(
266260
return true
267261
}
268262

269-
private fun FileCopyDetails.writeToZip(entryName: String) {
263+
private fun FileCopyDetails.writeToZip(entryName: String, bytes: ByteArray? = null) {
270264
val entry =
271265
zipEntry(entryName, preserveFileTimestamps, lastModified) {
272266
unixMode = UnixStat.FILE_FLAG or permissions.toUnixNumeric()
273267
}
274268
zipOutStr.putNextEntry(entry)
275-
copyTo(zipOutStr)
269+
if (bytes == null) {
270+
copyTo(zipOutStr)
271+
} else {
272+
zipOutStr.write(bytes)
273+
}
276274
zipOutStr.closeEntry()
277275
}
278276
}
279277

280278
public companion object {
281279
private val logger = Logging.getLogger(ShadowCopyAction::class.java)
280+
private val multiReleaseRegex = "^META-INF/versions/\\d+/".toRegex()
282281

283282
private val ZipOutputStream.entries: List<ZipEntry>
284283
get() =

src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/RelocatorRemapperTest.kt renamed to src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/RelocatorsTest.kt

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,42 @@ package com.github.jengelman.gradle.plugins.shadow.relocation
22

33
import assertk.assertThat
44
import assertk.assertions.isEqualTo
5-
import com.github.jengelman.gradle.plugins.shadow.internal.RelocatorRemapper
5+
import com.github.jengelman.gradle.plugins.shadow.internal.mapName
6+
import org.junit.jupiter.api.Test
67
import org.junit.jupiter.params.ParameterizedTest
78
import org.junit.jupiter.params.provider.Arguments
89
import org.junit.jupiter.params.provider.MethodSource
910

10-
class RelocatorRemapperTest {
11+
class RelocatorsTest {
1112
@ParameterizedTest
1213
@MethodSource("signaturePatternsProvider")
1314
fun relocateSignaturePatterns(input: String, expected: String) {
14-
val relocator =
15-
RelocatorRemapper(relocators = setOf(SimpleRelocator("org.package", "shadow.org.package")))
16-
assertThat(relocator.map(input)).isEqualTo(expected)
15+
val actual = setOf(SimpleRelocator("org.package", "shadow.org.package")).mapName(name = input)
16+
assertThat(actual).isEqualTo(expected)
17+
}
18+
19+
/**
20+
* Verifies that a relocator with [Relocator.skipStringConstants] = true is skipped individually
21+
* when mapping literals, rather than short-circuiting the entire set of relocators.
22+
*/
23+
@Test
24+
fun skipStringConstantsIsPerRelocator() {
25+
val skippingRelocator =
26+
SimpleRelocator("org.package", "shadow.org.package", skipStringConstants = true)
27+
val normalRelocator = SimpleRelocator("com.example", "shadow.com.example")
28+
val relocators = linkedSetOf(skippingRelocator, normalRelocator)
29+
30+
// The skipping relocator cannot match "com.example.Foo", but the normal one can.
31+
// Ensure the normal relocator is still applied even though the first one has
32+
// skipStringConstants.
33+
assertThat(relocators.mapName("com.example.Foo", mapLiterals = true))
34+
.isEqualTo("shadow.com.example.Foo")
35+
// When mapLiterals=true, the skipping relocator should not apply to its own pattern.
36+
assertThat(relocators.mapName("org.package.Bar", mapLiterals = true))
37+
.isEqualTo("org.package.Bar")
38+
// When mapLiterals=false, the skipping relocator applies normally.
39+
assertThat(relocators.mapName("org.package.Bar", mapLiterals = false))
40+
.isEqualTo("shadow.org.package.Bar")
1741
}
1842

1943
private companion object {

0 commit comments

Comments
 (0)