From d71fd6a009a548a8c06d081ce879384204bc9740 Mon Sep 17 00:00:00 2001 From: Goooler Date: Wed, 26 Aug 2026 13:29:54 +0800 Subject: [PATCH 01/20] Optimize JAR class entry reading and use ZipFile in R8Minimizer --- .../plugins/shadow/internal/R8Minimizer.kt | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt index 7eb20e39c..7a5cf637d 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt @@ -4,7 +4,7 @@ import com.github.jengelman.gradle.plugins.shadow.relocation.Relocator import com.github.jengelman.gradle.plugins.shadow.relocation.relocateClass import java.io.File import java.nio.file.StandardCopyOption.REPLACE_EXISTING -import java.util.jar.JarFile +import java.util.zip.ZipFile import kotlin.io.path.moveTo import org.gradle.api.GradleException import org.gradle.api.file.FileCollection @@ -107,13 +107,14 @@ private fun createRules( keptDependencyFiles: Iterable, relocators: Iterable, ): List { + val jarClasses = jarClassEntries(inputJar) return buildList { add(baseDirectory.toBaseDirectoryRule()) if (shouldDisableOptimization(r8Spec, r8Args)) { add(DefaultR8Spec.DONT_OPTIMIZE_RULE) } - addAll(sourceProguardRules(inputJar, sourceSetsClassesDirs, relocators)) - addAll(keptDependencyRules(inputJar, keptDependencyFiles, relocators)) + addAll(sourceProguardRules(jarClasses, sourceSetsClassesDirs, relocators)) + addAll(keptDependencyRules(jarClasses, keptDependencyFiles, relocators)) addAll(serviceProguardRules(inputJar)) r8Spec.proguardRuleFiles .sortedBy { it.absolutePath } @@ -134,11 +135,10 @@ private fun shouldDisableOptimization(r8Spec: DefaultR8Spec, r8Args: List, sourceSetsClassesDirs: Iterable, relocators: Iterable, ): List { - val jarClasses = jarClassEntries(inputJar) return sourceSetsClassesDirs .asSequence() .filter(File::isDirectory) @@ -162,11 +162,10 @@ private fun sourceProguardRules( // Keep dependencies users explicitly excluded from minimization, matching the existing // minimize { exclude(...) } contract for the default analyzer. private fun keptDependencyRules( - inputJar: File, + jarClasses: Set, keptDependencyFiles: Iterable, relocators: Iterable, ): List { - val jarClasses = jarClassEntries(inputJar) return keptDependencyFiles .asSequence() .flatMap { it.classNames() } @@ -183,8 +182,8 @@ private fun keptDependencyRules( // interface and every listed provider even if R8 sees no direct references. private fun serviceProguardRules(inputJar: File): List { val rules = linkedSetOf() - JarFile(inputJar).use { jarFile -> - jarFile + ZipFile(inputJar).use { zipFile -> + zipFile .entries() .asSequence() .filter { !it.isDirectory && it.name.startsWith(SERVICES_PATH) } @@ -194,7 +193,7 @@ private fun serviceProguardRules(inputJar: File): List { if (serviceClass.isJavaTypeName()) { rules += "-keep,allowrepackage class $serviceClass { *; }" } - jarFile.getInputStream(entry).bufferedReader().useLines { lines -> + zipFile.getInputStream(entry).bufferedReader().useLines { lines -> lines .map { it.substringBefore('#').trim() } .filter { it.isNotEmpty() && it.isJavaTypeName() } @@ -206,8 +205,8 @@ private fun serviceProguardRules(inputJar: File): List { } private fun jarClassEntries(inputJar: File): Set { - return JarFile(inputJar).use { jarFile -> - jarFile + return ZipFile(inputJar).use { zipFile -> + zipFile .entries() .asSequence() .filter { !it.isDirectory && it.name.endsWith(".class") } @@ -243,9 +242,9 @@ private fun File.classNames(): Sequence { it.toClassName(relativeTo = this) } isFile -> - JarFile(this) - .use { jarFile -> - jarFile + ZipFile(this) + .use { zipFile -> + zipFile .entries() .asSequence() .filter { !it.isDirectory && it.name.endsWith(".class") } From b1cf7b9c3ff39cae819b2cd3af8991ff892d46c0 Mon Sep 17 00:00:00 2001 From: Goooler Date: Wed, 26 Aug 2026 13:32:36 +0800 Subject: [PATCH 02/20] Optimize class name conversion and Java type name validation in R8Minimizer --- .../plugins/shadow/internal/R8Minimizer.kt | 41 +++++++++++++------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt index 7a5cf637d..7f11ca366 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt @@ -152,7 +152,7 @@ private fun sourceProguardRules( } .map { relocators.relocateClass(it) } .filter { it.isJavaTypeName() } - .filter { className -> "${className.replace('.', '/')}.class" in jarClasses } + .filter { className -> className in jarClasses } .distinct() .sorted() .map { "-keep,includedescriptorclasses class $it { *; }" } @@ -171,7 +171,7 @@ private fun keptDependencyRules( .flatMap { it.classNames() } .map { relocators.relocateClass(it) } .filter { it.isJavaTypeName() } - .filter { className -> "${className.replace('.', '/')}.class" in jarClasses } + .filter { className -> className in jarClasses } .distinct() .sorted() .map { "-keep class $it { *; }" } @@ -210,19 +210,16 @@ private fun jarClassEntries(inputJar: File): Set { .entries() .asSequence() .filter { !it.isDirectory && it.name.endsWith(".class") } - .map { it.name } + .mapNotNull { it.name.toClassName() } .toSet() } } private fun File.toClassName(relativeTo: File): String? { if (name == "module-info.class" || name == "package-info.class") return null - return relativeTo - .toPath() - .relativize(toPath()) - .toString() - .replace(File.separatorChar, '/') + return toRelativeString(relativeTo) .removeSuffix(".class") + .replace(File.separatorChar, '.') .replace('/', '.') } @@ -262,10 +259,30 @@ private fun String.toClassName(): String? { return removeSuffix(".class").replace('/', '.') } -private fun String.isJavaTypeName(): Boolean = javaTypeNameRegex.matches(this) +// Keep only ordinary dot-separated Java type names in generated rules. This filters out blank +// service lines, comments, malformed providers, and JVM-only names R8 would reject. +private fun String.isJavaTypeName(): Boolean { + if (isEmpty()) return false + var startOfIdentifier = true + for (i in indices) { + val c = this[i] + if (c == '.') { + if (startOfIdentifier) return false + startOfIdentifier = true + } else if (startOfIdentifier) { + if (!c.isJavaTypeNameStart()) return false + startOfIdentifier = false + } else { + if (!c.isJavaTypeNamePart()) return false + } + } + return !startOfIdentifier +} + +private fun Char.isJavaTypeNameStart(): Boolean = + this in 'a'..'z' || this in 'A'..'Z' || this == '_' || this == '$' + +private fun Char.isJavaTypeNamePart(): Boolean = isJavaTypeNameStart() || this in '0'..'9' private const val R8_MAIN_CLASS = "com.android.tools.r8.R8" private const val SERVICES_PATH = "META-INF/services/" -// Keep only ordinary dot-separated Java type names in generated rules. This filters out blank -// service lines, comments, malformed providers, and JVM-only names R8 would reject. -private val javaTypeNameRegex = Regex("[A-Za-z_$][A-Za-z0-9_$]*(\\.[A-Za-z_$][A-Za-z0-9_$]*)*") From 1ba2b25ccf0c4da7c022351759bf03c0fa71267d Mon Sep 17 00:00:00 2001 From: Goooler Date: Wed, 26 Aug 2026 13:37:20 +0800 Subject: [PATCH 03/20] Add and reuse File.useZip extension --- .../plugins/shadow/internal/R8Minimizer.kt | 23 ++++++++----------- .../gradle/plugins/shadow/internal/Zip.kt | 3 +++ .../gradle/plugins/shadow/tasks/ShadowJar.kt | 8 +++---- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt index 7f11ca366..6c27dc32e 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt @@ -4,7 +4,6 @@ import com.github.jengelman.gradle.plugins.shadow.relocation.Relocator import com.github.jengelman.gradle.plugins.shadow.relocation.relocateClass import java.io.File import java.nio.file.StandardCopyOption.REPLACE_EXISTING -import java.util.zip.ZipFile import kotlin.io.path.moveTo import org.gradle.api.GradleException import org.gradle.api.file.FileCollection @@ -182,7 +181,7 @@ private fun keptDependencyRules( // interface and every listed provider even if R8 sees no direct references. private fun serviceProguardRules(inputJar: File): List { val rules = linkedSetOf() - ZipFile(inputJar).use { zipFile -> + inputJar.useZip { zipFile -> zipFile .entries() .asSequence() @@ -205,7 +204,7 @@ private fun serviceProguardRules(inputJar: File): List { } private fun jarClassEntries(inputJar: File): Set { - return ZipFile(inputJar).use { zipFile -> + return inputJar.useZip { zipFile -> zipFile .entries() .asSequence() @@ -238,16 +237,14 @@ private fun File.classNames(): Sequence { .mapNotNull { it.toClassName(relativeTo = this) } - isFile -> - ZipFile(this) - .use { zipFile -> - zipFile - .entries() - .asSequence() - .filter { !it.isDirectory && it.name.endsWith(".class") } - .mapNotNull { it.name.toClassName() } - .toList() - } + isFile -> useZip { zipFile -> + zipFile + .entries() + .asSequence() + .filter { !it.isDirectory && it.name.endsWith(".class") } + .mapNotNull { it.name.toClassName() } + .toList() + } .asSequence() else -> emptySequence() } diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/Zip.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/Zip.kt index 7a58401ed..21639ac8c 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/Zip.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/Zip.kt @@ -3,6 +3,7 @@ package com.github.jengelman.gradle.plugins.shadow.internal import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar.Companion.CONSTANT_TIME_FOR_ZIP_ENTRIES import java.io.File import java.io.OutputStream +import java.util.zip.ZipFile import org.apache.tools.zip.UnixStat import org.apache.tools.zip.Zip64Mode import org.apache.tools.zip.ZipEntry @@ -44,6 +45,8 @@ internal val ZipOutputStream.entries: List ?: this::class.java.getDeclaredField("entries").apply { isAccessible = true }.get(this) as List +internal inline fun File.useZip(block: (ZipFile) -> R): R = ZipFile(this).use(block) + internal fun File.createZipOutputStream( entryCompression: ZipEntryCompression, isZip64: 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 e8d8487f8..d09382fff 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 @@ -20,6 +20,7 @@ import com.github.jengelman.gradle.plugins.shadow.internal.multiReleaseAttribute import com.github.jengelman.gradle.plugins.shadow.internal.property import com.github.jengelman.gradle.plugins.shadow.internal.setProperty import com.github.jengelman.gradle.plugins.shadow.internal.sourceSets +import com.github.jengelman.gradle.plugins.shadow.internal.useZip import com.github.jengelman.gradle.plugins.shadow.relocation.CacheableRelocator import com.github.jengelman.gradle.plugins.shadow.relocation.Relocator import com.github.jengelman.gradle.plugins.shadow.relocation.SimpleRelocator @@ -35,7 +36,6 @@ import java.io.IOException import java.util.GregorianCalendar import java.util.jar.JarFile import java.util.zip.ZipException -import java.util.zip.ZipFile import javax.inject.Inject import kotlin.reflect.full.hasAnnotation import org.gradle.api.Action @@ -591,8 +591,8 @@ public abstract class ShadowJar : Jar() { } val prefix = relocationPrefix.get() return includedDependencies.flatMap { file -> - JarFile(file).use { jarFile -> - jarFile + file.useZip { zipFile -> + zipFile .entries() .toList() .filter { it.name.endsWith(".class") && it.name != "module-info.class" } @@ -607,7 +607,7 @@ public abstract class ShadowJar : Jar() { val isAar: File.() -> Boolean = { try { extension.equals("aar", ignoreCase = true) && - ZipFile(this).use { zip -> zip.getEntry("AndroidManifest.xml") != null } + useZip { zip -> zip.getEntry("AndroidManifest.xml") != null } } catch (_: ZipException) { // File is not a valid ZIP, so it cannot be an AAR. false From f11c58c8e97eedfc8190b9a0ddd1201b583dfbd6 Mon Sep 17 00:00:00 2001 From: Goooler Date: Wed, 26 Aug 2026 13:41:56 +0800 Subject: [PATCH 04/20] Revert isJavaTypeName back to Regex-based implementation --- .../plugins/shadow/internal/R8Minimizer.kt | 28 +++---------------- 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt index 6c27dc32e..95f96d475 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt @@ -256,30 +256,10 @@ private fun String.toClassName(): String? { return removeSuffix(".class").replace('/', '.') } -// Keep only ordinary dot-separated Java type names in generated rules. This filters out blank -// service lines, comments, malformed providers, and JVM-only names R8 would reject. -private fun String.isJavaTypeName(): Boolean { - if (isEmpty()) return false - var startOfIdentifier = true - for (i in indices) { - val c = this[i] - if (c == '.') { - if (startOfIdentifier) return false - startOfIdentifier = true - } else if (startOfIdentifier) { - if (!c.isJavaTypeNameStart()) return false - startOfIdentifier = false - } else { - if (!c.isJavaTypeNamePart()) return false - } - } - return !startOfIdentifier -} - -private fun Char.isJavaTypeNameStart(): Boolean = - this in 'a'..'z' || this in 'A'..'Z' || this == '_' || this == '$' - -private fun Char.isJavaTypeNamePart(): Boolean = isJavaTypeNameStart() || this in '0'..'9' +private fun String.isJavaTypeName(): Boolean = javaTypeNameRegex.matches(this) private const val R8_MAIN_CLASS = "com.android.tools.r8.R8" private const val SERVICES_PATH = "META-INF/services/" +// Keep only ordinary dot-separated Java type names in generated rules. This filters out blank +// service lines, comments, malformed providers, and JVM-only names R8 would reject. +private val javaTypeNameRegex = Regex("[A-Za-z_$][A-Za-z0-9_$]*(\\.[A-Za-z_$][A-Za-z0-9_$]*)*") From 78dc86718b45093d50f45c7579b719e5e55a1b19 Mon Sep 17 00:00:00 2001 From: Goooler Date: Wed, 26 Aug 2026 13:42:22 +0800 Subject: [PATCH 05/20] Remove stray character in R8Minimizer From f5e0b26762c5710194d9d09f37d4231c3fb8f2df Mon Sep 17 00:00:00 2001 From: Goooler Date: Wed, 26 Aug 2026 13:43:44 +0800 Subject: [PATCH 06/20] Extract findJarClasses helper in R8Minimizer --- .../plugins/shadow/internal/R8Minimizer.kt | 29 +++++++------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt index 95f96d475..98f8036ee 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt @@ -139,21 +139,7 @@ private fun sourceProguardRules( relocators: Iterable, ): List { return sourceSetsClassesDirs - .asSequence() - .filter(File::isDirectory) - .flatMap { dir -> - dir - .walkTopDown() - .filter { it.isFile && it.name.endsWith(".class") } - .mapNotNull { file -> - file.toClassName(relativeTo = dir) - } - } - .map { relocators.relocateClass(it) } - .filter { it.isJavaTypeName() } - .filter { className -> className in jarClasses } - .distinct() - .sorted() + .findJarClasses(jarClasses, relocators) .map { "-keep,includedescriptorclasses class $it { *; }" } .toList() } @@ -166,15 +152,22 @@ private fun keptDependencyRules( relocators: Iterable, ): List { return keptDependencyFiles - .asSequence() + .findJarClasses(jarClasses, relocators) + .map { "-keep class $it { *; }" } + .toList() +} + +private fun Iterable.findJarClasses( + jarClasses: Set, + relocators: Iterable, +): Sequence { + return asSequence() .flatMap { it.classNames() } .map { relocators.relocateClass(it) } .filter { it.isJavaTypeName() } .filter { className -> className in jarClasses } .distinct() .sorted() - .map { "-keep class $it { *; }" } - .toList() } // Service descriptors are usage edges for downstream ServiceLoader calls, so keep the service From 4daa1af7a2c44892edae760e305cc2f140c6b85e Mon Sep 17 00:00:00 2001 From: Goooler Date: Wed, 26 Aug 2026 13:46:30 +0800 Subject: [PATCH 07/20] Use jarFile names --- .../gradle/plugins/shadow/internal/R8Minimizer.kt | 14 +++++++------- .../gradle/plugins/shadow/tasks/ShadowJar.kt | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt index 98f8036ee..d4f5c6964 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt @@ -174,8 +174,8 @@ private fun Iterable.findJarClasses( // interface and every listed provider even if R8 sees no direct references. private fun serviceProguardRules(inputJar: File): List { val rules = linkedSetOf() - inputJar.useZip { zipFile -> - zipFile + inputJar.useZip { jarFile -> + jarFile .entries() .asSequence() .filter { !it.isDirectory && it.name.startsWith(SERVICES_PATH) } @@ -185,7 +185,7 @@ private fun serviceProguardRules(inputJar: File): List { if (serviceClass.isJavaTypeName()) { rules += "-keep,allowrepackage class $serviceClass { *; }" } - zipFile.getInputStream(entry).bufferedReader().useLines { lines -> + jarFile.getInputStream(entry).bufferedReader().useLines { lines -> lines .map { it.substringBefore('#').trim() } .filter { it.isNotEmpty() && it.isJavaTypeName() } @@ -197,8 +197,8 @@ private fun serviceProguardRules(inputJar: File): List { } private fun jarClassEntries(inputJar: File): Set { - return inputJar.useZip { zipFile -> - zipFile + return inputJar.useZip { jarFile -> + jarFile .entries() .asSequence() .filter { !it.isDirectory && it.name.endsWith(".class") } @@ -230,8 +230,8 @@ private fun File.classNames(): Sequence { .mapNotNull { it.toClassName(relativeTo = this) } - isFile -> useZip { zipFile -> - zipFile + isFile -> useZip { jarFile -> + jarFile .entries() .asSequence() .filter { !it.isDirectory && it.name.endsWith(".class") } 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 d09382fff..22f837626 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 @@ -591,8 +591,8 @@ public abstract class ShadowJar : Jar() { } val prefix = relocationPrefix.get() return includedDependencies.flatMap { file -> - file.useZip { zipFile -> - zipFile + file.useZip { jarFile -> + jarFile .entries() .toList() .filter { it.name.endsWith(".class") && it.name != "module-info.class" } From 93eee9794d101147adcf29b4537e1009adffb836 Mon Sep 17 00:00:00 2001 From: Goooler Date: Wed, 26 Aug 2026 13:47:43 +0800 Subject: [PATCH 08/20] Simplify path separator replacement in File.toClassName --- .../jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt index d4f5c6964..e881b6be8 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt @@ -209,10 +209,7 @@ private fun jarClassEntries(inputJar: File): Set { private fun File.toClassName(relativeTo: File): String? { if (name == "module-info.class" || name == "package-info.class") return null - return toRelativeString(relativeTo) - .removeSuffix(".class") - .replace(File.separatorChar, '.') - .replace('/', '.') + return toRelativeString(relativeTo).removeSuffix(".class").replace(File.separatorChar, '.') } private fun File.toBaseDirectoryRule(): String { From 6bcad2640b6cee0bfcdddeb5d3f5234e625e2b07 Mon Sep 17 00:00:00 2001 From: Goooler Date: Wed, 26 Aug 2026 13:52:49 +0800 Subject: [PATCH 09/20] Inline R8_MAIN_CLASS --- .../jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt index e881b6be8..6eee37505 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt @@ -87,7 +87,7 @@ internal fun minimizeWithR8( logger.info("Running R8 to minimize {}.", inputJar) execOperations.javaexec { it.classpath = r8Classpath - it.mainClass.set(R8_MAIN_CLASS) + it.mainClass.set("com.android.tools.r8.R8") if (launcher != null) { it.executable = launcher.executablePath.asFile.absolutePath } @@ -248,7 +248,6 @@ private fun String.toClassName(): String? { private fun String.isJavaTypeName(): Boolean = javaTypeNameRegex.matches(this) -private const val R8_MAIN_CLASS = "com.android.tools.r8.R8" private const val SERVICES_PATH = "META-INF/services/" // Keep only ordinary dot-separated Java type names in generated rules. This filters out blank // service lines, comments, malformed providers, and JVM-only names R8 would reject. From d41587fda0f16ac0095a4440f7fbc33f36ba5ad7 Mon Sep 17 00:00:00 2001 From: Goooler Date: Wed, 26 Aug 2026 14:06:02 +0800 Subject: [PATCH 10/20] Simplify jarClassEntries and optimize File.classNames return type --- .../plugins/shadow/internal/R8Minimizer.kt | 22 ++++--------------- 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt index 6eee37505..8ebfa7d02 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt @@ -106,7 +106,7 @@ private fun createRules( keptDependencyFiles: Iterable, relocators: Iterable, ): List { - val jarClasses = jarClassEntries(inputJar) + val jarClasses = inputJar.classNames().toSet() return buildList { add(baseDirectory.toBaseDirectoryRule()) if (shouldDisableOptimization(r8Spec, r8Args)) { @@ -196,17 +196,6 @@ private fun serviceProguardRules(inputJar: File): List { return rules.toList() } -private fun jarClassEntries(inputJar: File): Set { - return inputJar.useZip { jarFile -> - jarFile - .entries() - .asSequence() - .filter { !it.isDirectory && it.name.endsWith(".class") } - .mapNotNull { it.name.toClassName() } - .toSet() - } -} - private fun File.toClassName(relativeTo: File): String? { if (name == "module-info.class" || name == "package-info.class") return null return toRelativeString(relativeTo).removeSuffix(".class").replace(File.separatorChar, '.') @@ -224,18 +213,15 @@ private fun File.classNames(): Sequence { isDirectory -> walkTopDown() .filter { it.isFile && it.name.endsWith(".class") } - .mapNotNull { - it.toClassName(relativeTo = this) - } - isFile -> useZip { jarFile -> + .mapNotNull { it.toClassName(relativeTo = this) } + isFile -> + useZip { jarFile -> jarFile .entries() .asSequence() .filter { !it.isDirectory && it.name.endsWith(".class") } .mapNotNull { it.name.toClassName() } - .toList() } - .asSequence() else -> emptySequence() } } From 95707363eb4b00947c2ae00cd804cac68644c4f0 Mon Sep 17 00:00:00 2001 From: Goooler Date: Wed, 26 Aug 2026 14:14:47 +0800 Subject: [PATCH 11/20] Must return set for classNames --- .../gradle/plugins/shadow/internal/R8Minimizer.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt index 8ebfa7d02..72fbe086e 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt @@ -106,7 +106,7 @@ private fun createRules( keptDependencyFiles: Iterable, relocators: Iterable, ): List { - val jarClasses = inputJar.classNames().toSet() + val jarClasses = inputJar.classNames() return buildList { add(baseDirectory.toBaseDirectoryRule()) if (shouldDisableOptimization(r8Spec, r8Args)) { @@ -208,12 +208,13 @@ private fun File.toBaseDirectoryRule(): String { return "-basedirectory '$normalizedPath'" } -private fun File.classNames(): Sequence { +private fun File.classNames(): Set { return when { isDirectory -> walkTopDown() .filter { it.isFile && it.name.endsWith(".class") } .mapNotNull { it.toClassName(relativeTo = this) } + .toSet() isFile -> useZip { jarFile -> jarFile @@ -221,8 +222,9 @@ private fun File.classNames(): Sequence { .asSequence() .filter { !it.isDirectory && it.name.endsWith(".class") } .mapNotNull { it.name.toClassName() } + .toSet() } - else -> emptySequence() + else -> emptySet() } } From 282bd8f7fd774b66dc9a78ac4643cebc36c2f114 Mon Sep 17 00:00:00 2001 From: Goooler Date: Wed, 26 Aug 2026 14:18:50 +0800 Subject: [PATCH 12/20] Merge sourceProguardRules and keptDependencyRules into toKeepRules --- .../plugins/shadow/internal/R8Minimizer.kt | 41 ++++++------------- 1 file changed, 12 insertions(+), 29 deletions(-) diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt index 72fbe086e..d47a9cfab 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt @@ -112,8 +112,14 @@ private fun createRules( if (shouldDisableOptimization(r8Spec, r8Args)) { add(DefaultR8Spec.DONT_OPTIMIZE_RULE) } - addAll(sourceProguardRules(jarClasses, sourceSetsClassesDirs, relocators)) - addAll(keptDependencyRules(jarClasses, keptDependencyFiles, relocators)) + // Project classes are the public surface of the shadowed jar, even when nothing in the input + // jar refers to every class directly. + addAll( + sourceSetsClassesDirs.toKeepRules(jarClasses, relocators, "-keep,includedescriptorclasses") + ) + // Keep dependencies users explicitly excluded from minimization, matching the existing + // minimize { exclude(...) } contract for the default analyzer. + addAll(keptDependencyFiles.toKeepRules(jarClasses, relocators, "-keep")) addAll(serviceProguardRules(inputJar)) r8Spec.proguardRuleFiles .sortedBy { it.absolutePath } @@ -131,36 +137,11 @@ private fun shouldDisableOptimization(r8Spec: DefaultR8Spec, r8Args: List.toKeepRules( jarClasses: Set, - sourceSetsClassesDirs: Iterable, relocators: Iterable, + rulePrefix: String, ): List { - return sourceSetsClassesDirs - .findJarClasses(jarClasses, relocators) - .map { "-keep,includedescriptorclasses class $it { *; }" } - .toList() -} - -// Keep dependencies users explicitly excluded from minimization, matching the existing -// minimize { exclude(...) } contract for the default analyzer. -private fun keptDependencyRules( - jarClasses: Set, - keptDependencyFiles: Iterable, - relocators: Iterable, -): List { - return keptDependencyFiles - .findJarClasses(jarClasses, relocators) - .map { "-keep class $it { *; }" } - .toList() -} - -private fun Iterable.findJarClasses( - jarClasses: Set, - relocators: Iterable, -): Sequence { return asSequence() .flatMap { it.classNames() } .map { relocators.relocateClass(it) } @@ -168,6 +149,8 @@ private fun Iterable.findJarClasses( .filter { className -> className in jarClasses } .distinct() .sorted() + .map { "$rulePrefix class $it { *; }" } + .toList() } // Service descriptors are usage edges for downstream ServiceLoader calls, so keep the service From 6e503ed39ed7716b7dfda00353810a45f76911c8 Mon Sep 17 00:00:00 2001 From: Goooler Date: Wed, 26 Aug 2026 14:56:18 +0800 Subject: [PATCH 13/20] Call this in useZip --- .../gradle/plugins/shadow/internal/R8Minimizer.kt | 12 +++++------- .../jengelman/gradle/plugins/shadow/internal/Zip.kt | 2 +- .../gradle/plugins/shadow/tasks/ShadowJar.kt | 7 +++---- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt index d47a9cfab..30e40a4d2 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt @@ -157,9 +157,8 @@ private fun Iterable.toKeepRules( // interface and every listed provider even if R8 sees no direct references. private fun serviceProguardRules(inputJar: File): List { val rules = linkedSetOf() - inputJar.useZip { jarFile -> - jarFile - .entries() + inputJar.useZip { + entries() .asSequence() .filter { !it.isDirectory && it.name.startsWith(SERVICES_PATH) } .sortedBy { it.name } @@ -168,7 +167,7 @@ private fun serviceProguardRules(inputJar: File): List { if (serviceClass.isJavaTypeName()) { rules += "-keep,allowrepackage class $serviceClass { *; }" } - jarFile.getInputStream(entry).bufferedReader().useLines { lines -> + getInputStream(entry).bufferedReader().useLines { lines -> lines .map { it.substringBefore('#').trim() } .filter { it.isNotEmpty() && it.isJavaTypeName() } @@ -199,9 +198,8 @@ private fun File.classNames(): Set { .mapNotNull { it.toClassName(relativeTo = this) } .toSet() isFile -> - useZip { jarFile -> - jarFile - .entries() + useZip { + entries() .asSequence() .filter { !it.isDirectory && it.name.endsWith(".class") } .mapNotNull { it.name.toClassName() } diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/Zip.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/Zip.kt index 21639ac8c..5e08ad60a 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/Zip.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/Zip.kt @@ -45,7 +45,7 @@ internal val ZipOutputStream.entries: List ?: this::class.java.getDeclaredField("entries").apply { isAccessible = true }.get(this) as List -internal inline fun File.useZip(block: (ZipFile) -> R): R = ZipFile(this).use(block) +internal inline fun File.useZip(block: ZipFile.() -> R): R = ZipFile(this).use(block) internal fun File.createZipOutputStream( entryCompression: ZipEntryCompression, 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 22f837626..9eae97c3b 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 @@ -591,9 +591,8 @@ public abstract class ShadowJar : Jar() { } val prefix = relocationPrefix.get() return includedDependencies.flatMap { file -> - file.useZip { jarFile -> - jarFile - .entries() + file.useZip { + entries() .toList() .filter { it.name.endsWith(".class") && it.name != "module-info.class" } .map { it.name.substringBeforeLast('/').replace('/', '.') } @@ -607,7 +606,7 @@ public abstract class ShadowJar : Jar() { val isAar: File.() -> Boolean = { try { extension.equals("aar", ignoreCase = true) && - useZip { zip -> zip.getEntry("AndroidManifest.xml") != null } + useZip { getEntry("AndroidManifest.xml") != null } } catch (_: ZipException) { // File is not a valid ZIP, so it cannot be an AAR. false From 6be732e2f1e68216402df4dcff079e3e7d4c4ec8 Mon Sep 17 00:00:00 2001 From: Goooler Date: Wed, 26 Aug 2026 15:09:56 +0800 Subject: [PATCH 14/20] Rename relativeTo to base --- .../jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt index 30e40a4d2..b77a81f79 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt @@ -178,9 +178,9 @@ private fun serviceProguardRules(inputJar: File): List { return rules.toList() } -private fun File.toClassName(relativeTo: File): String? { +private fun File.toClassName(base: File): String? { if (name == "module-info.class" || name == "package-info.class") return null - return toRelativeString(relativeTo).removeSuffix(".class").replace(File.separatorChar, '.') + return toRelativeString(base).removeSuffix(".class").replace(File.separatorChar, '.') } private fun File.toBaseDirectoryRule(): String { @@ -195,7 +195,7 @@ private fun File.classNames(): Set { isDirectory -> walkTopDown() .filter { it.isFile && it.name.endsWith(".class") } - .mapNotNull { it.toClassName(relativeTo = this) } + .mapNotNull { it.toClassName(base = this) } .toSet() isFile -> useZip { From aa93f5b9a9c9118a346407e5f010c738fb2321eb Mon Sep 17 00:00:00 2001 From: Goooler Date: Wed, 26 Aug 2026 15:12:34 +0800 Subject: [PATCH 15/20] Simplify isDirectory for entries --- .../jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt index b77a81f79..f7379b351 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt @@ -194,14 +194,14 @@ private fun File.classNames(): Set { return when { isDirectory -> walkTopDown() - .filter { it.isFile && it.name.endsWith(".class") } + .filter { it.name.endsWith(".class") && it.isFile } .mapNotNull { it.toClassName(base = this) } .toSet() isFile -> useZip { entries() .asSequence() - .filter { !it.isDirectory && it.name.endsWith(".class") } + .filter { it.name.endsWith(".class") } .mapNotNull { it.name.toClassName() } .toSet() } From eaed57a43ae88c5a59a7a921a24b2f51ec2870c9 Mon Sep 17 00:00:00 2001 From: Goooler Date: Wed, 26 Aug 2026 19:18:10 +0800 Subject: [PATCH 16/20] Optimize input jar analysis and rule processing in R8Minimizer --- .../plugins/shadow/internal/R8Minimizer.kt | 42 ++++++++++++------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt index f7379b351..e73766027 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt @@ -4,6 +4,7 @@ import com.github.jengelman.gradle.plugins.shadow.relocation.Relocator import com.github.jengelman.gradle.plugins.shadow.relocation.relocateClass import java.io.File import java.nio.file.StandardCopyOption.REPLACE_EXISTING +import java.util.zip.ZipEntry import kotlin.io.path.moveTo import org.gradle.api.GradleException import org.gradle.api.file.FileCollection @@ -106,7 +107,7 @@ private fun createRules( keptDependencyFiles: Iterable, relocators: Iterable, ): List { - val jarClasses = inputJar.classNames() + val (jarClasses, serviceRules) = inputJar.analyzeInputJar() return buildList { add(baseDirectory.toBaseDirectoryRule()) if (shouldDisableOptimization(r8Spec, r8Args)) { @@ -120,7 +121,7 @@ private fun createRules( // Keep dependencies users explicitly excluded from minimization, matching the existing // minimize { exclude(...) } contract for the default analyzer. addAll(keptDependencyFiles.toKeepRules(jarClasses, relocators, "-keep")) - addAll(serviceProguardRules(inputJar)) + addAll(serviceRules) r8Spec.proguardRuleFiles .sortedBy { it.absolutePath } .forEach { file -> @@ -147,35 +148,48 @@ private fun Iterable.toKeepRules( .map { relocators.relocateClass(it) } .filter { it.isJavaTypeName() } .filter { className -> className in jarClasses } - .distinct() - .sorted() + .toSortedSet() .map { "$rulePrefix class $it { *; }" } - .toList() } +// Extracts all class names and generates keep rules for service descriptors in a single pass. // Service descriptors are usage edges for downstream ServiceLoader calls, so keep the service // interface and every listed provider even if R8 sees no direct references. -private fun serviceProguardRules(inputJar: File): List { - val rules = linkedSetOf() - inputJar.useZip { - entries() - .asSequence() - .filter { !it.isDirectory && it.name.startsWith(SERVICES_PATH) } +private fun File.analyzeInputJar(): Pair, List> { + val classes = mutableSetOf() + val serviceEntries = mutableListOf() + val serviceRules = linkedSetOf() + + useZip { + entries().asSequence().forEach { entry -> + val name = entry.name + when { + name.endsWith(".class") -> { + name.toClassName()?.let { classes += it } + } + !entry.isDirectory && name.startsWith(SERVICES_PATH) -> { + serviceEntries += entry + } + } + } + + serviceEntries .sortedBy { it.name } .forEach { entry -> val serviceClass = entry.name.removePrefix(SERVICES_PATH).replace('/', '.') if (serviceClass.isJavaTypeName()) { - rules += "-keep,allowrepackage class $serviceClass { *; }" + serviceRules += "-keep,allowrepackage class $serviceClass { *; }" } getInputStream(entry).bufferedReader().useLines { lines -> lines .map { it.substringBefore('#').trim() } .filter { it.isNotEmpty() && it.isJavaTypeName() } - .forEach { provider -> rules += "-keep,allowrepackage class $provider { *; }" } + .forEach { provider -> serviceRules += "-keep,allowrepackage class $provider { *; }" } } } } - return rules.toList() + + return classes to serviceRules.toList() } private fun File.toClassName(base: File): String? { From 9d75fcf2142a313d04e9ee7d670fc0fd3838f46b Mon Sep 17 00:00:00 2001 From: Goooler Date: Wed, 26 Aug 2026 19:27:22 +0800 Subject: [PATCH 17/20] Return List from classNames and skip META-INF classes in String.toClassName --- .../gradle/plugins/shadow/internal/R8Minimizer.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt index e73766027..54968f669 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt @@ -204,26 +204,27 @@ private fun File.toBaseDirectoryRule(): String { return "-basedirectory '$normalizedPath'" } -private fun File.classNames(): Set { +private fun File.classNames(): List { return when { isDirectory -> walkTopDown() .filter { it.name.endsWith(".class") && it.isFile } .mapNotNull { it.toClassName(base = this) } - .toSet() + .toList() isFile -> useZip { entries() .asSequence() .filter { it.name.endsWith(".class") } .mapNotNull { it.name.toClassName() } - .toSet() + .toList() } - else -> emptySet() + else -> emptyList() } } private fun String.toClassName(): String? { + if (startsWith("META-INF/")) return null val name = substringAfterLast('/') if (name == "module-info.class" || name == "package-info.class") return null return removeSuffix(".class").replace('/', '.') From 3757be5570c6634a6fa7a0bd81af22b57fe6cacf Mon Sep 17 00:00:00 2001 From: Goooler Date: Wed, 26 Aug 2026 19:29:27 +0800 Subject: [PATCH 18/20] Filter proguardRuleFiles with isFile before sorting --- .../gradle/plugins/shadow/internal/R8Minimizer.kt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt index 54968f669..149cac00f 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt @@ -123,12 +123,9 @@ private fun createRules( addAll(keptDependencyFiles.toKeepRules(jarClasses, relocators, "-keep")) addAll(serviceRules) r8Spec.proguardRuleFiles + .filter { it.isFile } .sortedBy { it.absolutePath } - .forEach { file -> - if (file.isFile) { - addAll(file.readLines()) - } - } + .forEach { file -> addAll(file.readLines()) } addAll(r8Spec.proguardRules.get()) } } From a870964ae653ff0aba2621cb2d8c1ccd20966956 Mon Sep 17 00:00:00 2001 From: Goooler Date: Wed, 26 Aug 2026 19:31:35 +0800 Subject: [PATCH 19/20] Short-circuit regex matching in toKeepRules and avoid toList copy in analyzeInputJar --- .../jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt index 149cac00f..c9842cfe8 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt @@ -143,8 +143,8 @@ private fun Iterable.toKeepRules( return asSequence() .flatMap { it.classNames() } .map { relocators.relocateClass(it) } - .filter { it.isJavaTypeName() } .filter { className -> className in jarClasses } + .filter { it.isJavaTypeName() } .toSortedSet() .map { "$rulePrefix class $it { *; }" } } @@ -152,7 +152,7 @@ private fun Iterable.toKeepRules( // Extracts all class names and generates keep rules for service descriptors in a single pass. // Service descriptors are usage edges for downstream ServiceLoader calls, so keep the service // interface and every listed provider even if R8 sees no direct references. -private fun File.analyzeInputJar(): Pair, List> { +private fun File.analyzeInputJar(): Pair, Set> { val classes = mutableSetOf() val serviceEntries = mutableListOf() val serviceRules = linkedSetOf() @@ -186,7 +186,7 @@ private fun File.analyzeInputJar(): Pair, List> { } } - return classes to serviceRules.toList() + return classes to serviceRules } private fun File.toClassName(base: File): String? { From 2752456720e8f525c3314e2679531a85655c9982 Mon Sep 17 00:00:00 2001 From: Goooler Date: Wed, 26 Aug 2026 19:39:04 +0800 Subject: [PATCH 20/20] Short-circuit isDirectory matching --- .../jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt index c9842cfe8..e5c6d15d1 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/R8Minimizer.kt @@ -161,10 +161,11 @@ private fun File.analyzeInputJar(): Pair, Set> { entries().asSequence().forEach { entry -> val name = entry.name when { + entry.isDirectory -> Unit name.endsWith(".class") -> { name.toClassName()?.let { classes += it } } - !entry.isDirectory && name.startsWith(SERVICES_PATH) -> { + name.startsWith(SERVICES_PATH) -> { serviceEntries += entry } }