Add execution options to settings plugin
Also add out of process workers and jvm args to R8
This new DSL will allow the user to set global jvm args and out of
process worker settings for high memory usage tools through the settings
plugin. (for now only R8 is supported)
Bug: 225314061
Test: SettingsPluginTest
Change-Id: I4136dc353c05fda611b8395a130c5e9f8abf54e2
diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/core/SettingsOptions.kt b/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/core/SettingsOptions.kt
new file mode 100644
index 0000000..e77f7a1
--- /dev/null
+++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/core/SettingsOptions.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.core
+
+data class ToolExecutionOptions (val jvmArgs: List<String>, val runInSeparateProcess: Boolean)
+
+data class ExecutionProfileOptions(val name: String, val r8Options: ToolExecutionOptions)
+
+/**
+ * Data class containing options specified in the [com.android.build.gradle.internal.plugins.SettingsPlugin]
+ *
+ * This class is for options that are exclusive to the android settings plugin, such as execution
+ * profiles, and not compileSdkVersion and other options that directly translate to DSL options that
+ * exist in AGP plugins.
+ *
+ * executionProfiles is the container of [com.android.build.api.dsl.ExecutionProfile] transformed
+ * into a simple map of [ExecutionProfileOptions]
+ *
+ * defaultProfile is the default profile to use in case no profile is declared.
+ */
+data class SettingsOptions(val executionProfile: ExecutionProfileOptions?)
+
+
+val DEFAULT_EXECUTION_PROFILE = ExecutionProfileOptions(
+ name = "default",
+ r8Options = ToolExecutionOptions(
+ jvmArgs = listOf(),
+ runInSeparateProcess = false
+ )
+)
diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/plugins/BasePlugin.kt b/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/plugins/BasePlugin.kt
index d773f74..b3e73fb 100644
--- a/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/plugins/BasePlugin.kt
+++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/plugins/BasePlugin.kt
@@ -19,6 +19,7 @@
import com.android.SdkConstants
import com.android.build.api.dsl.BuildFeatures
import com.android.build.api.dsl.CommonExtension
+import com.android.build.api.dsl.ExecutionProfile
import com.android.build.api.dsl.SettingsExtension
import com.android.build.api.dsl.SingleVariant
import com.android.build.api.extension.impl.VariantApiOperationsRegistrar
@@ -41,6 +42,10 @@
import com.android.build.gradle.internal.component.TestComponentCreationConfig
import com.android.build.gradle.internal.component.TestFixturesCreationConfig
import com.android.build.gradle.internal.component.VariantCreationConfig
+import com.android.build.gradle.internal.core.DEFAULT_EXECUTION_PROFILE
+import com.android.build.gradle.internal.core.ExecutionProfileOptions
+import com.android.build.gradle.internal.core.SettingsOptions
+import com.android.build.gradle.internal.core.ToolExecutionOptions
import com.android.build.gradle.internal.core.dsl.VariantDslInfo
import com.android.build.gradle.internal.crash.afterEvaluate
import com.android.build.gradle.internal.crash.runAction
@@ -95,7 +100,7 @@
import com.android.build.gradle.internal.variant.VariantInputModel
import com.android.build.gradle.internal.variant.VariantModel
import com.android.build.gradle.internal.variant.VariantModelImpl
-import com.android.build.gradle.options.BooleanOption
+import com.android.build.gradle.options.StringOption
import com.android.build.gradle.options.SyncOptions
import com.android.builder.errors.IssueReporter.Type
import com.android.builder.model.v2.ide.ProjectType
@@ -209,7 +214,8 @@
bootClasspathConfig,
createCustomLintPublishConfig(project),
createCustomLintChecksConfig(project),
- createAndroidJarConfig(project)
+ createAndroidJarConfig(project),
+ createSettingsOptions()
)
}
}
@@ -782,15 +788,23 @@
return false
}
- protected fun initExtensionFromSettings(extension: AndroidT) {
+ private val settingsExtension: SettingsExtension? by lazy(LazyThreadSafetyMode.NONE) {
// Query for the settings extension via extra properties.
// This is deposited here by the SettingsPlugin
- val properties = project?.extensions?.extraProperties ?: return
- if (properties.has("_android_settings")) {
- val settings = properties.get("_android_settings") as? SettingsExtension
- settings?.let {
- extension.doInitExtensionFromSettings(it)
- }
+ val properties = project?.extensions?.extraProperties
+ if (properties == null) {
+ null
+ } else if (properties.has("_android_settings")) {
+ properties.get("_android_settings") as? SettingsExtension
+ } else {
+ null
+ }
+ }
+
+ // Initialize the android extension with values from the android settings extension
+ protected fun initExtensionFromSettings(extension: AndroidT) {
+ settingsExtension?.let {
+ extension.doInitExtensionFromSettings(it)
}
}
@@ -816,6 +830,68 @@
}
}
+
+ // Create settings options, to be used in the global config,
+ // with values from the android settings extension
+ private fun createSettingsOptions(): SettingsOptions {
+ // resolve settings extension
+ val actualSettingsExtension = settingsExtension ?: run {
+ dslServices.logger.info("Using default execution profile")
+ return SettingsOptions(DEFAULT_EXECUTION_PROFILE)
+ }
+
+ // Map the profiles to make it easier to look them up
+ val executionProfiles = actualSettingsExtension.execution.profiles.associate { profile ->
+ profile.name to profile
+ }
+
+ val buildProfileOptions = { profile: ExecutionProfile ->
+ ExecutionProfileOptions(
+ name = profile.name,
+ r8Options = profile.r8.let { r8 ->
+ ToolExecutionOptions(
+ jvmArgs = r8.jvmOptions,
+ runInSeparateProcess = r8.runInSeparateProcess
+ )
+ }
+ )
+ }
+
+ // If the string option is set use that one instead
+ val actualProfileName =
+ dslServices.projectOptions[StringOption.EXECUTION_PROFILE_SELECTION] ?:
+ actualSettingsExtension.execution.defaultProfile
+ // Find the selected (or the only) profile
+ val executionProfile =
+ if (actualProfileName == null) {
+ if (executionProfiles.isEmpty()) { // No profiles declared, and none selected, return default
+ dslServices.logger.info("Using default execution profile")
+ DEFAULT_EXECUTION_PROFILE
+ } else if (executionProfiles.size == 1) { // if there is exactly one profile use that
+ dslServices.logger.info("Using only execution profile '${executionProfiles.keys.first()}'")
+ buildProfileOptions(executionProfiles.values.first())
+ } else { // no profile selected
+ dslServices.issueReporter.reportError(Type.GENERIC, "Found ${executionProfiles.size} execution profiles ${executionProfiles.keys}, but no profile was selected.\n")
+ null
+ }
+ } else {
+ if (!executionProfiles.containsKey(actualProfileName)) { // invalid profile selected
+ dslServices.issueReporter.reportError(Type.GENERIC,"Selected profile '$actualProfileName' does not exist")
+ null
+ } else {
+ if (actualProfileName == dslServices.projectOptions[StringOption.EXECUTION_PROFILE_SELECTION]) {
+ dslServices.logger.info("Using execution profile from android.settings.executionProfile '$actualProfileName'")
+ } else {
+ dslServices.logger.info("Using execution profile from dsl '$actualProfileName'")
+ }
+
+ buildProfileOptions(executionProfiles[actualProfileName]!!)
+ }
+ }
+
+ return SettingsOptions(executionProfile = executionProfile)
+ }
+
// Create the "special" configuration for test buddy APKs. It will be resolved by the test
// running task, so that we can install all the found APKs before running tests.
private fun createAndroidTestUtilConfiguration(project: Project) {
diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/tasks/R8Task.kt b/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/tasks/R8Task.kt
index 2615c57..82df2ff 100644
--- a/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/tasks/R8Task.kt
+++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/tasks/R8Task.kt
@@ -26,6 +26,7 @@
import com.android.build.gradle.internal.component.ApkCreationConfig
import com.android.build.gradle.internal.component.ApplicationCreationConfig
import com.android.build.gradle.internal.component.ConsumableCreationConfig
+import com.android.build.gradle.internal.core.ToolExecutionOptions
import com.android.build.gradle.internal.errors.MessageReceiverImpl
import com.android.build.gradle.internal.profile.ProfileAwareWorkAction
import com.android.build.gradle.internal.publishing.AndroidArtifacts
@@ -39,6 +40,7 @@
import com.android.build.gradle.internal.utils.getFilteredConfigurationFiles
import com.android.build.gradle.internal.utils.setDisallowChanges
import com.android.build.gradle.options.BooleanOption
+import com.android.build.gradle.options.StringOption
import com.android.build.gradle.options.SyncOptions
import com.android.builder.dexing.DexingType
import com.android.builder.dexing.MainDexListConfig
@@ -55,6 +57,7 @@
import org.gradle.api.file.FileSystemLocation
import org.gradle.api.file.ProjectLayout
import org.gradle.api.file.RegularFileProperty
+import org.gradle.api.logging.LogLevel
import org.gradle.api.logging.Logging
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
@@ -72,6 +75,8 @@
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskProvider
+import org.gradle.workers.WorkAction
+import org.gradle.workers.WorkParameters
import java.io.File
import java.nio.file.Path
import javax.inject.Inject
@@ -140,6 +145,9 @@
lateinit var dexingType: DexingType
private set
+ @get:Internal
+ abstract val executionOptions: Property<ToolExecutionOptions>
+
@get:Optional
@get:Classpath
abstract val featureClassJars: ConfigurableFileCollection
@@ -325,6 +333,9 @@
task.dexingType = creationConfig.dexingType
task.useFullR8.setDisallowChanges(creationConfig.services.projectOptions[BooleanOption.FULL_R8])
+ task.executionOptions.setDisallowChanges(
+ creationConfig.global.settingsOptions.executionProfile?.r8Options)
+
if (!creationConfig.services.projectOptions[BooleanOption.R8_FAIL_ON_MISSING_CLASSES]) {
// Keep until AGP 8.0. It used to be necessary because of http://b/72683872.
proguardConfigurations.add("-ignorewarnings")
@@ -458,8 +469,7 @@
|Current version is: ${getR8Version()}.
|""".trimMargin()
)
- workerExecutor.noIsolation().submit(R8Runnable::class.java) {
- it.initializeFromAndroidVariantTask(this)
+ val workerAction = { it: R8Runnable.Params ->
it.bootClasspath.from(bootClasspath.toList())
it.minSdkVersion.set(minSdkVersion.get())
it.debuggable.set(debuggable.get())
@@ -522,6 +532,15 @@
it.outputKeepRulesDir.set(projectOutputKeepRules.asFile.orNull)
it.errorFormatMode.set(errorFormatMode.get())
}
+ if (executionOptions.get().runInSeparateProcess) {
+ workerExecutor.processIsolation { spec ->
+ spec.forkOptions { forkOptions ->
+ forkOptions.jvmArgs?.addAll(executionOptions.get().jvmArgs)
+ }
+ }.submit(R8Runnable::class.java, workerAction)
+ } else {
+ workerExecutor.noIsolation().submit(R8Runnable::class.java, workerAction)
+ }
}
companion object {
@@ -559,7 +578,7 @@
outputKeepRulesDir: File?,
errorFormatMode: SyncOptions.ErrorFormatMode,
) {
- val logger = LoggerWrapper.getLogger(R8ParallelBuildService::class.java)
+ val logger = LoggerWrapper.getLogger(R8Task::class.java)
val r8OutputType: R8OutputType
val outputFormat: Format
@@ -638,7 +657,7 @@
featureDexDir?.toPath(),
featureJavaResourceOutputDir?.toPath(),
libConfiguration,
- outputKeepRulesFile?.toPath()
+ outputKeepRulesFile?.toPath(),
)
}
@@ -653,9 +672,9 @@
}
}
- abstract class R8Runnable : ProfileAwareWorkAction<R8Runnable.Params>() {
+ abstract class R8Runnable : WorkAction<R8Runnable.Params> {
- abstract class Params : ProfileAwareWorkAction.Parameters() {
+ abstract class Params : WorkParameters {
abstract val bootClasspath: ConfigurableFileCollection
abstract val minSdkVersion: Property<Int>
abstract val debuggable: Property<Boolean>
@@ -690,7 +709,7 @@
abstract val errorFormatMode: Property<SyncOptions.ErrorFormatMode>
}
- override fun run() {
+ override fun execute() {
shrink(
parameters.bootClasspath.files.toList(),
parameters.minSdkVersion.get(),
diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/tasks/factory/GlobalTaskCreationConfig.kt b/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/tasks/factory/GlobalTaskCreationConfig.kt
index bfca388..73aa058 100644
--- a/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/tasks/factory/GlobalTaskCreationConfig.kt
+++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/tasks/factory/GlobalTaskCreationConfig.kt
@@ -30,6 +30,7 @@
import com.android.build.api.dsl.TestOptions
import com.android.build.api.transform.Transform
import com.android.build.gradle.internal.SdkComponentsBuildService
+import com.android.build.gradle.internal.core.SettingsOptions
import com.android.build.gradle.internal.dsl.LanguageSplitOptions
import com.android.build.gradle.internal.packaging.JarCreatorType
import com.android.build.gradle.internal.scope.InternalArtifactType
@@ -135,4 +136,7 @@
val jarCreatorType: JarCreatorType
val apkCreatorType: ApkCreatorType
+
+ // Options from the settings plugin
+ val settingsOptions: SettingsOptions
}
diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/tasks/factory/GlobalTaskCreationConfigImpl.kt b/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/tasks/factory/GlobalTaskCreationConfigImpl.kt
index dcc9a76..b239a55 100644
--- a/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/tasks/factory/GlobalTaskCreationConfigImpl.kt
+++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/tasks/factory/GlobalTaskCreationConfigImpl.kt
@@ -34,6 +34,7 @@
import com.android.build.api.transform.Transform
import com.android.build.gradle.BaseExtension
import com.android.build.gradle.internal.SdkComponentsBuildService
+import com.android.build.gradle.internal.core.SettingsOptions
import com.android.build.gradle.internal.dsl.CommonExtensionImpl
import com.android.build.gradle.internal.dsl.LanguageSplitOptions
import com.android.build.gradle.internal.instrumentation.ASM_API_VERSION_FOR_INSTRUMENTATION
@@ -68,7 +69,8 @@
bootClasspathConfig: BootClasspathConfigImpl,
override val lintPublish: Configuration,
override val lintChecks: Configuration,
- private val androidJar: Configuration
+ private val androidJar: Configuration,
+ override val settingsOptions: SettingsOptions
) : GlobalTaskCreationConfig, BootClasspathConfig by bootClasspathConfig {
companion object {
diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/options/StringOption.kt b/build-system/gradle-core/src/main/java/com/android/build/gradle/options/StringOption.kt
index c5c50d9..0e87f29 100644
--- a/build-system/gradle-core/src/main/java/com/android/build/gradle/options/StringOption.kt
+++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/options/StringOption.kt
@@ -85,6 +85,9 @@
// if the flag is not set, the debuggable value will fallback to using the DSL 'debuggable'.
PROFILING_MODE("android.profilingMode", ApiStage.Stable),
+ // Override for the default execution profile in the settings plugin.
+ EXECUTION_PROFILE_SELECTION("android.settings.executionProfile", ApiStage.Stable),
+
/* -----------------
* EXPERIMENTAL APIs
*/
diff --git a/build-system/gradle-settings-api/src/main/java/com/android/build/api/dsl/Execution.kt b/build-system/gradle-settings-api/src/main/java/com/android/build/api/dsl/Execution.kt
new file mode 100644
index 0000000..d28e3ca
--- /dev/null
+++ b/build-system/gradle-settings-api/src/main/java/com/android/build/api/dsl/Execution.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.api.dsl
+
+import org.gradle.api.Action
+import org.gradle.api.NamedDomainObjectContainer
+
+interface Execution {
+ /** Container where profiles can be declared. */
+ val profiles: NamedDomainObjectContainer<ExecutionProfile>
+
+ /** Container where profiles can be declared. */
+ fun profiles(action: Action<NamedDomainObjectContainer<ExecutionProfile>>)
+
+ /** Container where profiles can be declared. */
+ fun profiles(action: NamedDomainObjectContainer<ExecutionProfile>.() -> Unit)
+
+ /** Select execution profile */
+ var defaultProfile: String?
+}
diff --git a/build-system/gradle-settings-api/src/main/java/com/android/build/api/dsl/ExecutionProfile.kt b/build-system/gradle-settings-api/src/main/java/com/android/build/api/dsl/ExecutionProfile.kt
new file mode 100644
index 0000000..a02a2e2
--- /dev/null
+++ b/build-system/gradle-settings-api/src/main/java/com/android/build/api/dsl/ExecutionProfile.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.api.dsl
+
+import org.gradle.api.Action
+import org.gradle.api.Named
+
+interface ExecutionProfile: Named {
+ /** Specify R8 execution options. */
+ val r8: ToolOptions
+
+ /** Specify R8 execution options. */
+ fun r8(action: Action<ToolOptions>)
+
+ /** Specify R8 execution options. */
+ fun r8(action: ToolOptions.() -> Unit)
+}
diff --git a/build-system/gradle-settings-api/src/main/java/com/android/build/api/dsl/SettingsExtension.kt b/build-system/gradle-settings-api/src/main/java/com/android/build/api/dsl/SettingsExtension.kt
index 914582c..fb1306a 100644
--- a/build-system/gradle-settings-api/src/main/java/com/android/build/api/dsl/SettingsExtension.kt
+++ b/build-system/gradle-settings-api/src/main/java/com/android/build/api/dsl/SettingsExtension.kt
@@ -16,6 +16,7 @@
package com.android.build.api.dsl
+import org.gradle.api.Action
import org.gradle.api.Incubating
/**
@@ -84,4 +85,14 @@
var minSdk: Int?
var minSdkPreview: String?
+
+
+ /** Set execution profiles and options for tools. */
+ val execution: Execution
+
+ /** Set execution profiles and options for tools. */
+ fun execution(action: Action<Execution>)
+
+ /** Set execution profiles and options for tools. */
+ fun execution(action: Execution.() -> Unit)
}
diff --git a/build-system/gradle-settings-api/src/main/java/com/android/build/api/dsl/ToolOptions.kt b/build-system/gradle-settings-api/src/main/java/com/android/build/api/dsl/ToolOptions.kt
new file mode 100644
index 0000000..eda6d34
--- /dev/null
+++ b/build-system/gradle-settings-api/src/main/java/com/android/build/api/dsl/ToolOptions.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.api.dsl
+
+interface ToolOptions {
+ /** If true, run tool workers out of process. */
+ var runInSeparateProcess: Boolean
+
+ /**
+ * Extra JVM options to give to the out of process worker JVM. Useful for
+ * setting things like max memory usage
+ */
+ val jvmOptions: MutableList<String>
+
+ fun setJvmOptions(from: List<String>)
+}
diff --git a/build-system/gradle-settings/src/main/java/com/android/build/gradle/internal/dsl/ExecutionImpl.kt b/build-system/gradle-settings/src/main/java/com/android/build/gradle/internal/dsl/ExecutionImpl.kt
new file mode 100644
index 0000000..654ac7d
--- /dev/null
+++ b/build-system/gradle-settings/src/main/java/com/android/build/gradle/internal/dsl/ExecutionImpl.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.dsl
+
+import com.android.build.api.dsl.Execution
+import com.android.build.api.dsl.ExecutionProfile
+import org.gradle.api.Action
+import org.gradle.api.NamedDomainObjectContainer
+import org.gradle.api.model.ObjectFactory
+import javax.inject.Inject
+
+internal open class ExecutionImpl @Inject constructor(objectFactory: ObjectFactory): Execution {
+ override val profiles: NamedDomainObjectContainer<ExecutionProfile> = objectFactory.domainObjectContainer(ExecutionProfile::class.java, ExecutionProfileFactory(objectFactory))
+
+ override fun profiles(action: Action<NamedDomainObjectContainer<ExecutionProfile>>) {
+ action.execute(profiles)
+ }
+
+ override fun profiles(action: NamedDomainObjectContainer<ExecutionProfile>.() -> Unit) {
+ action.invoke(profiles)
+ }
+
+ override var defaultProfile: String? = null
+}
diff --git a/build-system/gradle-settings/src/main/java/com/android/build/gradle/internal/dsl/ExecutionProfileFactory.kt b/build-system/gradle-settings/src/main/java/com/android/build/gradle/internal/dsl/ExecutionProfileFactory.kt
new file mode 100644
index 0000000..9e28517
--- /dev/null
+++ b/build-system/gradle-settings/src/main/java/com/android/build/gradle/internal/dsl/ExecutionProfileFactory.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.dsl
+
+import com.android.build.api.dsl.ExecutionProfile
+import org.gradle.api.NamedDomainObjectFactory
+import org.gradle.api.model.ObjectFactory
+
+/** Factory to create ExecutionProfileExtension object using an [ObjectFactory] to add the DSL methods. */
+class ExecutionProfileFactory(private val objectFactory: ObjectFactory) :
+ NamedDomainObjectFactory<ExecutionProfile> {
+
+ override fun create(name: String): ExecutionProfile {
+ return objectFactory.newInstance(ExecutionProfileImpl::class.java, name, objectFactory)
+ }
+}
diff --git a/build-system/gradle-settings/src/main/java/com/android/build/gradle/internal/dsl/ExecutionProfileImpl.kt b/build-system/gradle-settings/src/main/java/com/android/build/gradle/internal/dsl/ExecutionProfileImpl.kt
new file mode 100644
index 0000000..06740d8
--- /dev/null
+++ b/build-system/gradle-settings/src/main/java/com/android/build/gradle/internal/dsl/ExecutionProfileImpl.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.dsl
+
+import com.android.build.api.dsl.ExecutionProfile
+import com.android.build.api.dsl.ToolOptions
+import org.gradle.api.Action
+import org.gradle.api.model.ObjectFactory
+import javax.inject.Inject
+
+open class ExecutionProfileImpl @Inject constructor(private val name: String, objectFactory: ObjectFactory): ExecutionProfile {
+
+ /**
+ * Name of this profile.
+ */
+ override fun getName(): String {
+ return name
+ }
+
+ override val r8: ToolOptions = objectFactory.newInstance(ToolOptionsImpl::class.java)
+
+ override fun r8(action: Action<ToolOptions>) {
+ action.execute(r8)
+ }
+
+ override fun r8(action: ToolOptions.() -> Unit) {
+ action.invoke(r8)
+ }
+}
diff --git a/build-system/gradle-settings/src/main/java/com/android/build/gradle/internal/dsl/SettingsExtensionImpl.kt b/build-system/gradle-settings/src/main/java/com/android/build/gradle/internal/dsl/SettingsExtensionImpl.kt
index a8d9c96..5842580 100644
--- a/build-system/gradle-settings/src/main/java/com/android/build/gradle/internal/dsl/SettingsExtensionImpl.kt
+++ b/build-system/gradle-settings/src/main/java/com/android/build/gradle/internal/dsl/SettingsExtensionImpl.kt
@@ -16,9 +16,13 @@
package com.android.build.gradle.internal.dsl
+import com.android.build.api.dsl.Execution
import com.android.build.api.dsl.SettingsExtension
+import org.gradle.api.Action
+import org.gradle.api.model.ObjectFactory
+import javax.inject.Inject
-internal open class SettingsExtensionImpl: SettingsExtension {
+internal open class SettingsExtensionImpl @Inject constructor(objectFactory: ObjectFactory): SettingsExtension {
private var _compileSdk: Int? = null
override var compileSdk: Int?
@@ -90,4 +94,13 @@
_minSdkPreview = value
_minSdk = null
}
+
+ override val execution: Execution = objectFactory.newInstance(ExecutionImpl::class.java, objectFactory)
+
+ override fun execution(action: Action<Execution>) {
+ action.execute(execution)
+ }
+ override fun execution(action: Execution.() -> Unit) {
+ action.invoke(execution)
+ }
}
diff --git a/build-system/gradle-settings/src/main/java/com/android/build/gradle/internal/dsl/ToolOptionsImpl.kt b/build-system/gradle-settings/src/main/java/com/android/build/gradle/internal/dsl/ToolOptionsImpl.kt
new file mode 100644
index 0000000..bc187ba
--- /dev/null
+++ b/build-system/gradle-settings/src/main/java/com/android/build/gradle/internal/dsl/ToolOptionsImpl.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.dsl
+
+import com.android.build.api.dsl.ToolOptions
+
+open class ToolOptionsImpl: ToolOptions {
+ override var runInSeparateProcess: Boolean = false
+
+ override val jvmOptions: MutableList<String> = mutableListOf()
+
+ override fun setJvmOptions(from: List<String>) {
+ jvmOptions.clear()
+ jvmOptions.addAll(from)
+ }
+}
diff --git a/build-system/gradle-settings/src/main/java/com/android/build/gradle/internal/plugins/SettingsPlugin.kt b/build-system/gradle-settings/src/main/java/com/android/build/gradle/internal/plugins/SettingsPlugin.kt
index cd30f40..963d113 100644
--- a/build-system/gradle-settings/src/main/java/com/android/build/gradle/internal/plugins/SettingsPlugin.kt
+++ b/build-system/gradle-settings/src/main/java/com/android/build/gradle/internal/plugins/SettingsPlugin.kt
@@ -20,14 +20,17 @@
import com.android.build.gradle.internal.dsl.SettingsExtensionImpl
import org.gradle.api.Plugin
import org.gradle.api.initialization.Settings
+import org.gradle.api.model.ObjectFactory
+import javax.inject.Inject
-class SettingsPlugin: Plugin<Settings> {
+class SettingsPlugin @Inject constructor (private val objectFactory: ObjectFactory): Plugin<Settings> {
override fun apply(settings: Settings) {
val settingsExtension = settings.extensions.create(
SettingsExtension::class.java,
"android",
- SettingsExtensionImpl::class.java
+ SettingsExtensionImpl::class.java,
+ objectFactory
)
// as Project objects cannot query for the Settings object (and its extensions), we
diff --git a/build-system/gradle-settings/src/test/kotlin/com/android/build/gradle/internal/dsl/SettingsExtensionImplTest.kt b/build-system/gradle-settings/src/test/kotlin/com/android/build/gradle/internal/dsl/SettingsExtensionImplTest.kt
index 7ccdfc6..4e3f014 100644
--- a/build-system/gradle-settings/src/test/kotlin/com/android/build/gradle/internal/dsl/SettingsExtensionImplTest.kt
+++ b/build-system/gradle-settings/src/test/kotlin/com/android/build/gradle/internal/dsl/SettingsExtensionImplTest.kt
@@ -18,10 +18,32 @@
import com.google.common.truth.ComparableSubject
import com.google.common.truth.Truth.assertWithMessage
+import org.gradle.api.Project
+import org.gradle.testfixtures.ProjectBuilder
+import org.junit.Before
+import org.junit.Rule
import org.junit.Test
+import org.junit.rules.TemporaryFolder
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
internal class SettingsExtensionImplTest {
- private val settings = SettingsExtensionImpl()
+ @get: Rule
+ val temporaryFolder = TemporaryFolder()
+
+ @get:Rule
+ val mockitoJUnitRule: MockitoRule = MockitoJUnit.rule()
+
+ private val project: Project by lazy {
+ ProjectBuilder.builder().withProjectDir(temporaryFolder.newFolder()).build()
+ }
+
+ private lateinit var settings: SettingsExtensionImpl
+
+ @Before
+ fun setup() {
+ settings = SettingsExtensionImpl(project.objects)
+ }
@Test
fun testDefaults() {
diff --git a/build-system/integration-test/application/src/test/java/com/android/build/gradle/integration/application/SettingsPluginTest.kt b/build-system/integration-test/application/src/test/java/com/android/build/gradle/integration/application/SettingsPluginTest.kt
new file mode 100644
index 0000000..945a85b
--- /dev/null
+++ b/build-system/integration-test/application/src/test/java/com/android/build/gradle/integration/application/SettingsPluginTest.kt
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.integration.application
+
+import com.android.build.gradle.integration.common.fixture.DEFAULT_COMPILE_SDK_VERSION
+import com.android.build.gradle.integration.common.fixture.DEFAULT_MIN_SDK_VERSION
+import com.android.build.gradle.integration.common.fixture.GradleTestProject
+import com.android.build.gradle.integration.common.fixture.GradleTestProject.Companion.ANDROID_GRADLE_PLUGIN_VERSION
+import com.android.build.gradle.integration.common.fixture.testprojects.PluginType
+import com.android.build.gradle.integration.common.fixture.testprojects.createGradleProject
+import com.android.build.gradle.integration.common.fixture.testprojects.prebuilts.setUpHelloWorld
+import com.android.build.gradle.integration.common.truth.ScannerSubject
+import com.android.build.gradle.options.StringOption
+import org.junit.Rule
+import org.junit.Test
+
+class SettingsPluginTest {
+ @get:Rule
+ var project = createGradleProject {
+ settings {
+ plugins.add(PluginType.ANDROID_SETTINGS)
+ }
+ rootProject {
+ plugins.add(PluginType.ANDROID_APP)
+ android {
+
+ setUpHelloWorld(setupDefaultCompileSdk = false)
+ }
+ }
+ }
+
+ data class Profile(
+ val name: String,
+ val r8JvmOptions: List<String>,
+ val r8RunInSeparateProcess: Boolean)
+
+
+ private val defaultProfiles: List<Profile> =
+ listOf(
+ Profile("low", listOf("-Xms200m", "-Xmx200m"), false),
+ Profile("high", listOf("-Xms800m", "-Xmx800m"), true),
+ Profile("pizza", listOf("-Xms801m", "-Xmx801m", "-XX:+HeapDumpOnOutOfMemoryError"), false),
+ )
+
+ private fun addSettingsBlock(
+ minSdk: Int = DEFAULT_MIN_SDK_VERSION,
+ compileSdk: Int = DEFAULT_COMPILE_SDK_VERSION,
+ execProfile: String?,
+ profiles: List<Profile> = defaultProfiles
+ ) {
+ var profileBlocks = ""
+ val expandList = { it: List<String> ->
+ if (it.isNotEmpty()) "\"${it.joinToString("\", \"")}\"" else ""
+ }
+ profiles.forEach {
+ profileBlocks +=
+ """|
+ | ${it.name} {
+ | r8 {
+ | jvmOptions = [${expandList(it.r8JvmOptions)}]
+ | runInSeparateProcess ${it.r8RunInSeparateProcess}
+ | }
+ | }
+ """.trimMargin("|")
+ }
+ project.settingsFile.appendText(
+ """|
+ |
+ |android {
+ | compileSdk $compileSdk
+ | minSdk $minSdk
+ | execution {
+ | profiles {
+ |$profileBlocks
+ | }
+ | ${execProfile?.run {"""defaultProfile "$execProfile""""} ?: ""}
+ | }
+ |}
+ """.trimMargin("|")
+ )
+ }
+ private fun withShrinker() {
+ project.buildFile.appendText("android.buildTypes.debug.minifyEnabled true")
+ }
+
+ @Test
+ fun testInvalidProfile() {
+ withShrinker()
+ addSettingsBlock(execProfile = "invalid")
+ val result = project.executor().expectFailure().run("assembleDebug")
+
+ result.stderr.use {
+ ScannerSubject.assertThat(it).contains("Selected profile 'invalid' does not exist")
+ }
+ }
+
+ @Test
+ fun testProfileOverride() {
+ withShrinker()
+ // First try to build with invalid profile
+ addSettingsBlock(execProfile = "invalid")
+ var result = project.executor().expectFailure().run("assembleDebug")
+
+ result.stderr.use {
+ ScannerSubject.assertThat(it).contains("Selected profile 'invalid' does not exist")
+ }
+
+ // Make sure it builds when overriding the profile
+ result = project.executor().with(StringOption.EXECUTION_PROFILE_SELECTION, "low").run("clean", "assembleDebug")
+
+ result.stdout.use {
+ ScannerSubject.assertThat(it).contains("Using execution profile from android.settings.executionProfile 'low'")
+ }
+ }
+
+ @Test
+ fun testProfileAutoSelection() {
+ withShrinker()
+ // Building with no profiles and no selection should go to default
+ addSettingsBlock(execProfile = null, profiles = listOf())
+ project.execute("assembleDebug")
+
+ // Adding one profile should auto-select it, so it should also work
+ project.settingsFile.appendText(
+ """|
+ |android.execution.profiles {
+ | profileOne {
+ | r8.jvmOptions = []
+ | r8.runInSeparateProcess false
+ | }
+ |}
+ """.trimMargin()
+ )
+ var result = project.executor().run("clean", "assembleDebug")
+
+ result.stdout.use {
+ ScannerSubject.assertThat(it).contains("Using only execution profile 'profileOne'")
+ }
+
+ // Adding another profile with no selection should fail
+ project.settingsFile.appendText(
+ """|
+ |android.execution.profiles {
+ | profileTwo {
+ | }
+ |}
+ |""".trimMargin()
+ )
+ result = project.executor().expectFailure().run("clean", "assembleDebug")
+
+ result.stderr.use {
+ ScannerSubject.assertThat(it).contains("Found 2 execution profiles [profileOne, profileTwo], but no profile was selected.\n")
+ }
+
+ // Selecting a profile through override should work
+ project.executor().with(StringOption.EXECUTION_PROFILE_SELECTION, "profileOne").run("clean", "assembleDebug")
+
+ // So should adding the profile selection to the settings file
+ project.settingsFile.appendText(
+ """android.execution.defaultProfile "profileTwo" """
+ )
+ project.execute("clean", "assembleDebug")
+ }
+}