Skip to content

Commit 8d995b2

Browse files
authored
Split relocators remapping (#1967)
* Extract FileCopyDetails.remapClass * Split RelocatorRemapper and mapName * Rename RelocatorRemapperTest * Cleanups
1 parent bddc370 commit 8d995b2

4 files changed

Lines changed: 126 additions & 110 deletions

File tree

Lines changed: 65 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,70 @@
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
54
import com.github.jengelman.gradle.plugins.shadow.relocation.relocatePath
6-
import java.util.regex.Pattern
5+
import java.util.zip.ZipException
6+
import org.apache.tools.zip.UnixStat
7+
import org.apache.tools.zip.ZipOutputStream
8+
import org.gradle.api.GradleException
9+
import org.gradle.api.file.FileCopyDetails
10+
import org.gradle.api.logging.Logger
11+
import org.vafer.jdeb.shaded.objectweb.asm.ClassReader
12+
import org.vafer.jdeb.shaded.objectweb.asm.ClassWriter
713
import org.vafer.jdeb.shaded.objectweb.asm.Opcodes
14+
import org.vafer.jdeb.shaded.objectweb.asm.commons.ClassRemapper
815
import org.vafer.jdeb.shaded.objectweb.asm.commons.Remapper
916

17+
/**
18+
* Applies remapping to the given class with the specified relocation path. The remapped class is
19+
* then written to the zip file.
20+
*/
21+
internal fun FileCopyDetails.remapClass(
22+
relocators: Set<Relocator>,
23+
zipOutStr: ZipOutputStream,
24+
preserveFileTimestamps: Boolean,
25+
lastModified: Long,
26+
logger: Logger,
27+
) =
28+
file.readBytes().let { bytes ->
29+
var modified = false
30+
val remapper = RelocatorRemapper(relocators) { modified = true }
31+
32+
// We don't pass the ClassReader here. This forces the ClassWriter to rebuild the constant pool.
33+
// Copying the original constant pool should be avoided because it would keep references to the
34+
// original class names. This is not a problem at runtime (because these entries in the constant
35+
// pool are never used), but confuses some tools such as Felix's maven-bundle-plugin that use
36+
// the constant pool to determine the dependencies of a class.
37+
val cw = ClassWriter(0)
38+
val cr = ClassReader(bytes)
39+
val cv = ClassRemapper(cw, remapper)
40+
41+
try {
42+
cr.accept(cv, ClassReader.EXPAND_FRAMES)
43+
} catch (t: Throwable) {
44+
throw GradleException("Error in ASM processing class $path", t)
45+
}
46+
47+
// If we didn't need to change anything, keep the original bytes as-is.
48+
val newBytes = if (modified) cw.toByteArray() else bytes
49+
50+
// Temporarily remove the multi-release prefix.
51+
val multiReleasePrefix = "^META-INF/versions/\\d+/".toRegex().find(path)?.value.orEmpty()
52+
val newPath = path.replace(multiReleasePrefix, "")
53+
val relocatedPath = multiReleasePrefix + relocators.relocatePath(newPath)
54+
try {
55+
val entry =
56+
zipEntry(relocatedPath, preserveFileTimestamps, lastModified) {
57+
unixMode = UnixStat.FILE_FLAG or permissions.toUnixNumeric()
58+
}
59+
// Now we put it back on so the class file is written out with the right extension.
60+
zipOutStr.putNextEntry(entry)
61+
zipOutStr.write(newBytes)
62+
zipOutStr.closeEntry()
63+
} catch (_: ZipException) {
64+
logger.warn("We have a duplicate $relocatedPath in source project")
65+
}
66+
}
67+
1068
/**
1169
* Modified from
1270
* [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).
@@ -15,58 +73,19 @@ import org.vafer.jdeb.shaded.objectweb.asm.commons.Remapper
1573
*
1674
* @author John Engelman
1775
*/
18-
internal class RelocatorRemapper(
76+
private class RelocatorRemapper(
1977
private val relocators: Set<Relocator>,
20-
private val onModified: () -> Unit = {},
78+
private val onModified: () -> Unit,
2179
) : Remapper(Opcodes.ASM9) {
2280

2381
override fun mapValue(value: Any): Any {
2482
return if (value is String) {
25-
mapName(value, mapLiterals = true)
83+
relocators.mapName(name = value, mapLiterals = true, onModified = onModified)
2684
} else {
2785
super.mapValue(value)
2886
}
2987
}
3088

31-
override fun map(internalName: String): String = mapName(internalName)
32-
33-
private fun mapName(name: String, mapLiterals: Boolean = false): String {
34-
// Maybe a list of types.
35-
val newName = name.split(';').joinToString(";") { mapNameImpl(it, mapLiterals) }
36-
37-
if (newName != name) {
38-
onModified()
39-
}
40-
return newName
41-
}
42-
43-
private fun mapNameImpl(name: String, mapLiterals: Boolean): String {
44-
var newName = name
45-
var prefix = ""
46-
var suffix = ""
47-
48-
val matcher = classPattern.matcher(newName)
49-
if (matcher.matches()) {
50-
prefix = matcher.group(1) + "L"
51-
suffix = ""
52-
newName = matcher.group(2)
53-
}
54-
55-
for (relocator in relocators) {
56-
if (mapLiterals && relocator.skipStringConstants) {
57-
return name
58-
} else if (relocator.canRelocateClass(newName)) {
59-
return prefix + relocator.relocateClass(newName) + suffix
60-
} else if (relocator.canRelocatePath(newName)) {
61-
return prefix + relocator.relocatePath(newName) + suffix
62-
}
63-
}
64-
65-
return name
66-
}
67-
68-
private companion object {
69-
/** https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html */
70-
val classPattern: Pattern = Pattern.compile("([\\[()BCDFIJSZ]*)?L([^;]+);?")
71-
}
89+
override fun map(internalName: String): String =
90+
relocators.mapName(name = internalName, onModified = onModified)
7291
}
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+
return name
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: 8 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
package com.github.jengelman.gradle.plugins.shadow.tasks
66

7-
import com.github.jengelman.gradle.plugins.shadow.internal.RelocatorRemapper
87
import com.github.jengelman.gradle.plugins.shadow.internal.cast
8+
import com.github.jengelman.gradle.plugins.shadow.internal.remapClass
99
import com.github.jengelman.gradle.plugins.shadow.internal.zipEntry
1010
import com.github.jengelman.gradle.plugins.shadow.relocation.Relocator
1111
import com.github.jengelman.gradle.plugins.shadow.relocation.relocateClass
@@ -14,7 +14,6 @@ import com.github.jengelman.gradle.plugins.shadow.transformers.ResourceTransform
1414
import com.github.jengelman.gradle.plugins.shadow.transformers.TransformerContext
1515
import java.io.File
1616
import java.util.GregorianCalendar
17-
import java.util.zip.ZipException
1817
import kotlin.metadata.jvm.KmModule
1918
import kotlin.metadata.jvm.KmPackageParts
2019
import kotlin.metadata.jvm.KotlinModuleMetadata
@@ -32,9 +31,6 @@ import org.gradle.api.internal.file.copy.FileCopyDetailsInternal
3231
import org.gradle.api.logging.Logging
3332
import org.gradle.api.tasks.WorkResult
3433
import org.gradle.api.tasks.WorkResults
35-
import org.vafer.jdeb.shaded.objectweb.asm.ClassReader
36-
import org.vafer.jdeb.shaded.objectweb.asm.ClassWriter
37-
import org.vafer.jdeb.shaded.objectweb.asm.commons.ClassRemapper
3834

3935
/**
4036
* Modified from
@@ -178,7 +174,13 @@ constructor(
178174
if (relocators.isEmpty()) {
179175
fileDetails.writeToZip(path)
180176
} else {
181-
fileDetails.remapClass()
177+
fileDetails.remapClass(
178+
relocators = relocators,
179+
zipOutStr = zipOutStr,
180+
preserveFileTimestamps = preserveFileTimestamps,
181+
lastModified = fileDetails.lastModified,
182+
logger = logger,
183+
)
182184
}
183185
}
184186
enableKotlinModuleRemapping && path.endsWith(".kotlin_module") -> {
@@ -205,59 +207,6 @@ constructor(
205207
}
206208
}
207209

208-
/**
209-
* Applies remapping to the given class with the specified relocation path. The remapped class
210-
* is then written to the zip file.
211-
*/
212-
private fun FileCopyDetails.remapClass() =
213-
file.readBytes().let { bytes ->
214-
var modified = false
215-
val remapper = RelocatorRemapper(relocators) { modified = true }
216-
217-
// We don't pass the ClassReader here. This forces the ClassWriter to rebuild the constant
218-
// pool.
219-
// Copying the original constant pool should be avoided because it would keep references
220-
// to the original class names. This is not a problem at runtime (because these entries in
221-
// the
222-
// constant pool are never used), but confuses some tools such as Felix's
223-
// maven-bundle-plugin
224-
// that use the constant pool to determine the dependencies of a class.
225-
val cw = ClassWriter(0)
226-
val cr = ClassReader(bytes)
227-
val cv = ClassRemapper(cw, remapper)
228-
229-
try {
230-
cr.accept(cv, ClassReader.EXPAND_FRAMES)
231-
} catch (t: Throwable) {
232-
throw GradleException("Error in ASM processing class $path", t)
233-
}
234-
235-
val newBytes =
236-
if (modified) {
237-
cw.toByteArray()
238-
} else {
239-
// If we didn't need to change anything, keep the original bytes as-is
240-
bytes
241-
}
242-
243-
// Temporarily remove the multi-release prefix.
244-
val multiReleasePrefix = "^META-INF/versions/\\d+/".toRegex().find(path)?.value.orEmpty()
245-
val newPath = path.replace(multiReleasePrefix, "")
246-
val relocatedPath = multiReleasePrefix + relocators.relocatePath(newPath)
247-
try {
248-
val entry =
249-
zipEntry(relocatedPath, preserveFileTimestamps, lastModified) {
250-
unixMode = UnixStat.FILE_FLAG or permissions.toUnixNumeric()
251-
}
252-
// Now we put it back on so the class file is written out with the right extension.
253-
zipOutStr.putNextEntry(entry)
254-
zipOutStr.write(newBytes)
255-
zipOutStr.closeEntry()
256-
} catch (_: ZipException) {
257-
logger.warn("We have a duplicate $relocatedPath in source project")
258-
}
259-
}
260-
261210
/**
262211
* Applies remapping to the given kotlin module with the specified relocation path. The remapped
263212
* module is then written to the zip file.

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: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,19 @@ 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
66
import org.junit.jupiter.params.ParameterizedTest
77
import org.junit.jupiter.params.provider.Arguments
88
import org.junit.jupiter.params.provider.MethodSource
99

10-
class RelocatorRemapperTest {
10+
class RelocatorsTest {
1111
@ParameterizedTest
1212
@MethodSource("signaturePatternsProvider")
1313
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)
14+
val actual =
15+
setOf(SimpleRelocator("org.package", "shadow.org.package"))
16+
.mapName(name = input, onModified = {})
17+
assertThat(actual).isEqualTo(expected)
1718
}
1819

1920
private companion object {

0 commit comments

Comments
 (0)