From c77e1c81a617b7f93574a0757e78fba4b56ae049 Mon Sep 17 00:00:00 2001 From: Goooler Date: Mon, 10 Aug 2026 11:41:54 +0800 Subject: [PATCH] Use Gradle Worker API for R8 minimization Refs: - https://docs.gradle.org/current/userguide/worker_api.html - https://github.com/Goooler/agp-sources/blob/d383e6c2d704f6f93397fceb9a424e0082dfa164/9.3.1/com.android.tools.build/gradle/com/android/build/gradle/internal/tasks/R8Task.kt#L327-L337 - https://github.com/Goooler/agp-sources/blob/d383e6c2d704f6f93397fceb9a424e0082dfa164/9.3.1/com.android.tools.build/gradle/com/android/build/gradle/internal/tasks/R8Task.kt#L523-L579 --- api/shadow.api | 2 + docs/changes/README.md | 2 + .../gradle/plugins/shadow/MinimizeTest.kt | 43 +++++++++++++++++++ .../plugins/shadow/internal/DefaultR8Spec.kt | 3 ++ .../plugins/shadow/internal/R8Minimizer.kt | 31 ++++++++++--- .../gradle/plugins/shadow/tasks/R8Spec.kt | 11 +++++ .../gradle/plugins/shadow/tasks/ShadowJar.kt | 9 +++- .../shadow/internal/MinimizeSpecsTest.kt | 1 + 8 files changed, 93 insertions(+), 9 deletions(-) diff --git a/api/shadow.api b/api/shadow.api index aa115ced9..e4f4c5303 100644 --- a/api/shadow.api +++ b/api/shadow.api @@ -229,6 +229,7 @@ public abstract interface class com/github/jengelman/gradle/plugins/shadow/tasks public abstract fun getConfigurationFile ()Lorg/gradle/api/file/RegularFileProperty; public fun getKeepRuleFiles ()Lorg/gradle/api/file/ConfigurableFileCollection; public fun getKeepRules ()Lorg/gradle/api/provider/ListProperty; + public abstract fun getMaxHeapSize ()Lorg/gradle/api/provider/Property; public abstract fun getProguardRuleFiles ()Lorg/gradle/api/file/ConfigurableFileCollection; public abstract fun getProguardRules ()Lorg/gradle/api/provider/ListProperty; } @@ -279,6 +280,7 @@ public abstract class com/github/jengelman/gradle/plugins/shadow/tasks/ShadowJar public fun getSourceSetsClassesDirs ()Lorg/gradle/api/file/ConfigurableFileCollection; public fun getToMinimize ()Lorg/gradle/api/file/ConfigurableFileCollection; public fun getTransformers ()Lorg/gradle/api/provider/SetProperty; + protected abstract fun getWorkerExecutor ()Lorg/gradle/workers/WorkerExecutor; public fun mergeGroovyExtensionModules ()V public final fun mergeServiceFiles ()V public fun mergeServiceFiles (Ljava/lang/String;)V diff --git a/docs/changes/README.md b/docs/changes/README.md index b92137389..912907b25 100644 --- a/docs/changes/README.md +++ b/docs/changes/README.md @@ -6,6 +6,7 @@ ### Added - Allow configuring the final R8 configuration file with `R8Spec.configurationFile`. ([#2133](https://github.com/GradleUp/shadow/pull/2133)) +- Allow configuring the R8 worker maximum heap size with `R8Spec.maxHeapSize`. ([#2172](https://github.com/GradleUp/shadow/pull/2172)) ### Changed @@ -13,6 +14,7 @@ - Remove runtime dependencies on Commons Codec and Commons IO by using JDK APIs. ([#2136](https://github.com/GradleUp/shadow/pull/2136)) - **POTENTIALLY BREAKING:** Remove `Serializable` from `DependencyFilter`. ([#2144](https://github.com/GradleUp/shadow/pull/2144)) - Bump default R8 from `9.1.31` to `9.2.23`. ([#2157](https://github.com/GradleUp/shadow/pull/2157)) +- Use Gradle Worker API for R8 minimization. ([#2172](https://github.com/GradleUp/shadow/pull/2172)) - Allow repackaging Service file classes with R8. ([#2174](https://github.com/GradleUp/shadow/pull/2174)) ### Deprecated 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 772c8184b..6ba4d3a27 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 @@ -658,6 +658,49 @@ class MinimizeTest : BasePluginTest() { assertThat(result.output).contains("R8 launcher JDK ${JavaVersion.current().majorVersion}") } + @Test + fun minimizeWithR8UsesGradleDaemonMaxHeapSize() { + writeR8Repository() + writeR8ClientAndServerModules( + serverShadowBlock = + """ + |minimize { + | r8 {} + |} + """ + .trimMargin() + ) + path("gradle.properties").writeText("org.gradle.jvmargs=-Xmx768m") + + val result = runWithSuccess(serverShadowJarPath, infoArgument) + + val workerCommand = + result.output.lineSequence().single { "Starting process 'Gradle Worker Daemon" in it } + assertThat(workerCommand).contains("-Xmx${768L * 1024 * 1024}") + } + + @Test + fun minimizeWithR8CanConfigureMaxHeapSize() { + writeR8Repository() + writeR8ClientAndServerModules( + serverShadowBlock = + """ + |minimize { + | r8 { + | maxHeapSize.set("640m") + | } + |} + """ + .trimMargin() + ) + + val result = runWithSuccess(serverShadowJarPath, infoArgument) + + val workerCommand = + result.output.lineSequence().single { "Starting process 'Gradle Worker Daemon" in it } + assertThat(workerCommand).contains("-Xmx640m") + } + private fun writeApiLibAndImplModules() { settingsScript.appendText( """ diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/DefaultR8Spec.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/DefaultR8Spec.kt index d415f5a34..44ebb2139 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/DefaultR8Spec.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/DefaultR8Spec.kt @@ -22,6 +22,9 @@ constructor( @get:Input val optimizationEnabled: Property = objectFactory.property(false) + override val maxHeapSize: Property = + objectFactory.property(Runtime.getRuntime().maxMemory().toString()) + override val args: ListProperty = objectFactory.listProperty(defaultArgs) override val proguardRules: ListProperty = objectFactory.listProperty() 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 2e0b515d3..20b5149df 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 @@ -11,9 +11,12 @@ import org.apache.tools.zip.ZipOutputStream import org.gradle.api.GradleException import org.gradle.api.file.FileCollection import org.gradle.api.logging.Logger +import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Provider import org.gradle.jvm.toolchain.JavaLauncher -import org.gradle.process.ExecOperations +import org.gradle.workers.WorkAction +import org.gradle.workers.WorkParameters +import org.gradle.workers.WorkerExecutor /** * Runs R8 as a final-archive shrinker. @@ -36,7 +39,7 @@ import org.gradle.process.ExecOperations internal fun minimizeWithR8( inputJar: File, temporaryDir: File, - execOperations: ExecOperations, + workerExecutor: WorkerExecutor, logger: Logger, r8Classpath: FileCollection, r8Spec: DefaultR8Spec, @@ -95,14 +98,15 @@ internal fun minimizeWithR8( } logger.info("Running R8 to minimize {}.", inputJar) - execOperations.javaexec { - it.classpath = r8Classpath - it.mainClass.set(R8_MAIN_CLASS) + val workQueue = workerExecutor.processIsolation { + it.classpath.from(r8Classpath) + it.forkOptions.maxHeapSize = r8Spec.maxHeapSize.get() if (launcher != null) { - it.executable = launcher.executablePath.asFile.absolutePath + it.forkOptions.executable = launcher.executablePath.asFile.absolutePath } - it.args(arguments) } + workQueue.submit(R8WorkAction::class.java) { it.r8Args.set(arguments) } + workQueue.await() normalizeJar( inputJar = r8Output, @@ -339,6 +343,19 @@ internal fun normalizeJar( } } +internal abstract class R8WorkAction : WorkAction { + override fun execute() { + val args = parameters.r8Args.get().toTypedArray() + val r8Class = Class.forName(R8_MAIN_CLASS, true, javaClass.classLoader) + val mainMethod = r8Class.getMethod("main", Array::class.java) + mainMethod(null, args) + } + + interface Parameters : WorkParameters { + val r8Args: ListProperty + } +} + 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 diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/R8Spec.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/R8Spec.kt index 429bff47a..23883aa57 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/R8Spec.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/R8Spec.kt @@ -4,8 +4,10 @@ import com.github.jengelman.gradle.plugins.shadow.ShadowDsl import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property import org.gradle.api.tasks.Input import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.Internal import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.PathSensitive import org.gradle.api.tasks.PathSensitivity @@ -13,6 +15,15 @@ import org.gradle.api.tasks.PathSensitivity /** Minimal R8 configuration for [ShadowJar.minimize]. */ @ShadowDsl public interface R8Spec { + /** + * The maximum heap size for the R8 worker process. + * + * Defaults to the effective maximum heap size of the Gradle daemon. The value uses JVM memory + * notation, such as `2g` or `512m`. + */ + @get:Internal // Doesn't affect the output. + public val maxHeapSize: Property + /** * Additional R8 command line arguments. * 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 d1f7b11e7..e2dd90063 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 @@ -71,6 +71,7 @@ import org.gradle.api.tasks.options.Option import org.gradle.jvm.toolchain.JavaLauncher import org.gradle.language.base.plugins.LifecycleBasePlugin import org.gradle.process.ExecOperations +import org.gradle.workers.WorkerExecutor @ShadowDsl @CacheableTask @@ -330,10 +331,14 @@ public abstract class ShadowJar : Jar() { */ override fun getDuplicatesStrategy(): DuplicatesStrategy = super.getDuplicatesStrategy() - @get:Inject protected abstract val execOperations: ExecOperations + @Deprecated("Replace the logic with `workerExecutor`. This will be removed in Shadow 10.") + @get:Inject + protected abstract val execOperations: ExecOperations @get:Inject protected abstract val archiveOperations: ArchiveOperations + @get:Inject protected abstract val workerExecutor: WorkerExecutor + /** Enable minimization and execute the [action] with the [MinimizeSpec] for minimize. */ @JvmOverloads public open fun minimize(action: Action = Action {}) { @@ -703,7 +708,7 @@ public abstract class ShadowJar : Jar() { minimizeWithR8( inputJar = archiveFile.get().asFile, temporaryDir = temporaryDir, - execOperations = execOperations, + workerExecutor = workerExecutor, logger = logger, r8Classpath = r8Classpath, r8Spec = defaultMinimizeSpec.r8Spec, diff --git a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/MinimizeSpecsTest.kt b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/MinimizeSpecsTest.kt index 0c3b5d006..046a686fb 100644 --- a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/MinimizeSpecsTest.kt +++ b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/MinimizeSpecsTest.kt @@ -39,6 +39,7 @@ class MinimizeSpecsTest { assertThat(args.get()).containsExactly(DefaultR8Spec.NO_MINIFICATION_ARG) assertThat(obfuscationEnabled.get()).isFalse() assertThat(optimizationEnabled.get()).isFalse() + assertThat(maxHeapSize.get()).isEqualTo(Runtime.getRuntime().maxMemory().toString()) assertThat(proguardRules.get()).isEmpty() assertThat(proguardRuleFiles.files).isEmpty() assertThat(configurationFile.get().asFile)