From 8b438d9c0a6b6039076b8c8a3e03a5a1f3ad6521 Mon Sep 17 00:00:00 2001 From: phinner <62483793+phinner@users.noreply.github.com> Date: Sun, 10 Aug 2025 15:29:37 +0800 Subject: [PATCH 01/29] Code landing and cleanups https://github.com/xpdustry/kotlin-shadow-relocator/tree/b597930547718a9f7373cda2beabc6557da3e63d/src/main/kotlin/com/xpdustry/ksr --- build.gradle.kts | 1 + gradle/libs.versions.toml | 1 + .../com/xpdustry/ksr/KotlinRelocation.kt | 131 ++++++++++++++++++ .../ksr/KotlinShadowRelocatorPlugin.kt | 43 ++++++ .../xpdustry/ksr/MetadataAnnotationScanner.kt | 71 ++++++++++ .../com/xpdustry/ksr/RelocatingClassWriter.kt | 69 +++++++++ 6 files changed, 316 insertions(+) create mode 100644 src/main/kotlin/com/xpdustry/ksr/KotlinRelocation.kt create mode 100644 src/main/kotlin/com/xpdustry/ksr/KotlinShadowRelocatorPlugin.kt create mode 100644 src/main/kotlin/com/xpdustry/ksr/MetadataAnnotationScanner.kt create mode 100644 src/main/kotlin/com/xpdustry/ksr/RelocatingClassWriter.kt diff --git a/build.gradle.kts b/build.gradle.kts index d7be3d25f..ebd8f4a07 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -112,6 +112,7 @@ dependencies { implementation(libs.asm) implementation(libs.jdependency) implementation(libs.jdom2) + implementation(libs.kotlin.metadata) implementation(libs.plexus.utils) implementation(libs.plexus.xml) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7c6f1ee05..4c90a098b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,6 +13,7 @@ asm = "org.ow2.asm:asm-commons:9.8" # jdependency should be updated together with ASM, see https://github.com/tcurdt/jdependency/issues/325. jdependency = "org.vafer:jdependency:2.13" jdom2 = "org.jdom:jdom2:2.0.6.1" +kotlin-metadata = { module = "org.jetbrains.kotlin:kotlin-metadata-jvm", version.ref = "kotlin" } kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" } plexus-utils = "org.codehaus.plexus:plexus-utils:4.0.2" plexus-xml = "org.codehaus.plexus:plexus-xml:4.1.0" diff --git a/src/main/kotlin/com/xpdustry/ksr/KotlinRelocation.kt b/src/main/kotlin/com/xpdustry/ksr/KotlinRelocation.kt new file mode 100644 index 000000000..5ab4967b6 --- /dev/null +++ b/src/main/kotlin/com/xpdustry/ksr/KotlinRelocation.kt @@ -0,0 +1,131 @@ +/* + * This file is part of KSR, a gradle plugin for handling Kotlin metadata relocation. + * + * MIT License + * + * Copyright (c) 2025 Xpdustry + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.xpdustry.ksr + +import com.github.jengelman.gradle.plugins.shadow.relocation.CacheableRelocator +import com.github.jengelman.gradle.plugins.shadow.relocation.RelocateClassContext +import com.github.jengelman.gradle.plugins.shadow.relocation.RelocatePathContext +import com.github.jengelman.gradle.plugins.shadow.relocation.SimpleRelocator +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import java.nio.file.FileSystems +import java.nio.file.Files +import java.nio.file.Path +import kotlin.io.path.name +import kotlin.metadata.jvm.JvmMetadataVersion +import kotlin.metadata.jvm.KmModule +import kotlin.metadata.jvm.KmPackageParts +import kotlin.metadata.jvm.KotlinModuleMetadata +import kotlin.metadata.jvm.UnstableMetadataApi +import org.gradle.api.Action +import org.objectweb.asm.ClassReader + +/** + * A wrapper around [ShadowJar.relocate] that will also take care of kotlin metadata. Only use it + * for kotlin libraries. Using it on normal JVM libraries will just increase compilation time. + */ +internal fun ShadowJar.kotlinRelocate( + pattern: String, + shadedPattern: String, + action: Action = Action { }, +) { + val relocator = KotlinRelocator(pattern, shadedPattern) + val intersections = relocators.get() + .filterIsInstance() + .filter { it.canRelocatePath(pattern) } + require(intersections.isEmpty()) { + "Can't relocate from $pattern to $shadedPattern as it clashes with another paths: ${intersections.joinToString()}" + } + relocate(relocator, action) +} + +internal fun relocateMetadata(task: ShadowJar) { + val relocators = task.relocators.get().filterIsInstance() + val zip = task.archiveFile.get().asFile.toPath() + FileSystems.newFileSystem(zip, null as ClassLoader?).use { fs -> + Files.walk(fs.getPath("/")).forEach { path -> + if (!Files.isRegularFile(path)) return@forEach + if (path.name.endsWith(".class")) relocateClass(path, relocators) + if (path.name.endsWith(".kotlin_module")) relocateKotlinModule(path, relocators) + } + } +} + +internal fun Iterable.applyPathRelocation(value: String): String = fold(value) { string, relocator -> relocator.relocatePath(RelocatePathContext(string)) } + +internal fun Iterable.applyClassRelocation(value: String): String = fold(value) { string, relocator -> relocator.relocateClass(RelocateClassContext(string)) } + +@CacheableRelocator +internal class KotlinRelocator(pattern: String, shadedPattern: String) : SimpleRelocator(pattern, shadedPattern, emptyList(), emptyList()) { + // I hate these hacks... + private val shadedPattern = shadedPattern.replace('/', '.') + private val shadedPathPattern = shadedPattern.replace('.', '/') + private val pattern = pattern.replace('/', '.') + private val pathPattern = pattern.replace('.', '/') + + // Replace all instead of first + override fun relocateClass(context: RelocateClassContext): String = context.className.replace(pattern.toRegex(), shadedPattern) + + // Replace all instead of first + override fun relocatePath(context: RelocatePathContext): String = context.path.replace(pathPattern.toRegex(), shadedPathPattern) +} + +private fun relocateClass(file: Path, relocators: List) { + Files.newInputStream(file).use { ins -> + val cr = ClassReader(ins) + val cw = RelocatingClassWriter(cr, 0, relocators) + val scanner = MetadataAnnotationScanner(cw, relocators) + cr.accept(scanner, 0) + if (scanner.isRelocated || cw.isRelocated) { + ins.close() + Files.delete(file) + Files.write(file, cw.toByteArray()) + } + } +} + +@OptIn(UnstableMetadataApi::class) +private fun relocateKotlinModule(file: Path, relocators: List) { + Files.newInputStream(file).use { ins -> + val metadata = KotlinModuleMetadata.read(ins.readBytes()) + val result = KmModule() + for ((pkg, parts) in metadata.kmModule.packageParts) { + result.packageParts[relocators.applyPathRelocation(pkg)] = + KmPackageParts( + parts.fileFacades.mapTo(mutableListOf(), relocators::applyPathRelocation), + parts.multiFileClassParts.entries.associateTo(mutableMapOf()) { (name, facade) -> + relocators.applyClassRelocation(name) to + relocators.applyPathRelocation(facade) + }, + ) + } + ins.close() + Files.delete(file) + Files.write( + file, + KotlinModuleMetadata(result, JvmMetadataVersion.LATEST_STABLE_SUPPORTED).write(), + ) + } +} diff --git a/src/main/kotlin/com/xpdustry/ksr/KotlinShadowRelocatorPlugin.kt b/src/main/kotlin/com/xpdustry/ksr/KotlinShadowRelocatorPlugin.kt new file mode 100644 index 000000000..b1267277e --- /dev/null +++ b/src/main/kotlin/com/xpdustry/ksr/KotlinShadowRelocatorPlugin.kt @@ -0,0 +1,43 @@ +/* + * This file is part of KSR, a gradle plugin for handling Kotlin metadata relocation. + * + * MIT License + * + * Copyright (c) 2025 Xpdustry + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.xpdustry.ksr + +import com.github.jengelman.gradle.plugins.shadow.ShadowJavaPlugin +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import org.gradle.api.Plugin +import org.gradle.api.Project + +internal class KotlinShadowRelocatorPlugin : Plugin { + override fun apply(target: Project) { + target.plugins.withType(ShadowJavaPlugin::class.java).configureEach { + target.tasks.withType(ShadowJar::class.java).configureEach { task -> + task.doLast("relocateMetadata") { + relocateMetadata(task) + } + } + } + } +} diff --git a/src/main/kotlin/com/xpdustry/ksr/MetadataAnnotationScanner.kt b/src/main/kotlin/com/xpdustry/ksr/MetadataAnnotationScanner.kt new file mode 100644 index 000000000..cd097f2a3 --- /dev/null +++ b/src/main/kotlin/com/xpdustry/ksr/MetadataAnnotationScanner.kt @@ -0,0 +1,71 @@ +/* + * This file is part of KSR, a gradle plugin for handling Kotlin metadata relocation. + * + * MIT License + * + * Copyright (c) 2025 Xpdustry + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.xpdustry.ksr + +import org.objectweb.asm.AnnotationVisitor +import org.objectweb.asm.ClassVisitor +import org.objectweb.asm.ClassWriter +import org.objectweb.asm.Opcodes + +internal class MetadataAnnotationScanner( + private val cw: ClassWriter, + private val relocators: List, +) : ClassVisitor(Opcodes.ASM9, cw) { + internal var isRelocated = false + + override fun visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor { + return if (descriptor == "Lkotlin/Metadata;") { + MetadataVisitor(cw.visitAnnotation(descriptor, visible)) + } else { + cw.visitAnnotation(descriptor, visible) + } + } + + inner class MetadataVisitor( + av: AnnotationVisitor, + private val thatArray: Boolean = false, + ) : AnnotationVisitor(Opcodes.ASM9, av) { + override fun visit(name: String?, value: Any) { + val newValue = when { + thatArray && value is String && value.startsWith("(") -> { + relocators.applyPathRelocation(value).also { + if (it != value) isRelocated = true + } + } + else -> value + } + av.visit(name, newValue) + } + + override fun visitArray(name: String): AnnotationVisitor { + return if (name == "d2") { + MetadataVisitor(av.visitArray(name), thatArray = true) + } else { + av.visitArray(name) + } + } + } +} diff --git a/src/main/kotlin/com/xpdustry/ksr/RelocatingClassWriter.kt b/src/main/kotlin/com/xpdustry/ksr/RelocatingClassWriter.kt new file mode 100644 index 000000000..8a9b75129 --- /dev/null +++ b/src/main/kotlin/com/xpdustry/ksr/RelocatingClassWriter.kt @@ -0,0 +1,69 @@ +/* + * This file is part of KSR, a gradle plugin for handling Kotlin metadata relocation. + * + * MIT License + * + * Copyright (c) 2025 Xpdustry + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.xpdustry.ksr + +import org.objectweb.asm.ClassReader +import org.objectweb.asm.ClassWriter + +internal class RelocatingClassWriter( + reader: ClassReader, + flags: Int, + relocators: List, +) : ClassWriter(reader, flags) { + + internal var isRelocated = false + + init { + val symbolTable = symbolTableField.get(this) + + @Suppress("UNCHECKED_CAST") + val entries = entriesField.get(symbolTable) as Array + entries.forEach { entryObj -> + if (entryObj != null) { + (symbolValueField.get(entryObj) as? String)?.let { value -> + val newValue = relocators.applyPathRelocation(value) + if (value != newValue) { + symbolValueField.set(entryObj, newValue) + isRelocated = true + } + } + } + } + } + + companion object { + private val classWriterClass = ClassWriter::class.java + private val symbolTableClass = Class.forName("org.objectweb.asm.SymbolTable") // Package private. + private val symbolClass = Class.forName("org.objectweb.asm.Symbol") // Package private. + + private val symbolTableField = classWriterClass.getDeclaredField("symbolTable") + .apply { isAccessible = true } + private val entriesField = symbolTableClass.getDeclaredField("entries") + .apply { isAccessible = true } + private val symbolValueField = symbolClass.getDeclaredField("value") + .apply { isAccessible = true } + } +} From 05a5f937e60cdf698e99750688b73019953b784d Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 21 Aug 2025 16:36:48 +0800 Subject: [PATCH 02/29] Reuse `RelocationClassWriter` --- .../shadow/internal/RelocationClassWriter.kt | 73 +++++++++++++++++++ .../plugins/shadow/tasks/ShadowCopyAction.kt | 29 ++++---- .../com/xpdustry/ksr/KotlinRelocation.kt | 6 +- .../com/xpdustry/ksr/RelocatingClassWriter.kt | 69 ------------------ 4 files changed, 89 insertions(+), 88 deletions(-) create mode 100644 src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocationClassWriter.kt delete mode 100644 src/main/kotlin/com/xpdustry/ksr/RelocatingClassWriter.kt diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocationClassWriter.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocationClassWriter.kt new file mode 100644 index 000000000..384eeda1c --- /dev/null +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocationClassWriter.kt @@ -0,0 +1,73 @@ +package com.github.jengelman.gradle.plugins.shadow.internal + +import com.github.jengelman.gradle.plugins.shadow.relocation.RelocateClassContext +import com.github.jengelman.gradle.plugins.shadow.relocation.RelocatePathContext +import com.github.jengelman.gradle.plugins.shadow.relocation.Relocator +import java.lang.reflect.Field +import org.objectweb.asm.AnnotationVisitor +import org.objectweb.asm.ClassReader +import org.objectweb.asm.ClassVisitor +import org.objectweb.asm.ClassWriter +import org.objectweb.asm.Opcodes + +internal class RelocationClassWriter( + classReader: ClassReader, + relocators: Set, + flags: Int = 0, +) : ClassWriter(classReader, flags) { + internal var isRelocated = false + + init { + // If the class is a Kotlin class, we need to apply relocations to the symbol table. + if (classReader.isKotlinClass()) { + val symbolTable = symbolTableField.get(this) + + @Suppress("UNCHECKED_CAST") + val entries = entriesField.get(symbolTable) as Array + entries.forEach { entryObj -> + if (entryObj != null) { + (symbolValueField.get(entryObj) as? String)?.let { value -> + val newValue = relocators.applyClassRelocation(value) + if (value != newValue) { + symbolValueField.set(entryObj, newValue) + isRelocated = true + } + } + } + } + } + } + + private fun ClassReader.isKotlinClass(): Boolean { + var flag = false + accept( + object : ClassVisitor(Opcodes.ASM9) { + override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? { + if (desc == "Lkotlin/Metadata;") { + flag = true + } + return super.visitAnnotation(desc, visible) + } + }, + ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES, + ) + return flag + } + + companion object Companion { + private val classWriterClass = ClassWriter::class.java + private val symbolTableClass: Class<*> = Class.forName("org.objectweb.asm.SymbolTable") // Package private. + private val symbolClass: Class<*> = Class.forName("org.objectweb.asm.Symbol") // Package private. + + private val symbolTableField: Field = classWriterClass.getDeclaredField("symbolTable") + .apply { isAccessible = true } + private val entriesField: Field = symbolTableClass.getDeclaredField("entries") + .apply { isAccessible = true } + private val symbolValueField: Field = symbolClass.getDeclaredField("value") + .apply { isAccessible = true } + + fun Iterable.applyClassRelocation(value: String): String = fold(value) { string, relocator -> + relocator.relocateClass(RelocateClassContext(string)) + } + } +} 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 d28a37fc3..533b79c00 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 @@ -1,5 +1,6 @@ package com.github.jengelman.gradle.plugins.shadow.tasks +import com.github.jengelman.gradle.plugins.shadow.internal.RelocationClassWriter 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.zipEntry @@ -23,7 +24,6 @@ import org.gradle.api.logging.Logging import org.gradle.api.tasks.WorkResult import org.gradle.api.tasks.WorkResults import org.objectweb.asm.ClassReader -import org.objectweb.asm.ClassWriter import org.objectweb.asm.commons.ClassRemapper /** @@ -156,20 +156,17 @@ public open class ShadowCopyAction( private fun visitFile(fileDetails: FileCopyDetails) { val path = fileDetails.path - when { - path.endsWith(".class") -> { - if (isUnused(path)) return - if (relocators.isEmpty()) { - fileDetails.writeToZip(path) - } else { - fileDetails.remapClass() - } - } - else -> { - val mapped = RelocatorRemapper(relocators).map(path) - if (transform(fileDetails, mapped)) return - fileDetails.writeToZip(mapped) + if (path.endsWith(".class")) { + if (isUnused(path)) return + if (relocators.isEmpty()) { + fileDetails.writeToZip(path) + return } + fileDetails.remapClass(relocators) + } else { + val mapped = RelocatorRemapper(relocators).map(path) + if (transform(fileDetails, mapped)) return + fileDetails.writeToZip(mapped) } } @@ -186,7 +183,7 @@ public open class ShadowCopyAction( * 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 -> + private fun FileCopyDetails.remapClass(relocators: Set) = file.readBytes().let { bytes -> var modified = false val remapper = RelocatorRemapper(relocators) { modified = true } @@ -195,8 +192,8 @@ public open class ShadowCopyAction( // 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 cw = RelocationClassWriter(classReader = cr, relocators = relocators) val cv = ClassRemapper(cw, remapper) try { diff --git a/src/main/kotlin/com/xpdustry/ksr/KotlinRelocation.kt b/src/main/kotlin/com/xpdustry/ksr/KotlinRelocation.kt index 5ab4967b6..84c0ece80 100644 --- a/src/main/kotlin/com/xpdustry/ksr/KotlinRelocation.kt +++ b/src/main/kotlin/com/xpdustry/ksr/KotlinRelocation.kt @@ -25,6 +25,8 @@ */ package com.xpdustry.ksr +import com.github.jengelman.gradle.plugins.shadow.internal.RelocationClassWriter +import com.github.jengelman.gradle.plugins.shadow.internal.RelocationClassWriter.Companion.applyClassRelocation import com.github.jengelman.gradle.plugins.shadow.relocation.CacheableRelocator import com.github.jengelman.gradle.plugins.shadow.relocation.RelocateClassContext import com.github.jengelman.gradle.plugins.shadow.relocation.RelocatePathContext @@ -75,8 +77,6 @@ internal fun relocateMetadata(task: ShadowJar) { internal fun Iterable.applyPathRelocation(value: String): String = fold(value) { string, relocator -> relocator.relocatePath(RelocatePathContext(string)) } -internal fun Iterable.applyClassRelocation(value: String): String = fold(value) { string, relocator -> relocator.relocateClass(RelocateClassContext(string)) } - @CacheableRelocator internal class KotlinRelocator(pattern: String, shadedPattern: String) : SimpleRelocator(pattern, shadedPattern, emptyList(), emptyList()) { // I hate these hacks... @@ -95,7 +95,7 @@ internal class KotlinRelocator(pattern: String, shadedPattern: String) : SimpleR private fun relocateClass(file: Path, relocators: List) { Files.newInputStream(file).use { ins -> val cr = ClassReader(ins) - val cw = RelocatingClassWriter(cr, 0, relocators) + val cw = RelocationClassWriter(cr, relocators.toSet()) val scanner = MetadataAnnotationScanner(cw, relocators) cr.accept(scanner, 0) if (scanner.isRelocated || cw.isRelocated) { diff --git a/src/main/kotlin/com/xpdustry/ksr/RelocatingClassWriter.kt b/src/main/kotlin/com/xpdustry/ksr/RelocatingClassWriter.kt deleted file mode 100644 index 8a9b75129..000000000 --- a/src/main/kotlin/com/xpdustry/ksr/RelocatingClassWriter.kt +++ /dev/null @@ -1,69 +0,0 @@ -/* - * This file is part of KSR, a gradle plugin for handling Kotlin metadata relocation. - * - * MIT License - * - * Copyright (c) 2025 Xpdustry - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package com.xpdustry.ksr - -import org.objectweb.asm.ClassReader -import org.objectweb.asm.ClassWriter - -internal class RelocatingClassWriter( - reader: ClassReader, - flags: Int, - relocators: List, -) : ClassWriter(reader, flags) { - - internal var isRelocated = false - - init { - val symbolTable = symbolTableField.get(this) - - @Suppress("UNCHECKED_CAST") - val entries = entriesField.get(symbolTable) as Array - entries.forEach { entryObj -> - if (entryObj != null) { - (symbolValueField.get(entryObj) as? String)?.let { value -> - val newValue = relocators.applyPathRelocation(value) - if (value != newValue) { - symbolValueField.set(entryObj, newValue) - isRelocated = true - } - } - } - } - } - - companion object { - private val classWriterClass = ClassWriter::class.java - private val symbolTableClass = Class.forName("org.objectweb.asm.SymbolTable") // Package private. - private val symbolClass = Class.forName("org.objectweb.asm.Symbol") // Package private. - - private val symbolTableField = classWriterClass.getDeclaredField("symbolTable") - .apply { isAccessible = true } - private val entriesField = symbolTableClass.getDeclaredField("entries") - .apply { isAccessible = true } - private val symbolValueField = symbolClass.getDeclaredField("value") - .apply { isAccessible = true } - } -} From 5c3357dc7485a9bf509c3681937598b2400cfec4 Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 21 Aug 2025 17:40:27 +0800 Subject: [PATCH 03/29] Port `remapKotlinModule` --- .../shadow/internal/RelocationClassWriter.kt | 1 - .../plugins/shadow/tasks/ShadowCopyAction.kt | 74 ++++++++-- .../com/xpdustry/ksr/KotlinRelocation.kt | 131 ------------------ .../ksr/KotlinShadowRelocatorPlugin.kt | 43 ------ .../xpdustry/ksr/MetadataAnnotationScanner.kt | 71 ---------- 5 files changed, 64 insertions(+), 256 deletions(-) delete mode 100644 src/main/kotlin/com/xpdustry/ksr/KotlinRelocation.kt delete mode 100644 src/main/kotlin/com/xpdustry/ksr/KotlinShadowRelocatorPlugin.kt delete mode 100644 src/main/kotlin/com/xpdustry/ksr/MetadataAnnotationScanner.kt diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocationClassWriter.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocationClassWriter.kt index 384eeda1c..026a1a0b5 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocationClassWriter.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocationClassWriter.kt @@ -1,7 +1,6 @@ package com.github.jengelman.gradle.plugins.shadow.internal import com.github.jengelman.gradle.plugins.shadow.relocation.RelocateClassContext -import com.github.jengelman.gradle.plugins.shadow.relocation.RelocatePathContext import com.github.jengelman.gradle.plugins.shadow.relocation.Relocator import java.lang.reflect.Field import org.objectweb.asm.AnnotationVisitor 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 533b79c00..76775ecd1 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 @@ -1,15 +1,22 @@ package com.github.jengelman.gradle.plugins.shadow.tasks import com.github.jengelman.gradle.plugins.shadow.internal.RelocationClassWriter +import com.github.jengelman.gradle.plugins.shadow.internal.RelocationClassWriter.Companion.applyClassRelocation 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.zipEntry +import com.github.jengelman.gradle.plugins.shadow.relocation.RelocatePathContext import com.github.jengelman.gradle.plugins.shadow.relocation.Relocator import com.github.jengelman.gradle.plugins.shadow.transformers.ResourceTransformer 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.JvmMetadataVersion +import kotlin.metadata.jvm.KmModule +import kotlin.metadata.jvm.KmPackageParts +import kotlin.metadata.jvm.KotlinModuleMetadata +import kotlin.metadata.jvm.UnstableMetadataApi import org.apache.tools.zip.UnixStat import org.apache.tools.zip.Zip64RequiredException import org.apache.tools.zip.ZipEntry @@ -156,17 +163,27 @@ public open class ShadowCopyAction( private fun visitFile(fileDetails: FileCopyDetails) { val path = fileDetails.path - if (path.endsWith(".class")) { - if (isUnused(path)) return - if (relocators.isEmpty()) { - fileDetails.writeToZip(path) - return + when { + path.endsWith(".class") -> { + if (isUnused(path)) return + if (relocators.isEmpty()) { + fileDetails.writeToZip(path) + } else { + fileDetails.remapClass(relocators) + } + } + path.endsWith(".kotlin_module") -> { + if (relocators.isEmpty()) { + fileDetails.writeToZip(path) + } else { + fileDetails.remapKotlinModule(relocators) + } + } + else -> { + val mapped = RelocatorRemapper(relocators).map(path) + if (transform(fileDetails, mapped)) return + fileDetails.writeToZip(mapped) } - fileDetails.remapClass(relocators) - } else { - val mapped = RelocatorRemapper(relocators).map(path) - if (transform(fileDetails, mapped)) return - fileDetails.writeToZip(mapped) } } @@ -226,6 +243,39 @@ public open class ShadowCopyAction( } } + /** + * Applies remapping to the given kotlin module with the specified relocation path. + * The remapped module is then written to the zip file. + */ + @OptIn(UnstableMetadataApi::class) + private fun FileCopyDetails.remapKotlinModule(relocators: Set) { + val mappedPath = RelocatorRemapper(relocators).mapPath(path) + file.inputStream().use { ins -> + val metadata = KotlinModuleMetadata.read(ins.readBytes()) + val result = KmModule() + for ((pkg, parts) in metadata.kmModule.packageParts) { + result.packageParts[relocators.applyPathRelocation(pkg)] = + KmPackageParts( + parts.fileFacades.mapTo(mutableListOf()) { + relocators.applyPathRelocation(it) + }, + parts.multiFileClassParts.entries.associateTo(mutableMapOf()) { (name, facade) -> + relocators.applyClassRelocation(name) to relocators.applyPathRelocation(facade) + }, + ) + } + + val newMetadata = KotlinModuleMetadata(result, JvmMetadataVersion.LATEST_STABLE_SUPPORTED) + + val entry = zipEntry(mappedPath, preserveFileTimestamps, lastModified) { + unixMode = UnixStat.FILE_FLAG or permissions.toUnixNumeric() + } + zipOutStr.putNextEntry(entry) + zipOutStr.write(newMetadata.write()) + zipOutStr.closeEntry() + } + } + private fun transform(fileDetails: FileCopyDetails, mapped: String): Boolean { val transformer = transformers.find { it.canTransformResource(fileDetails) } ?: return false fileDetails.file.inputStream().use { inputStream -> @@ -256,6 +306,10 @@ public open class ShadowCopyAction( private val ZipOutputStream.entries: List get() = this::class.java.getDeclaredField("entries").apply { isAccessible = true }.get(this).cast() + internal fun Iterable.applyPathRelocation(value: String): String = fold(value) { string, relocator -> + relocator.relocatePath(RelocatePathContext(string)) + } + /** * A copy of [org.gradle.api.internal.file.archive.ZipEntryConstants.CONSTANT_TIME_FOR_ZIP_ENTRIES]. * diff --git a/src/main/kotlin/com/xpdustry/ksr/KotlinRelocation.kt b/src/main/kotlin/com/xpdustry/ksr/KotlinRelocation.kt deleted file mode 100644 index 84c0ece80..000000000 --- a/src/main/kotlin/com/xpdustry/ksr/KotlinRelocation.kt +++ /dev/null @@ -1,131 +0,0 @@ -/* - * This file is part of KSR, a gradle plugin for handling Kotlin metadata relocation. - * - * MIT License - * - * Copyright (c) 2025 Xpdustry - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package com.xpdustry.ksr - -import com.github.jengelman.gradle.plugins.shadow.internal.RelocationClassWriter -import com.github.jengelman.gradle.plugins.shadow.internal.RelocationClassWriter.Companion.applyClassRelocation -import com.github.jengelman.gradle.plugins.shadow.relocation.CacheableRelocator -import com.github.jengelman.gradle.plugins.shadow.relocation.RelocateClassContext -import com.github.jengelman.gradle.plugins.shadow.relocation.RelocatePathContext -import com.github.jengelman.gradle.plugins.shadow.relocation.SimpleRelocator -import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar -import java.nio.file.FileSystems -import java.nio.file.Files -import java.nio.file.Path -import kotlin.io.path.name -import kotlin.metadata.jvm.JvmMetadataVersion -import kotlin.metadata.jvm.KmModule -import kotlin.metadata.jvm.KmPackageParts -import kotlin.metadata.jvm.KotlinModuleMetadata -import kotlin.metadata.jvm.UnstableMetadataApi -import org.gradle.api.Action -import org.objectweb.asm.ClassReader - -/** - * A wrapper around [ShadowJar.relocate] that will also take care of kotlin metadata. Only use it - * for kotlin libraries. Using it on normal JVM libraries will just increase compilation time. - */ -internal fun ShadowJar.kotlinRelocate( - pattern: String, - shadedPattern: String, - action: Action = Action { }, -) { - val relocator = KotlinRelocator(pattern, shadedPattern) - val intersections = relocators.get() - .filterIsInstance() - .filter { it.canRelocatePath(pattern) } - require(intersections.isEmpty()) { - "Can't relocate from $pattern to $shadedPattern as it clashes with another paths: ${intersections.joinToString()}" - } - relocate(relocator, action) -} - -internal fun relocateMetadata(task: ShadowJar) { - val relocators = task.relocators.get().filterIsInstance() - val zip = task.archiveFile.get().asFile.toPath() - FileSystems.newFileSystem(zip, null as ClassLoader?).use { fs -> - Files.walk(fs.getPath("/")).forEach { path -> - if (!Files.isRegularFile(path)) return@forEach - if (path.name.endsWith(".class")) relocateClass(path, relocators) - if (path.name.endsWith(".kotlin_module")) relocateKotlinModule(path, relocators) - } - } -} - -internal fun Iterable.applyPathRelocation(value: String): String = fold(value) { string, relocator -> relocator.relocatePath(RelocatePathContext(string)) } - -@CacheableRelocator -internal class KotlinRelocator(pattern: String, shadedPattern: String) : SimpleRelocator(pattern, shadedPattern, emptyList(), emptyList()) { - // I hate these hacks... - private val shadedPattern = shadedPattern.replace('/', '.') - private val shadedPathPattern = shadedPattern.replace('.', '/') - private val pattern = pattern.replace('/', '.') - private val pathPattern = pattern.replace('.', '/') - - // Replace all instead of first - override fun relocateClass(context: RelocateClassContext): String = context.className.replace(pattern.toRegex(), shadedPattern) - - // Replace all instead of first - override fun relocatePath(context: RelocatePathContext): String = context.path.replace(pathPattern.toRegex(), shadedPathPattern) -} - -private fun relocateClass(file: Path, relocators: List) { - Files.newInputStream(file).use { ins -> - val cr = ClassReader(ins) - val cw = RelocationClassWriter(cr, relocators.toSet()) - val scanner = MetadataAnnotationScanner(cw, relocators) - cr.accept(scanner, 0) - if (scanner.isRelocated || cw.isRelocated) { - ins.close() - Files.delete(file) - Files.write(file, cw.toByteArray()) - } - } -} - -@OptIn(UnstableMetadataApi::class) -private fun relocateKotlinModule(file: Path, relocators: List) { - Files.newInputStream(file).use { ins -> - val metadata = KotlinModuleMetadata.read(ins.readBytes()) - val result = KmModule() - for ((pkg, parts) in metadata.kmModule.packageParts) { - result.packageParts[relocators.applyPathRelocation(pkg)] = - KmPackageParts( - parts.fileFacades.mapTo(mutableListOf(), relocators::applyPathRelocation), - parts.multiFileClassParts.entries.associateTo(mutableMapOf()) { (name, facade) -> - relocators.applyClassRelocation(name) to - relocators.applyPathRelocation(facade) - }, - ) - } - ins.close() - Files.delete(file) - Files.write( - file, - KotlinModuleMetadata(result, JvmMetadataVersion.LATEST_STABLE_SUPPORTED).write(), - ) - } -} diff --git a/src/main/kotlin/com/xpdustry/ksr/KotlinShadowRelocatorPlugin.kt b/src/main/kotlin/com/xpdustry/ksr/KotlinShadowRelocatorPlugin.kt deleted file mode 100644 index b1267277e..000000000 --- a/src/main/kotlin/com/xpdustry/ksr/KotlinShadowRelocatorPlugin.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * This file is part of KSR, a gradle plugin for handling Kotlin metadata relocation. - * - * MIT License - * - * Copyright (c) 2025 Xpdustry - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package com.xpdustry.ksr - -import com.github.jengelman.gradle.plugins.shadow.ShadowJavaPlugin -import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar -import org.gradle.api.Plugin -import org.gradle.api.Project - -internal class KotlinShadowRelocatorPlugin : Plugin { - override fun apply(target: Project) { - target.plugins.withType(ShadowJavaPlugin::class.java).configureEach { - target.tasks.withType(ShadowJar::class.java).configureEach { task -> - task.doLast("relocateMetadata") { - relocateMetadata(task) - } - } - } - } -} diff --git a/src/main/kotlin/com/xpdustry/ksr/MetadataAnnotationScanner.kt b/src/main/kotlin/com/xpdustry/ksr/MetadataAnnotationScanner.kt deleted file mode 100644 index cd097f2a3..000000000 --- a/src/main/kotlin/com/xpdustry/ksr/MetadataAnnotationScanner.kt +++ /dev/null @@ -1,71 +0,0 @@ -/* - * This file is part of KSR, a gradle plugin for handling Kotlin metadata relocation. - * - * MIT License - * - * Copyright (c) 2025 Xpdustry - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package com.xpdustry.ksr - -import org.objectweb.asm.AnnotationVisitor -import org.objectweb.asm.ClassVisitor -import org.objectweb.asm.ClassWriter -import org.objectweb.asm.Opcodes - -internal class MetadataAnnotationScanner( - private val cw: ClassWriter, - private val relocators: List, -) : ClassVisitor(Opcodes.ASM9, cw) { - internal var isRelocated = false - - override fun visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor { - return if (descriptor == "Lkotlin/Metadata;") { - MetadataVisitor(cw.visitAnnotation(descriptor, visible)) - } else { - cw.visitAnnotation(descriptor, visible) - } - } - - inner class MetadataVisitor( - av: AnnotationVisitor, - private val thatArray: Boolean = false, - ) : AnnotationVisitor(Opcodes.ASM9, av) { - override fun visit(name: String?, value: Any) { - val newValue = when { - thatArray && value is String && value.startsWith("(") -> { - relocators.applyPathRelocation(value).also { - if (it != value) isRelocated = true - } - } - else -> value - } - av.visit(name, newValue) - } - - override fun visitArray(name: String): AnnotationVisitor { - return if (name == "d2") { - MetadataVisitor(av.visitArray(name), thatArray = true) - } else { - av.visitArray(name) - } - } - } -} From d408181d5119ad275a22b55f5895a41ebb12446c Mon Sep 17 00:00:00 2001 From: Goooler Date: Tue, 2 Sep 2025 18:48:02 +0800 Subject: [PATCH 04/29] Fix merge --- .../shadow/internal/RelocationClassWriter.kt | 8 +-- .../plugins/shadow/tasks/ShadowCopyAction.kt | 57 ++++++++----------- 2 files changed, 26 insertions(+), 39 deletions(-) diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocationClassWriter.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocationClassWriter.kt index 026a1a0b5..972d20aaf 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocationClassWriter.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocationClassWriter.kt @@ -1,7 +1,7 @@ package com.github.jengelman.gradle.plugins.shadow.internal -import com.github.jengelman.gradle.plugins.shadow.relocation.RelocateClassContext import com.github.jengelman.gradle.plugins.shadow.relocation.Relocator +import com.github.jengelman.gradle.plugins.shadow.relocation.relocateClass import java.lang.reflect.Field import org.objectweb.asm.AnnotationVisitor import org.objectweb.asm.ClassReader @@ -26,7 +26,7 @@ internal class RelocationClassWriter( entries.forEach { entryObj -> if (entryObj != null) { (symbolValueField.get(entryObj) as? String)?.let { value -> - val newValue = relocators.applyClassRelocation(value) + val newValue = relocators.relocateClass(value) if (value != newValue) { symbolValueField.set(entryObj, newValue) isRelocated = true @@ -64,9 +64,5 @@ internal class RelocationClassWriter( .apply { isAccessible = true } private val symbolValueField: Field = symbolClass.getDeclaredField("value") .apply { isAccessible = true } - - fun Iterable.applyClassRelocation(value: String): String = fold(value) { string, relocator -> - relocator.relocateClass(RelocateClassContext(string)) - } } } 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 225f8d21b..8d6f97ef3 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 @@ -1,12 +1,11 @@ package com.github.jengelman.gradle.plugins.shadow.tasks import com.github.jengelman.gradle.plugins.shadow.internal.RelocationClassWriter -import com.github.jengelman.gradle.plugins.shadow.internal.RelocationClassWriter.Companion.applyClassRelocation 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.zipEntry -import com.github.jengelman.gradle.plugins.shadow.relocation.RelocatePathContext 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 com.github.jengelman.gradle.plugins.shadow.transformers.ResourceTransformer import com.github.jengelman.gradle.plugins.shadow.transformers.TransformerContext @@ -170,14 +169,14 @@ public open class ShadowCopyAction( if (relocators.isEmpty()) { fileDetails.writeToZip(path) } else { - fileDetails.remapClass(relocators) + fileDetails.remapClass() } } path.endsWith(".kotlin_module") -> { if (relocators.isEmpty()) { fileDetails.writeToZip(path) } else { - fileDetails.remapKotlinModule(relocators) + fileDetails.remapKotlinModule() } } else -> { @@ -201,7 +200,7 @@ public open class ShadowCopyAction( * 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(relocators: Set) = file.readBytes().let { bytes -> + private fun FileCopyDetails.remapClass() = file.readBytes().let { bytes -> var modified = false val remapper = RelocatorRemapper(relocators) { modified = true } @@ -249,32 +248,28 @@ public open class ShadowCopyAction( * The remapped module is then written to the zip file. */ @OptIn(UnstableMetadataApi::class) - private fun FileCopyDetails.remapKotlinModule(relocators: Set) { - val mappedPath = RelocatorRemapper(relocators).mapPath(path) - file.inputStream().use { ins -> - val metadata = KotlinModuleMetadata.read(ins.readBytes()) - val result = KmModule() - for ((pkg, parts) in metadata.kmModule.packageParts) { - result.packageParts[relocators.applyPathRelocation(pkg)] = - KmPackageParts( - parts.fileFacades.mapTo(mutableListOf()) { - relocators.applyPathRelocation(it) - }, - parts.multiFileClassParts.entries.associateTo(mutableMapOf()) { (name, facade) -> - relocators.applyClassRelocation(name) to relocators.applyPathRelocation(facade) - }, - ) - } - - val newMetadata = KotlinModuleMetadata(result, JvmMetadataVersion.LATEST_STABLE_SUPPORTED) + private fun FileCopyDetails.remapKotlinModule() = file.readBytes().let { bytes -> + val metadata = KotlinModuleMetadata.read(bytes) + val result = KmModule() + metadata.kmModule.packageParts.forEach { (pkg, parts) -> + result.packageParts[relocators.relocatePath(pkg)] = + KmPackageParts( + parts.fileFacades.mapTo(mutableListOf()) { + relocators.relocatePath(it) + }, + parts.multiFileClassParts.entries.associateTo(mutableMapOf()) { (name, facade) -> + relocators.relocateClass(name) to relocators.relocatePath(facade) + }, + ) + } - val entry = zipEntry(mappedPath, preserveFileTimestamps, lastModified) { - unixMode = UnixStat.FILE_FLAG or permissions.toUnixNumeric() - } - zipOutStr.putNextEntry(entry) - zipOutStr.write(newMetadata.write()) - zipOutStr.closeEntry() + val newMetadata = KotlinModuleMetadata(result, JvmMetadataVersion.LATEST_STABLE_SUPPORTED) + val entry = zipEntry(relocators.relocatePath(path), preserveFileTimestamps, lastModified) { + unixMode = UnixStat.FILE_FLAG or permissions.toUnixNumeric() } + zipOutStr.putNextEntry(entry) + zipOutStr.write(newMetadata.write()) + zipOutStr.closeEntry() } private fun transform(fileDetails: FileCopyDetails, path: String): Boolean { @@ -307,10 +302,6 @@ public open class ShadowCopyAction( private val ZipOutputStream.entries: List get() = this::class.java.getDeclaredField("entries").apply { isAccessible = true }.get(this).cast() - internal fun Iterable.applyPathRelocation(value: String): String = fold(value) { string, relocator -> - relocator.relocatePath(RelocatePathContext(string)) - } - /** * A copy of [org.gradle.api.internal.file.archive.ZipEntryConstants.CONSTANT_TIME_FOR_ZIP_ENTRIES]. * From be927c6e652b52277ae7c14e61d1babc4866f94b Mon Sep 17 00:00:00 2001 From: Goooler Date: Tue, 2 Sep 2025 22:19:24 +0800 Subject: [PATCH 05/29] Replace `RelocationClassWriter` with `RelocationClassVisitor` --- .../shadow/internal/RelocationClassVisitor.kt | 48 +++++++++++++ .../shadow/internal/RelocationClassWriter.kt | 68 ------------------- .../plugins/shadow/tasks/ShadowCopyAction.kt | 8 +-- 3 files changed, 52 insertions(+), 72 deletions(-) create mode 100644 src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocationClassVisitor.kt delete mode 100644 src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocationClassWriter.kt diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocationClassVisitor.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocationClassVisitor.kt new file mode 100644 index 000000000..b3ec60848 --- /dev/null +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocationClassVisitor.kt @@ -0,0 +1,48 @@ +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.relocatePath +import org.objectweb.asm.AnnotationVisitor +import org.objectweb.asm.ClassWriter +import org.objectweb.asm.Opcodes +import org.objectweb.asm.commons.ClassRemapper +import org.objectweb.asm.commons.Remapper + +internal class RelocationClassVisitor( + private val classWriter: ClassWriter, + remapper: Remapper, + private val relocators: Set, +) : ClassRemapper(classWriter, remapper) { + + override fun visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor { + val av = classWriter.visitAnnotation(descriptor, visible) + return if (descriptor == "Lkotlin/Metadata;") { + KotlinMetadataVisitor(av) + } else { + av + } + } + + private inner class KotlinMetadataVisitor( + av: AnnotationVisitor, + private val thatArray: Boolean = false, + ) : AnnotationVisitor(Opcodes.ASM9, av) { + override fun visit(name: String?, value: Any) { + val newValue = when { + thatArray && value is String && value.startsWith("(") -> { + relocators.relocatePath(value) + } + else -> value + } + av.visit(name, newValue) + } + + override fun visitArray(name: String): AnnotationVisitor { + return if (name == "d2") { + KotlinMetadataVisitor(av.visitArray(name), thatArray = true) + } else { + av.visitArray(name) + } + } + } +} diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocationClassWriter.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocationClassWriter.kt deleted file mode 100644 index 972d20aaf..000000000 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocationClassWriter.kt +++ /dev/null @@ -1,68 +0,0 @@ -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 java.lang.reflect.Field -import org.objectweb.asm.AnnotationVisitor -import org.objectweb.asm.ClassReader -import org.objectweb.asm.ClassVisitor -import org.objectweb.asm.ClassWriter -import org.objectweb.asm.Opcodes - -internal class RelocationClassWriter( - classReader: ClassReader, - relocators: Set, - flags: Int = 0, -) : ClassWriter(classReader, flags) { - internal var isRelocated = false - - init { - // If the class is a Kotlin class, we need to apply relocations to the symbol table. - if (classReader.isKotlinClass()) { - val symbolTable = symbolTableField.get(this) - - @Suppress("UNCHECKED_CAST") - val entries = entriesField.get(symbolTable) as Array - entries.forEach { entryObj -> - if (entryObj != null) { - (symbolValueField.get(entryObj) as? String)?.let { value -> - val newValue = relocators.relocateClass(value) - if (value != newValue) { - symbolValueField.set(entryObj, newValue) - isRelocated = true - } - } - } - } - } - } - - private fun ClassReader.isKotlinClass(): Boolean { - var flag = false - accept( - object : ClassVisitor(Opcodes.ASM9) { - override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? { - if (desc == "Lkotlin/Metadata;") { - flag = true - } - return super.visitAnnotation(desc, visible) - } - }, - ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES, - ) - return flag - } - - companion object Companion { - private val classWriterClass = ClassWriter::class.java - private val symbolTableClass: Class<*> = Class.forName("org.objectweb.asm.SymbolTable") // Package private. - private val symbolClass: Class<*> = Class.forName("org.objectweb.asm.Symbol") // Package private. - - private val symbolTableField: Field = classWriterClass.getDeclaredField("symbolTable") - .apply { isAccessible = true } - private val entriesField: Field = symbolTableClass.getDeclaredField("entries") - .apply { isAccessible = true } - private val symbolValueField: Field = symbolClass.getDeclaredField("value") - .apply { isAccessible = true } - } -} 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 8d6f97ef3..85d14dd7e 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 @@ -1,6 +1,6 @@ package com.github.jengelman.gradle.plugins.shadow.tasks -import com.github.jengelman.gradle.plugins.shadow.internal.RelocationClassWriter +import com.github.jengelman.gradle.plugins.shadow.internal.RelocationClassVisitor 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.zipEntry @@ -31,7 +31,7 @@ import org.gradle.api.logging.Logging import org.gradle.api.tasks.WorkResult import org.gradle.api.tasks.WorkResults import org.objectweb.asm.ClassReader -import org.objectweb.asm.commons.ClassRemapper +import org.objectweb.asm.ClassWriter /** * Modified from [org.gradle.api.internal.file.archive.ZipCopyAction.java](https://github.com/gradle/gradle/blob/b893c2b085046677cf858fb3d5ce00e68e556c3a/platforms/core-configuration/file-operations/src/main/java/org/gradle/api/internal/file/archive/ZipCopyAction.java). @@ -209,9 +209,9 @@ public open class ShadowCopyAction( // 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 cw = RelocationClassWriter(classReader = cr, relocators = relocators) - val cv = ClassRemapper(cw, remapper) + val cv = RelocationClassVisitor(classWriter = cw, remapper = remapper, relocators = relocators) try { cr.accept(cv, ClassReader.EXPAND_FRAMES) From 43c24103a7414617545e587df6a5963f21a3db2d Mon Sep 17 00:00:00 2001 From: Goooler Date: Wed, 3 Sep 2025 17:45:13 +0800 Subject: [PATCH 06/29] Rename `RelocatorRemapper` and correct `remapKotlinModule` --- ...catorRemapper.kt => RelocationRemapper.kt} | 2 +- .../plugins/shadow/tasks/ShadowCopyAction.kt | 31 +++++++++---------- 2 files changed, 16 insertions(+), 17 deletions(-) rename src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/{RelocatorRemapper.kt => RelocationRemapper.kt} (98%) 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/RelocationRemapper.kt similarity index 98% rename from src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt rename to src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocationRemapper.kt index 61f824852..a231b835c 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/RelocationRemapper.kt @@ -12,7 +12,7 @@ import org.objectweb.asm.commons.Remapper * * @author John Engelman */ -internal class RelocatorRemapper( +internal class RelocationRemapper( private val relocators: Set, private val onModified: () -> Unit = {}, ) : Remapper() { 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 85d14dd7e..419941b96 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 @@ -1,7 +1,7 @@ package com.github.jengelman.gradle.plugins.shadow.tasks import com.github.jengelman.gradle.plugins.shadow.internal.RelocationClassVisitor -import com.github.jengelman.gradle.plugins.shadow.internal.RelocatorRemapper +import com.github.jengelman.gradle.plugins.shadow.internal.RelocationRemapper import com.github.jengelman.gradle.plugins.shadow.internal.cast import com.github.jengelman.gradle.plugins.shadow.internal.zipEntry import com.github.jengelman.gradle.plugins.shadow.relocation.Relocator @@ -12,7 +12,6 @@ import com.github.jengelman.gradle.plugins.shadow.transformers.TransformerContex import java.io.File import java.util.GregorianCalendar import java.util.zip.ZipException -import kotlin.metadata.jvm.JvmMetadataVersion import kotlin.metadata.jvm.KmModule import kotlin.metadata.jvm.KmPackageParts import kotlin.metadata.jvm.KotlinModuleMetadata @@ -202,7 +201,7 @@ public open class ShadowCopyAction( */ private fun FileCopyDetails.remapClass() = file.readBytes().let { bytes -> var modified = false - val remapper = RelocatorRemapper(relocators) { modified = true } + val remapper = RelocationRemapper(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 @@ -249,26 +248,26 @@ public open class ShadowCopyAction( */ @OptIn(UnstableMetadataApi::class) private fun FileCopyDetails.remapKotlinModule() = file.readBytes().let { bytes -> - val metadata = KotlinModuleMetadata.read(bytes) - val result = KmModule() - metadata.kmModule.packageParts.forEach { (pkg, parts) -> - result.packageParts[relocators.relocatePath(pkg)] = - KmPackageParts( - parts.fileFacades.mapTo(mutableListOf()) { - relocators.relocatePath(it) - }, - parts.multiFileClassParts.entries.associateTo(mutableMapOf()) { (name, facade) -> + val kmMetadata = KotlinModuleMetadata.read(bytes) + val newKmModule = KmModule().apply { + optionalAnnotationClasses += kmMetadata.kmModule.optionalAnnotationClasses + packageParts += kmMetadata.kmModule.packageParts.map { (pkg, parts) -> + val relocatedPkg = relocators.relocatePath(pkg) + val relocatedParts = KmPackageParts( + parts.fileFacades.mapTo(mutableListOf()) { relocators.relocatePath(it) }, + parts.multiFileClassParts.map { (name, facade) -> relocators.relocateClass(name) to relocators.relocatePath(facade) - }, + }.toMap().toMutableMap(), ) + relocatedPkg to relocatedParts + } } - - val newMetadata = KotlinModuleMetadata(result, JvmMetadataVersion.LATEST_STABLE_SUPPORTED) + val newKmMetadata = KotlinModuleMetadata(newKmModule, kmMetadata.version) val entry = zipEntry(relocators.relocatePath(path), preserveFileTimestamps, lastModified) { unixMode = UnixStat.FILE_FLAG or permissions.toUnixNumeric() } zipOutStr.putNextEntry(entry) - zipOutStr.write(newMetadata.write()) + zipOutStr.write(newKmMetadata.write()) zipOutStr.closeEntry() } From 2eb1fe80549b14d4cb77ae3ca8d0fc24b2561cde Mon Sep 17 00:00:00 2001 From: Goooler Date: Wed, 3 Sep 2025 19:16:42 +0800 Subject: [PATCH 07/29] Replace `RelocationClassVisitor` with normal `ClassRemapper` --- .../shadow/internal/RelocationClassVisitor.kt | 48 ------------------- .../plugins/shadow/tasks/ShadowCopyAction.kt | 4 +- 2 files changed, 2 insertions(+), 50 deletions(-) delete mode 100644 src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocationClassVisitor.kt diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocationClassVisitor.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocationClassVisitor.kt deleted file mode 100644 index b3ec60848..000000000 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocationClassVisitor.kt +++ /dev/null @@ -1,48 +0,0 @@ -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.relocatePath -import org.objectweb.asm.AnnotationVisitor -import org.objectweb.asm.ClassWriter -import org.objectweb.asm.Opcodes -import org.objectweb.asm.commons.ClassRemapper -import org.objectweb.asm.commons.Remapper - -internal class RelocationClassVisitor( - private val classWriter: ClassWriter, - remapper: Remapper, - private val relocators: Set, -) : ClassRemapper(classWriter, remapper) { - - override fun visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor { - val av = classWriter.visitAnnotation(descriptor, visible) - return if (descriptor == "Lkotlin/Metadata;") { - KotlinMetadataVisitor(av) - } else { - av - } - } - - private inner class KotlinMetadataVisitor( - av: AnnotationVisitor, - private val thatArray: Boolean = false, - ) : AnnotationVisitor(Opcodes.ASM9, av) { - override fun visit(name: String?, value: Any) { - val newValue = when { - thatArray && value is String && value.startsWith("(") -> { - relocators.relocatePath(value) - } - else -> value - } - av.visit(name, newValue) - } - - override fun visitArray(name: String): AnnotationVisitor { - return if (name == "d2") { - KotlinMetadataVisitor(av.visitArray(name), thatArray = true) - } else { - av.visitArray(name) - } - } - } -} 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 419941b96..1fa8f6131 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 @@ -1,6 +1,5 @@ package com.github.jengelman.gradle.plugins.shadow.tasks -import com.github.jengelman.gradle.plugins.shadow.internal.RelocationClassVisitor import com.github.jengelman.gradle.plugins.shadow.internal.RelocationRemapper import com.github.jengelman.gradle.plugins.shadow.internal.cast import com.github.jengelman.gradle.plugins.shadow.internal.zipEntry @@ -31,6 +30,7 @@ import org.gradle.api.tasks.WorkResult import org.gradle.api.tasks.WorkResults import org.objectweb.asm.ClassReader import org.objectweb.asm.ClassWriter +import org.objectweb.asm.commons.ClassRemapper /** * Modified from [org.gradle.api.internal.file.archive.ZipCopyAction.java](https://github.com/gradle/gradle/blob/b893c2b085046677cf858fb3d5ce00e68e556c3a/platforms/core-configuration/file-operations/src/main/java/org/gradle/api/internal/file/archive/ZipCopyAction.java). @@ -210,7 +210,7 @@ public open class ShadowCopyAction( // that use the constant pool to determine the dependencies of a class. val cw = ClassWriter(0) val cr = ClassReader(bytes) - val cv = RelocationClassVisitor(classWriter = cw, remapper = remapper, relocators = relocators) + val cv = ClassRemapper(cw, remapper) try { cr.accept(cv, ClassReader.EXPAND_FRAMES) From 6597d52991e6f3b339dffccea15c1f76b3b6fce3 Mon Sep 17 00:00:00 2001 From: Goooler Date: Wed, 3 Sep 2025 19:36:36 +0800 Subject: [PATCH 08/29] Fix `remapKotlinModule` --- .../jengelman/gradle/plugins/shadow/tasks/ShadowCopyAction.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 1fa8f6131..01ebc5288 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 @@ -252,11 +252,11 @@ public open class ShadowCopyAction( val newKmModule = KmModule().apply { optionalAnnotationClasses += kmMetadata.kmModule.optionalAnnotationClasses packageParts += kmMetadata.kmModule.packageParts.map { (pkg, parts) -> - val relocatedPkg = relocators.relocatePath(pkg) + val relocatedPkg = relocators.relocateClass(pkg) val relocatedParts = KmPackageParts( parts.fileFacades.mapTo(mutableListOf()) { relocators.relocatePath(it) }, parts.multiFileClassParts.map { (name, facade) -> - relocators.relocateClass(name) to relocators.relocatePath(facade) + relocators.relocatePath(name) to relocators.relocatePath(facade) }.toMap().toMutableMap(), ) relocatedPkg to relocatedParts From ac6766d9ab3a04dbca985beb81d7d98f950ee367 Mon Sep 17 00:00:00 2001 From: Goooler Date: Wed, 3 Sep 2025 19:49:13 +0800 Subject: [PATCH 09/29] Revert rename --- .../internal/{RelocationRemapper.kt => RelocatorRemapper.kt} | 2 +- .../jengelman/gradle/plugins/shadow/tasks/ShadowCopyAction.kt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/{RelocationRemapper.kt => RelocatorRemapper.kt} (98%) diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocationRemapper.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt similarity index 98% rename from src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocationRemapper.kt rename to src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt index a231b835c..61f824852 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocationRemapper.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt @@ -12,7 +12,7 @@ import org.objectweb.asm.commons.Remapper * * @author John Engelman */ -internal class RelocationRemapper( +internal class RelocatorRemapper( private val relocators: Set, private val onModified: () -> Unit = {}, ) : Remapper() { 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 01ebc5288..2978e7808 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 @@ -1,6 +1,6 @@ package com.github.jengelman.gradle.plugins.shadow.tasks -import com.github.jengelman.gradle.plugins.shadow.internal.RelocationRemapper +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.zipEntry import com.github.jengelman.gradle.plugins.shadow.relocation.Relocator @@ -201,7 +201,7 @@ public open class ShadowCopyAction( */ private fun FileCopyDetails.remapClass() = file.readBytes().let { bytes -> var modified = false - val remapper = RelocationRemapper(relocators) { modified = true } + 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 From be7e1eafe8ded9a9d2531e0d75d0fa883a7d86d2 Mon Sep 17 00:00:00 2001 From: Goooler Date: Wed, 3 Sep 2025 20:01:41 +0800 Subject: [PATCH 10/29] Use `associateTo` --- .../jengelman/gradle/plugins/shadow/tasks/ShadowCopyAction.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 2978e7808..c78ea58cd 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 @@ -255,9 +255,9 @@ public open class ShadowCopyAction( val relocatedPkg = relocators.relocateClass(pkg) val relocatedParts = KmPackageParts( parts.fileFacades.mapTo(mutableListOf()) { relocators.relocatePath(it) }, - parts.multiFileClassParts.map { (name, facade) -> + parts.multiFileClassParts.entries.associateTo(mutableMapOf()) { (name, facade) -> relocators.relocatePath(name) to relocators.relocatePath(facade) - }.toMap().toMutableMap(), + }, ) relocatedPkg to relocatedParts } From ba5ca7370138dfc26527ff5d51ab0cbb11c77c0d Mon Sep 17 00:00:00 2001 From: Goooler Date: Wed, 3 Sep 2025 20:53:42 +0800 Subject: [PATCH 11/29] Don't try to relocate kotlin builtins --- .../jengelman/gradle/plugins/shadow/tasks/ShadowCopyAction.kt | 4 ++++ 1 file changed, 4 insertions(+) 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 c78ea58cd..93773713e 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 @@ -178,6 +178,10 @@ public open class ShadowCopyAction( fileDetails.remapKotlinModule() } } + path.endsWith(".kotlin_builtins") -> { + // Don't try to relocate kotlin builtins (kotlin/kotlin.kotlin_builtins). + fileDetails.writeToZip(path) + } else -> { val relocated = relocators.relocatePath(path) if (transform(fileDetails, relocated)) return From af153e7bc860ad8fb8e2757b4aac039af79e489e Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 4 Sep 2025 09:18:07 +0800 Subject: [PATCH 12/29] Hardcoded `kotlin-stdlib` in Kotlin @Metadata.d2 cannot be relocated --- .../jengelman/gradle/plugins/shadow/tasks/ShadowJar.kt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowJar.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowJar.kt index cbe130cc9..24ae592a3 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowJar.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowJar.kt @@ -445,7 +445,15 @@ public abstract class ShadowJar : Jar() { .filter { it.name.endsWith(".class") && it.name != "module-info.class" } .map { it.name.substringBeforeLast('/').replace('/', '.') } .toSet() - .map { SimpleRelocator(it, "$prefix.$it") } + .map { + SimpleRelocator( + pattern = it, + shadedPattern = "$prefix.$it", + excludes = listOf( + "kotlin-stdlib", // Hardcoded `kotlin-stdlib` in Kotlin @Metadata.d2 cannot be relocated. + ), + ) + } } } } From 631e57e567ea86efd93e1ffcb8079d14a1b1f614 Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 4 Sep 2025 14:01:48 +0800 Subject: [PATCH 13/29] Revert "Hardcoded `kotlin-stdlib` in Kotlin @Metadata.d2 cannot be relocated" This reverts commit af153e7bc860ad8fb8e2757b4aac039af79e489e. --- .../jengelman/gradle/plugins/shadow/tasks/ShadowJar.kt | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowJar.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowJar.kt index 24ae592a3..cbe130cc9 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowJar.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowJar.kt @@ -445,15 +445,7 @@ public abstract class ShadowJar : Jar() { .filter { it.name.endsWith(".class") && it.name != "module-info.class" } .map { it.name.substringBeforeLast('/').replace('/', '.') } .toSet() - .map { - SimpleRelocator( - pattern = it, - shadedPattern = "$prefix.$it", - excludes = listOf( - "kotlin-stdlib", // Hardcoded `kotlin-stdlib` in Kotlin @Metadata.d2 cannot be relocated. - ), - ) - } + .map { SimpleRelocator(it, "$prefix.$it") } } } } From 820b7caf73108fb3678f3d9a50e7160007cefaee Mon Sep 17 00:00:00 2001 From: Goooler Date: Fri, 5 Sep 2025 15:36:43 +0800 Subject: [PATCH 14/29] Test `relocateKotlinModuleFiles` --- .../gradle/plugins/shadow/RelocationTest.kt | 64 ++++++++++++++++++ .../META-INF/kotlin-stdlib-jdk8.kotlin_module | Bin 0 -> 323 bytes 2 files changed, 64 insertions(+) create mode 100644 src/functionalTest/resources/META-INF/kotlin-stdlib-jdk8.kotlin_module diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt index fc466d1a8..d35991d4e 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt @@ -3,11 +3,15 @@ package com.github.jengelman.gradle.plugins.shadow import assertk.assertFailure import assertk.assertThat import assertk.assertions.contains +import assertk.assertions.isEmpty import assertk.assertions.isEqualTo import assertk.assertions.isInstanceOf import assertk.assertions.isNotEmpty +import assertk.assertions.isNotEqualTo import assertk.fail import com.github.jengelman.gradle.plugins.shadow.internal.mainClassAttributeKey +import com.github.jengelman.gradle.plugins.shadow.internal.requireResourceAsStream +import com.github.jengelman.gradle.plugins.shadow.internal.requireResourceAsText import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowCopyAction.Companion.CONSTANT_TIME_FOR_ZIP_ENTRIES import com.github.jengelman.gradle.plugins.shadow.util.Issue import com.github.jengelman.gradle.plugins.shadow.util.containsOnly @@ -16,6 +20,8 @@ import com.github.jengelman.gradle.plugins.shadow.util.runProcess import java.net.URLClassLoader import kotlin.io.path.appendText import kotlin.io.path.writeText +import kotlin.metadata.jvm.KotlinModuleMetadata +import kotlin.metadata.jvm.UnstableMetadataApi import kotlin.time.Duration.Companion.seconds import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedTest @@ -554,6 +560,64 @@ class RelocationTest : BasePluginTest() { assertThat(relocatedBytes).isEqualTo(originalBytes) } + @UnstableMetadataApi + @Issue( + "https://github.com/GradleUp/shadow/issues/843", + ) + @Test + fun relocateKotlinModuleFiles() { + val moduleFilePath = "META-INF/kotlin-stdlib-jdk8.kotlin_module" + val stdlibJar = buildJar("stdlib.jar") { + insert(moduleFilePath, requireResourceAsText(moduleFilePath)) + } + projectScript.appendText( + """ + dependencies { + ${implementationFiles(stdlibJar)} + } + $shadowJarTask { + relocate 'kotlin', 'my.kotlin' + } + """.trimIndent(), + ) + + run(shadowJarPath) + + assertThat(outputShadowedJar).useAll { + containsOnly( + moduleFilePath, + *manifestEntries, + ) + } + + val originalModule = KotlinModuleMetadata.read(requireResourceAsStream(moduleFilePath).readBytes()) + val relocatedModule = outputShadowedJar.use { + KotlinModuleMetadata.read(it.getBytes(moduleFilePath)) + } + + assertThat(relocatedModule.version).isEqualTo(originalModule.version) + // They are both empty. + assertThat(relocatedModule.kmModule.optionalAnnotationClasses) + .isEqualTo(originalModule.kmModule.optionalAnnotationClasses) + + val originalPkgParts = originalModule.kmModule.packageParts.entries + val relocatedPkgParts = relocatedModule.kmModule.packageParts.entries + // They are not empty and different. + assertThat(originalPkgParts).isNotEqualTo(relocatedPkgParts) + + relocatedPkgParts.forEachIndexed { index, (pkg, parts) -> + val (originalPkg, originalParts) = originalPkgParts.elementAt(index) + assertThat(pkg).isNotEqualTo(originalPkg) + assertThat(pkg).isEqualTo(originalPkg.replace("kotlin.", "my.kotlin.")) + + assertThat(parts.fileFacades).isNotEqualTo(originalParts.fileFacades) + assertThat(parts.fileFacades).isEqualTo(originalParts.fileFacades.map { it.replace("kotlin/", "my/kotlin/") }) + + // They are both empty. + assertThat(parts.multiFileClassParts).isEqualTo(originalParts.multiFileClassParts) + } + } + private fun writeClassWithStringRef() { writeClass { """ diff --git a/src/functionalTest/resources/META-INF/kotlin-stdlib-jdk8.kotlin_module b/src/functionalTest/resources/META-INF/kotlin-stdlib-jdk8.kotlin_module new file mode 100644 index 0000000000000000000000000000000000000000..8b5ad6cfb682a721ce7ef79b50dcba96e1f2f25a GIT binary patch literal 323 zcmZXPyAHxI3`Jc!VI&>}3Fv?jY#BOqVh0B3fCOI*Qjj6c@^kwrsHeKK7ezl~Ec1&JjIZREqWXl{$*M;Ka{TI3mC$ zj=n@8+8t!F(aH|*F%;{!Ifq0Q$4tl7H+ZQ@02USrkh8}UC`R-N|e?K)=q4) n)0ViVze^f~@z{qn{*@Hh+bZrg6yJ;CuTD+%8xHTy0vJ32yi;YD literal 0 HcmV?d00001 From 98131f5957d20685287c687666386b69fe656e73 Mon Sep 17 00:00:00 2001 From: Goooler Date: Fri, 5 Sep 2025 16:34:52 +0800 Subject: [PATCH 15/29] Revert "Don't try to relocate kotlin builtins" This reverts commit ba5ca7370138dfc26527ff5d51ab0cbb11c77c0d. --- .../jengelman/gradle/plugins/shadow/tasks/ShadowCopyAction.kt | 4 ---- 1 file changed, 4 deletions(-) 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 93773713e..c78ea58cd 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 @@ -178,10 +178,6 @@ public open class ShadowCopyAction( fileDetails.remapKotlinModule() } } - path.endsWith(".kotlin_builtins") -> { - // Don't try to relocate kotlin builtins (kotlin/kotlin.kotlin_builtins). - fileDetails.writeToZip(path) - } else -> { val relocated = relocators.relocatePath(path) if (transform(fileDetails, relocated)) return From 874652fe8c9ce188040f610418f2be353364cf02 Mon Sep 17 00:00:00 2001 From: Goooler Date: Fri, 12 Sep 2025 10:20:55 +0800 Subject: [PATCH 16/29] Use `kotlin("metadata-jvm")` --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index ebd8f4a07..6f5a35d50 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -112,7 +112,7 @@ dependencies { implementation(libs.asm) implementation(libs.jdependency) implementation(libs.jdom2) - implementation(libs.kotlin.metadata) + implementation(kotlin("metadata-jvm")) // libs.kotlin.metadata can't be added on my local device, but it works on CI. implementation(libs.plexus.utils) implementation(libs.plexus.xml) From 4d76f4055b3f2540c9fa5b6b0e0838bceecba9e0 Mon Sep 17 00:00:00 2001 From: Goooler Date: Fri, 12 Sep 2025 10:41:42 +0800 Subject: [PATCH 17/29] Rename kotlin_module file path --- .../gradle/plugins/shadow/RelocationTest.kt | 12 ++++++------ .../gradle/plugins/shadow/tasks/ShadowCopyAction.kt | 12 +++++++++++- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt index 7dd718da9..2f4a0a34f 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt @@ -3,7 +3,6 @@ package com.github.jengelman.gradle.plugins.shadow import assertk.assertFailure import assertk.assertThat import assertk.assertions.contains -import assertk.assertions.isEmpty import assertk.assertions.isEqualTo import assertk.assertions.isInstanceOf import assertk.assertions.isNotEmpty @@ -605,9 +604,10 @@ class RelocationTest : BasePluginTest() { ) @Test fun relocateKotlinModuleFiles() { - val moduleFilePath = "META-INF/kotlin-stdlib-jdk8.kotlin_module" + val originalModuleFilePath = "META-INF/kotlin-stdlib-jdk8.kotlin_module" + val relocatedModuleFilePath = "META-INF/kotlin-stdlib-jdk8.shadow.kotlin_module" val stdlibJar = buildJar("stdlib.jar") { - insert(moduleFilePath, requireResourceAsText(moduleFilePath)) + insert(originalModuleFilePath, requireResourceAsText(originalModuleFilePath)) } projectScript.appendText( """ @@ -624,14 +624,14 @@ class RelocationTest : BasePluginTest() { assertThat(outputShadowedJar).useAll { containsOnly( - moduleFilePath, + relocatedModuleFilePath, *manifestEntries, ) } - val originalModule = KotlinModuleMetadata.read(requireResourceAsStream(moduleFilePath).readBytes()) + val originalModule = KotlinModuleMetadata.read(requireResourceAsStream(originalModuleFilePath).readBytes()) val relocatedModule = outputShadowedJar.use { - KotlinModuleMetadata.read(it.getBytes(moduleFilePath)) + KotlinModuleMetadata.read(it.getBytes(relocatedModuleFilePath)) } assertThat(relocatedModule.version).isEqualTo(originalModule.version) 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 c78ea58cd..2418b1d4e 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 @@ -263,7 +263,17 @@ public open class ShadowCopyAction( } } val newKmMetadata = KotlinModuleMetadata(newKmModule, kmMetadata.version) - val entry = zipEntry(relocators.relocatePath(path), preserveFileTimestamps, lastModified) { + + val newBytes = newKmMetadata.write() + val relocatedPath = relocators.relocatePath(path) + val entryName = when { + relocatedPath != path -> relocatedPath + // Nothing changed, so keep the original path. + newBytes.contentEquals(bytes) -> path + // Content changed but path didn't, so rename to avoid name clash. The filename does not matter to the compiler. + else -> path.replace(".kotlin_module", ".shadow.kotlin_module") + } + val entry = zipEntry(entryName, preserveFileTimestamps, lastModified) { unixMode = UnixStat.FILE_FLAG or permissions.toUnixNumeric() } zipOutStr.putNextEntry(entry) From e54ee7ec4ee332497d2ae007356f7650204736c1 Mon Sep 17 00:00:00 2001 From: Goooler Date: Fri, 12 Sep 2025 11:05:16 +0800 Subject: [PATCH 18/29] Update `JarBuilder` --- .../jengelman/gradle/plugins/shadow/util/JarBuilder.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/JarBuilder.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/JarBuilder.kt index 6da7edaf8..3fc1eb220 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/JarBuilder.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/JarBuilder.kt @@ -8,11 +8,13 @@ import kotlin.io.path.outputStream class JarBuilder( private val outputPath: Path, ) { - private val contents = mutableMapOf() + private val contents = mutableMapOf() private val entries = mutableSetOf() private val jos = JarOutputStream(outputPath.outputStream()) - fun insert(entry: String, content: String): JarBuilder = apply { + fun insert(entry: String, content: String): JarBuilder = insert(entry, content.toByteArray()) + + fun insert(entry: String, content: ByteArray): JarBuilder = apply { contents[entry] = content } @@ -25,7 +27,7 @@ class JarBuilder( } if (entries.add(entry)) { jos.putNextEntry(JarEntry(entry)) - content.byteInputStream().use { it.copyTo(jos) } + content.inputStream().use { it.copyTo(jos) } } } } From 69b763423561ac961ae2b3e5e2c27a4c1fbf413c Mon Sep 17 00:00:00 2001 From: Goooler Date: Fri, 12 Sep 2025 20:15:28 +0800 Subject: [PATCH 19/29] Revert "Use `kotlin("metadata-jvm")`" This reverts commit 874652fe8c9ce188040f610418f2be353364cf02. --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 6f5a35d50..ebd8f4a07 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -112,7 +112,7 @@ dependencies { implementation(libs.asm) implementation(libs.jdependency) implementation(libs.jdom2) - implementation(kotlin("metadata-jvm")) // libs.kotlin.metadata can't be added on my local device, but it works on CI. + implementation(libs.kotlin.metadata) implementation(libs.plexus.utils) implementation(libs.plexus.xml) From 909b0667977fbdfc2a2acc3f7fb84c06c2df05cd Mon Sep 17 00:00:00 2001 From: Goooler Date: Fri, 12 Sep 2025 21:27:00 +0800 Subject: [PATCH 20/29] No need to relocated `optionalAnnotationClasses` --- .../jengelman/gradle/plugins/shadow/RelocationTest.kt | 7 ++++--- .../gradle/plugins/shadow/tasks/ShadowCopyAction.kt | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt index 2f4a0a34f..8a7124a28 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt @@ -3,6 +3,7 @@ package com.github.jengelman.gradle.plugins.shadow import assertk.assertFailure import assertk.assertThat import assertk.assertions.contains +import assertk.assertions.isEmpty import assertk.assertions.isEqualTo import assertk.assertions.isInstanceOf import assertk.assertions.isNotEmpty @@ -635,9 +636,9 @@ class RelocationTest : BasePluginTest() { } assertThat(relocatedModule.version).isEqualTo(originalModule.version) - // They are both empty. - assertThat(relocatedModule.kmModule.optionalAnnotationClasses) - .isEqualTo(originalModule.kmModule.optionalAnnotationClasses) + // No implementation for writing this property yet. + // https://github.com/JetBrains/kotlin/blob/81502985ae0a2f5b21e121ffc180c3f4dd467e17/libraries/kotlinx-metadata/jvm/src/kotlin/metadata/jvm/KotlinModuleMetadata.kt#L71 + assertThat(relocatedModule.kmModule.optionalAnnotationClasses).isEmpty() val originalPkgParts = originalModule.kmModule.packageParts.entries val relocatedPkgParts = relocatedModule.kmModule.packageParts.entries 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 2418b1d4e..53fdd74ac 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 @@ -250,6 +250,7 @@ public open class ShadowCopyAction( private fun FileCopyDetails.remapKotlinModule() = file.readBytes().let { bytes -> val kmMetadata = KotlinModuleMetadata.read(bytes) val newKmModule = KmModule().apply { + // We don't need to relocate the nested properties in `optionalAnnotationClasses`, there is a very special use case for Kotlin Multiplatform. optionalAnnotationClasses += kmMetadata.kmModule.optionalAnnotationClasses packageParts += kmMetadata.kmModule.packageParts.map { (pkg, parts) -> val relocatedPkg = relocators.relocateClass(pkg) From 2a49961b405bb3aa502066008dc94825b9cac645 Mon Sep 17 00:00:00 2001 From: Goooler Date: Fri, 12 Sep 2025 21:35:07 +0800 Subject: [PATCH 21/29] Test `kotlin-stdlib.kotlin_module` --- .../gradle/plugins/shadow/RelocationTest.kt | 37 +++++++++++++----- .../META-INF/kotlin-stdlib-jdk8.kotlin_module | Bin 323 -> 0 bytes .../META-INF/kotlin-stdlib.kotlin_module | Bin 0 -> 7998 bytes 3 files changed, 27 insertions(+), 10 deletions(-) delete mode 100644 src/functionalTest/resources/META-INF/kotlin-stdlib-jdk8.kotlin_module create mode 100644 src/functionalTest/resources/META-INF/kotlin-stdlib.kotlin_module diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt index 8a7124a28..0f9af52b9 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt @@ -10,8 +10,8 @@ import assertk.assertions.isNotEmpty import assertk.assertions.isNotEqualTo import assertk.fail import com.github.jengelman.gradle.plugins.shadow.internal.mainClassAttributeKey +import com.github.jengelman.gradle.plugins.shadow.internal.requireResourceAsPath import com.github.jengelman.gradle.plugins.shadow.internal.requireResourceAsStream -import com.github.jengelman.gradle.plugins.shadow.internal.requireResourceAsText import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowCopyAction.Companion.CONSTANT_TIME_FOR_ZIP_ENTRIES import com.github.jengelman.gradle.plugins.shadow.util.Issue import com.github.jengelman.gradle.plugins.shadow.util.containsOnly @@ -19,6 +19,7 @@ import com.github.jengelman.gradle.plugins.shadow.util.getBytes import com.github.jengelman.gradle.plugins.shadow.util.runProcess import java.net.URLClassLoader import kotlin.io.path.appendText +import kotlin.io.path.readBytes import kotlin.io.path.writeText import kotlin.metadata.jvm.KotlinModuleMetadata import kotlin.metadata.jvm.UnstableMetadataApi @@ -605,10 +606,9 @@ class RelocationTest : BasePluginTest() { ) @Test fun relocateKotlinModuleFiles() { - val originalModuleFilePath = "META-INF/kotlin-stdlib-jdk8.kotlin_module" - val relocatedModuleFilePath = "META-INF/kotlin-stdlib-jdk8.shadow.kotlin_module" + val originalModuleFilePath = "META-INF/kotlin-stdlib.kotlin_module" val stdlibJar = buildJar("stdlib.jar") { - insert(originalModuleFilePath, requireResourceAsText(originalModuleFilePath)) + insert(originalModuleFilePath, requireResourceAsPath(originalModuleFilePath).readBytes()) } projectScript.appendText( """ @@ -623,6 +623,7 @@ class RelocationTest : BasePluginTest() { run(shadowJarPath) + val relocatedModuleFilePath = "META-INF/kotlin-stdlib.shadow.kotlin_module" assertThat(outputShadowedJar).useAll { containsOnly( relocatedModuleFilePath, @@ -635,7 +636,9 @@ class RelocationTest : BasePluginTest() { KotlinModuleMetadata.read(it.getBytes(relocatedModuleFilePath)) } - assertThat(relocatedModule.version).isEqualTo(originalModule.version) + assertThat(relocatedModule.version.toString()).isEqualTo("2.2.0") + assertThat(originalModule.version.toString()).isEqualTo("2.2.0") + // No implementation for writing this property yet. // https://github.com/JetBrains/kotlin/blob/81502985ae0a2f5b21e121ffc180c3f4dd467e17/libraries/kotlinx-metadata/jvm/src/kotlin/metadata/jvm/KotlinModuleMetadata.kt#L71 assertThat(relocatedModule.kmModule.optionalAnnotationClasses).isEmpty() @@ -648,13 +651,27 @@ class RelocationTest : BasePluginTest() { relocatedPkgParts.forEachIndexed { index, (pkg, parts) -> val (originalPkg, originalParts) = originalPkgParts.elementAt(index) assertThat(pkg).isNotEqualTo(originalPkg) - assertThat(pkg).isEqualTo(originalPkg.replace("kotlin.", "my.kotlin.")) + assertThat(pkg).isEqualTo(originalPkg.replace("kotlin", "my.kotlin")) - assertThat(parts.fileFacades).isNotEqualTo(originalParts.fileFacades) - assertThat(parts.fileFacades).isEqualTo(originalParts.fileFacades.map { it.replace("kotlin/", "my/kotlin/") }) + if (originalParts.fileFacades.isEmpty()) { + assertThat(parts.fileFacades).isEmpty() + } else { + assertThat(parts.fileFacades).isNotEmpty() + assertThat(parts.fileFacades).isNotEqualTo(originalParts.fileFacades) + assertThat(parts.fileFacades).isEqualTo(originalParts.fileFacades.map { it.replace("kotlin/", "my/kotlin/") }) + } - // They are both empty. - assertThat(parts.multiFileClassParts).isEqualTo(originalParts.multiFileClassParts) + if (originalParts.multiFileClassParts.isEmpty()) { + assertThat(parts.multiFileClassParts).isEmpty() + } else { + assertThat(parts.multiFileClassParts).isNotEmpty() + assertThat(parts.multiFileClassParts).isNotEqualTo(originalParts.multiFileClassParts) + assertThat(parts.multiFileClassParts).isEqualTo( + originalParts.multiFileClassParts.entries.associateTo(mutableMapOf()) { (name, facade) -> + name.replace("kotlin/", "my/kotlin/") to facade.replace("kotlin/", "my/kotlin/") + }, + ) + } } } diff --git a/src/functionalTest/resources/META-INF/kotlin-stdlib-jdk8.kotlin_module b/src/functionalTest/resources/META-INF/kotlin-stdlib-jdk8.kotlin_module deleted file mode 100644 index 8b5ad6cfb682a721ce7ef79b50dcba96e1f2f25a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 323 zcmZXPyAHxI3`Jc!VI&>}3Fv?jY#BOqVh0B3fCOI*Qjj6c@^kwrsHeKK7ezl~Ec1&JjIZREqWXl{$*M;Ka{TI3mC$ zj=n@8+8t!F(aH|*F%;{!Ifq0Q$4tl7H+ZQ@02USrkh8}UC`R-N|e?K)=q4) n)0ViVze^f~@z{qn{*@Hh+bZrg6yJ;CuTD+%8xHTy0vJ32yi;YD diff --git a/src/functionalTest/resources/META-INF/kotlin-stdlib.kotlin_module b/src/functionalTest/resources/META-INF/kotlin-stdlib.kotlin_module new file mode 100644 index 0000000000000000000000000000000000000000..aa2c0b61c78402e80bfcd2d02e2a3ba31239c80b GIT binary patch literal 7998 zcmb_gOLH5?5e7gKTntHYxqOI(Lvq&SD5mcS5PX|ap! zE+o;nxbpZP$t8aw7gtU>@SH<#smc#YZ5gLga2>cOCQ9E#nty4$>M^-LREU8Gc@XICs7q3qbM>up=F z_uUS7uEcf=r&8?FI04J^M;>pxPREMWFZn+A1SI*!S6pB*R_cN2bW9K8Tdm>T$PMSf zpE>PQ&vmSCIH|c%@;vhl7I~KATWzv-u6$~GWw*nnT&6&fX3B1_Z+g7p0+`?CykFz( zf#+MVIS}lr>7NoK&G*cJTaFcAe(lI}`>^G##!tA%oi^X*Ht%8qiPLJ_AK0`mQy=(! z?vPqq13JDD;FfLI=jO2uPd-Q{*niW^kf)^Bc5R!tMOqlk!XP-Q)sizAFykT` zMyw2RYVGSD@QY)3Aa9h1LgX2YWJ(Fw-n{_qxeL@P&NA6T(Okcb7Py`-H@=jW$Y8y5 zfpvcn(5^A8+~Z~pL)wq8!)7QpkxjIy#$V$=^UiaNi>yPj8KI*NMesA>xr>&>S3tnp z6VDy=EvHMntGQHgMS*b=SXTGVz6d797glh(%}-2uFcxBz&{Nqs3e1LrTQrd>&X3@* zXraynA%RAzOg4_fl6eKUzAu%QnNX=!F5Ej2D=M-?n&q(B(Hu^LUu z4!ODT)yDEA6gqs#$EoRQ_944Ee*TKb1Iz9pZH&1+%MZ3Bv>UL$C1b~x>ZRr5N-}V2 zB7ht@0@C$1LXDH7jC7UOnFOjJ-&wqkIr?e==EgRKj3 zGS+;3**-% zyUp|=N8p+5z&94?UZCkPDL#)m7Nr5X&wV3H>nvvg_X*2a%5F#tFP3VrZ^NyhvFnix z#awKgxJhktz3UiP$}uRqfWZ2ST^nbGLE(LGtjCRMwT6&Py4(;!k%&XNR5DqZjbqr` z%k+fji#!lIMxHht5?Ff!PalIZzhzn+MDZ`Ql7}51m4-uCp&IM45bx1I6>l?9U zh%}mG3)#PuETcrZ#`+FUl}K@#9GL;St6txApeh}~)e&+H2lItz1=M|A8KE>h&R>}J zD-iUEKuXXibJRuE103rffitnz!^j{DeU}b4b$*VI_EHEQ>2M5OA)#@iVL`n|7a#2? z%1*K{9SUZhs5V+|5xt;GcZE@q>nkBlskVha@8QSQLrEbOBu3s_ngr03Aqn-1-0TKp zp&OBX#sYG=Y@5E1n?l~n>?8{RNP5nT+9Wy_v>gztk08iHINPcfXt;uEtf4-d_Hz_^ z>PVt47pvLbsGh{-6bFuPo^T_xk49VwVmoXm)VPN@8@RiJQ*r~1Pf2VXA$>62lsIt^ zaSIXyd&uU(eLOq{+@7JUOsxqRTWQfO(^OMt#re9ZO$JC9Ne4AE7S#;brShgXSA>TuqMFIdhie8+zj)m-n#r@w{*#P~uFrov zpeKcIT(7Gs>8yaL0vlHqO4t+u(wD0d9A~C@UmKxB`-VN%+B>LVyxJV4#lc*fnjSc4 zCbp(98(+e1MG^7O{DAk24C)2-SrkItzOwNBV=_t{dOaHkJ=cVVG?B*5kKn+51qTM~ zVp*wAZ!Y=RK%p8Es!j)m2CHwxkTtw~-&WoR9FHPY1iAmUcP{|@dWq8B0{qVvIW`_(?hov!ga`I{ni}$q95|sp$ISjw+IVh1~NrS%PbsDwN zXmh5+uop7X78}bK1Gv5Wi2B?V`&V+9NVpA+_qGS}25gE~e5)0Qr5motQFLxbVMD`Z zA{)m!MUjk5f$#u22ywAUN+yIrSfn?5-3&+#-jU_anA_*3Kfo6VsE9Q>3aCr-x;yaN zz=&FyRohF|*%y&zgMrmC7Ml1^>e46^AR9-8yQC#9r|R;{&Z|#zm%Rf}%*6+SoN5H= zAQrLwQQ^-S^?i$_UivKk%s;@#EyhZY;|8J`V3Q7E(lSKq$lvpWj%^(?W)2so8>ZLg z0n3`U?Y`k13HnSQZWgYXD({u*b*Ak!56X?|;Q`Z*YKKRaTH|XrQ$9SXH)_ptgk)DAUf&wRSgg(Z_a)x8Q^A+qYBV$@6xAM()Qx(=Iq zZQ29Q7Kh3XY3cf5vsSLKnYx8azAL^5vgz=Ivx$$2pE2#34;gqfvUIiUxE^OqqcRnB zt9nqXeN9Xs)4xZ=iO(y6)xO9PV)gw29&dcxZ4ao8;~h5fpt#A>4tQR3HdU4pLrC!k z5c34f`1Gwv0JA)1I$Mq_bs%;q^(|856SlO2KI|Km-KxLM`}o{PX-%Y0*<1}*mxZ1Y z?yfp)x`!%nb~)=SmVD``nz#IXCt}R|kkMG}?m#dANx9ED^ z`yIFK-seiSsPA==`@DD>^z1y^SZo}aXb9PqjV~cKo7xk@^ovsMpnC8GId*Vx z*eFplwPbQl?Nlp!+bp|xST60go|kIX(iSpd{&{JyS!o@WYNh=OrBtTz^--m?S1r{U zi<$k963gl4Ek}qCPI>+$bdR#taq~7R0Qd6#piiOaog!Nv?h#Lx**B)=U_XJQ0Di=@ z!{e7Sb{YSTbt0oR#oyvp#-_Jiw6&%)ni3Ji+*A0*-|^fYp%}g^53`#v=N6l<3Bq8JnUrWn`sk3;zL zaQsq?OZrzKTo&Vsz8&H#A-og9Pr~tOI94%ELi#gEy{+#G>}`EdU=#WwuK<7!Z?%7#4|H6zuemohP%}3`i>mX)ya}^*lMN%^8}O zGbT0d{i{O8Dapu^jK|6KtpqX7Uu3*-p4@<-I}lWe1Sxxz9m*~!5SNl4n!`zyF3kA} z8dlH@E5g~4*pKqwJsdIDI6+}Tg<_wp@Ri|3-EQ&r}aeqP+~4thRzqOr1O6`xs|P#2SC}9 z(r{AZDe|7Mphg*zSNS*|)ra5fpp=Hx9^bS}n- literal 0 HcmV?d00001 From 96224e82c931222d29e8eec0bf17c78c19a2e0f4 Mon Sep 17 00:00:00 2001 From: Goooler Date: Fri, 12 Sep 2025 21:58:09 +0800 Subject: [PATCH 22/29] Update changelog --- docs/changes/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/changes/README.md b/docs/changes/README.md index c210100a6..99cf9ee87 100644 --- a/docs/changes/README.md +++ b/docs/changes/README.md @@ -15,6 +15,9 @@ mainClass = "my.Main" } ``` +- Support relocating Kotlin module files. ([#1539](https://github.com/GradleUp/shadow/pull/1539)) + The current implementation relocates all properties in `KotlinModuleMetadata` but `KmModule.optionalAnnotationClasses` + due to very limited usages of it. See more discussion [here](https://github.com/GradleUp/shadow/pull/1539#discussion_r2344237151). ### Changed From 65ba6d63884f0f702e73880a11b1b23b0f97429a Mon Sep 17 00:00:00 2001 From: Goooler Date: Tue, 16 Sep 2025 14:58:35 +0800 Subject: [PATCH 23/29] Fix merge --- .../github/jengelman/gradle/plugins/shadow/RelocationTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt index c1660df4e..7ab02e0e8 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt @@ -10,11 +10,11 @@ import assertk.assertions.isNotEmpty import assertk.assertions.isNotEqualTo import assertk.fail import com.github.jengelman.gradle.plugins.shadow.internal.mainClassAttributeKey -import com.github.jengelman.gradle.plugins.shadow.internal.requireResourceAsPath -import com.github.jengelman.gradle.plugins.shadow.internal.requireResourceAsStream import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowCopyAction.Companion.CONSTANT_TIME_FOR_ZIP_ENTRIES import com.github.jengelman.gradle.plugins.shadow.testkit.containsOnly import com.github.jengelman.gradle.plugins.shadow.testkit.getBytes +import com.github.jengelman.gradle.plugins.shadow.testkit.requireResourceAsPath +import com.github.jengelman.gradle.plugins.shadow.testkit.requireResourceAsStream import com.github.jengelman.gradle.plugins.shadow.util.Issue import com.github.jengelman.gradle.plugins.shadow.util.runProcess import java.net.URLClassLoader From c5c71f1b80e11655f5a241cf31b73befdef19872 Mon Sep 17 00:00:00 2001 From: Goooler Date: Fri, 19 Sep 2025 13:28:52 +0800 Subject: [PATCH 24/29] Use `OptIn` --- .../github/jengelman/gradle/plugins/shadow/RelocationTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt index 7ab02e0e8..63d48e7f7 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt @@ -600,7 +600,7 @@ class RelocationTest : BasePluginTest() { assertThat(relocatedBytes).isEqualTo(originalBytes) } - @UnstableMetadataApi + @OptIn(UnstableMetadataApi::class) @Issue( "https://github.com/GradleUp/shadow/issues/843", ) From 3dca3016d5a0dfe5d9702e6007c3ebe2ae2c9483 Mon Sep 17 00:00:00 2001 From: Zongle Wang Date: Fri, 19 Sep 2025 13:35:36 +0800 Subject: [PATCH 25/29] Update src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowCopyAction.kt Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../jengelman/gradle/plugins/shadow/tasks/ShadowCopyAction.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 53fdd74ac..b62a7fac4 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 @@ -278,7 +278,7 @@ public open class ShadowCopyAction( unixMode = UnixStat.FILE_FLAG or permissions.toUnixNumeric() } zipOutStr.putNextEntry(entry) - zipOutStr.write(newKmMetadata.write()) + zipOutStr.write(newBytes) zipOutStr.closeEntry() } From 26a13370e4d597483b34529eef0c98c7d38b00eb Mon Sep 17 00:00:00 2001 From: Zongle Wang Date: Fri, 19 Sep 2025 13:36:33 +0800 Subject: [PATCH 26/29] Update docs/changes/README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/changes/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changes/README.md b/docs/changes/README.md index 7fcf582fa..122e2cd78 100644 --- a/docs/changes/README.md +++ b/docs/changes/README.md @@ -19,7 +19,7 @@ This is useful when you want to customize the output directory of the start scripts and the application distribution. - Support relocating Kotlin module files. ([#1539](https://github.com/GradleUp/shadow/pull/1539)) The current implementation relocates all properties in `KotlinModuleMetadata` but `KmModule.optionalAnnotationClasses` - due to very limited usages of it. See more discussion [here](https://github.com/GradleUp/shadow/pull/1539#discussion_r2344237151). + due to very limited usage of it. See more discussion [here](https://github.com/GradleUp/shadow/pull/1539#discussion_r2344237151). ### Changed From 22fc945637b94cddc6ed4f6134803addd45aaa7a Mon Sep 17 00:00:00 2001 From: Goooler Date: Fri, 19 Sep 2025 13:42:18 +0800 Subject: [PATCH 27/29] Update `relocateKotlinModuleFiles` --- .../gradle/plugins/shadow/RelocationTest.kt | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt index 63d48e7f7..78bdac931 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt @@ -616,7 +616,7 @@ class RelocationTest : BasePluginTest() { ${implementationFiles(stdlibJar)} } $shadowJarTask { - relocate 'kotlin', 'my.kotlin' + relocate('kotlin', 'my.kotlin') } """.trimIndent(), ) @@ -647,28 +647,32 @@ class RelocationTest : BasePluginTest() { val relocatedPkgParts = relocatedModule.kmModule.packageParts.entries // They are not empty and different. assertThat(originalPkgParts).isNotEqualTo(relocatedPkgParts) + assertThat(originalPkgParts.size).isEqualTo(relocatedPkgParts.size) - relocatedPkgParts.forEachIndexed { index, (pkg, parts) -> + relocatedPkgParts.forEachIndexed { index, (relocatedPkg, relocatedParts) -> val (originalPkg, originalParts) = originalPkgParts.elementAt(index) - assertThat(pkg).isNotEqualTo(originalPkg) - assertThat(pkg).isEqualTo(originalPkg.replace("kotlin", "my.kotlin")) + assertThat(relocatedPkg).isNotEqualTo(originalPkg) + assertThat(relocatedPkg).isEqualTo(originalPkg.replace("kotlin", "my.kotlin")) if (originalParts.fileFacades.isEmpty()) { - assertThat(parts.fileFacades).isEmpty() + assertThat(relocatedParts.fileFacades).isEmpty() } else { - assertThat(parts.fileFacades).isNotEmpty() - assertThat(parts.fileFacades).isNotEqualTo(originalParts.fileFacades) - assertThat(parts.fileFacades).isEqualTo(originalParts.fileFacades.map { it.replace("kotlin/", "my/kotlin/") }) + assertThat(relocatedParts.fileFacades).isNotEmpty() + assertThat(relocatedParts.fileFacades).isNotEqualTo(originalParts.fileFacades) + assertThat(relocatedParts.fileFacades).isEqualTo( + originalParts.fileFacades.map { it.replace("kotlin/", "my/kotlin/") }, + ) } if (originalParts.multiFileClassParts.isEmpty()) { - assertThat(parts.multiFileClassParts).isEmpty() + assertThat(relocatedParts.multiFileClassParts).isEmpty() } else { - assertThat(parts.multiFileClassParts).isNotEmpty() - assertThat(parts.multiFileClassParts).isNotEqualTo(originalParts.multiFileClassParts) - assertThat(parts.multiFileClassParts).isEqualTo( + assertThat(relocatedParts.multiFileClassParts).isNotEmpty() + assertThat(relocatedParts.multiFileClassParts).isNotEqualTo(originalParts.multiFileClassParts) + assertThat(relocatedParts.multiFileClassParts).isEqualTo( originalParts.multiFileClassParts.entries.associateTo(mutableMapOf()) { (name, facade) -> - name.replace("kotlin/", "my/kotlin/") to facade.replace("kotlin/", "my/kotlin/") + name.replace("kotlin/", "my/kotlin/") to + facade.replace("kotlin/", "my/kotlin/") }, ) } From d3215ec8e9d6c6c2b8c077e6be89b469197de0a4 Mon Sep 17 00:00:00 2001 From: Goooler Date: Sun, 21 Sep 2025 15:34:41 +0800 Subject: [PATCH 28/29] Cleanup --- .../github/jengelman/gradle/plugins/shadow/RelocationTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt index 78bdac931..d82b86b0c 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt @@ -600,11 +600,11 @@ class RelocationTest : BasePluginTest() { assertThat(relocatedBytes).isEqualTo(originalBytes) } - @OptIn(UnstableMetadataApi::class) @Issue( "https://github.com/GradleUp/shadow/issues/843", ) @Test + @OptIn(UnstableMetadataApi::class) fun relocateKotlinModuleFiles() { val originalModuleFilePath = "META-INF/kotlin-stdlib.kotlin_module" val stdlibJar = buildJar("stdlib.jar") { From 02160df9e2582356a6c4e888ca3341076aa83e46 Mon Sep 17 00:00:00 2001 From: Zongle Wang Date: Sun, 21 Sep 2025 15:39:07 +0800 Subject: [PATCH 29/29] Update src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../github/jengelman/gradle/plugins/shadow/RelocationTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt index d82b86b0c..668fccd2e 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt @@ -639,7 +639,7 @@ class RelocationTest : BasePluginTest() { assertThat(relocatedModule.version.toString()).isEqualTo("2.2.0") assertThat(originalModule.version.toString()).isEqualTo("2.2.0") - // No implementation for writing this property yet. + // No implementation for writing the optionalAnnotationClasses property yet. // https://github.com/JetBrains/kotlin/blob/81502985ae0a2f5b21e121ffc180c3f4dd467e17/libraries/kotlinx-metadata/jvm/src/kotlin/metadata/jvm/KotlinModuleMetadata.kt#L71 assertThat(relocatedModule.kmModule.optionalAnnotationClasses).isEmpty()