blob: a982681a3fe4f6019687b020e642bde0e7e10558 [file] [log] [blame]
/*
* 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.plugins
import com.android.Version
import com.android.build.api.dsl.ExecutionProfile
import com.android.build.api.dsl.SettingsExtension
import com.android.build.gradle.internal.attribution.BuildAnalyzerConfiguratorService
import com.android.build.gradle.internal.attribution.BuildAnalyzerService
import com.android.build.gradle.internal.configurationCacheActive
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.errors.DeprecationReporterImpl
import com.android.build.gradle.internal.errors.SyncIssueReporterImpl
import com.android.build.gradle.internal.lint.LintFromMaven.Companion.from
import com.android.build.gradle.internal.profile.AnalyticsConfiguratorService
import com.android.build.gradle.internal.profile.AnalyticsService
import com.android.build.gradle.internal.profile.AnalyticsUtil
import com.android.build.gradle.internal.profile.NoOpAnalyticsConfiguratorService
import com.android.build.gradle.internal.profile.NoOpAnalyticsService
import com.android.build.gradle.internal.projectIsolationActive
import com.android.build.gradle.internal.registerDependencyCheck
import com.android.build.gradle.internal.res.Aapt2FromMaven.Companion.create
import com.android.build.gradle.internal.scope.ProjectInfo
import com.android.build.gradle.internal.services.AndroidLocationsBuildService
import com.android.build.gradle.internal.services.DslServices
import com.android.build.gradle.internal.services.ProjectServices
import com.android.build.gradle.internal.utils.MINIMUM_INTEGRATED_KOTLIN_VERSION
import com.android.build.gradle.internal.utils.MUTUALLY_EXCLUSIVE_ANDROID_GRADLE_PLUGINS
import com.android.build.gradle.options.BooleanOption
import com.android.build.gradle.options.ProjectOptionService
import com.android.build.gradle.options.ProjectOptions
import com.android.build.gradle.options.StringOption
import com.android.build.gradle.options.SyncOptions
import com.android.builder.errors.IssueReporter.Type
import com.google.common.base.CharMatcher
import com.google.wireless.android.sdk.stats.GradleBuildProfileSpan.ExecutionType
import com.google.wireless.android.sdk.stats.GradleBuildProject
import org.gradle.api.JavaVersion
import org.gradle.api.Project
import org.gradle.api.configuration.BuildFeatures
import org.gradle.api.tasks.StopExecutionException
import org.gradle.build.event.BuildEventsListenerRegistry
import org.jetbrains.kotlin.gradle.plugin.KotlinBaseApiPlugin
import java.util.Locale
abstract class AndroidPluginBaseServices(
private val listenerRegistry: BuildEventsListenerRegistry
) {
private val optionService: ProjectOptionService by lazy {
withProject("optionService") {
ProjectOptionService.RegistrationAction(it).execute().get()
}
}
protected val syncIssueReporter: SyncIssueReporterImpl by lazy {
withProject("syncIssueReporter") {
SyncIssueReporterImpl(
SyncOptions.getModelQueryMode(optionService.projectOptions),
SyncOptions.getErrorFormatMode(optionService.projectOptions),
it.logger
)
}
}
@JvmField
protected var project: Project? = null
protected val projectServices: ProjectServices by lazy {
withProject("projectServices") { project ->
val projectOptions = optionService.projectOptions
ProjectServices(
syncIssueReporter,
DeprecationReporterImpl(syncIssueReporter, projectOptions, project.path),
project.objects,
project.logger,
project.providers,
project.layout,
projectOptions,
project.gradle.sharedServices,
from(project, projectOptions, syncIssueReporter),
create(project, projectOptions::get),
project.gradle.startParameter.maxWorkerCount,
ProjectInfo(project),
{ o: Any -> project.file(o) },
project.configurations,
project.dependencies,
project.extensions.extraProperties,
{ name: String -> project.tasks.register(name) }
)
}
}
protected val configuratorService: AnalyticsConfiguratorService by lazy {
withProject("configuratorService") { project ->
val projectOptions: ProjectOptions = projectServices.projectOptions
if (projectOptions.isAnalyticsEnabled) {
AnalyticsConfiguratorService.RegistrationAction(project).execute().get()
} else {
NoOpAnalyticsConfiguratorService.RegistrationAction(project).execute().get()
}
}
}
protected open fun basePluginApply(project: Project, buildFeatures: BuildFeatures) {
// We run by default in headless mode, so the JVM doesn't steal focus.
System.setProperty("java.awt.headless", "true")
this.project = project
AndroidLocationsBuildService.RegistrationAction(project).execute()
checkPluginsCompatibility(project)
checkMinJvmVersion()
val projectOptions: ProjectOptions = projectServices.projectOptions
if (projectOptions.isAnalyticsEnabled) {
AnalyticsService.RegistrationAction(
project,
configuratorService,
listenerRegistry,
buildFeatures.configurationCacheActive(),
buildFeatures.projectIsolationActive(),
).execute()
} else {
NoOpAnalyticsService.RegistrationAction(project).execute()
}
if (projectOptions.get(BooleanOption.ENABLE_TEST_FIXTURES_KOTLIN_SUPPORT)
|| projectOptions.get(BooleanOption.ENABLE_SCREENSHOT_TEST)) {
try {
project.plugins.apply(KotlinBaseApiPlugin::class.java)
} catch (e: Throwable) {
if (e is ClassNotFoundException || e is NoClassDefFoundError) {
val message =
"""
The Kotlin Gradle plugin was not found on the project's buildscript
classpath. Add "org.jetbrains.kotlin:kotlin-gradle-plugin:$MINIMUM_INTEGRATED_KOTLIN_VERSION" to the
buildscript classpath in order to use any of the following Gradle
properties:
${BooleanOption.ENABLE_SCREENSHOT_TEST.propertyName},
${BooleanOption.ENABLE_TEST_FIXTURES_KOTLIN_SUPPORT.propertyName}
""".trimIndent()
syncIssueReporter.reportError(Type.GENERIC, message)
} else {
throw e
}
}
}
SyncIssueReporterImpl.GlobalSyncIssueService.RegistrationAction(
project,
SyncOptions.getModelQueryMode(projectOptions),
SyncOptions.getErrorFormatMode(projectOptions)
).execute()
registerDependencyCheck(project, projectOptions)
checkPathForErrors()
val attributionFileLocation =
projectOptions.get(StringOption.IDE_ATTRIBUTION_FILE_LOCATION)
if (attributionFileLocation != null) {
BuildAnalyzerService.RegistrationAction(
project,
attributionFileLocation,
listenerRegistry,
BuildAnalyzerConfiguratorService.RegistrationAction(
project
).execute().get()
).execute()
}
configuratorService.getProjectBuilder(project.path)?.let {
it
.setAndroidPluginVersion(Version.ANDROID_GRADLE_PLUGIN_VERSION)
.setAndroidPlugin(getAnalyticsPluginType())
.setPluginGeneration(GradleBuildProject.PluginGeneration.FIRST).options =
AnalyticsUtil.toProto(projectOptions)
}
configuratorService.recordBlock(
ExecutionType.BASE_PLUGIN_PROJECT_CONFIGURE,
project.path,
null
) { configureProject(project) }
configuratorService.recordBlock(
ExecutionType.BASE_PLUGIN_PROJECT_BASE_EXTENSION_CREATION,
project.path,
null,
) {
configureExtension(project)
}
configuratorService.recordBlock(
ExecutionType.BASE_PLUGIN_PROJECT_TASKS_CREATION,
project.path,
null,
) {
createTasks(project)
}
}
private fun checkPluginsCompatibility(project: Project) {
val currentPlugin = MUTUALLY_EXCLUSIVE_ANDROID_GRADLE_PLUGINS[this::class.java]
val incompatiblePlugin = currentPlugin?.let {
MUTUALLY_EXCLUSIVE_ANDROID_GRADLE_PLUGINS.entries.firstOrNull {
it.value != currentPlugin && project.pluginManager.hasPlugin(it.value)
}
}
if (incompatiblePlugin != null) {
error(
"'$currentPlugin' and '${incompatiblePlugin.value}' plugins cannot be applied in the same project."
)
}
}
protected 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
if (properties?.has("_android_settings") == true) {
properties.get("_android_settings") as? SettingsExtension
} else {
null
}
}
// Create settings options, to be used in the global config,
// with values from the android settings extension
protected fun createSettingsOptions(
dslServices: DslServices
): 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.associateBy { profile -> profile.name }
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)
}
private fun checkPathForErrors() {
// See if we're on Windows:
if (!System.getProperty("os.name").lowercase(Locale.US).contains("windows")) {
return
}
// See if the user disabled the check:
if (projectServices.projectOptions.get(BooleanOption.OVERRIDE_PATH_CHECK_PROPERTY)) {
return
}
// See if the path contains non-ASCII characters.
if (CharMatcher.ascii().matchesAllOf(project!!.rootDir.absolutePath)) {
return
}
val message = ("Your project path contains non-ASCII characters. This will most likely "
+ "cause the build to fail on Windows. Please move your project to a different "
+ "directory. See http://b.android.com/95744 for details. "
+ "This warning can be disabled by adding the line '"
+ BooleanOption.OVERRIDE_PATH_CHECK_PROPERTY.propertyName
+ "=true' to gradle.properties file in the project directory.")
throw StopExecutionException(message)
}
protected open fun checkMinJvmVersion() {
val current: JavaVersion = JavaVersion.current()
val minRequired: JavaVersion = JavaVersion.VERSION_17
if (!current.isCompatibleWith(minRequired)) {
syncIssueReporter.reportError(
Type.AGP_USED_JAVA_VERSION_TOO_LOW,
"Android Gradle plugin requires Java $minRequired to run. " +
"You are currently using Java $current.\n Your current JDK is located " +
"in ${System.getProperty("java.home")}\n " +
"You can try some of the following options:\n" +
" - changing the IDE settings.\n" +
" - changing the JAVA_HOME environment variable.\n" +
" - changing `org.gradle.java.home` in `gradle.properties`."
)
}
}
protected abstract fun configureProject(project: Project)
protected abstract fun configureExtension(project: Project)
protected abstract fun createTasks(project: Project)
protected abstract fun getAnalyticsPluginType(): GradleBuildProject.PluginType?
/**
* Runs a lambda function if [project] has been initialized and return the function's result or
* generate an exception if [project] is null.
*
* This is useful to have not nullable val field that depends on [project] being initialized.
*/
protected fun <T> withProject(context: String, action: (project: Project) -> T): T =
project?.let {
action(it)
} ?: throw IllegalStateException("Cannot obtain $context until Project is known")
}