blob: fed2fe29b18def679c23d5887bb95adfb7bd8458 [file] [log] [blame]
/*
* Copyright (C) 2018 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.tasks
import com.android.build.api.artifact.impl.ArtifactsImpl
import com.android.build.api.component.impl.ComponentPropertiesImpl
import com.android.build.gradle.internal.profile.PROPERTY_VARIANT_NAME_KEY
import com.android.build.gradle.internal.scope.InternalArtifactType.ANNOTATION_PROCESSOR_LIST
import com.android.build.gradle.internal.scope.InternalArtifactType.AP_GENERATED_SOURCES
import com.android.build.gradle.internal.scope.InternalArtifactType.DATA_BINDING_ARTIFACT
import com.android.build.gradle.internal.scope.InternalArtifactType.DATA_BINDING_EXPORT_CLASS_LIST
import com.android.build.gradle.internal.scope.InternalArtifactType.JAVAC
import com.android.build.gradle.internal.tasks.factory.TaskCreationAction
import com.android.sdklib.AndroidTargetHash
import org.gradle.api.JavaVersion
import org.gradle.api.Task
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFile
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.api.tasks.util.PatternSet
import java.util.concurrent.Callable
/**
* [TaskCreationAction] for the [JavaCompile] task.
*
* Note that when Kapt is not used (e.g., in Java-only projects), [JavaCompile] performs both
* annotation processing and compilation. When Kapt is used (e.g., in most Kotlin-only or hybrid
* Kotlin-Java projects), [JavaCompile] performs compilation only, without annotation processing.
*/
class JavaCompileCreationAction(private val componentProperties: ComponentPropertiesImpl) :
TaskCreationAction<JavaCompile>() {
private val globalScope = componentProperties.globalScope
private val classesOutputDirectory = globalScope.project.objects.directoryProperty()
private val annotationProcessorOutputDirectory = globalScope.project.objects.directoryProperty()
private val dataBindingArtifactDir = globalScope.project.objects.directoryProperty()
private val dataBindingExportClassListFile = globalScope.project.objects.fileProperty()
init {
val compileSdkVersion = globalScope.extension.compileSdkVersion
if (compileSdkVersion != null && isPostN(compileSdkVersion) && !JavaVersion.current().isJava8Compatible) {
throw RuntimeException(
"compileSdkVersion '$compileSdkVersion' requires JDK 1.8 or later to compile."
)
}
}
override val name: String
get() = componentProperties.computeTaskName("compile", "JavaWithJavac")
override val type: Class<JavaCompile>
get() = JavaCompile::class.java
override fun handleProvider(taskProvider: TaskProvider<JavaCompile>) {
super.handleProvider(taskProvider)
componentProperties.taskContainer.javacTask = taskProvider
val artifacts = componentProperties.artifacts
artifacts
.setInitialProvider(taskProvider) { classesOutputDirectory }
.withName("classes")
.on(JAVAC)
artifacts
.setInitialProvider(taskProvider) { annotationProcessorOutputDirectory }
// Setting a name is not required, but a lot of AGP and IDE tests are assuming this name
// so we leave it here for now.
.withName(AP_GENERATED_SOURCES_DIR_NAME)
.on(AP_GENERATED_SOURCES)
if (componentProperties.buildFeatures.dataBinding) {
// Data binding artifacts are part of the annotation processing outputs
registerDataBindingOutputs(
dataBindingArtifactDir,
dataBindingExportClassListFile,
componentProperties.variantType.isExportDataBindingClassList,
true, /* firstRegistration */
taskProvider,
artifacts
)
}
}
override fun configure(task: JavaCompile) {
task.dependsOn(componentProperties.taskContainer.preBuildTask)
task.extensions.add(PROPERTY_VARIANT_NAME_KEY, componentProperties.name)
task.configureProperties(componentProperties)
task.configurePropertiesForAnnotationProcessing(componentProperties)
// Wrap sources in Callable to evaluate them just before execution, b/117161463.
val sourcesToCompile = Callable { listOf(componentProperties.javaSources) }
// Include only java sources, otherwise we hit b/144249620.
val javaSourcesFilter = PatternSet().include("**/*.java")
task.source = task.project.files(sourcesToCompile).asFileTree.matching(javaSourcesFilter)
task.options.isIncremental = globalScope.extension.compileOptions.incremental
?: DEFAULT_INCREMENTAL_COMPILATION
// Record apList as input. It impacts handleAnnotationProcessors() below.
val apList = componentProperties.artifacts.get(ANNOTATION_PROCESSOR_LIST)
task.inputs.files(apList).withPathSensitivity(PathSensitivity.NONE)
.withPropertyName("annotationProcessorList")
task.handleAnnotationProcessors(apList, componentProperties.name)
// Set up the outputs
task.setDestinationDir(classesOutputDirectory.asFile)
// Can't write `task.options.setAnnotationProcessorGeneratedSourcesDirectory(
// annotationProcessorOutputDirectory.asFile)` due to a new requirement in Gradle 7.0:
// https://docs.gradle.org/current/userguide/upgrading_version_6.html#querying_a_mapped_output_property_of_a_task_before_the_task_has_completed
// (currently caught by BasicInstantExecutionTest).
task.options.annotationProcessorGeneratedSourcesDirectory =
componentProperties.artifacts
.getOutputPath(AP_GENERATED_SOURCES, AP_GENERATED_SOURCES_DIR_NAME)
// The API of JavaCompile to set up outputs only accepts File or Provider<File> (see above).
// However, for task wiring to work correctly, we will need to register the corresponding
// DirectoryProperty / RegularFileProperty as outputs too (they are not yet annotated as
// outputs).
task.outputs.dir(classesOutputDirectory).withPropertyName("classesOutputDirectory")
task.outputs.dir(annotationProcessorOutputDirectory)
.withPropertyName("annotationProcessorOutputDirectory")
// Also do that for data binding artifacts
if (componentProperties.buildFeatures.dataBinding) {
task.outputs.dir(dataBindingArtifactDir).withPropertyName("dataBindingArtifactDir")
if (componentProperties.variantType.isExportDataBindingClassList) {
task.outputs.file(dataBindingExportClassListFile)
.withPropertyName("dataBindingExportClassListFile")
}
}
task.logger.info(
"Configuring Java sources compilation with source level " +
"${task.sourceCompatibility} and target level ${task.targetCompatibility}."
)
}
}
/**
* Registers data binding outputs as outputs of the JavaCompile task (or the Kapt task if Kapt is
* used).
*/
fun registerDataBindingOutputs(
dataBindingArtifactDir: DirectoryProperty,
dataBindingExportClassListFile: RegularFileProperty,
isExportDataBindingClassList: Boolean,
firstRegistration: Boolean,
taskProvider: TaskProvider<out Task>,
artifacts: ArtifactsImpl
) {
if (firstRegistration) {
artifacts
.setInitialProvider(taskProvider) { dataBindingArtifactDir }
// a name is required, or DataBindingCachingTest would fail (somehow adding a name
// solves the issue of overlapping outputs between Kapt and JavaCompile when this
// artifact is set as the output of both tasks).
.withName("out")
.on(DATA_BINDING_ARTIFACT)
} else {
artifacts.use(taskProvider)
.wiredWith { dataBindingArtifactDir }
.toCreate(DATA_BINDING_ARTIFACT)
}
if (isExportDataBindingClassList) {
if (firstRegistration) {
artifacts
.setInitialProvider(taskProvider) { dataBindingExportClassListFile }
.on(DATA_BINDING_EXPORT_CLASS_LIST)
} else {
artifacts.use(taskProvider)
.wiredWith { dataBindingExportClassListFile }
.toCreate(DATA_BINDING_EXPORT_CLASS_LIST)
}
}
}
private fun JavaCompile.handleAnnotationProcessors(
processorListFile: Provider<RegularFile>,
variantName: String
) {
val hasKapt = this.project.pluginManager.hasPlugin(KOTLIN_KAPT_PLUGIN_ID)
val projectPath = this.project.path
doFirst {
val annotationProcessors =
readAnnotationProcessorsFromJsonFile(processorListFile.get().asFile)
val nonIncrementalAPs =
annotationProcessors.filter { it.value == java.lang.Boolean.FALSE }
val allAPsAreIncremental = nonIncrementalAPs.isEmpty()
// Warn users about non-incremental annotation processors
if (!hasKapt && !allAPsAreIncremental && options.isIncremental) {
logger
.warn(
"The following annotation processors are not incremental:" +
" ${nonIncrementalAPs.keys.joinToString(", ")}.\n" +
"Make sure all annotation processors are incremental" +
" to improve your build speed."
)
}
// Record annotation processors for analytics purposes. This recording needs to happen here
// instead of JavaPreCompileTask as it needs to be done even in incremental builds where
// JavaPreCompileTask may be UP-TO-DATE.
recordAnnotationProcessorsForAnalytics(
annotationProcessors, projectPath, variantName
)
}
}
private fun isPostN(compileSdkVersion: String): Boolean {
val hash = AndroidTargetHash.getVersionFromHash(compileSdkVersion)
return hash != null && hash.apiLevel >= 24
}
private const val AP_GENERATED_SOURCES_DIR_NAME = "out"