blob: eb6ae19dbbe0e92fcdfa31bd7657c2800ea4aa0a [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.gradle.internal.profile.PROPERTY_VARIANT_NAME_KEY
import com.android.build.gradle.internal.scope.BuildArtifactsHolder.OperationType.APPEND
import com.android.build.gradle.internal.scope.BuildArtifactsHolder.OperationType.INITIAL
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.JAVAC
import com.android.build.gradle.internal.scope.VariantScope
import com.android.build.gradle.internal.scope.getOutputDirectory
import com.android.build.gradle.internal.tasks.factory.TaskCreationAction
import com.android.build.gradle.options.BooleanOption
import com.android.sdklib.AndroidTargetHash
import com.google.common.annotations.VisibleForTesting
import com.google.common.base.Joiner
import org.gradle.api.JavaVersion
import org.gradle.api.file.Directory
import org.gradle.api.file.RegularFile
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 java.util.concurrent.Callable
/**
* Task to perform compilation for Java source code (JavaCompile), without or with annotation
* processing depending on whether annotation processing is done by a separate task or not.
*
* The separate annotation processing task can be either KaptTask or the process-annotations task
* (task created in [ProcessAnnotationsTaskCreationAction]).
*
* Process-annotations task is needed if all of the following conditions are met:
* 1. Incremental compilation is requested (either by the user through the DSL or by default)
* 2. Kapt is not used
* 3. The [BooleanOption.ENABLE_SEPARATE_ANNOTATION_PROCESSING] flag is enabled
*
* When Kapt is used (e.g., in most Kotlin-only or hybrid Kotlin-Java projects):
* + Process-annotations task is not created.
* + KaptTask performs annotation processing only, without compiling.
* + JavaCompile and KotlinCompile perform compilation only, without annotation processing.
* When Kapt is not used, (e.g., in Java-only projects):
* + If process-annotations task is needed (see above), process-annotations task first performs
* annotation processing only, without compiling, and JavaCompile then performs
* compilation only, without annotation processing.
* + Otherwise, process-annotations task is not created, and JavaCompile performs both
* annotation processing and compilation.
*/
class JavaCompileCreationAction(
private val variantScope: VariantScope,
private val processAnnotationsTaskCreated: Boolean
) : TaskCreationAction<JavaCompile>() {
private val classesOutputDirectory =
variantScope.globalScope.project.objects.directoryProperty()
private val annotationProcessorOutputDirectory =
variantScope.globalScope.project.objects.directoryProperty()
private val bundleArtifactFolderForDataBinding =
variantScope.globalScope.project.objects.directoryProperty()
init {
// Initialize in the constructor as configure can be invoked before handleProvider,
// and it invokes get() on this provider.
classesOutputDirectory.set(
JAVAC.getOutputDirectory(
variantScope.globalScope.project.layout.buildDirectory,
variantScope.artifacts.getIdentifier(),
"classes"
)
)
val compileSdkVersion = variantScope.globalScope.extension.compileSdkVersion
if (isPostN(compileSdkVersion) && !JavaVersion.current().isJava8Compatible) {
throw RuntimeException(
"compileSdkVersion '$compileSdkVersion' requires JDK 1.8 or later to compile."
)
}
}
override val name: String
get() = variantScope.getTaskName("compile", "JavaWithJavac")
override val type: Class<JavaCompile>
get() = JavaCompile::class.java
override fun handleProvider(taskProvider: TaskProvider<out JavaCompile>) {
super.handleProvider(taskProvider)
variantScope.taskContainer.javacTask = taskProvider
variantScope.artifacts.producesDir(
JAVAC,
APPEND,
taskProvider,
{ classesOutputDirectory },
fileName = "classes"
)
// When doing annotation processing, register its output
if (!processAnnotationsTaskCreated) {
annotationProcessorOutputDirectory.set(
AP_GENERATED_SOURCES.getOutputDirectory(
variantScope.globalScope.project.layout.buildDirectory,
variantScope.artifacts.getIdentifier(),
"out" // in order to preserve previous output path, pass out as taskName
)
)
variantScope.artifacts.producesDir(
AP_GENERATED_SOURCES,
INITIAL,
taskProvider,
{ annotationProcessorOutputDirectory }
)
}
// Data binding artifact is one of the annotation processing outputs
if (variantScope.globalScope.extension.dataBinding.isEnabled) {
bundleArtifactFolderForDataBinding.set(
DATA_BINDING_ARTIFACT.getOutputDirectory(
variantScope.globalScope.project.layout.buildDirectory,
variantScope.artifacts.getIdentifier(),
"out" // in order to preserve previous output path, pass out as taskName
)
)
variantScope.artifacts.producesDir(
DATA_BINDING_ARTIFACT,
APPEND,
taskProvider,
{ bundleArtifactFolderForDataBinding }
)
}
}
override fun configure(task: JavaCompile) {
task.dependsOn(variantScope.taskContainer.preBuildTask)
task.extensions.add(PROPERTY_VARIANT_NAME_KEY, variantScope.fullVariantName)
val globalScope = variantScope.globalScope
val compileOptions = globalScope.extension.compileOptions
val separateAnnotationProcessingFlag = globalScope
.projectOptions
.get(BooleanOption.ENABLE_SEPARATE_ANNOTATION_PROCESSING)
// Configure properties that are shared between JavaCompile and process-annotations task.
task.configureProperties(variantScope)
val sourcesToCompile = if (processAnnotationsTaskCreated) {
// Don't run if only annotation processing is requested, and separate task runs.
task.onlyIf { PROC_ONLY !in task.options.compilerArgs}
// Although we will disable annotation processing (see handleAnnotationProcessors()),
// we still need to set the annotation processor path because:
// 1. Java compiler plugins like Error Prone share their classpath with annotation
// processors.
// 2. We may enable annotation processing at execution time to support the Lombok
// annotation processor.
// See https://issuetracker.google.com/130531986.
task.configureAnnotationProcessorPath(variantScope)
val separateTaskAnnotationProcessorOutputDirectory: Provider<Directory> =
variantScope.artifacts.getFinalProduct(AP_GENERATED_SOURCES)
val generatedSources =
globalScope.project.fileTree(
separateTaskAnnotationProcessorOutputDirectory
).builtBy(separateTaskAnnotationProcessorOutputDirectory)
// Wrap sources in Callable to evaluate them just before execution, b/117161463.
Callable { listOf(variantScope.variantData.javaSources, generatedSources) }
} else {
// Configure properties for annotation processing, because this task is actually
// going to perform annotation processing (i.e., annotation processing is not done
// by process-annotations task).
task.configurePropertiesForAnnotationProcessing(
variantScope,
annotationProcessorOutputDirectory
)
// Wrap sources in Callable to evaluate them just before execution, b/117161463.
Callable { listOf(variantScope.variantData.javaSources) }
}
task.source = task.project.files(sourcesToCompile).asFileTree
task.options.isIncremental =
compileOptions.incremental ?: DEFAULT_INCREMENTAL_COMPILATION
val apList =
variantScope.artifacts.getFinalProduct<RegularFile>(ANNOTATION_PROCESSOR_LIST)
// Record these as inputs. They impact doFirst() below.
task.inputs.property("__separateAnnotationProcessingFlag", separateAnnotationProcessingFlag)
task.inputs.property("__processAnnotationsTaskCreated", processAnnotationsTaskCreated)
task.inputs.files(apList).withPathSensitivity(PathSensitivity.NONE)
task.handleAnnotationProcessors(
apList,
separateAnnotationProcessingFlag,
processAnnotationsTaskCreated,
variantScope.fullVariantName
)
task.destinationDir = classesOutputDirectory.asFile.get()
// Manually declare our output directory as a Task output since it's not annotated as
// an OutputDirectoy on the task implementation.
task.outputs.dir(classesOutputDirectory)
task.outputs.dir(annotationProcessorOutputDirectory).optional()
task.outputs.dir(bundleArtifactFolderForDataBinding).optional()
task.logger.info(
"Configuring Java sources compilation with source level " +
"${task.sourceCompatibility} and target level ${task.targetCompatibility}."
)
}
}
private fun JavaCompile.handleAnnotationProcessors(
processorListFile: Provider<RegularFile>,
separateAnnotationProcessingFlag: Boolean,
processAnnotationsTaskCreated: Boolean,
variantName: String
) {
doFirst {
var procNoneOptionSetByAGP = false
if (processAnnotationsTaskCreated) {
if (!options.compilerArgs.contains(PROC_NONE)) {
options.compilerArgs.add(PROC_NONE)
procNoneOptionSetByAGP = true
}
}
val hasKapt = this.project.pluginManager.hasPlugin(KOTLIN_KAPT_PLUGIN_ID)
val annotationProcessors =
readAnnotationProcessorsFromJsonFile(processorListFile.get().asFile)
val nonIncrementalAPs =
annotationProcessors.filter { it.value == java.lang.Boolean.FALSE }
val allAPsAreIncremental = nonIncrementalAPs.isEmpty()
// If incremental compilation is requested and annotation processing is performed by this
// task, but not all of the annotation processors are incremental, then compilation will not
// be incremental. We warn users about non-incremental annotation processors and tell them
// to enable the separateAnnotationProcessing flag to make compilation incremental.
if (options.isIncremental
&& !hasKapt
&& !separateAnnotationProcessingFlag
&& !allAPsAreIncremental
) {
logger
.warn(
"Gradle may disable incremental compilation" +
" as the following annotation processors are not incremental:" +
" ${Joiner.on(", ").join(nonIncrementalAPs.keys)}.\n" +
"Consider setting the experimental feature flag" +
" ${BooleanOption.ENABLE_SEPARATE_ANNOTATION_PROCESSING.propertyName}" +
"=true in the gradle.properties file to run annotation processing" +
" in a separate task and make compilation incremental."
)
}
/*
* Support Lombok (https://issuetracker.google.com/130531986). Note that we can't support
* Lombok with Kapt (https://youtrack.jetbrains.com/issue/KT-7112) correctly yet, as we
* can't tell if -proc:none is requested by Kapt or by the user.
*/
if (procNoneOptionSetByAGP) {
val lomboks = annotationProcessors.filter { it.key.contains(LOMBOK) }
if (lomboks.isNotEmpty()) {
configureCompilerArgumentsForLombok(this.options.compilerArgs)
// In case the version of Lombok being used is not incremental, print out a warning.
val nonIncrementalLomboks =
lomboks.filter { it.value == java.lang.Boolean.FALSE }
if (nonIncrementalLomboks.isNotEmpty()) {
logger.warn(
"Gradle may disable incremental compilation" +
" as the following annotation processors are not incremental:" +
" ${Joiner.on(", ").join(nonIncrementalLomboks.keys)}."
)
}
}
}
// Record annotation processors that has been executed by another task or will be executed
// by this task 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, project.path, variantName
)
}
}
private fun isPostN(compileSdkVersion: String): Boolean {
val hash = AndroidTargetHash.getVersionFromHash(compileSdkVersion)
return hash != null && hash.apiLevel >= 24
}
/**
* Configures compiler arguments to run Lombok (and only Lombok) when no annotation
* processing (-proc:none) was requested earlier.
*/
@VisibleForTesting
fun configureCompilerArgumentsForLombok(compilerArgs: MutableList<String>) {
check(compilerArgs.contains(PROC_NONE))
{ "compilerArgs $compilerArgs does not contain $PROC_NONE" }
compilerArgs.removeAll { it == PROC_NONE }
// Remove -processor and the class names that follow it
val filteredArgs =
compilerArgs.filterIndexed { index, _ ->
(compilerArgs[index] != PROCESSOR)
&& (index == 0 || compilerArgs[index - 1] != PROCESSOR)
}
compilerArgs.clear()
compilerArgs.addAll(filteredArgs)
// Run Lombok only by setting the -processor argument (doing this is better than
// filtering the annotation processor path to include only Lombok, since we don't
// want to remove dependencies used by Java compiler plugins like Error Prone which
// are also put on the annotation processor path).
compilerArgs.add(PROCESSOR)
compilerArgs.add(LOMBOK_ANNOTATION_PROCESSOR)
}