From 319fa1989d5eed229e9515cef0788e4de43a054f Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 12 Mar 2026 15:34:04 +0800 Subject: [PATCH 1/5] Extract FileCopyDetails.remapClass --- .../shadow/internal/RelocatorRemapper.kt | 68 +++++++++++++++++++ .../plugins/shadow/tasks/ShadowCopyAction.kt | 67 +++--------------- 2 files changed, 76 insertions(+), 59 deletions(-) diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt index c10dddab5..030b856fd 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt @@ -4,7 +4,16 @@ import com.github.jengelman.gradle.plugins.shadow.relocation.Relocator import com.github.jengelman.gradle.plugins.shadow.relocation.relocateClass import com.github.jengelman.gradle.plugins.shadow.relocation.relocatePath import java.util.regex.Pattern +import java.util.zip.ZipException +import org.apache.tools.zip.UnixStat +import org.apache.tools.zip.ZipOutputStream +import org.gradle.api.GradleException +import org.gradle.api.file.FileCopyDetails +import org.gradle.api.logging.Logger +import org.vafer.jdeb.shaded.objectweb.asm.ClassReader +import org.vafer.jdeb.shaded.objectweb.asm.ClassWriter import org.vafer.jdeb.shaded.objectweb.asm.Opcodes +import org.vafer.jdeb.shaded.objectweb.asm.commons.ClassRemapper import org.vafer.jdeb.shaded.objectweb.asm.commons.Remapper /** @@ -70,3 +79,62 @@ internal class RelocatorRemapper( val classPattern: Pattern = Pattern.compile("([\\[()BCDFIJSZ]*)?L([^;]+);?") } } + +/** + * Applies remapping to the given class with the specified relocation path. The remapped class is + * then written to the zip file. + */ +internal fun FileCopyDetails.remapClass( + relocators: Set, + zipOutStr: ZipOutputStream, + preserveFileTimestamps: Boolean, + lastModified: Long, + logger: Logger, +) = + file.readBytes().let { bytes -> + var modified = false + val remapper = RelocatorRemapper(relocators) { modified = true } + + // We don't pass the ClassReader here. This forces the ClassWriter to rebuild the constant + // pool. + // Copying the original constant pool should be avoided because it would keep references + // to the original class names. This is not a problem at runtime (because these entries in + // the + // constant pool are never used), but confuses some tools such as Felix's + // maven-bundle-plugin + // that use the constant pool to determine the dependencies of a class. + val cw = ClassWriter(0) + val cr = ClassReader(bytes) + val cv = ClassRemapper(cw, remapper) + + try { + cr.accept(cv, ClassReader.EXPAND_FRAMES) + } catch (t: Throwable) { + throw GradleException("Error in ASM processing class $path", t) + } + + val newBytes = + if (modified) { + cw.toByteArray() + } else { + // If we didn't need to change anything, keep the original bytes as-is + bytes + } + + // Temporarily remove the multi-release prefix. + val multiReleasePrefix = "^META-INF/versions/\\d+/".toRegex().find(path)?.value.orEmpty() + val newPath = path.replace(multiReleasePrefix, "") + val relocatedPath = multiReleasePrefix + relocators.relocatePath(newPath) + try { + val entry = + zipEntry(relocatedPath, preserveFileTimestamps, lastModified) { + unixMode = UnixStat.FILE_FLAG or permissions.toUnixNumeric() + } + // Now we put it back on so the class file is written out with the right extension. + zipOutStr.putNextEntry(entry) + zipOutStr.write(newBytes) + zipOutStr.closeEntry() + } catch (_: ZipException) { + logger.warn("We have a duplicate $relocatedPath in source project") + } + } diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowCopyAction.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowCopyAction.kt index b965e0459..9555bfca0 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowCopyAction.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowCopyAction.kt @@ -4,8 +4,8 @@ package com.github.jengelman.gradle.plugins.shadow.tasks -import com.github.jengelman.gradle.plugins.shadow.internal.RelocatorRemapper import com.github.jengelman.gradle.plugins.shadow.internal.cast +import com.github.jengelman.gradle.plugins.shadow.internal.remapClass import com.github.jengelman.gradle.plugins.shadow.internal.zipEntry import com.github.jengelman.gradle.plugins.shadow.relocation.Relocator import com.github.jengelman.gradle.plugins.shadow.relocation.relocateClass @@ -14,7 +14,6 @@ import com.github.jengelman.gradle.plugins.shadow.transformers.ResourceTransform import com.github.jengelman.gradle.plugins.shadow.transformers.TransformerContext import java.io.File import java.util.GregorianCalendar -import java.util.zip.ZipException import kotlin.metadata.jvm.KmModule import kotlin.metadata.jvm.KmPackageParts import kotlin.metadata.jvm.KotlinModuleMetadata @@ -32,9 +31,6 @@ import org.gradle.api.internal.file.copy.FileCopyDetailsInternal import org.gradle.api.logging.Logging import org.gradle.api.tasks.WorkResult import org.gradle.api.tasks.WorkResults -import org.vafer.jdeb.shaded.objectweb.asm.ClassReader -import org.vafer.jdeb.shaded.objectweb.asm.ClassWriter -import org.vafer.jdeb.shaded.objectweb.asm.commons.ClassRemapper /** * Modified from @@ -178,7 +174,13 @@ constructor( if (relocators.isEmpty()) { fileDetails.writeToZip(path) } else { - fileDetails.remapClass() + fileDetails.remapClass( + relocators = relocators, + zipOutStr = zipOutStr, + preserveFileTimestamps = preserveFileTimestamps, + lastModified = fileDetails.lastModified, + logger = logger, + ) } } enableKotlinModuleRemapping && path.endsWith(".kotlin_module") -> { @@ -205,59 +207,6 @@ constructor( } } - /** - * Applies remapping to the given class with the specified relocation path. The remapped class - * is then written to the zip file. - */ - private fun FileCopyDetails.remapClass() = - file.readBytes().let { bytes -> - var modified = false - val remapper = RelocatorRemapper(relocators) { modified = true } - - // We don't pass the ClassReader here. This forces the ClassWriter to rebuild the constant - // pool. - // Copying the original constant pool should be avoided because it would keep references - // to the original class names. This is not a problem at runtime (because these entries in - // the - // constant pool are never used), but confuses some tools such as Felix's - // maven-bundle-plugin - // that use the constant pool to determine the dependencies of a class. - val cw = ClassWriter(0) - val cr = ClassReader(bytes) - val cv = ClassRemapper(cw, remapper) - - try { - cr.accept(cv, ClassReader.EXPAND_FRAMES) - } catch (t: Throwable) { - throw GradleException("Error in ASM processing class $path", t) - } - - val newBytes = - if (modified) { - cw.toByteArray() - } else { - // If we didn't need to change anything, keep the original bytes as-is - bytes - } - - // Temporarily remove the multi-release prefix. - val multiReleasePrefix = "^META-INF/versions/\\d+/".toRegex().find(path)?.value.orEmpty() - val newPath = path.replace(multiReleasePrefix, "") - val relocatedPath = multiReleasePrefix + relocators.relocatePath(newPath) - try { - val entry = - zipEntry(relocatedPath, preserveFileTimestamps, lastModified) { - unixMode = UnixStat.FILE_FLAG or permissions.toUnixNumeric() - } - // Now we put it back on so the class file is written out with the right extension. - zipOutStr.putNextEntry(entry) - zipOutStr.write(newBytes) - zipOutStr.closeEntry() - } catch (_: ZipException) { - logger.warn("We have a duplicate $relocatedPath in source project") - } - } - /** * Applies remapping to the given kotlin module with the specified relocation path. The remapped * module is then written to the zip file. From 004536db346ec4623042fb8fe9581e90df142fc6 Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 12 Mar 2026 15:54:58 +0800 Subject: [PATCH 2/5] Split RelocatorRemapper and mapName --- .../shadow/internal/RelocatorRemapper.kt | 91 +++++-------------- .../plugins/shadow/internal/Relocators.kt | 47 ++++++++++ .../relocation/RelocatorRemapperTest.kt | 7 +- 3 files changed, 75 insertions(+), 70 deletions(-) create mode 100644 src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/Relocators.kt diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt index 030b856fd..447167a20 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt @@ -1,9 +1,7 @@ package com.github.jengelman.gradle.plugins.shadow.internal import com.github.jengelman.gradle.plugins.shadow.relocation.Relocator -import com.github.jengelman.gradle.plugins.shadow.relocation.relocateClass import com.github.jengelman.gradle.plugins.shadow.relocation.relocatePath -import java.util.regex.Pattern import java.util.zip.ZipException import org.apache.tools.zip.UnixStat import org.apache.tools.zip.ZipOutputStream @@ -16,70 +14,6 @@ import org.vafer.jdeb.shaded.objectweb.asm.Opcodes import org.vafer.jdeb.shaded.objectweb.asm.commons.ClassRemapper import org.vafer.jdeb.shaded.objectweb.asm.commons.Remapper -/** - * Modified from - * [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). - * Modified from - * [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). - * - * @author John Engelman - */ -internal class RelocatorRemapper( - private val relocators: Set, - private val onModified: () -> Unit = {}, -) : Remapper(Opcodes.ASM9) { - - override fun mapValue(value: Any): Any { - return if (value is String) { - mapName(value, mapLiterals = true) - } else { - super.mapValue(value) - } - } - - override fun map(internalName: String): String = mapName(internalName) - - private fun mapName(name: String, mapLiterals: Boolean = false): String { - // Maybe a list of types. - val newName = name.split(';').joinToString(";") { mapNameImpl(it, mapLiterals) } - - if (newName != name) { - onModified() - } - return newName - } - - private fun mapNameImpl(name: String, mapLiterals: Boolean): String { - var newName = name - var prefix = "" - var suffix = "" - - val matcher = classPattern.matcher(newName) - if (matcher.matches()) { - prefix = matcher.group(1) + "L" - suffix = "" - newName = matcher.group(2) - } - - for (relocator in relocators) { - if (mapLiterals && relocator.skipStringConstants) { - return name - } else if (relocator.canRelocateClass(newName)) { - return prefix + relocator.relocateClass(newName) + suffix - } else if (relocator.canRelocatePath(newName)) { - return prefix + relocator.relocatePath(newName) + suffix - } - } - - return name - } - - private companion object { - /** https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html */ - val classPattern: Pattern = Pattern.compile("([\\[()BCDFIJSZ]*)?L([^;]+);?") - } -} - /** * Applies remapping to the given class with the specified relocation path. The remapped class is * then written to the zip file. @@ -138,3 +72,28 @@ internal fun FileCopyDetails.remapClass( logger.warn("We have a duplicate $relocatedPath in source project") } } + +/** + * Modified from + * [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). + * Modified from + * [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). + * + * @author John Engelman + */ +private class RelocatorRemapper( + private val relocators: Set, + private val onModified: () -> Unit = {}, +) : Remapper(Opcodes.ASM9) { + + override fun mapValue(value: Any): Any { + return if (value is String) { + relocators.mapName(name = value, mapLiterals = true, onModified = onModified) + } else { + super.mapValue(value) + } + } + + override fun map(internalName: String): String = + relocators.mapName(name = internalName, onModified = onModified) +} diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/Relocators.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/Relocators.kt new file mode 100644 index 000000000..d8537cfca --- /dev/null +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/Relocators.kt @@ -0,0 +1,47 @@ +package com.github.jengelman.gradle.plugins.shadow.internal + +import com.github.jengelman.gradle.plugins.shadow.relocation.Relocator +import com.github.jengelman.gradle.plugins.shadow.relocation.relocateClass +import com.github.jengelman.gradle.plugins.shadow.relocation.relocatePath +import java.util.regex.Pattern + +/** https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html */ +private val classPattern: Pattern = Pattern.compile("([\\[()BCDFIJSZ]*)?L([^;]+);?") + +internal fun Set.mapName( + name: String, + mapLiterals: Boolean = false, + onModified: () -> Unit = {}, +): String { + // Maybe a list of types. + val newName = name.split(';').joinToString(";") { realMap(it, mapLiterals) } + if (newName != name) { + onModified() + } + return newName +} + +private fun Set.realMap(name: String, mapLiterals: Boolean): String { + var newName = name + var prefix = "" + var suffix = "" + + val matcher = classPattern.matcher(newName) + if (matcher.matches()) { + prefix = matcher.group(1) + "L" + suffix = "" + newName = matcher.group(2) + } + + for (relocator in this) { + if (mapLiterals && relocator.skipStringConstants) { + return name + } else if (relocator.canRelocateClass(newName)) { + return prefix + relocator.relocateClass(newName) + suffix + } else if (relocator.canRelocatePath(newName)) { + return prefix + relocator.relocatePath(newName) + suffix + } + } + + return name +} diff --git a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/RelocatorRemapperTest.kt b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/RelocatorRemapperTest.kt index 5cc83d4ea..961ae6ff2 100644 --- a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/RelocatorRemapperTest.kt +++ b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/RelocatorRemapperTest.kt @@ -2,7 +2,7 @@ package com.github.jengelman.gradle.plugins.shadow.relocation import assertk.assertThat import assertk.assertions.isEqualTo -import com.github.jengelman.gradle.plugins.shadow.internal.RelocatorRemapper +import com.github.jengelman.gradle.plugins.shadow.internal.mapName import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource @@ -11,9 +11,8 @@ class RelocatorRemapperTest { @ParameterizedTest @MethodSource("signaturePatternsProvider") fun relocateSignaturePatterns(input: String, expected: String) { - val relocator = - RelocatorRemapper(relocators = setOf(SimpleRelocator("org.package", "shadow.org.package"))) - assertThat(relocator.map(input)).isEqualTo(expected) + val relocators = setOf(SimpleRelocator("org.package", "shadow.org.package")) + assertThat(relocators.mapName(input)).isEqualTo(expected) } private companion object { From 5095baa69eadd1e6ebe49f3a9133a961627fc754 Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 12 Mar 2026 15:59:58 +0800 Subject: [PATCH 3/5] Rename RelocatorRemapperTest --- .../relocation/{RelocatorRemapperTest.kt => RelocatorsTest.kt} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/{RelocatorRemapperTest.kt => RelocatorsTest.kt} (98%) diff --git a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/RelocatorRemapperTest.kt b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/RelocatorsTest.kt similarity index 98% rename from src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/RelocatorRemapperTest.kt rename to src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/RelocatorsTest.kt index 961ae6ff2..2c6fdf5ca 100644 --- a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/RelocatorRemapperTest.kt +++ b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/RelocatorsTest.kt @@ -7,7 +7,7 @@ import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource -class RelocatorRemapperTest { +class RelocatorsTest { @ParameterizedTest @MethodSource("signaturePatternsProvider") fun relocateSignaturePatterns(input: String, expected: String) { From 31aff58d44d728dfbdb6edbf6d9a182c9a185a5b Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 12 Mar 2026 16:05:44 +0800 Subject: [PATCH 4/5] Cleanups --- .../shadow/internal/RelocatorRemapper.kt | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt index 447167a20..b6bccf6f7 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt @@ -29,14 +29,11 @@ internal fun FileCopyDetails.remapClass( var modified = false val remapper = RelocatorRemapper(relocators) { modified = true } - // We don't pass the ClassReader here. This forces the ClassWriter to rebuild the constant - // pool. - // Copying the original constant pool should be avoided because it would keep references - // to the original class names. This is not a problem at runtime (because these entries in - // the - // constant pool are never used), but confuses some tools such as Felix's - // maven-bundle-plugin - // that use the constant pool to determine the dependencies of a class. + // We don't pass the ClassReader here. This forces the ClassWriter to rebuild the constant pool. + // Copying the original constant pool should be avoided because it would keep references to the + // original class names. This is not a problem at runtime (because these entries in the constant + // pool are never used), but confuses some tools such as Felix's maven-bundle-plugin that use + // the constant pool to determine the dependencies of a class. val cw = ClassWriter(0) val cr = ClassReader(bytes) val cv = ClassRemapper(cw, remapper) @@ -47,13 +44,8 @@ internal fun FileCopyDetails.remapClass( throw GradleException("Error in ASM processing class $path", t) } - val newBytes = - if (modified) { - cw.toByteArray() - } else { - // If we didn't need to change anything, keep the original bytes as-is - bytes - } + // If we didn't need to change anything, keep the original bytes as-is. + val newBytes = if (modified) cw.toByteArray() else bytes // Temporarily remove the multi-release prefix. val multiReleasePrefix = "^META-INF/versions/\\d+/".toRegex().find(path)?.value.orEmpty() From f58489d92eab94e5c3c9fb8ccda700c683ceb6ea Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 12 Mar 2026 16:28:14 +0800 Subject: [PATCH 5/5] Remove default values --- .../gradle/plugins/shadow/internal/RelocatorRemapper.kt | 2 +- .../jengelman/gradle/plugins/shadow/internal/Relocators.kt | 2 +- .../gradle/plugins/shadow/relocation/RelocatorsTest.kt | 6 ++++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt index b6bccf6f7..b14a2a3e4 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt @@ -75,7 +75,7 @@ internal fun FileCopyDetails.remapClass( */ private class RelocatorRemapper( private val relocators: Set, - private val onModified: () -> Unit = {}, + private val onModified: () -> Unit, ) : Remapper(Opcodes.ASM9) { override fun mapValue(value: Any): Any { diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/Relocators.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/Relocators.kt index d8537cfca..1110c7fa8 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/Relocators.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/Relocators.kt @@ -11,7 +11,7 @@ private val classPattern: Pattern = Pattern.compile("([\\[()BCDFIJSZ]*)?L([^;]+) internal fun Set.mapName( name: String, mapLiterals: Boolean = false, - onModified: () -> Unit = {}, + onModified: () -> Unit, ): String { // Maybe a list of types. val newName = name.split(';').joinToString(";") { realMap(it, mapLiterals) } diff --git a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/RelocatorsTest.kt b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/RelocatorsTest.kt index 2c6fdf5ca..014a71b8c 100644 --- a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/RelocatorsTest.kt +++ b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/RelocatorsTest.kt @@ -11,8 +11,10 @@ class RelocatorsTest { @ParameterizedTest @MethodSource("signaturePatternsProvider") fun relocateSignaturePatterns(input: String, expected: String) { - val relocators = setOf(SimpleRelocator("org.package", "shadow.org.package")) - assertThat(relocators.mapName(input)).isEqualTo(expected) + val actual = + setOf(SimpleRelocator("org.package", "shadow.org.package")) + .mapName(name = input, onModified = {}) + assertThat(actual).isEqualTo(expected) } private companion object {