From 9040faaf116ad1c54ce48f3f52a19a607ffd96c0 Mon Sep 17 00:00:00 2001 From: Goooler Date: Sat, 5 Sep 2026 15:57:54 +0800 Subject: [PATCH 01/14] Gate default source inputs by generateSourcesJar --- .../gradle/plugins/shadow/ShadowJavaPlugin.kt | 10 ++++- .../gradle/plugins/shadow/ShadowKmpPlugin.kt | 8 +++- .../gradle/plugins/shadow/tasks/ShadowJar.kt | 42 +++++++++++-------- .../plugins/shadow/ShadowPropertiesTest.kt | 9 ++-- 4 files changed, 46 insertions(+), 23 deletions(-) diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.kt index 9ed6780d7..c44b5f4e2 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.kt @@ -44,10 +44,18 @@ constructor(private val softwareComponentFactory: SoftwareComponentFactory) : Pl val taskProvider = registerShadowJarCommon(tasks.named("jar", Jar::class.java)) { task -> task.from(mainSourceSet.map { it.output }) - task.sourceSetsSourceDirs.convention(mainSourceSet.map { it.allSource.srcDirs }) task.generateSourcesJar.convention( provider { configurations.findByName(SOURCES_ELEMENTS_CONFIGURATION_NAME) != null } ) + task.sourceSetsSourceDirs.convention( + task.generateSourcesJar.flatMap { generate -> + if (generate) { + mainSourceSet.map { it.allSource.srcDirs } + } else { + provider { emptySet() } + } + } + ) task.configurations.convention(provider { listOf(runtimeConfiguration) }) } artifacts.add(configurations.shadow.name, taskProvider) diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowKmpPlugin.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowKmpPlugin.kt index 68c1d389f..7a765e81e 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowKmpPlugin.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowKmpPlugin.kt @@ -37,7 +37,13 @@ public abstract class ShadowKmpPlugin : Plugin { registerShadowJarCommon(tasks.named(target.artifactsTaskName, Jar::class.java)) { task -> task.from(kotlinJvmMain.map { it.output.allOutputs }) task.sourceSetsSourceDirs.convention( - kotlinJvmMain.map { it.allKotlinSourceSets.flatMap { ss -> ss.kotlin.srcDirs } } + task.generateSourcesJar.flatMap { generate -> + if (generate) { + kotlinJvmMain.map { it.allKotlinSourceSets.flatMap { ss -> ss.kotlin.srcDirs } } + } else { + provider { emptySet() } + } + } ) task.configurations.convention( kotlinJvmMain 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 e1ed6c22e..6aaaa6d60 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 @@ -201,6 +201,21 @@ public abstract class ShadowJar : Jar() { dependencyFilter.zip(configurations) { df, cs -> df.resolve(cs) } } + /** + * If `true`, generates a companion shadowed sources JAR containing project and dependency + * sources. + * + * In projects applying the `shadow` plugin for Java, this convention defaults to `true` when + * `java.withSourcesJar()` is enabled, and `false` otherwise. + */ + @get:Input + @get:Option( + option = "generate-sources-jar", + description = + "Generates a companion shadowed sources JAR containing project and dependency sources.", + ) + public open val generateSourcesJar: Property = objectFactory.property(false) + /** * Source JARs resolved from bundled dependencies to be merged into the companion shadowed sources * JAR. @@ -208,8 +223,16 @@ public abstract class ShadowJar : Jar() { @get:InputFiles @get:PathSensitive(PathSensitivity.NONE) public open val includedSourcesJars: ConfigurableFileCollection = objectFactory.fileCollection { - dependencyFilter.zip(configurations) { df, cs -> - (df as? DefaultDependencyFilter)?.resolveSourcesJars(cs) ?: project.files() + // Avoid resolving sources JARs during task input snapshotting when sources JAR generation is + // disabled. + generateSourcesJar.flatMap { + if (it) { + dependencyFilter.zip(configurations) { df, cs -> + (df as? DefaultDependencyFilter)?.resolveSourcesJars(cs) ?: project.files() + } + } else { + project.provider { emptySet() } + } } } @@ -224,21 +247,6 @@ public abstract class ShadowJar : Jar() { @get:PathSensitive(PathSensitivity.RELATIVE) public open val sourceSetsSourceDirs: ConfigurableFileCollection = objectFactory.fileCollection() - /** - * If `true`, generates a companion shadowed sources JAR containing project and dependency - * sources. - * - * In projects applying the `shadow` plugin for Java, this convention defaults to `true` when - * `java.withSourcesJar()` is enabled, and `false` otherwise. - */ - @get:Input - @get:Option( - option = "generate-sources-jar", - description = - "Generates a companion shadowed sources JAR containing project and dependency sources.", - ) - public open val generateSourcesJar: Property = objectFactory.property(false) - /** * The destination location of the companion shadowed sources JAR. * diff --git a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPropertiesTest.kt b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPropertiesTest.kt index 04994e251..d18d8eaa7 100644 --- a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPropertiesTest.kt +++ b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPropertiesTest.kt @@ -168,10 +168,7 @@ class ShadowPropertiesTest { isEqualTo(destinationDirectory.file("my-project-1.0.0-all-sources.jar").get().asFile) isEqualTo(projectDir.resolve("build/libs/my-project-1.0.0-all-sources.jar")) } - assertThat(sourceSetsSourceDirs.files) - .containsOnly( - *javaPluginExtension.sourceSets.getByName("main").allSource.srcDirs.toTypedArray() - ) + assertThat(sourceSetsSourceDirs.files).isEmpty() assertThat(includedSourcesJars.files).isEmpty() } } @@ -183,6 +180,10 @@ class ShadowPropertiesTest { javaPluginExtension.withSourcesJar() val shadowJarTask = tasks.shadowJar.get() assertThat(shadowJarTask.generateSourcesJar.get()).isTrue() + assertThat(shadowJarTask.sourceSetsSourceDirs.files) + .containsOnly( + *javaPluginExtension.sourceSets.getByName("main").allSource.srcDirs.toTypedArray() + ) } @Test From 74ac176a82429866a07ea779e7d23289edb89846 Mon Sep 17 00:00:00 2001 From: Goooler Date: Sat, 5 Sep 2026 16:02:58 +0800 Subject: [PATCH 02/14] Skip shadowSourcesElements variant when generateSourcesJar is disabled --- .../gradle/plugins/shadow/PublishingTest.kt | 40 +++++++++++++++++++ .../gradle/plugins/shadow/ShadowJavaPlugin.kt | 9 +++-- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt index 4c3fab5e3..eb763ec5e 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt @@ -348,6 +348,46 @@ class PublishingTest : BasePluginTest() { assertShadowSourcesVariantCommon(gmm) } + @Test + fun dontPublishSourcesWhenGenerateSourcesJarDisabled() { + projectScript.appendText( + publishConfiguration( + projectBlock = + """ + |java { + | withSourcesJar() + |} + """ + .trimMargin(), + shadowBlock = + """ + |archiveClassifier = '' + |generateSourcesJar = false + """ + .trimMargin(), + publicationsBlock = + """ + |shadow(MavenPublication) { + | from components.shadow + |} + """ + .trimMargin(), + ) + ) + + val result = publish(infoArgument) + + assertThat(result.output) + .contains("Skipping adding shadowSourcesElements variant to shadow component.") + val artifactRoot = "my/maven/1.0" + assertThat(repoPath(artifactRoot).entries.filter { it.contains("sources") }).isEmpty() + assertShadowJarCommon(repoJarPath("$artifactRoot/maven-1.0.jar")) + assertPomCommon(repoPath("$artifactRoot/maven-1.0.pom")) + val gmm = gmmAdapter.fromJson(repoPath("$artifactRoot/maven-1.0.module")) + assertShadowVariantCommon(gmm) + assertThat(gmm.variantNames).containsOnly(SHADOW_RUNTIME_ELEMENTS_CONFIGURATION_NAME) + } + @Test fun publishCustomShadowJar() { projectScript.appendText( diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.kt index c44b5f4e2..802b139d8 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.kt @@ -138,8 +138,11 @@ constructor(private val softwareComponentFactory: SoftwareComponentFactory) : Pl val addIntoJavaComponent = shadow.addShadowVariantIntoJavaComponent val shadowRuntimeElements = configurations.shadowRuntimeElements val shadowSourcesElements = configurations.shadowSourcesElements - // If `withSourcesJar` is present. + // If `withSourcesJar` is present and `generateSourcesJar` is enabled. val sourcesElements = { configurations.findByName(SOURCES_ELEMENTS_CONFIGURATION_NAME) } + val shouldAddSources = { + sourcesElements() != null && tasks.shadowJar.flatMap { it.generateSourcesJar }.get() + } val shadowComponent = softwareComponentFactory.adhoc(COMPONENT_NAME) components.add(shadowComponent) @@ -152,7 +155,7 @@ constructor(private val softwareComponentFactory: SoftwareComponentFactory) : Pl shadowComponent.addVariants( outgoingConfiguration = shadowSourcesElements, logger = logger, - shouldAdd = { sourcesElements() != null }, + shouldAdd = shouldAddSources, ) components.named("java", AdhocComponentWithVariants::class.java) { component -> @@ -166,7 +169,7 @@ constructor(private val softwareComponentFactory: SoftwareComponentFactory) : Pl component.addVariants( outgoingConfiguration = shadowSourcesElements, logger = logger, - shouldAdd = { addIntoJavaComponent.get() && sourcesElements() != null }, + shouldAdd = { addIntoJavaComponent.get() && shouldAddSources() }, ) { mapToOptional() } From 00d47e92418d5efa5917a2eff3699212ac18c203 Mon Sep 17 00:00:00 2001 From: Goooler Date: Sat, 5 Sep 2026 16:07:19 +0800 Subject: [PATCH 03/14] Document replacing standard artifacts and avoiding sourcesJar task conflict with empty classifier --- docs/publishing/README.md | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/docs/publishing/README.md b/docs/publishing/README.md index 147227b6b..b229f3c25 100644 --- a/docs/publishing/README.md +++ b/docs/publishing/README.md @@ -295,8 +295,13 @@ You may want to publish the shadowed JAR instead of the original JAR. This can b ``` Because the default `archiveClassifier` of [`Jar`][Jar] is `""` (empty), setting the `archiveClassifier` of -[`ShadowJar`][ShadowJar] to `""` (empty) will make collisions between the outputs of these two tasks in some cases. If -you don't need the standard JAR, you can disable the `jar` task like: +[`ShadowJar`][ShadowJar] to `""` (empty) will make collisions between the outputs of standard tasks and `shadowJar`: + +- The binary shadowed JAR is output to `-.jar`, conflicting with the `jar` task. +- When `generateSourcesJar` is enabled (such as when `java.withSourcesJar()` is used), the companion shadowed sources + JAR is output to `--sources.jar`, conflicting with the standard `sourcesJar` task. + +If you want to replace standard JARs with the shadowed ones, disable the standard `jar` and `sourcesJar` tasks: === ":material-language-kotlin: build.gradle.kts" @@ -304,6 +309,11 @@ you don't need the standard JAR, you can disable the `jar` task like: tasks.jar { enabled = false } + + // If `java.withSourcesJar()` is enabled: + tasks.matching { it.name == "sourcesJar" }.configureEach { + enabled = false + } ``` === ":simple-apachegroovy: build.gradle" @@ -312,9 +322,14 @@ you don't need the standard JAR, you can disable the `jar` task like: tasks.named('jar', Jar) { enabled = false } + + // If `java.withSourcesJar()` is enabled: + tasks.matching { it.name == 'sourcesJar' }.configureEach { + enabled = false + } ``` -Or set a different `archiveClassifier` for the standard [`Jar`][Jar] like: +Or set different `archiveClassifier` values for the standard tasks: === ":material-language-kotlin: build.gradle.kts" @@ -322,6 +337,11 @@ Or set a different `archiveClassifier` for the standard [`Jar`][Jar] like: tasks.jar { archiveClassifier = "ignored" } + + // If `java.withSourcesJar()` is enabled: + tasks.matching { it.name == "sourcesJar" }.configureEach { + (this as org.gradle.jvm.tasks.Jar).archiveClassifier = "ignored-sources" + } ``` === ":simple-apachegroovy: build.gradle" @@ -330,6 +350,11 @@ Or set a different `archiveClassifier` for the standard [`Jar`][Jar] like: tasks.named('jar', Jar) { archiveClassifier = 'ignored' } + + // If `java.withSourcesJar()` is enabled: + tasks.matching { it.name == 'sourcesJar' }.configureEach { + archiveClassifier = 'ignored-sources' + } ``` ## Publishing the Shadowed Gradle Plugins From 52536ee56a04e7435ad416548dc8bfd63bcb80f1 Mon Sep 17 00:00:00 2001 From: Goooler Date: Sat, 5 Sep 2026 16:17:46 +0800 Subject: [PATCH 04/14] Track compiled class source files with ASM to prevent minimize from deleting used sources --- .../gradle/plugins/shadow/BasePluginTest.kt | 8 +- .../shadow/util/LocalMavenRepository.kt | 7 +- .../plugins/shadow/internal/SourcesJar.kt | 108 ++++++++++++--- .../gradle/plugins/shadow/tasks/ShadowJar.kt | 2 + .../plugins/shadow/internal/SourcesJarTest.kt | 130 ++++-------------- 5 files changed, 129 insertions(+), 126 deletions(-) diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/BasePluginTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/BasePluginTest.kt index 0c3b31f68..77b64140f 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/BasePluginTest.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/BasePluginTest.kt @@ -408,10 +408,16 @@ abstract class BasePluginTest { } } - fun createEmptyClassBytes(internalName: String): ByteArray { + fun createEmptyClassBytes( + internalName: String, + sourceFile: String? = "${internalName.substringAfterLast('/')}.java", + ): ByteArray { return ClassWriter(0) .apply { visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, internalName, null, "java/lang/Object", null) + if (sourceFile != null) { + visitSource(sourceFile, null) + } visitEnd() } .toByteArray() diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/LocalMavenRepository.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/LocalMavenRepository.kt index 26dfb6d0e..5eead420c 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/LocalMavenRepository.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/LocalMavenRepository.kt @@ -80,8 +80,11 @@ fun createDefaultLocalMavenRepository(junitJar: Path): AppendableMavenRepository val k = jarModule("my", "k", "1.0") { buildJar { - insert("k/CustomUtils.class", createEmptyClassBytes("k/CustomUtils")) - insert("k/CustomUnusedUtils.class", createEmptyClassBytes("k/CustomUnusedUtils")) + insert("k/CustomUtils.class", createEmptyClassBytes("k/CustomUtils", "Utils.kt")) + insert( + "k/CustomUnusedUtils.class", + createEmptyClassBytes("k/CustomUnusedUtils", "UnusedUtils.kt"), + ) } buildSourcesJar { insert( diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/SourcesJar.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/SourcesJar.kt index 3a6e40c27..48c3e0895 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/SourcesJar.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/SourcesJar.kt @@ -10,6 +10,8 @@ internal fun generateShadowedSourcesJar( sourcesJarFile: File, sourceSetsSourceDirs: Iterable, includedSourcesJars: Iterable, + classesDirs: Iterable = emptyList(), + dependencies: Iterable = emptyList(), relocators: Iterable, unusedClasses: Set = emptySet(), entryCompression: ZipEntryCompression, @@ -22,6 +24,12 @@ internal fun generateShadowedSourcesJar( val visitedFiles = mutableSetOf() val charset = metadataCharset?.let(Charset::forName) ?: Charsets.UTF_8 + val sourceToClasses = + if (unusedClasses.isNotEmpty()) { + buildSourceToClassesMap(classesDirs = classesDirs, dependencies = dependencies) + } else { + emptyMap() + } try { sourcesJarFile @@ -55,9 +63,9 @@ internal fun generateShadowedSourcesJar( val text = file.readText(charset) val pkg = extractPackage(text) val simpleName = file.name - if (isUnused(simpleName, pkg, text, unusedClasses)) return@forEach val canonicalPath = if (pkg.isEmpty()) simpleName else "${pkg.replace('.', '/')}/$simpleName" + if (isUnused(canonicalPath, unusedClasses, sourceToClasses)) return@forEach val relocatedPath = relocators.relocatePath(canonicalPath) if (visitedFiles.add(relocatedPath)) { var transformedText = text @@ -113,9 +121,9 @@ internal fun generateShadowedSourcesJar( val text = getInputStream(entry).bufferedReader(charset).readText() val pkg = extractPackage(text) val simpleName = name.substringAfterLast('/') - if (isUnused(simpleName, pkg, text, unusedClasses)) return@forEach val canonicalPath = if (pkg.isEmpty()) simpleName else "${pkg.replace('.', '/')}/$simpleName" + if (isUnused(canonicalPath, unusedClasses, sourceToClasses)) return@forEach val relocatedPath = relocators.relocatePath(canonicalPath) if (visitedFiles.add(relocatedPath)) { var transformedText = text @@ -171,34 +179,92 @@ internal fun generateShadowedSourcesJar( private val packageRegex = """(?:^|\n)\s*package\s+([a-zA-Z0-9_.]+)""".toRegex() -private val jvmNameRegex = - """@file\s*:\s*(?:\[[^]]*?)?(?:kotlin\s*\.\s*jvm\s*\.\s*)?JvmName\s*\(\s*(?:name\s*=\s*)?"([^"]+)"""" - .toRegex() - internal fun extractPackage(text: String): String { val matches = packageRegex.findAll(text).map { it.groupValues[1] }.toList() return if (matches.isEmpty()) "" else matches.joinToString(".") } +internal fun buildSourceToClassesMap( + classesDirs: Iterable, + dependencies: Iterable, +): Map> { + val sourceToClasses = mutableMapOf>() + + fun processClassBytes(bytes: ByteArray) { + try { + var internalName: String? = null + var sourceFile: String? = null + val reader = org.vafer.jdeb.shaded.objectweb.asm.ClassReader(bytes) + reader.accept( + object : + org.vafer.jdeb.shaded.objectweb.asm.ClassVisitor( + org.vafer.jdeb.shaded.objectweb.asm.Opcodes.ASM9 + ) { + override fun visit( + version: Int, + access: Int, + name: String, + signature: String?, + superName: String?, + interfaces: Array?, + ) { + internalName = name + super.visit(version, access, name, signature, superName, interfaces) + } + + override fun visitSource(source: String?, debug: String?) { + sourceFile = source + super.visitSource(source, debug) + } + }, + org.vafer.jdeb.shaded.objectweb.asm.ClassReader.SKIP_CODE or + org.vafer.jdeb.shaded.objectweb.asm.ClassReader.SKIP_FRAMES, + ) + + val name = internalName ?: return + val source = sourceFile ?: return + val pkg = name.substringBeforeLast('/', "") + val canonicalSourcePath = if (pkg.isEmpty()) source else "$pkg/$source" + val className = name.replace('/', '.') + sourceToClasses.getOrPut(canonicalSourcePath) { mutableSetOf() }.add(className) + } catch (_: Exception) { + // Ignore invalid class files + } + } + + for (dir in classesDirs.filter { it.isDirectory }) { + dir + .walkTopDown() + .filter { it.isFile && it.name.endsWith(".class") } + .forEach { file -> processClassBytes(file.readBytes()) } + } + + for (file in + dependencies.filter { it.isFile && (it.name.endsWith(".jar") || it.name.endsWith(".zip")) }) { + try { + file.useZip { + entries() + .toList() + .filter { !it.isDirectory && it.name.endsWith(".class") } + .forEach { entry -> processClassBytes(getInputStream(entry).readBytes()) } + } + } catch (_: Exception) { + // Ignore invalid archives + } + } + + return sourceToClasses +} + internal fun isUnused( - fileName: String, - pkg: String, - text: String, + canonicalPath: String, unusedClasses: Set, + sourceToClasses: Map>, ): Boolean { if (unusedClasses.isEmpty()) return false - val simpleName = fileName.substringBeforeLast('.') - val className = if (pkg.isEmpty()) simpleName else "$pkg.$simpleName" - if (unusedClasses.contains(className)) return true - - if (fileName.endsWith(".kt")) { - val customJvmName = jvmNameRegex.find(text)?.groupValues?.get(1) - val facadeName = customJvmName ?: "${simpleName}Kt" - val facadeClassName = if (pkg.isEmpty()) facadeName else "$pkg.$facadeName" - if (unusedClasses.contains(facadeClassName)) return true - } - - return false + val classes = sourceToClasses[canonicalPath] ?: return false + if (classes.isEmpty()) return false + return classes.all { it in unusedClasses } } private fun isSourceFile(path: String): Boolean { 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 6aaaa6d60..98fab688c 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 @@ -825,6 +825,8 @@ public abstract class ShadowJar : Jar() { sourcesJarFile = archiveSourcesFile.get().asFile, sourceSetsSourceDirs = sourceSetsSourceDirs.files, includedSourcesJars = includedSourcesJars.files, + classesDirs = sourceSetsClassesDirs.files, + dependencies = includedDependencies.files, relocators = relocators.get() + packageRelocators, unusedClasses = unusedClasses, entryCompression = entryCompression, diff --git a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/SourcesJarTest.kt b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/SourcesJarTest.kt index f02787e7d..aa5a4703d 100644 --- a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/SourcesJarTest.kt +++ b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/SourcesJarTest.kt @@ -67,115 +67,41 @@ class SourcesJarTest { val unusedSet = setOf( "com.example.UnusedJava", + "com.example.UnusedJava\$Inner", "com.example.UnusedKtClass", "com.example.DefaultFacadeKt", "com.example.CustomFacade", ) + val sourceToClasses = + mapOf( + "com/example/UnusedJava.java" to + setOf("com.example.UnusedJava", "com.example.UnusedJava\$Inner"), + "com/example/PartiallyUsedJava.java" to + setOf("com.example.UnusedJava", "com.example.UsedHelper"), + "com/example/UsedJava.java" to setOf("com.example.UsedJava"), + "com/example/UnusedKtClass.kt" to setOf("com.example.UnusedKtClass"), + "com/example/DefaultFacade.kt" to setOf("com.example.DefaultFacadeKt"), + "com/example/Utils.kt" to setOf("com.example.CustomFacade"), + "com/example/MixedUtils.kt" to setOf("com.example.CustomFacade", "com.example.UsedClass"), + "Main.java" to setOf("Main"), + ) - assertThat(isUnused("UnusedJava.java", "com.example", "class UnusedJava {}", unusedSet)) - .isTrue() - assertThat(isUnused("UsedJava.java", "com.example", "class UsedJava {}", unusedSet)).isFalse() + // All classes unused in file -> unused + assertThat(isUnused("com/example/UnusedJava.java", unusedSet, sourceToClasses)).isTrue() + assertThat(isUnused("com/example/UnusedKtClass.kt", unusedSet, sourceToClasses)).isTrue() + assertThat(isUnused("com/example/DefaultFacade.kt", unusedSet, sourceToClasses)).isTrue() + assertThat(isUnused("com/example/Utils.kt", unusedSet, sourceToClasses)).isTrue() + assertThat(isUnused("Main.java", setOf("Main"), sourceToClasses)).isTrue() - assertThat(isUnused("UnusedKtClass.kt", "com.example", "class UnusedKtClass", unusedSet)) - .isTrue() - assertThat( - isUnused( - "DefaultFacade.kt", - "com.example", - "fun topLevel() {}", - unusedSet, - ) - ) - .isTrue() - assertThat( - isUnused( - "Utils.kt", - "com.example", - """ - @file:JvmName("CustomFacade") - package com.example - fun util() {} - """ - .trimIndent(), - unusedSet, - ) - ) - .isTrue() - assertThat( - isUnused( - "Utils.kt", - "com.example", - """ - @file:kotlin.jvm.JvmName(name = "CustomFacade") - package com.example - fun util() {} - """ - .trimIndent(), - unusedSet, - ) - ) - .isTrue() - assertThat( - isUnused( - "UsedUtils.kt", - "com.example", - """ - @file:JvmName("UsedFacade") - package com.example - fun util() {} - """ - .trimIndent(), - unusedSet, - ) - ) - .isFalse() - assertThat( - isUnused( - "BracketedUtils.kt", - "com.example", - """ - @file:[JvmName("CustomFacade")] - package com.example - fun util() {} - """ - .trimIndent(), - unusedSet, - ) - ) - .isTrue() - assertThat( - isUnused( - "BracketedMultiUtils.kt", - "com.example", - """ - @file:[Suppress("unused") JvmName("CustomFacade")] - package com.example - fun util() {} - """ - .trimIndent(), - unusedSet, - ) - ) - .isTrue() - assertThat( - isUnused( - "BracketedMultiUtilsReversed.kt", - "com.example", - """ - @file:[JvmName("CustomFacade") Suppress("unused")] - package com.example - fun util() {} - """ - .trimIndent(), - unusedSet, - ) - ) - .isTrue() + // At least one class is used in file -> NOT unused (kept!) + assertThat(isUnused("com/example/PartiallyUsedJava.java", unusedSet, sourceToClasses)).isFalse() + assertThat(isUnused("com/example/MixedUtils.kt", unusedSet, sourceToClasses)).isFalse() + assertThat(isUnused("com/example/UsedJava.java", unusedSet, sourceToClasses)).isFalse() - assertThat(isUnused("UnusedJava.java", "com.example", "class UnusedJava {}", emptySet())) - .isFalse() - assertThat(isUnused("Main.java", "", "class Main {}", setOf("Main"))).isTrue() - assertThat(isUnused("Main.java", "", "class Main {}", setOf("Other"))).isFalse() + // Unknown source file or empty unused set -> kept + assertThat(isUnused("com/example/Unknown.java", unusedSet, sourceToClasses)).isFalse() + assertThat(isUnused("com/example/UnusedJava.java", emptySet(), sourceToClasses)).isFalse() + assertThat(isUnused("Main.java", setOf("Other"), sourceToClasses)).isFalse() } @Test From b2342dca81b4c2e4e21845ab5609f5c5d7ecbe8c Mon Sep 17 00:00:00 2001 From: Goooler Date: Sat, 5 Sep 2026 16:27:55 +0800 Subject: [PATCH 05/14] Fix include and dynamic exclude filtering in source content relocation --- .../shadow/relocation/SimpleRelocator.kt | 109 +++++++++++------- .../shadow/relocation/SimpleRelocatorTest.kt | 74 +++++++++++- 2 files changed, 139 insertions(+), 44 deletions(-) diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/SimpleRelocator.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/SimpleRelocator.kt index c9677d0d6..70f783f48 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/SimpleRelocator.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/SimpleRelocator.kt @@ -66,32 +66,16 @@ constructor( if (!excludes.isNullOrEmpty()) { this.excludes.addAll(excludes) } - - if (!rawString) { - // Create exclude pattern sets for sources. - for (exclude in this.excludes) { - // Excludes should be subpackages of the global pattern. - if (exclude.startsWith(this.pattern)) { - sourcePackageExcludes.add( - exclude.substring(this.pattern.length).replaceFirst("[.][*]$".toRegex(), "") - ) - } - // Excludes should be subpackages of the global pattern. - if (exclude.startsWith(pathPattern)) { - sourcePathExcludes.add( - exclude.substring(pathPattern.length).replaceFirst("/[*]$".toRegex(), "") - ) - } - } - } } public open fun include(pattern: String) { includes.addAll(normalizePatterns(listOf(pattern))) + includes.add(pattern) } public open fun exclude(pattern: String) { excludes.addAll(normalizePatterns(listOf(pattern))) + excludes.add(pattern) } override fun canRelocatePath(path: String): Boolean { @@ -128,10 +112,26 @@ constructor( } override fun applyToSourceContent(sourceContent: String): String { - if (rawString) return sourceContent + if (rawString || pattern.isEmpty()) return sourceContent + val sourceIncludes = getSourceSubpatterns(includes, pattern) + val sourceExcludes = getSourceSubpatterns(excludes, pattern) val content = - shadeSourceWithExcludes(sourceContent, pattern, shadedPattern, sourcePackageExcludes) - return shadeSourceWithExcludes(content, pathPattern, shadedPathPattern, sourcePathExcludes) + shadeSourceWithFilters( + sourceContent = sourceContent, + patternFrom = pattern, + patternTo = shadedPattern, + includedPatterns = sourceIncludes, + hasIncludes = includes.isNotEmpty(), + excludedPatterns = sourceExcludes, + ) + return shadeSourceWithFilters( + sourceContent = content, + patternFrom = pathPattern, + patternTo = shadedPathPattern, + includedPatterns = sourceIncludes, + hasIncludes = includes.isNotEmpty(), + excludedPatterns = sourceExcludes, + ) } override fun equals(other: Any?): Boolean { @@ -143,8 +143,6 @@ constructor( pathPattern == other.pathPattern && shadedPattern == other.shadedPattern && shadedPathPattern == other.shadedPathPattern && - sourcePackageExcludes == other.sourcePackageExcludes && - sourcePathExcludes == other.sourcePathExcludes && includes == other.includes && excludes == other.excludes } @@ -157,8 +155,6 @@ constructor( pathPattern, shadedPattern, shadedPathPattern, - sourcePackageExcludes, - sourcePathExcludes, includes, excludes, ) @@ -171,8 +167,6 @@ constructor( append("pathPattern='$pathPattern'").append(", ") append("shadedPattern='$shadedPattern'").append(", ") append("shadedPathPattern='$shadedPathPattern'").append(", ") - append("sourcePackageExcludes=$sourcePackageExcludes").append(", ") - append("sourcePathExcludes=$sourcePathExcludes").append(", ") append("includes=$includes").append(", ") append("excludes=$excludes") append(")") @@ -239,31 +233,63 @@ constructor( } } - fun shadeSourceWithExcludes( + fun getSourceSubpatterns(patterns: Set, patternPrefix: String): Set { + if (patternPrefix.isEmpty()) return emptySet() + val result = mutableSetOf() + val dotPrefix = patternPrefix.replace('/', '.') + val slashPrefix = patternPrefix.replace('.', '/') + val trailingWildcardRegex = "[./][*]+$".toRegex() + + for (pat in patterns) { + val dotPat = pat.replace('/', '.') + if (dotPat.startsWith(dotPrefix)) { + val sub = dotPat.substring(dotPrefix.length).replaceFirst(trailingWildcardRegex, "") + if (sub.isEmpty()) { + result.add("") + } else { + result.add(sub) + result.add(sub.replace('.', '/')) + } + } + val slashPat = pat.replace('.', '/') + if (slashPat.startsWith(slashPrefix)) { + val sub = slashPat.substring(slashPrefix.length).replaceFirst(trailingWildcardRegex, "") + if (sub.isEmpty()) { + result.add("") + } else { + result.add(sub) + result.add(sub.replace('/', '.')) + } + } + } + return result + } + + fun shadeSourceWithFilters( sourceContent: String, patternFrom: String, patternTo: String, + includedPatterns: Set, + hasIncludes: Boolean, excludedPatterns: Set, ): String { - // Usually shading makes package names a bit longer, so make buffer 10% bigger than original - // source. + if (hasIncludes && includedPatterns.isEmpty()) { + return sourceContent + } + val shadedSourceContent = StringBuilder(sourceContent.length * 11 / 10) - // Make sure that search pattern starts at word boundary and that we look for literal ".", not - // regex jokers. val snippets = sourceContent .split(("\\b" + patternFrom.replace(".", "[.]") + "\\b").toRegex()) .filter(CharSequence::isNotEmpty) + snippets.forEachIndexed { i, snippet -> val isFirstSnippet = i == 0 val previousSnippet = if (isFirstSnippet) "" else snippets[i - 1] - var doExclude = false - for (excludedPattern in excludedPatterns) { - if (snippet.startsWith(excludedPattern)) { - doExclude = true - break - } - } + + val isIncluded = !hasIncludes || includedPatterns.any { snippet.startsWith(it) } + val isExcluded = excludedPatterns.any { snippet.startsWith(it) } + if (isFirstSnippet) { shadedSourceContent.append(snippet) } else { @@ -271,8 +297,9 @@ constructor( val afterDotSlashSpace = RX_ENDS_WITH_DOT_SLASH_SPACE.matcher(previousSnippetOneLine).find() val afterJavaKeyWord = RX_ENDS_WITH_JAVA_KEYWORD.matcher(previousSnippetOneLine).find() - val shouldExclude = doExclude || afterDotSlashSpace && !afterJavaKeyWord - shadedSourceContent.append(if (shouldExclude) patternFrom else patternTo).append(snippet) + val shouldRelocate = + isIncluded && !isExcluded && (!afterDotSlashSpace || afterJavaKeyWord) + shadedSourceContent.append(if (shouldRelocate) patternTo else patternFrom).append(snippet) } } return shadedSourceContent.toString() diff --git a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/SimpleRelocatorTest.kt b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/SimpleRelocatorTest.kt index 4a88226aa..8f4bf487d 100644 --- a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/SimpleRelocatorTest.kt +++ b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/SimpleRelocatorTest.kt @@ -359,13 +359,17 @@ class SimpleRelocatorTest { @Test fun relocateSourceWithExcludes() { - // Main relocator with in-/excludes + // Main relocator with excludes val relocator = SimpleRelocator( "org.apache.maven", "com.acme.maven", - listOf("foo.bar", "zot.baz"), - listOf("irrelevant.exclude", "org.apache.maven.exclude1", "org.apache.maven.sub.exclude2"), + excludes = + listOf( + "irrelevant.exclude", + "org.apache.maven.exclude1", + "org.apache.maven.sub.exclude2", + ), ) // Make sure not to replace variables 'io' and 'ioInput', package 'java.io' val ioRelocator = SimpleRelocator("io", "shaded.io") @@ -383,6 +387,70 @@ class SimpleRelocatorTest { .isEqualTo(relocatedFile) } + @Test + fun relocateSourceWithIncludes() { + val relocator = + SimpleRelocator( + "org.apache.maven", + "com.acme.maven", + includes = listOf("org.apache.maven.hello.*", "org.apache.maven.In"), + ) + val input = + """ + |package org.apache.maven.hello; + |import org.apache.maven.hello.World; + |import org.apache.maven.other.Other; + |import org.apache.maven.In; + |import org.apache.maven.NotIn; + """ + .trimMargin() + val expected = + """ + |package com.acme.maven.hello; + |import com.acme.maven.hello.World; + |import org.apache.maven.other.Other; + |import com.acme.maven.In; + |import org.apache.maven.NotIn; + """ + .trimMargin() + assertThat(relocator.applyToSourceContent(input)).isEqualTo(expected) + } + + @Test + fun relocateSourceWithDslExcludeAndInclude() { + val relocatorExclude = SimpleRelocator("org.apache.maven", "com.acme.maven") + relocatorExclude.exclude("org.apache.maven.exclude1.*") + val inputExclude = + """ + |import org.apache.maven.hello.World; + |import org.apache.maven.exclude1.Ex1; + """ + .trimMargin() + val expectedExclude = + """ + |import com.acme.maven.hello.World; + |import org.apache.maven.exclude1.Ex1; + """ + .trimMargin() + assertThat(relocatorExclude.applyToSourceContent(inputExclude)).isEqualTo(expectedExclude) + + val relocatorInclude = SimpleRelocator("org.apache.maven", "com.acme.maven") + relocatorInclude.include("org.apache.maven.hello.*") + val inputInclude = + """ + |import org.apache.maven.hello.World; + |import org.apache.maven.other.Other; + """ + .trimMargin() + val expectedInclude = + """ + |import com.acme.maven.hello.World; + |import org.apache.maven.other.Other; + """ + .trimMargin() + assertThat(relocatorInclude.applyToSourceContent(inputInclude)).isEqualTo(expectedInclude) + } + private companion object { val sourceFile = """ From 3046fdaf22a0e53c551f4e84ddc3e1e2a1e273ac Mon Sep 17 00:00:00 2001 From: Goooler Date: Sat, 5 Sep 2026 16:38:07 +0800 Subject: [PATCH 06/14] Preserve SourceDirectorySet filters for project sources and match project dependencies by identifier --- .../gradle/plugins/shadow/RelocationTest.kt | 44 +++++++++ .../gradle/plugins/shadow/ShadowJavaPlugin.kt | 2 +- .../gradle/plugins/shadow/ShadowKmpPlugin.kt | 2 +- .../internal/DefaultDependencyFilter.kt | 38 ++++---- .../plugins/shadow/internal/SourcesJar.kt | 95 ++++++++++--------- .../plugins/shadow/ShadowPropertiesTest.kt | 2 +- 6 files changed, 118 insertions(+), 65 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 8f509f158..48b98c9bb 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 @@ -9,6 +9,8 @@ import assertk.assertions.isNotEqualTo import assertk.fail import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar.Companion.CONSTANT_TIME_FOR_ZIP_ENTRIES import com.github.jengelman.gradle.plugins.shadow.testkit.classLoader +import com.github.jengelman.gradle.plugins.shadow.testkit.containsAtLeast +import com.github.jengelman.gradle.plugins.shadow.testkit.containsNone 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.getContent @@ -882,6 +884,48 @@ class RelocationTest : BasePluginTest() { } } + @Test + fun relocateShadowedSourcesJarRespectsSourceDirectorySetFilters() { + path("src/main/java/my/Main.java") + .writeText( + """ + |package my; + |public class Main {} + """ + .trimMargin() + ) + path("src/main/java/my/Excluded.java") + .writeText( + """ + |package my; + |public class Excluded {} + """ + .trimMargin() + ) + projectScript.appendText( + """ + |sourceSets { + | main { + | java { + | exclude '**/Excluded.java' + | } + | } + |} + |$shadowJarTask { + | generateSourcesJar = true + |} + """ + .trimMargin() + ) + + runWithSuccess(shadowJarPath) + + assertThat(outputShadowedSourcesJar).useAll { + containsAtLeast("my/Main.java") + containsNone("my/Excluded.java") + } + } + @Test fun generateShadowedSourcesJarWithCustomIncludedSourcesJars() { writeClass() diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.kt index 802b139d8..1a8412cdd 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.kt @@ -50,7 +50,7 @@ constructor(private val softwareComponentFactory: SoftwareComponentFactory) : Pl task.sourceSetsSourceDirs.convention( task.generateSourcesJar.flatMap { generate -> if (generate) { - mainSourceSet.map { it.allSource.srcDirs } + mainSourceSet.map { it.allSource } } else { provider { emptySet() } } diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowKmpPlugin.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowKmpPlugin.kt index 7a765e81e..704f9390e 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowKmpPlugin.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowKmpPlugin.kt @@ -39,7 +39,7 @@ public abstract class ShadowKmpPlugin : Plugin { task.sourceSetsSourceDirs.convention( task.generateSourcesJar.flatMap { generate -> if (generate) { - kotlinJvmMain.map { it.allKotlinSourceSets.flatMap { ss -> ss.kotlin.srcDirs } } + kotlinJvmMain.map { it.allKotlinSourceSets.map { ss -> ss.kotlin } } } else { provider { emptySet() } } diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/DefaultDependencyFilter.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/DefaultDependencyFilter.kt index 12edefe99..86f5544ac 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/DefaultDependencyFilter.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/DefaultDependencyFilter.kt @@ -44,22 +44,23 @@ internal class DefaultDependencyFilter(@Transient private val project: Project) includedDependencies = includes, excludedDependencies = excludes, ) - val componentIds = - configuration.incoming.resolutionResult.allDependencies - .filterIsInstance() - .map { it.selected.id } - .toSet() + val allResolvedDependencies = + configuration.incoming.resolutionResult.allDependencies.filterIsInstance< + ResolvedDependencyResult + >() + + val includedDependenciesResults = allResolvedDependencies.filter { dep -> + includes.any { inc -> + inc.moduleGroup == dep.selected.moduleVersion?.group && + inc.moduleName == dep.selected.moduleVersion?.name && + inc.moduleVersion == dep.selected.moduleVersion?.version + } + } val externalComponentIds = - componentIds + includedDependenciesResults + .map { it.selected.id } .filterIsInstance() - .filter { id -> - includes.any { - it.moduleGroup == id.group && - it.moduleName == id.module && - it.moduleVersion == id.version - } - } .toSet() val externalSourcesFiles = @@ -73,7 +74,12 @@ internal class DefaultDependencyFilter(@Transient private val project: Project) .filterIsInstance() .map { it.file } - val includedProjectNames = includes.map { it.moduleName }.toSet() + val projectComponentIds = + includedDependenciesResults + .map { it.selected.id } + .filterIsInstance() + .toSet() + val projectSourcesFiles = try { configuration.incoming @@ -89,9 +95,7 @@ internal class DefaultDependencyFilter(@Transient private val project: Project) project.objects.named(DocsType::class.java, DocsType.SOURCES), ) } - view.componentFilter { id -> - id is ProjectComponentIdentifier && id.projectName in includedProjectNames - } + view.componentFilter { id -> id in projectComponentIds } view.lenient(true) } .files diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/SourcesJar.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/SourcesJar.kt index 48c3e0895..6456aa266 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/SourcesJar.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/SourcesJar.kt @@ -20,7 +20,6 @@ internal fun generateShadowedSourcesJar( preserveFileTimestamps: Boolean, ) { val sourcesJars = includedSourcesJars.filter { it.exists() && it.isFile }.sortedBy { it.path } - if (sourceSetsSourceDirs.none() && sourcesJars.isEmpty()) return val visitedFiles = mutableSetOf() val charset = metadataCharset?.let(Charset::forName) ?: Charsets.UTF_8 @@ -49,54 +48,60 @@ internal fun generateShadowedSourcesJar( write("Manifest-Version: 1.0\n\n".toByteArray(charset)) } - val sortedSourceDirs = sourceSetsSourceDirs.filter { it.exists() }.sortedBy { it.path } - for (srcDir in sortedSourceDirs) { - srcDir - .walkTopDown() - .filter { it.isFile } - .toList() - .sortedBy { it.relativeTo(srcDir).invariantSeparatorsPath } - .forEach { file -> - val relPath = file.relativeTo(srcDir).invariantSeparatorsPath - val isSource = isSourceFile(relPath) - if (isSource) { - val text = file.readText(charset) - val pkg = extractPackage(text) - val simpleName = file.name - val canonicalPath = - if (pkg.isEmpty()) simpleName else "${pkg.replace('.', '/')}/$simpleName" - if (isUnused(canonicalPath, unusedClasses, sourceToClasses)) return@forEach - val relocatedPath = relocators.relocatePath(canonicalPath) - if (visitedFiles.add(relocatedPath)) { - var transformedText = text - for (relocator in relocators) { - transformedText = relocator.applyToSourceContent(transformedText) - } - val bytes = transformedText.toByteArray(charset) - zos.writeEntry( - name = relocatedPath, - preserveLastModified = preserveFileTimestamps, - lastModified = file.lastModified(), - unixMode = UnixMode.file(), - ) { - write(bytes) - } + val sourceItems = sourceSetsSourceDirs.filter { it.exists() }.sortedBy { it.path } + for (item in sourceItems) { + val filesWithRelPaths: List> = + if (item.isDirectory) { + item + .walkTopDown() + .filter { it.isFile } + .toList() + .sortedBy { it.relativeTo(item).invariantSeparatorsPath } + .map { it to it.relativeTo(item).invariantSeparatorsPath } + } else { + listOf(item to item.name) + } + + for ((file, relPath) in filesWithRelPaths) { + val isSource = isSourceFile(relPath) + if (isSource) { + val text = file.readText(charset) + val pkg = extractPackage(text) + val simpleName = file.name + val canonicalPath = + if (pkg.isEmpty()) simpleName else "${pkg.replace('.', '/')}/$simpleName" + if (isUnused(canonicalPath, unusedClasses, sourceToClasses)) continue + val relocatedPath = relocators.relocatePath(canonicalPath) + if (visitedFiles.add(relocatedPath)) { + var transformedText = text + for (relocator in relocators) { + transformedText = relocator.applyToSourceContent(transformedText) } - } else { - val relocatedPath = relocators.relocatePath(relPath) - if (visitedFiles.add(relocatedPath)) { - val bytes = file.readBytes() - zos.writeEntry( - name = relocatedPath, - preserveLastModified = preserveFileTimestamps, - lastModified = file.lastModified(), - unixMode = UnixMode.file(), - ) { - write(bytes) - } + val bytes = transformedText.toByteArray(charset) + zos.writeEntry( + name = relocatedPath, + preserveLastModified = preserveFileTimestamps, + lastModified = file.lastModified(), + unixMode = UnixMode.file(), + ) { + write(bytes) + } + } + } else { + val relocatedPath = relocators.relocatePath(relPath) + if (visitedFiles.add(relocatedPath)) { + val bytes = file.readBytes() + zos.writeEntry( + name = relocatedPath, + preserveLastModified = preserveFileTimestamps, + lastModified = file.lastModified(), + unixMode = UnixMode.file(), + ) { + write(bytes) } } } + } } sourcesJars.forEach { jarFile -> diff --git a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPropertiesTest.kt b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPropertiesTest.kt index d18d8eaa7..73c9df4ca 100644 --- a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPropertiesTest.kt +++ b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPropertiesTest.kt @@ -182,7 +182,7 @@ class ShadowPropertiesTest { assertThat(shadowJarTask.generateSourcesJar.get()).isTrue() assertThat(shadowJarTask.sourceSetsSourceDirs.files) .containsOnly( - *javaPluginExtension.sourceSets.getByName("main").allSource.srcDirs.toTypedArray() + *javaPluginExtension.sourceSets.getByName("main").allSource.files.toTypedArray() ) } From 3f7a56e09e01d295ec8a858e59fa0f620fa83411 Mon Sep 17 00:00:00 2001 From: Goooler Date: Sat, 5 Sep 2026 17:00:48 +0800 Subject: [PATCH 07/14] Dynamically derive shadow sources artifact classifier from archiveClassifier --- docs/publishing/README.md | 58 ++++++++++++++++ .../gradle/plugins/shadow/PublishingTest.kt | 69 +++++++++++++++++++ .../gradle/plugins/shadow/ShadowJavaPlugin.kt | 14 ++-- 3 files changed, 137 insertions(+), 4 deletions(-) diff --git a/docs/publishing/README.md b/docs/publishing/README.md index b229f3c25..e61c9712b 100644 --- a/docs/publishing/README.md +++ b/docs/publishing/README.md @@ -613,6 +613,64 @@ When Gradle's standard `java.withSourcesJar()` is enabled, the Shadow plugin aut The published Maven publication will include both `--all.jar` and `--all-sources.jar`. +### Local File Names vs. Published Classifiers + +The Shadow plugin distinguishes between the **local output file** on disk and the **published artifact classifier** in Maven repositories and Gradle Module Metadata: + +| Configuration | Local Output File (`archiveSourcesFile` in `build/libs`) | Published Classifier | Published File (Maven Repository) | Use Case | +|:---|:---|:---|:---|:---| +| `archiveClassifier = "all"` *(default)* | `--all-sources.jar` | `all-sources` | `--all-sources.jar` | **Coexistence** (coexists with standard `sources`) | +| `archiveClassifier = "shaded"` | `--shaded-sources.jar` | `shaded-sources` | `--shaded-sources.jar` | **Coexistence** (custom classifier) | +| `archiveClassifier = ""` | `--sources.jar` | `sources` | `--sources.jar` | **Replacement** (replaces standard `sources`) | + +#### Coexistence Scenario + +When publishing alongside standard Java artifacts (e.g. publishing `from(components["java"])` with `shadow.addShadowVariantIntoJavaComponent = true`), the standard sources variant uses classifier `sources`. To prevent coordinate collisions within the same publication, the shadowed sources variant dynamically derives its classifier as `-sources` (such as `all-sources` or `shaded-sources`). + +#### Replacement Scenario + +When configuring `shadowJar` to replace the standard JAR (`archiveClassifier = ""`), the companion shadowed sources JAR automatically uses the standard `sources` classifier. In this scenario, ensure standard `jar` and `sourcesJar` tasks are disabled so that only the shadowed artifacts are produced and published without destination or coordinate conflicts: + +=== ":material-language-kotlin: build.gradle.kts" + + ```kotlin + java { + withSourcesJar() + } + + tasks.jar { + enabled = false + } + + tasks.named("sourcesJar") { + enabled = false + } + + tasks.shadowJar { + archiveClassifier = "" + } + ``` + +=== ":simple-apachegroovy: build.gradle" + + ```groovy + java { + withSourcesJar() + } + + tasks.named('jar') { + enabled = false + } + + tasks.named('sourcesJar') { + enabled = false + } + + tasks.named('shadowJar', com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) { + archiveClassifier = '' + } + ``` + > [!NOTE] > Generating the companion shadowed sources JAR is controlled by [`generateSourcesJar`][ShadowJar.generateSourcesJar]. > In Java projects, it defaults to `true` when `java.withSourcesJar()` is enabled, and `false` otherwise to avoid diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt index eb763ec5e..cfbb38164 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt @@ -348,6 +348,75 @@ class PublishingTest : BasePluginTest() { assertShadowSourcesVariantCommon(gmm) } + @Test + fun publishWithSourcesJarAndCustomClassifier() { + projectScript.appendText( + publishConfiguration( + projectBlock = + """ + |java { + | withSourcesJar() + |} + """ + .trimMargin(), + shadowBlock = + """ + |archiveClassifier = 'shaded' + """ + .trimMargin(), + publicationsBlock = + """ + |shadow(MavenPublication) { + | from components.shadow + |} + """ + .trimMargin(), + ) + ) + + publish() + + val artifactRoot = "my/maven/1.0" + assertThat(repoPath(artifactRoot).entries.filter { it.endsWith(".jar") }) + .containsOnly( + "maven-1.0-shaded.jar", + "maven-1.0-shaded-sources.jar", + ) + } + + @Test + fun publishJavaComponentWithShadowAndSourcesVariants() { + projectScript.appendText( + publishConfiguration( + projectBlock = + """ + |java { + | withSourcesJar() + |} + """ + .trimMargin(), + publicationsBlock = + """ + |shadow(MavenPublication) { + | from components.java + |} + """ + .trimMargin(), + ) + ) + + publish() + + val artifactRoot = "my/maven/1.0" + assertThat(repoPath(artifactRoot).entries.filter { it.endsWith(".jar") }) + .containsOnly( + "maven-1.0.jar", + "maven-1.0-sources.jar", + "maven-1.0-all.jar", + "maven-1.0-all-sources.jar", + ) + } + @Test fun dontPublishSourcesWhenGenerateSourcesJarDisabled() { projectScript.appendText( diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.kt index 1a8412cdd..02d651519 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.kt @@ -11,6 +11,7 @@ import javax.inject.Inject import org.gradle.api.NamedDomainObjectProvider import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.api.artifacts.ConfigurablePublishArtifact import org.gradle.api.artifacts.ConfigurationContainer import org.gradle.api.artifacts.ConsumableConfiguration import org.gradle.api.attributes.Bundling @@ -93,10 +94,15 @@ constructor(private val softwareComponentFactory: SoftwareComponentFactory) : Pl objects.named(DocsType::class.java, DocsType.SOURCES), ) } - outgoing.artifact(tasks.shadowJar.flatMap { it.archiveSourcesFile }) { artifact -> - artifact.builtBy(tasks.shadowJar) - artifact.classifier = "sources" - artifact.type = "jar" + val sourcesArtifact = + outgoing.artifact(tasks.shadowJar.flatMap { it.archiveSourcesFile }) { artifact -> + artifact.builtBy(tasks.shadowJar) + artifact.type = "jar" + } + tasks.shadowJar.configure { shadowJar -> + val shadowClassifier = shadowJar.archiveClassifier.orNull + (sourcesArtifact as? ConfigurablePublishArtifact)?.classifier = + if (shadowClassifier.isNullOrEmpty()) "sources" else "$shadowClassifier-sources" } } From 46f17b7c954f5824f1a6a10d1ff1f76bb39d116a Mon Sep 17 00:00:00 2001 From: Goooler Date: Sat, 5 Sep 2026 17:23:35 +0800 Subject: [PATCH 08/14] Fix shadow sources artifact classifier assignment and document replacement publishing --- docs/publishing/README.md | 74 ++++++++++++++++++- .../gradle/plugins/shadow/PublishingTest.kt | 1 + .../gradle/plugins/shadow/ShadowJavaPlugin.kt | 14 ++-- 3 files changed, 79 insertions(+), 10 deletions(-) diff --git a/docs/publishing/README.md b/docs/publishing/README.md index e61c9712b..31d35a07f 100644 --- a/docs/publishing/README.md +++ b/docs/publishing/README.md @@ -629,11 +629,21 @@ When publishing alongside standard Java artifacts (e.g. publishing `from(compone #### Replacement Scenario -When configuring `shadowJar` to replace the standard JAR (`archiveClassifier = ""`), the companion shadowed sources JAR automatically uses the standard `sources` classifier. In this scenario, ensure standard `jar` and `sourcesJar` tasks are disabled so that only the shadowed artifacts are produced and published without destination or coordinate conflicts: +When configuring `shadowJar` to replace the standard JAR (`archiveClassifier = ""`), the companion shadowed sources JAR automatically uses the standard `sources` classifier. + +To publish shadowed artifacts as the primary publication: + +1. **Publish from `components["shadow"]` (Recommended)**: Publish the `shadow` component directly in your Maven publication, and disable standard archive tasks to prevent destination file collisions in `build/libs`: === ":material-language-kotlin: build.gradle.kts" ```kotlin + plugins { + java + `maven-publish` + id("com.gradleup.shadow") + } + java { withSourcesJar() } @@ -649,11 +659,25 @@ When configuring `shadowJar` to replace the standard JAR (`archiveClassifier = " tasks.shadowJar { archiveClassifier = "" } + + publishing { + publications { + create("shadow") { + from(components["shadow"]) + } + } + } ``` === ":simple-apachegroovy: build.gradle" ```groovy + plugins { + id 'java' + id 'maven-publish' + id 'com.gradleup.shadow' + } + java { withSourcesJar() } @@ -669,6 +693,54 @@ When configuring `shadowJar` to replace the standard JAR (`archiveClassifier = " tasks.named('shadowJar', com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) { archiveClassifier = '' } + + publishing { + publications { + shadow(MavenPublication) { + from components.shadow + } + } + } + ``` + +2. **Publish from `components["java"]`**: If publishing `from(components["java"])`, disabling the `jar` or `sourcesJar` tasks does not remove standard variants from the `java` software component. You must also explicitly skip the standard publication variants: + +=== ":material-language-kotlin: build.gradle.kts" + + ```kotlin + plugins { + java + `maven-publish` + id("com.gradleup.shadow") + } + + java { + withSourcesJar() + } + + components.named("java") { + withVariantsFromConfiguration(configurations["runtimeElements"]) { skip() } + withVariantsFromConfiguration(configurations["sourcesElements"]) { skip() } + } + ``` + +=== ":simple-apachegroovy: build.gradle" + + ```groovy + plugins { + id 'java' + id 'maven-publish' + id 'com.gradleup.shadow' + } + + java { + withSourcesJar() + } + + components.named('java', org.gradle.api.component.AdhocComponentWithVariants) { + withVariantsFromConfiguration(configurations.runtimeElements) { skip() } + withVariantsFromConfiguration(configurations.sourcesElements) { skip() } + } ``` > [!NOTE] diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt index cfbb38164..d2e5f4f5a 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt @@ -362,6 +362,7 @@ class PublishingTest : BasePluginTest() { shadowBlock = """ |archiveClassifier = 'shaded' + |archiveSourcesFile = layout.buildDirectory.file('custom.jar') """ .trimMargin(), publicationsBlock = diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.kt index 02d651519..39359df98 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.kt @@ -11,7 +11,6 @@ import javax.inject.Inject import org.gradle.api.NamedDomainObjectProvider import org.gradle.api.Plugin import org.gradle.api.Project -import org.gradle.api.artifacts.ConfigurablePublishArtifact import org.gradle.api.artifacts.ConfigurationContainer import org.gradle.api.artifacts.ConsumableConfiguration import org.gradle.api.attributes.Bundling @@ -94,14 +93,11 @@ constructor(private val softwareComponentFactory: SoftwareComponentFactory) : Pl objects.named(DocsType::class.java, DocsType.SOURCES), ) } - val sourcesArtifact = - outgoing.artifact(tasks.shadowJar.flatMap { it.archiveSourcesFile }) { artifact -> - artifact.builtBy(tasks.shadowJar) - artifact.type = "jar" - } - tasks.shadowJar.configure { shadowJar -> - val shadowClassifier = shadowJar.archiveClassifier.orNull - (sourcesArtifact as? ConfigurablePublishArtifact)?.classifier = + outgoing.artifact(tasks.shadowJar.flatMap { it.archiveSourcesFile }) { artifact -> + artifact.builtBy(tasks.shadowJar) + artifact.type = "jar" + val shadowClassifier = tasks.shadowJar.flatMap { it.archiveClassifier }.orNull + artifact.classifier = if (shadowClassifier.isNullOrEmpty()) "sources" else "$shadowClassifier-sources" } } From effabb045139fc7b7312b70074fa72a1d9d8694c Mon Sep 17 00:00:00 2001 From: Goooler Date: Sat, 5 Sep 2026 17:52:26 +0800 Subject: [PATCH 09/14] Fix resource relative paths and include/exclude matching in shadowed sources JAR --- .../gradle/plugins/shadow/JavaPluginsTest.kt | 24 ++++ .../gradle/plugins/shadow/PublishingTest.kt | 3 + .../gradle/plugins/shadow/ShadowJavaPlugin.kt | 2 +- .../gradle/plugins/shadow/ShadowKmpPlugin.kt | 6 +- .../plugins/shadow/internal/SourcesJar.kt | 113 ++++++++++-------- .../shadow/relocation/SimpleRelocator.kt | 12 +- .../plugins/shadow/ShadowPropertiesTest.kt | 5 +- .../shadow/relocation/SimpleRelocatorTest.kt | 35 ++++++ 8 files changed, 146 insertions(+), 54 deletions(-) diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/JavaPluginsTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/JavaPluginsTest.kt index 979623bd8..0fb8980a1 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/JavaPluginsTest.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/JavaPluginsTest.kt @@ -1316,6 +1316,30 @@ class JavaPluginsTest : BasePluginTest() { ) } + @Test + fun sourcesJarPreservesResourceRelativePath() { + writeClass() + path("src/main/resources/config/sub/app.properties").writeText("key=value") + + projectScript.appendText( + """ + |$shadowJarTask { + | generateSourcesJar = true + |} + """ + .trimMargin() + ) + + runWithSuccess(shadowJarPath) + + assertThat(outputShadowedSourcesJar).useAll { + containsAtLeast( + "my/Main.java", + "config/sub/app.properties", + ) + } + } + private fun dependencies(configuration: String, vararg flags: String): String { return runWithSuccess("dependencies", "--configuration", configuration, *flags).output } diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt index d2e5f4f5a..d05fb5877 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt @@ -383,6 +383,9 @@ class PublishingTest : BasePluginTest() { "maven-1.0-shaded.jar", "maven-1.0-shaded-sources.jar", ) + val gmm = gmmAdapter.fromJson(repoPath("$artifactRoot/maven-1.0.module")) + assertThat(gmm.shadowSourcesElementsVariant.fileNames.single()) + .isEqualTo("maven-1.0-shaded-sources.jar") } @Test diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.kt index 39359df98..28b69c6e1 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.kt @@ -50,7 +50,7 @@ constructor(private val softwareComponentFactory: SoftwareComponentFactory) : Pl task.sourceSetsSourceDirs.convention( task.generateSourcesJar.flatMap { generate -> if (generate) { - mainSourceSet.map { it.allSource } + mainSourceSet.map { it.allSource.sourceDirectories + it.allSource } } else { provider { emptySet() } } diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowKmpPlugin.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowKmpPlugin.kt index 704f9390e..ddeba32c5 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowKmpPlugin.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowKmpPlugin.kt @@ -39,7 +39,11 @@ public abstract class ShadowKmpPlugin : Plugin { task.sourceSetsSourceDirs.convention( task.generateSourcesJar.flatMap { generate -> if (generate) { - kotlinJvmMain.map { it.allKotlinSourceSets.map { ss -> ss.kotlin } } + kotlinJvmMain.map { + it.allKotlinSourceSets.flatMap { ss -> + listOf(ss.kotlin.sourceDirectories, ss.kotlin) + } + } } else { provider { emptySet() } } diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/SourcesJar.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/SourcesJar.kt index 6456aa266..3bd56948c 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/SourcesJar.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/SourcesJar.kt @@ -48,57 +48,72 @@ internal fun generateShadowedSourcesJar( write("Manifest-Version: 1.0\n\n".toByteArray(charset)) } - val sourceItems = sourceSetsSourceDirs.filter { it.exists() }.sortedBy { it.path } - for (item in sourceItems) { - val filesWithRelPaths: List> = - if (item.isDirectory) { - item - .walkTopDown() - .filter { it.isFile } - .toList() - .sortedBy { it.relativeTo(item).invariantSeparatorsPath } - .map { it to it.relativeTo(item).invariantSeparatorsPath } - } else { - listOf(item to item.name) - } + val sourceItems = sourceSetsSourceDirs.filter { it.exists() } + val (dirs, files) = sourceItems.partition { it.isDirectory } + val sortedDirs = dirs.sortedByDescending { it.path.length } - for ((file, relPath) in filesWithRelPaths) { - val isSource = isSourceFile(relPath) - if (isSource) { - val text = file.readText(charset) - val pkg = extractPackage(text) - val simpleName = file.name - val canonicalPath = - if (pkg.isEmpty()) simpleName else "${pkg.replace('.', '/')}/$simpleName" - if (isUnused(canonicalPath, unusedClasses, sourceToClasses)) continue - val relocatedPath = relocators.relocatePath(canonicalPath) - if (visitedFiles.add(relocatedPath)) { - var transformedText = text - for (relocator in relocators) { - transformedText = relocator.applyToSourceContent(transformedText) - } - val bytes = transformedText.toByteArray(charset) - zos.writeEntry( - name = relocatedPath, - preserveLastModified = preserveFileTimestamps, - lastModified = file.lastModified(), - unixMode = UnixMode.file(), - ) { - write(bytes) - } + val filesWithRelPaths = mutableListOf>() + val dirsCoveredByFiles = mutableSetOf() + + for (file in files.sortedBy { it.path }) { + val matchingDir = sortedDirs.firstOrNull { file.startsWith(it) } + if (matchingDir != null) { + dirsCoveredByFiles.add(matchingDir) + filesWithRelPaths.add(file to file.relativeTo(matchingDir).invariantSeparatorsPath) + } else { + filesWithRelPaths.add(file to file.name) + } + } + + for (dir in dirs.sortedBy { it.path }) { + if (dir !in dirsCoveredByFiles) { + dir + .walkTopDown() + .filter { it.isFile } + .toList() + .sortedBy { it.relativeTo(dir).invariantSeparatorsPath } + .forEach { file -> + filesWithRelPaths.add(file to file.relativeTo(dir).invariantSeparatorsPath) } - } else { - val relocatedPath = relocators.relocatePath(relPath) - if (visitedFiles.add(relocatedPath)) { - val bytes = file.readBytes() - zos.writeEntry( - name = relocatedPath, - preserveLastModified = preserveFileTimestamps, - lastModified = file.lastModified(), - unixMode = UnixMode.file(), - ) { - write(bytes) - } + } + } + + for ((file, relPath) in filesWithRelPaths) { + val isSource = isSourceFile(relPath) + if (isSource) { + val text = file.readText(charset) + val pkg = extractPackage(text) + val simpleName = file.name + val canonicalPath = + if (pkg.isEmpty()) simpleName else "${pkg.replace('.', '/')}/$simpleName" + if (isUnused(canonicalPath, unusedClasses, sourceToClasses)) continue + val relocatedPath = relocators.relocatePath(canonicalPath) + if (visitedFiles.add(relocatedPath)) { + var transformedText = text + for (relocator in relocators) { + transformedText = relocator.applyToSourceContent(transformedText) + } + val bytes = transformedText.toByteArray(charset) + zos.writeEntry( + name = relocatedPath, + preserveLastModified = preserveFileTimestamps, + lastModified = file.lastModified(), + unixMode = UnixMode.file(), + ) { + write(bytes) + } + } + } else { + val relocatedPath = relocators.relocatePath(relPath) + if (visitedFiles.add(relocatedPath)) { + val bytes = file.readBytes() + zos.writeEntry( + name = relocatedPath, + preserveLastModified = preserveFileTimestamps, + lastModified = file.lastModified(), + unixMode = UnixMode.file(), + ) { + write(bytes) } } } diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/SimpleRelocator.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/SimpleRelocator.kt index 70f783f48..7ca7a75d5 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/SimpleRelocator.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/SimpleRelocator.kt @@ -265,6 +265,14 @@ constructor( return result } + private fun matchesSubpattern(snippet: String, subpattern: String): Boolean { + if (!snippet.startsWith(subpattern)) return false + if (subpattern.isEmpty() || snippet.length == subpattern.length) return true + if (subpattern.endsWith('.') || subpattern.endsWith('/')) return true + val nextChar = snippet[subpattern.length] + return !nextChar.isLetterOrDigit() && nextChar != '_' + } + fun shadeSourceWithFilters( sourceContent: String, patternFrom: String, @@ -287,8 +295,8 @@ constructor( val isFirstSnippet = i == 0 val previousSnippet = if (isFirstSnippet) "" else snippets[i - 1] - val isIncluded = !hasIncludes || includedPatterns.any { snippet.startsWith(it) } - val isExcluded = excludedPatterns.any { snippet.startsWith(it) } + val isIncluded = !hasIncludes || includedPatterns.any { matchesSubpattern(snippet, it) } + val isExcluded = excludedPatterns.any { matchesSubpattern(snippet, it) } if (isFirstSnippet) { shadedSourceContent.append(snippet) diff --git a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPropertiesTest.kt b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPropertiesTest.kt index 73c9df4ca..aae0aaf97 100644 --- a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPropertiesTest.kt +++ b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPropertiesTest.kt @@ -180,9 +180,12 @@ class ShadowPropertiesTest { javaPluginExtension.withSourcesJar() val shadowJarTask = tasks.shadowJar.get() assertThat(shadowJarTask.generateSourcesJar.get()).isTrue() + val mainSourceSet = javaPluginExtension.sourceSets.getByName("main") assertThat(shadowJarTask.sourceSetsSourceDirs.files) .containsOnly( - *javaPluginExtension.sourceSets.getByName("main").allSource.files.toTypedArray() + *(mainSourceSet.allSource.sourceDirectories + mainSourceSet.allSource) + .files + .toTypedArray() ) } diff --git a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/SimpleRelocatorTest.kt b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/SimpleRelocatorTest.kt index 8f4bf487d..030cfdb26 100644 --- a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/SimpleRelocatorTest.kt +++ b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/SimpleRelocatorTest.kt @@ -357,6 +357,41 @@ class SimpleRelocatorTest { assertThat(relocator.applyToSourceContent(sourceFile)).isEqualTo(sourceFile) } + @Test + fun relocateSourceFileWithPrefixCollision() { + val relocator = + SimpleRelocator( + "org.example", + "relocated.org.example", + includes = listOf("org.example.In"), + ) + val source = + """ + |import org.example.In; + |import org.example.Input; + |import org.example.In.Nested; + | + |public class Test { + | org.example.In a; + | org.example.Input b; + |} + """ + .trimMargin() + val expected = + """ + |import relocated.org.example.In; + |import org.example.Input; + |import relocated.org.example.In.Nested; + | + |public class Test { + | relocated.org.example.In a; + | org.example.Input b; + |} + """ + .trimMargin() + assertThat(relocator.applyToSourceContent(source)).isEqualTo(expected) + } + @Test fun relocateSourceWithExcludes() { // Main relocator with excludes From f9874eda473bf9ec9905169a5bbaba5fbffe137c Mon Sep 17 00:00:00 2001 From: Goooler Date: Sat, 5 Sep 2026 18:13:12 +0800 Subject: [PATCH 10/14] Dynamically resolve sources publish artifact classifier and ensure path boundary safe directory matching --- .../gradle/plugins/shadow/JavaPluginsTest.kt | 33 ++++++++++++++++ .../gradle/plugins/shadow/PublishingTest.kt | 39 +++++++++++++++++++ .../gradle/plugins/shadow/ShadowJavaPlugin.kt | 38 ++++++++++++++---- .../plugins/shadow/internal/SourcesJar.kt | 20 ++++++---- 4 files changed, 115 insertions(+), 15 deletions(-) diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/JavaPluginsTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/JavaPluginsTest.kt index 0fb8980a1..5658f00df 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/JavaPluginsTest.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/JavaPluginsTest.kt @@ -1340,6 +1340,39 @@ class JavaPluginsTest : BasePluginTest() { } } + @Test + fun sourcesJarHandlesOverlappingSourceDirectoryPrefixes() { + writeClass() + path("src/main/res/a.properties").writeText("a=1") + path("src/main/resources/b.properties").writeText("b=2") + + projectScript.appendText( + """ + |sourceSets { + | main { + | resources { + | srcDir 'src/main/res' + | } + | } + |} + |$shadowJarTask { + | generateSourcesJar = true + |} + """ + .trimMargin() + ) + + runWithSuccess(shadowJarPath) + + assertThat(outputShadowedSourcesJar).useAll { + containsAtLeast( + "my/Main.java", + "a.properties", + "b.properties", + ) + } + } + private fun dependencies(configuration: String, vararg flags: String): String { return runWithSuccess("dependencies", "--configuration", configuration, *flags).output } diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt index d05fb5877..6b228540d 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt @@ -388,6 +388,45 @@ class PublishingTest : BasePluginTest() { .isEqualTo("maven-1.0-shaded-sources.jar") } + @Test + fun publishWithSourcesJarAndCustomClassifierAfterPublishingBlock() { + projectScript.appendText( + """ + |apply plugin: 'maven-publish' + |java { + | withSourcesJar() + |} + |publishing { + | repositories { + | maven { url = '${remoteRepoPath.toUri()}' } + | } + | publications { + | shadow(MavenPublication) { + | from components.shadow + | } + | } + |} + |tasks.named('shadowJar', com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) { + | archiveClassifier = 'shaded' + | archiveSourcesFile = layout.buildDirectory.file('custom.jar') + |} + """ + .trimMargin() + ) + + publish() + + val artifactRoot = "my/maven/1.0" + assertThat(repoPath(artifactRoot).entries.filter { it.endsWith(".jar") }) + .containsOnly( + "maven-1.0-shaded.jar", + "maven-1.0-shaded-sources.jar", + ) + val gmm = gmmAdapter.fromJson(repoPath("$artifactRoot/maven-1.0.module")) + assertThat(gmm.shadowSourcesElementsVariant.fileNames.single()) + .isEqualTo("maven-1.0-shaded-sources.jar") + } + @Test fun publishJavaComponentWithShadowAndSourcesVariants() { projectScript.appendText( diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.kt index 28b69c6e1..eb96bf373 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.kt @@ -5,14 +5,18 @@ import com.github.jengelman.gradle.plugins.shadow.ShadowBasePlugin.Companion.sha import com.github.jengelman.gradle.plugins.shadow.internal.javaPluginExtension import com.github.jengelman.gradle.plugins.shadow.internal.runtimeConfiguration import com.github.jengelman.gradle.plugins.shadow.internal.sourceSets +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar.Companion.registerShadowJarCommon import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar.Companion.shadowJar +import java.io.File +import java.util.Date import javax.inject.Inject import org.gradle.api.NamedDomainObjectProvider import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.artifacts.ConfigurationContainer import org.gradle.api.artifacts.ConsumableConfiguration +import org.gradle.api.artifacts.PublishArtifact import org.gradle.api.attributes.Bundling import org.gradle.api.attributes.Category import org.gradle.api.attributes.DocsType @@ -25,6 +29,8 @@ import org.gradle.api.component.SoftwareComponentFactory import org.gradle.api.logging.Logger import org.gradle.api.plugins.JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME import org.gradle.api.plugins.JavaPlugin.SOURCES_ELEMENTS_CONFIGURATION_NAME +import org.gradle.api.tasks.TaskDependency +import org.gradle.api.tasks.TaskProvider import org.gradle.api.tasks.bundling.Jar public abstract class ShadowJavaPlugin @@ -93,13 +99,7 @@ constructor(private val softwareComponentFactory: SoftwareComponentFactory) : Pl objects.named(DocsType::class.java, DocsType.SOURCES), ) } - outgoing.artifact(tasks.shadowJar.flatMap { it.archiveSourcesFile }) { artifact -> - artifact.builtBy(tasks.shadowJar) - artifact.type = "jar" - val shadowClassifier = tasks.shadowJar.flatMap { it.archiveClassifier }.orNull - artifact.classifier = - if (shadowClassifier.isNullOrEmpty()) "sources" else "$shadowClassifier-sources" - } + outgoing.artifact(ShadowSourcesPublishArtifact(tasks.shadowJar)) } // See more details in #2086. @@ -232,3 +232,27 @@ constructor(private val softwareComponentFactory: SoftwareComponentFactory) : Pl get() = named(SHADOW_SOURCES_ELEMENTS_CONFIGURATION_NAME, ConsumableConfiguration::class.java) } } + +internal class ShadowSourcesPublishArtifact(private val shadowJarTask: TaskProvider) : + PublishArtifact { + override fun getName(): String = shadowJarTask.flatMap { it.archiveBaseName }.orNull ?: "" + + override fun getExtension(): String = + shadowJarTask.flatMap { it.archiveExtension }.orNull ?: "jar" + + override fun getType(): String = "jar" + + override fun getClassifier(): String { + val shadowClassifier = shadowJarTask.flatMap { it.archiveClassifier }.orNull + return if (shadowClassifier.isNullOrEmpty()) "sources" else "$shadowClassifier-sources" + } + + override fun getFile(): File = shadowJarTask.flatMap { it.archiveSourcesFile }.get().asFile + + override fun getDate(): Date? = null + + @Suppress("EagerGradleConfiguration") + override fun getBuildDependencies(): TaskDependency = TaskDependency { + setOf(shadowJarTask.get()) + } +} diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/SourcesJar.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/SourcesJar.kt index 3bd56948c..9c63abc29 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/SourcesJar.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/SourcesJar.kt @@ -50,30 +50,34 @@ internal fun generateShadowedSourcesJar( val sourceItems = sourceSetsSourceDirs.filter { it.exists() } val (dirs, files) = sourceItems.partition { it.isDirectory } - val sortedDirs = dirs.sortedByDescending { it.path.length } + val normalizedDirs = + dirs.map { it to it.normalize().toPath() }.sortedByDescending { it.second.nameCount } val filesWithRelPaths = mutableListOf>() - val dirsCoveredByFiles = mutableSetOf() + val coveredDirs = mutableSetOf() for (file in files.sortedBy { it.path }) { - val matchingDir = sortedDirs.firstOrNull { file.startsWith(it) } + val filePath = file.normalize().toPath() + val matchingDir = + normalizedDirs.firstOrNull { (_, dirPath) -> filePath.startsWith(dirPath) }?.first if (matchingDir != null) { - dirsCoveredByFiles.add(matchingDir) + coveredDirs.add(matchingDir) filesWithRelPaths.add(file to file.relativeTo(matchingDir).invariantSeparatorsPath) } else { filesWithRelPaths.add(file to file.name) } } - for (dir in dirs.sortedBy { it.path }) { - if (dir !in dirsCoveredByFiles) { + for ((dir, dirPath) in normalizedDirs.sortedBy { it.second.nameCount }) { + if (coveredDirs.none { dirPath.startsWith(it.normalize().toPath()) }) { + coveredDirs.add(dir) dir .walkTopDown() .filter { it.isFile } .toList() .sortedBy { it.relativeTo(dir).invariantSeparatorsPath } - .forEach { file -> - filesWithRelPaths.add(file to file.relativeTo(dir).invariantSeparatorsPath) + .forEach { f -> + filesWithRelPaths.add(f to f.relativeTo(dir).invariantSeparatorsPath) } } } From a5478943e1524f14a94895206d12b64053df8266 Mon Sep 17 00:00:00 2001 From: Goooler Date: Sat, 5 Sep 2026 18:26:44 +0800 Subject: [PATCH 11/14] Reformat docs --- docs/publishing/README.md | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/docs/publishing/README.md b/docs/publishing/README.md index 31d35a07f..51db8f198 100644 --- a/docs/publishing/README.md +++ b/docs/publishing/README.md @@ -615,25 +615,31 @@ The published Maven publication will include both `--all.ja ### Local File Names vs. Published Classifiers -The Shadow plugin distinguishes between the **local output file** on disk and the **published artifact classifier** in Maven repositories and Gradle Module Metadata: +The Shadow plugin distinguishes between the **local output file** on disk and the **published artifact classifier** in +Maven repositories and Gradle Module Metadata: -| Configuration | Local Output File (`archiveSourcesFile` in `build/libs`) | Published Classifier | Published File (Maven Repository) | Use Case | -|:---|:---|:---|:---|:---| -| `archiveClassifier = "all"` *(default)* | `--all-sources.jar` | `all-sources` | `--all-sources.jar` | **Coexistence** (coexists with standard `sources`) | -| `archiveClassifier = "shaded"` | `--shaded-sources.jar` | `shaded-sources` | `--shaded-sources.jar` | **Coexistence** (custom classifier) | -| `archiveClassifier = ""` | `--sources.jar` | `sources` | `--sources.jar` | **Replacement** (replaces standard `sources`) | +| Configuration | Local Output File (`archiveSourcesFile` in `build/libs`) | Published Classifier | Published File (Maven Repository) | Use Case | +|:----------------------------------------|:---------------------------------------------------------|:---------------------|:--------------------------------------------|:---------------------------------------------------| +| `archiveClassifier = "all"` *(default)* | `--all-sources.jar` | `all-sources` | `--all-sources.jar` | **Coexistence** (coexists with standard `sources`) | +| `archiveClassifier = "shaded"` | `--shaded-sources.jar` | `shaded-sources` | `--shaded-sources.jar` | **Coexistence** (custom classifier) | +| `archiveClassifier = ""` | `--sources.jar` | `sources` | `--sources.jar` | **Replacement** (replaces standard `sources`) | #### Coexistence Scenario -When publishing alongside standard Java artifacts (e.g. publishing `from(components["java"])` with `shadow.addShadowVariantIntoJavaComponent = true`), the standard sources variant uses classifier `sources`. To prevent coordinate collisions within the same publication, the shadowed sources variant dynamically derives its classifier as `-sources` (such as `all-sources` or `shaded-sources`). +When publishing alongside standard Java artifacts (e.g. publishing `from(components["java"])` with +`shadow.addShadowVariantIntoJavaComponent = true`), the standard sources variant uses classifier `sources`. To prevent +coordinate collisions within the same publication, the shadowed sources variant dynamically derives its classifier as +`-sources` (such as `all-sources` or `shaded-sources`). #### Replacement Scenario -When configuring `shadowJar` to replace the standard JAR (`archiveClassifier = ""`), the companion shadowed sources JAR automatically uses the standard `sources` classifier. +When configuring `shadowJar` to replace the standard JAR (`archiveClassifier = ""`), the companion shadowed sources JAR +automatically uses the standard `sources` classifier. To publish shadowed artifacts as the primary publication: -1. **Publish from `components["shadow"]` (Recommended)**: Publish the `shadow` component directly in your Maven publication, and disable standard archive tasks to prevent destination file collisions in `build/libs`: +1. **Publish from `components["shadow"]` (Recommended)**: Publish the `shadow` component directly in your Maven + publication, and disable standard archive tasks to prevent destination file collisions in `build/libs`: === ":material-language-kotlin: build.gradle.kts" @@ -703,7 +709,9 @@ To publish shadowed artifacts as the primary publication: } ``` -2. **Publish from `components["java"]`**: If publishing `from(components["java"])`, disabling the `jar` or `sourcesJar` tasks does not remove standard variants from the `java` software component. You must also explicitly skip the standard publication variants: +2. **Publish from `components["java"]`**: If publishing `from(components["java"])`, disabling the `jar` or `sourcesJar` + tasks does not remove standard variants from the `java` software component. You must also explicitly skip the + standard publication variants: === ":material-language-kotlin: build.gradle.kts" @@ -747,12 +755,14 @@ To publish shadowed artifacts as the primary publication: > Generating the companion shadowed sources JAR is controlled by [`generateSourcesJar`][ShadowJar.generateSourcesJar]. > In Java projects, it defaults to `true` when `java.withSourcesJar()` is enabled, and `false` otherwise to avoid > unnecessary build overhead for application builds. If `withSourcesJar()` is omitted, publishing from -> `components["shadow"]` will only publish the shadowed binary JAR, preserving backward compatibility for existing builds. +> `components["shadow"]` will only publish the shadowed binary JAR, preserving backward compatibility for existing +builds. > You can also explicitly toggle generation via `generateSourcesJar = true` (or `--generate-sources-jar`). ### Customizing the Sources Archive File -The companion shadowed sources JAR output location is configured via [`ShadowJar.archiveSourcesFile`][ShadowJar.archiveSourcesFile], +The companion shadowed sources JAR output location is configured via +[`ShadowJar.archiveSourcesFile`][ShadowJar.archiveSourcesFile], which defaults to the same destination and base name as `archiveFile` with `-sources.jar` suffix: === ":material-language-kotlin: build.gradle.kts" From 3d9235eff277c354e341bd22dcd4aec56ed93f32 Mon Sep 17 00:00:00 2001 From: Goooler Date: Sat, 5 Sep 2026 18:36:18 +0800 Subject: [PATCH 12/14] Update samples in docs --- docs/publishing/README.md | 54 +++++++++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/docs/publishing/README.md b/docs/publishing/README.md index 51db8f198..ba5d9d48d 100644 --- a/docs/publishing/README.md +++ b/docs/publishing/README.md @@ -306,12 +306,20 @@ If you want to replace standard JARs with the shadowed ones, disable the standar === ":material-language-kotlin: build.gradle.kts" ```kotlin + plugins { + java + id("com.gradleup.shadow") + } + + java { + withSourcesJar() + } + tasks.jar { enabled = false } - // If `java.withSourcesJar()` is enabled: - tasks.matching { it.name == "sourcesJar" }.configureEach { + tasks.named("sourcesJar") { enabled = false } ``` @@ -319,12 +327,20 @@ If you want to replace standard JARs with the shadowed ones, disable the standar === ":simple-apachegroovy: build.gradle" ```groovy + plugins { + id('java') + id('com.gradleup.shadow') + } + + java { + withSourcesJar() + } + tasks.named('jar', Jar) { enabled = false } - // If `java.withSourcesJar()` is enabled: - tasks.matching { it.name == 'sourcesJar' }.configureEach { + tasks.named('sourcesJar', Jar) { enabled = false } ``` @@ -334,12 +350,20 @@ Or set different `archiveClassifier` values for the standard tasks: === ":material-language-kotlin: build.gradle.kts" ```kotlin + plugins { + java + id("com.gradleup.shadow") + } + + java { + withSourcesJar() + } + tasks.jar { archiveClassifier = "ignored" } - // If `java.withSourcesJar()` is enabled: - tasks.matching { it.name == "sourcesJar" }.configureEach { + tasks.named("sourcesJar") { (this as org.gradle.jvm.tasks.Jar).archiveClassifier = "ignored-sources" } ``` @@ -347,12 +371,20 @@ Or set different `archiveClassifier` values for the standard tasks: === ":simple-apachegroovy: build.gradle" ```groovy + plugins { + id('java') + id('com.gradleup.shadow') + } + + java { + withSourcesJar() + } + tasks.named('jar', Jar) { archiveClassifier = 'ignored' } - // If `java.withSourcesJar()` is enabled: - tasks.matching { it.name == 'sourcesJar' }.configureEach { + tasks.named('sourcesJar', Jar) { archiveClassifier = 'ignored-sources' } ``` @@ -658,7 +690,7 @@ To publish shadowed artifacts as the primary publication: enabled = false } - tasks.named("sourcesJar") { + tasks.named("sourcesJar") { enabled = false } @@ -688,11 +720,11 @@ To publish shadowed artifacts as the primary publication: withSourcesJar() } - tasks.named('jar') { + tasks.named('jar', Jar) { enabled = false } - tasks.named('sourcesJar') { + tasks.named('sourcesJar', Jar) { enabled = false } From 5308d0a1bba5d33875f50d9b14b77e18c3917005 Mon Sep 17 00:00:00 2001 From: Goooler Date: Sat, 5 Sep 2026 19:09:45 +0800 Subject: [PATCH 13/14] Prefer containsOnly for assertions --- .../gradle/plugins/shadow/FilteringTest.kt | 8 ++----- .../gradle/plugins/shadow/JavaPluginsTest.kt | 20 +++++++++++++---- .../plugins/shadow/KotlinPluginsTest.kt | 14 +++++++++--- .../gradle/plugins/shadow/MinimizeTest.kt | 22 +++++++++++++++---- .../gradle/plugins/shadow/PublishingTest.kt | 14 ++++++++++-- .../gradle/plugins/shadow/RelocationTest.kt | 9 ++++---- .../shadow/transformers/TransformersTest.kt | 4 ++-- .../plugins/shadow/internal/SourcesJarTest.kt | 12 ++++++++-- 8 files changed, 76 insertions(+), 27 deletions(-) diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/FilteringTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/FilteringTest.kt index d349a07d8..74211d1c5 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/FilteringTest.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/FilteringTest.kt @@ -2,8 +2,6 @@ package com.github.jengelman.gradle.plugins.shadow import assertk.assertThat import com.github.jengelman.gradle.plugins.shadow.testkit.classLoader -import com.github.jengelman.gradle.plugins.shadow.testkit.containsAtLeast -import com.github.jengelman.gradle.plugins.shadow.testkit.containsNone import com.github.jengelman.gradle.plugins.shadow.testkit.containsOnly import com.github.jengelman.gradle.plugins.shadow.testkit.loadClass import kotlin.io.path.appendText @@ -260,12 +258,10 @@ class FilteringTest : BasePluginTest() { runWithSuccess(shadowJarPath) assertThat(outputShadowedJar).useAll { - containsAtLeast("g/G.class") - containsNone("h/H.class", "h/UnusedH.class") + containsOnly(*entriesInAB, "g/", "g/G.class", *manifestEntries) } assertThat(outputShadowedSourcesJar).useAll { - containsAtLeast("g/G.java") - containsNone("h/H.java", "h/UnusedH.java") + containsOnly("g/", "g/G.java", *manifestEntries) } } diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/JavaPluginsTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/JavaPluginsTest.kt index 5658f00df..fe75032fe 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/JavaPluginsTest.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/JavaPluginsTest.kt @@ -295,7 +295,7 @@ class JavaPluginsTest : BasePluginTest() { // The fact that server compiled successfully against `client.junit.framework.Test` // means it consumed the shadowed artifact during compilation. assertThat(jarPath("server/build/libs/server-1.0.jar")).useAll { - containsAtLeast("server/Server.class") + containsOnly("server/", "server/Server.class", *manifestEntries) } } @@ -1176,7 +1176,13 @@ class JavaPluginsTest : BasePluginTest() { runWithSuccess(":app:$SHADOW_JAR_TASK_NAME") assertThat(jarPath("app/build/libs/app-all.jar")).useAll { - containsAtLeast("com/company/Main.class", "com/company/Utils.class", manifestEntry) + containsOnly( + "com/", + "com/company/", + "com/company/Main.class", + "com/company/Utils.class", + *manifestEntries, + ) } } @@ -1333,9 +1339,13 @@ class JavaPluginsTest : BasePluginTest() { runWithSuccess(shadowJarPath) assertThat(outputShadowedSourcesJar).useAll { - containsAtLeast( + containsOnly( + "my/", + "config/", + "config/sub/", "my/Main.java", "config/sub/app.properties", + *manifestEntries, ) } } @@ -1365,10 +1375,12 @@ class JavaPluginsTest : BasePluginTest() { runWithSuccess(shadowJarPath) assertThat(outputShadowedSourcesJar).useAll { - containsAtLeast( + containsOnly( + "my/", "my/Main.java", "a.properties", "b.properties", + *manifestEntries, ) } } diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/KotlinPluginsTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/KotlinPluginsTest.kt index 76c98bdb3..f0b8d13c9 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/KotlinPluginsTest.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/KotlinPluginsTest.kt @@ -8,7 +8,6 @@ import com.github.jengelman.gradle.plugins.shadow.internal.mainClassAttributeKey import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar.Companion.SHADOW_JAR_TASK_NAME import com.github.jengelman.gradle.plugins.shadow.testkit.classLoader import com.github.jengelman.gradle.plugins.shadow.testkit.containsAtLeast -import com.github.jengelman.gradle.plugins.shadow.testkit.containsNone import com.github.jengelman.gradle.plugins.shadow.testkit.containsOnly import com.github.jengelman.gradle.plugins.shadow.testkit.getMainAttr import com.github.jengelman.gradle.plugins.shadow.testkit.loadClass @@ -337,6 +336,7 @@ class KotlinPluginsTest : BasePluginTest() { @Test fun generateShadowedSourcesJarNormalizesPackageDirectory() { + val stdlib = compileOnlyStdlib(true) path("src/main/kotlin/FlatFile.kt") .writeText( """ @@ -349,6 +349,9 @@ class KotlinPluginsTest : BasePluginTest() { projectScript.writeText( """ |${getDefaultProjectBuildScript(plugin = "org.jetbrains.kotlin.jvm")} + |dependencies { + | $stdlib + |} |$shadowJarTask { | generateSourcesJar = true | relocate 'my.custom', 'shadow.custom' @@ -360,8 +363,13 @@ class KotlinPluginsTest : BasePluginTest() { runWithSuccess(shadowJarPath) assertThat(outputShadowedSourcesJar).useAll { - containsAtLeast("shadow/custom/nested/FlatFile.kt") - containsNone("FlatFile.kt") + containsOnly( + "shadow/", + "shadow/custom/", + "shadow/custom/nested/", + "shadow/custom/nested/FlatFile.kt", + *manifestEntries, + ) } } diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/MinimizeTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/MinimizeTest.kt index 7239ad0e4..7868cf770 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/MinimizeTest.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/MinimizeTest.kt @@ -156,12 +156,26 @@ class MinimizeTest : BasePluginTest() { runWithSuccess(shadowJarPath) assertThat(outputShadowedJar).useAll { - containsAtLeast("my/Main.class", "h/H.class", "k/CustomUtils.class") - containsNone("h/UnusedH.class", "k/CustomUnusedUtils.class") + containsOnly( + "my/", + "h/", + "k/", + "my/Main.class", + "h/H.class", + "k/CustomUtils.class", + *manifestEntries, + ) } assertThat(outputShadowedSourcesJar).useAll { - containsAtLeast("my/Main.java", "h/H.java", "k/Utils.kt") - containsNone("h/UnusedH.java", "k/UnusedUtils.kt") + containsOnly( + "my/", + "h/", + "k/", + "my/Main.java", + "h/H.java", + "k/Utils.kt", + *manifestEntries, + ) } } diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt index 6b228540d..783a68314 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt @@ -919,21 +919,31 @@ class PublishingTest : BasePluginTest() { ) assertThat(repoJarPath("$artifactRoot/my-all-1.0.jar")).useAll { - containsAtLeast( + containsOnly( + "my/", + "g/", + "h/", "my/CommonMain.class", "my/JvmMain.class", "g/G.class", "h/H.class", + "h/UnusedH.class", + "META-INF/my_maven.kotlin_module", *manifestEntries, ) } assertThat(repoJarPath("$artifactRoot/my-all-1.0-sources.jar")).useAll { - containsAtLeast( + containsOnly( + "my/", + "g/", + "h/", "my/CommonMain.kt", "my/JvmMain.kt", "g/G.java", "h/H.java", + "h/UnusedH.java", + *manifestEntries, ) } 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 48b98c9bb..7879115f7 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 @@ -9,8 +9,6 @@ import assertk.assertions.isNotEqualTo import assertk.fail import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar.Companion.CONSTANT_TIME_FOR_ZIP_ENTRIES import com.github.jengelman.gradle.plugins.shadow.testkit.classLoader -import com.github.jengelman.gradle.plugins.shadow.testkit.containsAtLeast -import com.github.jengelman.gradle.plugins.shadow.testkit.containsNone 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.getContent @@ -921,8 +919,11 @@ class RelocationTest : BasePluginTest() { runWithSuccess(shadowJarPath) assertThat(outputShadowedSourcesJar).useAll { - containsAtLeast("my/Main.java") - containsNone("my/Excluded.java") + containsOnly( + "my/", + "my/Main.java", + *manifestEntries, + ) } } diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/TransformersTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/TransformersTest.kt index a707a2fc6..8f901d4bc 100644 --- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/TransformersTest.kt +++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/TransformersTest.kt @@ -219,7 +219,7 @@ class TransformersTest : BaseTransformerTest() { runWithSuccess(shadowJarPath) assertThat(outputShadowedJar).useAll { - containsOnly("META-INF/", "META-INF/LICENSE", *manifestEntries) + containsOnly("META-INF/LICENSE", *manifestEntries) getContent("META-INF/LICENSE") .isEqualTo( """ @@ -379,7 +379,7 @@ class TransformersTest : BaseTransformerTest() { runWithSuccess(shadowJarPath) assertThat(outputShadowedJar).useAll { - containsOnly("META-INF/", "META-INF/kotlin-stdlib.shadow.kotlin_module", *manifestEntries) + containsOnly("META-INF/kotlin-stdlib.shadow.kotlin_module", *manifestEntries) getBytes("META-INF/kotlin-stdlib.shadow.kotlin_module").isNotEqualTo(moduleBytes) } } diff --git a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/SourcesJarTest.kt b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/SourcesJarTest.kt index aa5a4703d..8bfc411ab 100644 --- a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/SourcesJarTest.kt +++ b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/SourcesJarTest.kt @@ -2,7 +2,7 @@ package com.github.jengelman.gradle.plugins.shadow.internal import assertk.assertFailure import assertk.assertThat -import assertk.assertions.containsAtLeast +import assertk.assertions.containsOnly import assertk.assertions.hasMessage import assertk.assertions.isEqualTo import assertk.assertions.isFalse @@ -131,7 +131,15 @@ class SourcesJarTest { assertThat(outputJar.exists()).isTrue() val entries = ZipFile(outputJar).use { zip -> zip.entries().toList().map { it.name } } - assertThat(entries).containsAtLeast("shadow/example/nested/Mismatched.kt") + assertThat(entries) + .containsOnly( + "META-INF/", + "META-INF/MANIFEST.MF", + "shadow/", + "shadow/example/", + "shadow/example/nested/", + "shadow/example/nested/Mismatched.kt", + ) } @Test From 1934fdf2f93a4c30ddb509a432d086e506ab06c8 Mon Sep 17 00:00:00 2001 From: Goooler Date: Sat, 5 Sep 2026 19:13:16 +0800 Subject: [PATCH 14/14] Cleanups --- .../gradle/plugins/shadow/ShadowJavaPlugin.kt | 51 ++++++----------- .../internal/DefaultDependencyFilter.kt | 21 ++++--- .../plugins/shadow/internal/SourcesJar.kt | 56 +++++++++---------- .../shadow/relocation/SimpleRelocator.kt | 4 +- 4 files changed, 57 insertions(+), 75 deletions(-) diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.kt index eb96bf373..0a842bb83 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.kt @@ -5,18 +5,14 @@ import com.github.jengelman.gradle.plugins.shadow.ShadowBasePlugin.Companion.sha import com.github.jengelman.gradle.plugins.shadow.internal.javaPluginExtension import com.github.jengelman.gradle.plugins.shadow.internal.runtimeConfiguration import com.github.jengelman.gradle.plugins.shadow.internal.sourceSets -import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar.Companion.registerShadowJarCommon import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar.Companion.shadowJar -import java.io.File -import java.util.Date import javax.inject.Inject import org.gradle.api.NamedDomainObjectProvider import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.artifacts.ConfigurationContainer import org.gradle.api.artifacts.ConsumableConfiguration -import org.gradle.api.artifacts.PublishArtifact import org.gradle.api.attributes.Bundling import org.gradle.api.attributes.Category import org.gradle.api.attributes.DocsType @@ -29,8 +25,6 @@ import org.gradle.api.component.SoftwareComponentFactory import org.gradle.api.logging.Logger import org.gradle.api.plugins.JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME import org.gradle.api.plugins.JavaPlugin.SOURCES_ELEMENTS_CONFIGURATION_NAME -import org.gradle.api.tasks.TaskDependency -import org.gradle.api.tasks.TaskProvider import org.gradle.api.tasks.bundling.Jar public abstract class ShadowJavaPlugin @@ -99,7 +93,22 @@ constructor(private val softwareComponentFactory: SoftwareComponentFactory) : Pl objects.named(DocsType::class.java, DocsType.SOURCES), ) } - outgoing.artifact(ShadowSourcesPublishArtifact(tasks.shadowJar)) + val shadowJarTask = tasks.shadowJar + outgoing.artifact(shadowJarTask.flatMap { it.archiveSourcesFile }) { artifact -> + with(artifact) { + builtBy(shadowJarTask) + name = shadowJarTask.flatMap { it.archiveBaseName }.orNull.orEmpty() + extension = shadowJarTask.flatMap { it.archiveExtension }.orNull ?: "jar" + type = "jar" + classifier = + shadowJarTask + .flatMap { it.archiveClassifier } + .orNull + .let { shadowClassifier -> + if (shadowClassifier.isNullOrEmpty()) "sources" else "$shadowClassifier-sources" + } + } + } } // See more details in #2086. @@ -141,9 +150,9 @@ constructor(private val softwareComponentFactory: SoftwareComponentFactory) : Pl val shadowRuntimeElements = configurations.shadowRuntimeElements val shadowSourcesElements = configurations.shadowSourcesElements // If `withSourcesJar` is present and `generateSourcesJar` is enabled. - val sourcesElements = { configurations.findByName(SOURCES_ELEMENTS_CONFIGURATION_NAME) } val shouldAddSources = { - sourcesElements() != null && tasks.shadowJar.flatMap { it.generateSourcesJar }.get() + configurations.findByName(SOURCES_ELEMENTS_CONFIGURATION_NAME) != null && + tasks.shadowJar.flatMap { it.generateSourcesJar }.get() } val shadowComponent = softwareComponentFactory.adhoc(COMPONENT_NAME) @@ -232,27 +241,3 @@ constructor(private val softwareComponentFactory: SoftwareComponentFactory) : Pl get() = named(SHADOW_SOURCES_ELEMENTS_CONFIGURATION_NAME, ConsumableConfiguration::class.java) } } - -internal class ShadowSourcesPublishArtifact(private val shadowJarTask: TaskProvider) : - PublishArtifact { - override fun getName(): String = shadowJarTask.flatMap { it.archiveBaseName }.orNull ?: "" - - override fun getExtension(): String = - shadowJarTask.flatMap { it.archiveExtension }.orNull ?: "jar" - - override fun getType(): String = "jar" - - override fun getClassifier(): String { - val shadowClassifier = shadowJarTask.flatMap { it.archiveClassifier }.orNull - return if (shadowClassifier.isNullOrEmpty()) "sources" else "$shadowClassifier-sources" - } - - override fun getFile(): File = shadowJarTask.flatMap { it.archiveSourcesFile }.get().asFile - - override fun getDate(): Date? = null - - @Suppress("EagerGradleConfiguration") - override fun getBuildDependencies(): TaskDependency = TaskDependency { - setOf(shadowJarTask.get()) - } -} diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/DefaultDependencyFilter.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/DefaultDependencyFilter.kt index 86f5544ac..03ac02909 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/DefaultDependencyFilter.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/DefaultDependencyFilter.kt @@ -44,18 +44,17 @@ internal class DefaultDependencyFilter(@Transient private val project: Project) includedDependencies = includes, excludedDependencies = excludes, ) - val allResolvedDependencies = - configuration.incoming.resolutionResult.allDependencies.filterIsInstance< - ResolvedDependencyResult - >() - val includedDependenciesResults = allResolvedDependencies.filter { dep -> - includes.any { inc -> - inc.moduleGroup == dep.selected.moduleVersion?.group && - inc.moduleName == dep.selected.moduleVersion?.name && - inc.moduleVersion == dep.selected.moduleVersion?.version - } - } + val includedDependenciesResults = + configuration.incoming.resolutionResult.allDependencies + .filterIsInstance() + .filter { dep -> + includes.any { inc -> + inc.moduleGroup == dep.selected.moduleVersion?.group && + inc.moduleName == dep.selected.moduleVersion?.name && + inc.moduleVersion == dep.selected.moduleVersion?.version + } + } val externalComponentIds = includedDependenciesResults diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/SourcesJar.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/SourcesJar.kt index 9c63abc29..b94dc63e5 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/SourcesJar.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/SourcesJar.kt @@ -5,6 +5,9 @@ import com.github.jengelman.gradle.plugins.shadow.relocation.relocatePath import java.io.File import java.nio.charset.Charset import org.gradle.api.tasks.bundling.ZipEntryCompression +import org.vafer.jdeb.shaded.objectweb.asm.ClassReader +import org.vafer.jdeb.shaded.objectweb.asm.ClassVisitor +import org.vafer.jdeb.shaded.objectweb.asm.Opcodes internal fun generateShadowedSourcesJar( sourcesJarFile: File, @@ -218,32 +221,28 @@ internal fun buildSourceToClassesMap( try { var internalName: String? = null var sourceFile: String? = null - val reader = org.vafer.jdeb.shaded.objectweb.asm.ClassReader(bytes) - reader.accept( - object : - org.vafer.jdeb.shaded.objectweb.asm.ClassVisitor( - org.vafer.jdeb.shaded.objectweb.asm.Opcodes.ASM9 - ) { - override fun visit( - version: Int, - access: Int, - name: String, - signature: String?, - superName: String?, - interfaces: Array?, - ) { - internalName = name - super.visit(version, access, name, signature, superName, interfaces) - } + ClassReader(bytes) + .accept( + object : ClassVisitor(Opcodes.ASM9) { + override fun visit( + version: Int, + access: Int, + name: String, + signature: String?, + superName: String?, + interfaces: Array?, + ) { + internalName = name + super.visit(version, access, name, signature, superName, interfaces) + } - override fun visitSource(source: String?, debug: String?) { - sourceFile = source - super.visitSource(source, debug) - } - }, - org.vafer.jdeb.shaded.objectweb.asm.ClassReader.SKIP_CODE or - org.vafer.jdeb.shaded.objectweb.asm.ClassReader.SKIP_FRAMES, - ) + override fun visitSource(source: String?, debug: String?) { + sourceFile = source + super.visitSource(source, debug) + } + }, + ClassReader.SKIP_CODE or ClassReader.SKIP_FRAMES, + ) val name = internalName ?: return val source = sourceFile ?: return @@ -256,7 +255,7 @@ internal fun buildSourceToClassesMap( } } - for (dir in classesDirs.filter { it.isDirectory }) { + for (dir in classesDirs.filter(File::isDirectory)) { dir .walkTopDown() .filter { it.isFile && it.name.endsWith(".class") } @@ -264,7 +263,7 @@ internal fun buildSourceToClassesMap( } for (file in - dependencies.filter { it.isFile && (it.name.endsWith(".jar") || it.name.endsWith(".zip")) }) { + dependencies.filter { it.isFile && (it.extension == "jar" || it.extension == "zip") }) { try { file.useZip { entries() @@ -287,8 +286,7 @@ internal fun isUnused( ): Boolean { if (unusedClasses.isEmpty()) return false val classes = sourceToClasses[canonicalPath] ?: return false - if (classes.isEmpty()) return false - return classes.all { it in unusedClasses } + return classes.isNotEmpty() && classes.all { it in unusedClasses } } private fun isSourceFile(path: String): Boolean { diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/SimpleRelocator.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/SimpleRelocator.kt index 7ca7a75d5..1e33e9d26 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/SimpleRelocator.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/SimpleRelocator.kt @@ -233,7 +233,7 @@ constructor( } } - fun getSourceSubpatterns(patterns: Set, patternPrefix: String): Set { + private fun getSourceSubpatterns(patterns: Set, patternPrefix: String): Set { if (patternPrefix.isEmpty()) return emptySet() val result = mutableSetOf() val dotPrefix = patternPrefix.replace('/', '.') @@ -273,7 +273,7 @@ constructor( return !nextChar.isLetterOrDigit() && nextChar != '_' } - fun shadeSourceWithFilters( + private fun shadeSourceWithFilters( sourceContent: String, patternFrom: String, patternTo: String,