Subplugin options refactoring & Gradle input improvements

Introduce FilesSubpluginOption Special kind of SubpluginOption that
holds a list of files together with the kind of these files with respect
to the task.

Add a logic for handling such options and converting them into
task inputs.

Refactor CompilerPluginOptions: make it store subplugin options to be
able to add options from KotlinCompile to the kapt tasks.

Introduce WrapperSubpluginOption for encoded and complex options.

Remove pluginOptions from the Gradle inputs built from the
compiler args.

Do not cache Kotlin compilation with kapt1 enabled: since we are going
to drop kapt1 it anyway, there's no point in implementing
proper cache for it, so just disable the caching of the task in case
kapt1 is used.
This commit is contained in:
Sergey Igushkin
2017-12-05 20:38:04 +03:00
parent 93097014a0
commit c852d0b6cb
8 changed files with 509 additions and 420 deletions
@@ -19,8 +19,24 @@ package org.jetbrains.kotlin.gradle.plugin
import org.gradle.api.Project
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.compile.AbstractCompile
import java.io.File
class SubpluginOption(val key: String, val value: String)
open class SubpluginOption(val key: String, open val value: String)
class FilesSubpluginOption(
key: String,
val kind: FileOptionKind,
val files: List<File>,
value: String = files.joinToString(File.pathSeparator) { it.canonicalPath })
: SubpluginOption(key, value)
class WrapperSubpluginOption(
key: String,
value: String,
val originalOptions: List<SubpluginOption>)
: SubpluginOption(key, value)
enum class FileOptionKind { INPUT_FILES, CLASSPATH_INPUT, OUTPUT_FILES, OUTPUT_DIRS, INTERNAL }
interface KotlinGradleSubplugin<in KotlinCompile : AbstractCompile> {
fun isApplicable(project: Project, task: AbstractCompile): Boolean
@@ -116,8 +116,13 @@ class AndroidSubplugin : KotlinGradleSubplugin<KotlinCompile> {
pluginOptions += SubpluginOption("package", applicationPackage)
fun addVariant(sourceSet: AndroidSourceSet) {
pluginOptions += SubpluginOption("variant", sourceSet.name + ';' +
sourceSet.res.srcDirs.joinToString(";") { it.absolutePath })
val optionValue = sourceSet.name + ';' +
sourceSet.res.srcDirs.joinToString(";") { it.absolutePath }
pluginOptions += WrapperSubpluginOption("variant", optionValue, listOf(
SubpluginOption("sourceSetName", sourceSet.name),
//use the INTERNAL option kind since the resources are tracked as sources (see below)
FilesSubpluginOption("resDirs", FileOptionKind.INTERNAL, sourceSet.res.srcDirs.toList())
))
kotlinCompile.source(project.files(getLayoutDirectories(sourceSet.res.srcDirs)))
}
@@ -160,11 +165,15 @@ class AndroidSubplugin : KotlinGradleSubplugin<KotlinCompile> {
pluginOptions += SubpluginOption("package", getApplicationPackage(project, mainSourceSet))
fun addVariant(name: String, resDirectories: List<File>) {
pluginOptions += SubpluginOption("variant", buildString {
val optionValue = buildString {
append(name)
append(';')
resDirectories.joinTo(this, separator = ";") { it.canonicalPath }
})
}
pluginOptions += WrapperSubpluginOption("variant", optionValue, listOf(
SubpluginOption("variantName", name),
// use INTERNAL option kind since the resources are tracked as sources (see below)
FilesSubpluginOption("resDirs", FileOptionKind.INTERNAL, resDirectories)))
kotlinCompile.source(project.files(getLayoutDirectories(resDirectories)))
}
@@ -36,6 +36,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jetbrains.kotlin.gradle.tasks.KotlinTasksProvider
import org.jetbrains.kotlin.gradle.tasks.kapt.generateAnnotationProcessorWrapper
import org.jetbrains.kotlin.gradle.tasks.kapt.generateKotlinAptAnnotation
import org.jetbrains.kotlin.gradle.tasks.shouldEnableGradleCache
import java.io.File
import java.io.IOException
import java.util.zip.ZipFile
@@ -78,6 +79,11 @@ internal fun Project.initKapt(
// javaTask.doLast {
// moveGeneratedJavaFilesToCorrespondingDirectories(kaptManager.aptOutputDir)
// }
if (shouldEnableGradleCache()) {
// Since Kapt1 is about to be dropped, disable the cache for it:
kotlinAfterJavaTask.outputs.doNotCacheIf("Caching is not supported with deprecated Kapt1") { true }
}
} else {
kotlinAfterJavaTask = null
kotlinTask.logger.kotlinDebug("kapt: Class file stubs are not used")
@@ -103,6 +109,12 @@ internal fun Project.initKapt(
}
kotlinTask.kaptOptions.annotationsFile = kaptManager.getAnnotationFile()
if (shouldEnableGradleCache()) {
// Since Kapt1 is about to be dropped, disable the cache for it:
kotlinTask.outputs.doNotCacheIf("Caching is not supported with deprecated Kapt1") { true }
}
return kotlinAfterJavaTask
}
@@ -1,403 +1,413 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* 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 org.jetbrains.kotlin.gradle.internal
import com.android.build.gradle.BaseExtension
import com.android.build.gradle.api.AndroidSourceSet
import com.android.builder.model.SourceProvider
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.Dependency
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.TaskDependency
import org.gradle.api.tasks.compile.AbstractCompile
import org.gradle.api.tasks.compile.JavaCompile
import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.tasks.CompilerPluginOptions
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import java.io.File
import org.jetbrains.kotlin.gradle.internal.Kapt3GradleSubplugin.Companion.getKaptGeneratedSourcesDir
import org.jetbrains.kotlin.gradle.internal.Kapt3GradleSubplugin.Companion.getKaptGeneratedClassesDir
import org.jetbrains.kotlin.gradle.internal.Kapt3GradleSubplugin.Companion.getKaptGeneratedKotlinSourcesDir
import org.jetbrains.kotlin.gradle.tasks.shouldEnableGradleCache
import org.jetbrains.kotlin.gradle.tasks.useBuildCacheIfSupported
import java.io.ByteArrayOutputStream
import java.io.ObjectOutputStream
import java.util.*
// apply plugin: 'kotlin-kapt'
class Kapt3GradleSubplugin : Plugin<Project> {
companion object {
fun isEnabled(project: Project) = project.plugins.findPlugin(Kapt3GradleSubplugin::class.java) != null
@JvmStatic
fun getKaptGeneratedClassesDir(project: Project, sourceSetName: String) =
File(project.project.buildDir, "tmp/kapt3/classes/$sourceSetName")
@JvmStatic
fun getKaptGeneratedSourcesDir(project: Project, sourceSetName: String) =
File(project.project.buildDir, "generated/source/kapt/$sourceSetName")
@JvmStatic
fun getKaptGeneratedKotlinSourcesDir(project: Project, sourceSetName: String) =
File(project.project.buildDir, "generated/source/kaptKotlin/$sourceSetName")
}
override fun apply(project: Project) {}
}
abstract class KaptVariantData<T>(val variantData: T) {
abstract val name: String
abstract val sourceProviders: Iterable<SourceProvider>
abstract fun addJavaSourceFoldersToModel(generatedFilesDir: File)
abstract val annotationProcessorOptions: Map<String, String>?
abstract fun registerGeneratedJavaSource(
project: Project,
kaptTask: KaptTask,
javaTask: AbstractCompile)
}
// Subplugin for the Kotlin Gradle plugin
class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
companion object {
private val VERBOSE_OPTION_NAME = "kapt.verbose"
val MAIN_KAPT_CONFIGURATION_NAME = "kapt"
val KAPT_GROUP_NAME = "org.jetbrains.kotlin"
val KAPT_ARTIFACT_NAME = "kotlin-annotation-processing-gradle"
fun getKaptConfigurationName(sourceSetName: String): String {
return if (sourceSetName != "main")
"$MAIN_KAPT_CONFIGURATION_NAME${sourceSetName.capitalize()}"
else
MAIN_KAPT_CONFIGURATION_NAME
}
fun Project.findKaptConfiguration(sourceSetName: String): Configuration? {
return project.configurations.findByName(getKaptConfigurationName(sourceSetName))
}
fun findMainKaptConfiguration(project: Project) = project.findKaptConfiguration(MAIN_KAPT_CONFIGURATION_NAME)
}
private val kotlinToKaptGenerateStubsTasksMap = mutableMapOf<KotlinCompile, KaptGenerateStubsTask>()
override fun isApplicable(project: Project, task: AbstractCompile) = task is KotlinCompile && Kapt3GradleSubplugin.isEnabled(project)
private fun Kapt3SubpluginContext.getKaptStubsDir() = createAndReturnTemporaryKaptDirectory("stubs")
private fun Kapt3SubpluginContext.getKaptIncrementalDataDir() = createAndReturnTemporaryKaptDirectory("incrementalData")
private fun Kapt3SubpluginContext.createAndReturnTemporaryKaptDirectory(name: String): File {
val dir = File(project.buildDir, "tmp/kapt3/$name/$sourceSetName")
dir.mkdirs()
return dir
}
private inner class Kapt3SubpluginContext(
val project: Project,
val kotlinCompile: KotlinCompile,
val javaCompile: AbstractCompile,
val kaptVariantData: KaptVariantData<*>?,
val sourceSetName: String,
val javaSourceSet: SourceSet?,
val kaptExtension: KaptExtension,
val kaptClasspath: MutableList<File>) {
val sourcesOutputDir = getKaptGeneratedSourcesDir(project, sourceSetName)
val kotlinSourcesOutputDir = getKaptGeneratedKotlinSourcesDir(project, sourceSetName)
val classesOutputDir = getKaptGeneratedClassesDir(project, sourceSetName)
val kaptClasspathArtifacts = project
.resolveSubpluginArtifacts(listOf(this@Kapt3KotlinGradleSubplugin))
.flatMap { it.value }
}
override fun apply(
project: Project,
kotlinCompile: KotlinCompile,
javaCompile: AbstractCompile,
variantData: Any?,
androidProjectHandler: Any?,
javaSourceSet: SourceSet?
): List<SubpluginOption> {
assert((variantData != null) xor (javaSourceSet != null))
val kaptClasspath = arrayListOf<File>()
val buildDependencies = arrayListOf<TaskDependency>()
val kaptConfigurations = arrayListOf<Configuration>()
fun handleSourceSet(sourceSetName: String) {
val kaptConfiguration = project.findKaptConfiguration(sourceSetName)
val filteredDependencies = kaptConfiguration?.dependencies?.filter {
it.group != getGroupName() || it.name != getArtifactName()
} ?: emptyList()
if (kaptConfiguration != null) {
kaptConfigurations += kaptConfiguration
buildDependencies += kaptConfiguration.buildDependencies
if (filteredDependencies.isNotEmpty()) {
kaptClasspath.addAll(kaptConfiguration.resolve())
}
}
}
val kaptVariantData = variantData as? KaptVariantData<*>
val sourceSetName = if (kaptVariantData != null) {
for (provider in kaptVariantData.sourceProviders) {
handleSourceSet((provider as AndroidSourceSet).name)
}
kaptVariantData.name
}
else {
if (javaSourceSet == null) error("Java source set should not be null")
handleSourceSet(javaSourceSet.name)
javaSourceSet.name
}
val kaptExtension = project.extensions.getByType(KaptExtension::class.java)
val context = Kapt3SubpluginContext(project, kotlinCompile, javaCompile,
kaptVariantData, sourceSetName, javaSourceSet, kaptExtension, kaptClasspath)
val kaptGenerateStubsTask = context.createKaptGenerateStubsTask()
val kaptTask = context.createKaptKotlinTask()
kaptGenerateStubsTask.source(*kaptConfigurations.toTypedArray())
kaptGenerateStubsTask.dependsOn(*buildDependencies.toTypedArray())
kaptGenerateStubsTask.dependsOn(*kotlinCompile.dependsOn.toTypedArray())
kaptTask.dependsOn(kaptGenerateStubsTask)
kotlinCompile.dependsOn(kaptTask)
/** Plugin options are applied to kapt*Compile inside [createKaptKotlinTask] */
return emptyList()
}
override fun getSubpluginKotlinTasks(project: Project, kotlinCompile: KotlinCompile): List<AbstractCompile> {
val kaptGenerateStubsTask = kotlinToKaptGenerateStubsTasksMap[kotlinCompile]
return if (kaptGenerateStubsTask == null) emptyList() else listOf(kaptGenerateStubsTask)
}
// This method should be called no more than once for each Kapt3SubpluginContext
private fun Kapt3SubpluginContext.buildOptions(aptMode: String): List<SubpluginOption> {
val pluginOptions = mutableListOf<SubpluginOption>()
val generatedFilesDir = getKaptGeneratedSourcesDir(project, sourceSetName)
kaptVariantData?.addJavaSourceFoldersToModel(generatedFilesDir)
pluginOptions += SubpluginOption("aptMode", aptMode)
disableAnnotationProcessingInJavaTask()
kaptClasspath.forEach { pluginOptions += SubpluginOption("apclasspath", it.absolutePath) }
javaCompile.source(generatedFilesDir)
pluginOptions += SubpluginOption("sources", generatedFilesDir.canonicalPath)
pluginOptions += SubpluginOption("classes", getKaptGeneratedClassesDir(project, sourceSetName).canonicalPath)
pluginOptions += SubpluginOption("incrementalData", getKaptIncrementalDataDir().canonicalPath)
val annotationProcessors = kaptExtension.processors
if (annotationProcessors.isNotEmpty()) {
pluginOptions += SubpluginOption("processors", annotationProcessors)
}
val androidPlugin = kaptVariantData?.let {
project.extensions.findByName("android") as? BaseExtension
}
val androidOptions = kaptVariantData?.annotationProcessorOptions ?: emptyMap()
kotlinSourcesOutputDir.mkdirs()
val apOptions = kaptExtension.getAdditionalArguments(
project,
kaptVariantData?.variantData,
androidPlugin
) + androidOptions + mapOf("kapt.kotlin.generated" to kotlinSourcesOutputDir.absolutePath)
pluginOptions += SubpluginOption("apoptions", encodeList(apOptions))
pluginOptions += SubpluginOption("javacArguments", encodeList(kaptExtension.getJavacOptions()))
addMiscOptions(pluginOptions)
return pluginOptions
}
private fun Kapt3SubpluginContext.buildAndAddOptionsTo(container: CompilerPluginOptions, aptMode: String) {
val compilerPluginId = getCompilerPluginId()
for (option in wrapPluginOptions(buildOptions(aptMode), "configuration")) {
container.addPluginArgument(compilerPluginId, option.key, option.value)
}
}
private fun encodeList(options: Map<String, String>): String {
val os = ByteArrayOutputStream()
val oos = ObjectOutputStream(os)
oos.writeInt(options.size)
for ((key, value) in options.entries) {
oos.writeUTF(key)
oos.writeUTF(value)
}
oos.flush()
return Base64.getEncoder().encodeToString(os.toByteArray())
}
private fun Kapt3SubpluginContext.addMiscOptions(pluginOptions: MutableList<SubpluginOption>) {
if (kaptExtension.generateStubs) {
project.logger.warn("'kapt.generateStubs' is not used by the 'kotlin-kapt' plugin")
}
pluginOptions += SubpluginOption("useLightAnalysis", "${kaptExtension.useLightAnalysis}")
pluginOptions += SubpluginOption("correctErrorTypes", "${kaptExtension.correctErrorTypes}")
pluginOptions += SubpluginOption("stubs", getKaptStubsDir().canonicalPath)
if (project.hasProperty(VERBOSE_OPTION_NAME) && project.property(VERBOSE_OPTION_NAME) == "true") {
pluginOptions += SubpluginOption("verbose", "true")
}
}
private fun Kapt3SubpluginContext.createKaptKotlinTask(): KaptTask {
val kaptTask = project.tasks.create(
getKaptTaskName("kapt"),
KaptTask::class.java)
kaptTask.useBuildCacheIfSupported()
kaptTask.kotlinCompileTask = kotlinCompile
kaptClasspathArtifacts.forEach { kaptTask.pluginOptions.addClasspathEntry(it) }
kaptTask.stubsDir = getKaptStubsDir()
kaptTask.destinationDir = sourcesOutputDir
kaptTask.kotlinSourcesDestinationDir = kotlinSourcesOutputDir
kaptTask.classesDir = classesOutputDir
javaSourceSet?.output?.apply {
if (tryAddClassesDir { project.files(classesOutputDir).builtBy(kaptTask) }) {
kotlinCompile.attachClassesDir { classesOutputDir }
}
}
kotlinCompile.source(sourcesOutputDir, kotlinSourcesOutputDir)
if (kaptVariantData != null) {
kaptVariantData.registerGeneratedJavaSource(project, kaptTask, javaCompile)
}
else {
registerGeneratedJavaSource(kaptTask, javaCompile)
}
kaptTask.kaptClasspath = kaptClasspath
kaptTask.useBuildCache = kaptExtension.useBuildCache
if (shouldEnableGradleCache()) {
val reason = "Caching is disabled by default for kapt because of arbitrary behavior of external " +
"annotation processors. You can enable it by adding 'kapt.useBuildCache = true' to the build script."
kaptTask.outputs.doNotCacheIf(reason) { !kaptTask.useBuildCache }
}
buildAndAddOptionsTo(kaptTask.pluginOptions, aptMode = "apt")
return kaptTask
}
private fun Kapt3SubpluginContext.createKaptGenerateStubsTask(): KaptGenerateStubsTask {
val kaptTask = project.tasks.create(
getKaptTaskName("kaptGenerateStubs"),
KaptGenerateStubsTask::class.java)
kaptTask.useBuildCacheIfSupported()
kaptTask.sourceSetName = sourceSetName
kaptTask.kotlinCompileTask = kotlinCompile
kotlinToKaptGenerateStubsTasksMap[kotlinCompile] = kaptTask
kaptClasspathArtifacts.forEach { kaptTask.pluginOptions.addClasspathEntry(it) }
kaptTask.stubsDir = getKaptStubsDir()
kaptTask.destinationDir = getKaptIncrementalDataDir()
kaptTask.mapClasspath { kotlinCompile.classpath }
kaptTask.generatedSourcesDir = sourcesOutputDir
mapKotlinTaskProperties(project, kaptTask)
kaptTask.kaptClasspath = kaptClasspath
buildAndAddOptionsTo(kaptTask.pluginOptions, aptMode = "stubs")
return kaptTask
}
private fun Kapt3SubpluginContext.getKaptTaskName(prefix: String): String {
// Replace compile*Kotlin to kapt*Kotlin
val baseName = kotlinCompile.name
assert(baseName.startsWith("compile"))
return baseName.replaceFirst("compile", prefix)
}
private fun Kapt3SubpluginContext.disableAnnotationProcessingInJavaTask() {
(javaCompile as? JavaCompile)?.let { javaCompile ->
val options = javaCompile.options
// 'android-apt' (com.neenbedankt) adds a File instance to compilerArgs (List<String>).
// Although it's not our problem, we need to handle this case properly.
val oldCompilerArgs: List<Any> = options.compilerArgs
val newCompilerArgs = oldCompilerArgs.filterTo(mutableListOf()) {
it !is CharSequence || !it.toString().startsWith("-proc:")
}
newCompilerArgs.add("-proc:none")
@Suppress("UNCHECKED_CAST")
options.compilerArgs = newCompilerArgs as List<String>
}
}
override fun getCompilerPluginId() = "org.jetbrains.kotlin.kapt3"
override fun getGroupName() = KAPT_GROUP_NAME
override fun getArtifactName() = KAPT_ARTIFACT_NAME
}
internal fun registerGeneratedJavaSource(kaptTask: KaptTask, javaTask: AbstractCompile) {
javaTask.source(kaptTask.destinationDir)
}
internal fun Configuration.getNamedDependencies(): List<Dependency> = allDependencies.filter { it.group != null && it.name != null }
internal fun checkAndroidAnnotationProcessorDependencyUsage(project: Project) {
val isKapt3Enabled = Kapt3GradleSubplugin.isEnabled(project)
val androidAPDependencies = project.configurations
.filter { it.name == "annotationProcessor"
|| (it.name.endsWith("AnnotationProcessor") && !it.name.startsWith("_")) }
.flatMap { it.getNamedDependencies() }
if (androidAPDependencies.isNotEmpty()) {
val artifactsRendered = androidAPDependencies.joinToString { "'${it.group}:${it.name}:${it.version}'" }
val andApplyKapt = if (isKapt3Enabled) "" else " and apply the kapt plugin: \"apply plugin: 'kotlin-kapt'\""
project.logger.warn("${project.name}: " +
"'annotationProcessor' dependencies won't be recognized as kapt annotation processors. " +
"Please change the configuration name to 'kapt' for these artifacts: $artifactsRendered$andApplyKapt.")
}
}
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* 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 org.jetbrains.kotlin.gradle.internal
import com.android.build.gradle.BaseExtension
import com.android.build.gradle.api.AndroidSourceSet
import com.android.builder.model.SourceProvider
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.Dependency
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.TaskDependency
import org.gradle.api.tasks.compile.AbstractCompile
import org.gradle.api.tasks.compile.JavaCompile
import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.tasks.CompilerPluginOptions
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import java.io.File
import org.jetbrains.kotlin.gradle.internal.Kapt3GradleSubplugin.Companion.getKaptGeneratedSourcesDir
import org.jetbrains.kotlin.gradle.internal.Kapt3GradleSubplugin.Companion.getKaptGeneratedClassesDir
import org.jetbrains.kotlin.gradle.internal.Kapt3GradleSubplugin.Companion.getKaptGeneratedKotlinSourcesDir
import org.jetbrains.kotlin.gradle.tasks.shouldEnableGradleCache
import org.jetbrains.kotlin.gradle.tasks.useBuildCacheIfSupported
import java.io.ByteArrayOutputStream
import java.io.ObjectOutputStream
import java.util.*
// apply plugin: 'kotlin-kapt'
class Kapt3GradleSubplugin : Plugin<Project> {
companion object {
fun isEnabled(project: Project) = project.plugins.findPlugin(Kapt3GradleSubplugin::class.java) != null
@JvmStatic
fun getKaptGeneratedClassesDir(project: Project, sourceSetName: String) =
File(project.project.buildDir, "tmp/kapt3/classes/$sourceSetName")
@JvmStatic
fun getKaptGeneratedSourcesDir(project: Project, sourceSetName: String) =
File(project.project.buildDir, "generated/source/kapt/$sourceSetName")
@JvmStatic
fun getKaptGeneratedKotlinSourcesDir(project: Project, sourceSetName: String) =
File(project.project.buildDir, "generated/source/kaptKotlin/$sourceSetName")
}
override fun apply(project: Project) {}
}
abstract class KaptVariantData<T>(val variantData: T) {
abstract val name: String
abstract val sourceProviders: Iterable<SourceProvider>
abstract fun addJavaSourceFoldersToModel(generatedFilesDir: File)
abstract val annotationProcessorOptions: Map<String, String>?
abstract fun registerGeneratedJavaSource(
project: Project,
kaptTask: KaptTask,
javaTask: AbstractCompile)
}
// Subplugin for the Kotlin Gradle plugin
class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
companion object {
private val VERBOSE_OPTION_NAME = "kapt.verbose"
val MAIN_KAPT_CONFIGURATION_NAME = "kapt"
val KAPT_GROUP_NAME = "org.jetbrains.kotlin"
val KAPT_ARTIFACT_NAME = "kotlin-annotation-processing-gradle"
fun getKaptConfigurationName(sourceSetName: String): String {
return if (sourceSetName != "main")
"$MAIN_KAPT_CONFIGURATION_NAME${sourceSetName.capitalize()}"
else
MAIN_KAPT_CONFIGURATION_NAME
}
fun Project.findKaptConfiguration(sourceSetName: String): Configuration? {
return project.configurations.findByName(getKaptConfigurationName(sourceSetName))
}
fun findMainKaptConfiguration(project: Project) = project.findKaptConfiguration(MAIN_KAPT_CONFIGURATION_NAME)
}
private val kotlinToKaptGenerateStubsTasksMap = mutableMapOf<KotlinCompile, KaptGenerateStubsTask>()
override fun isApplicable(project: Project, task: AbstractCompile) = task is KotlinCompile && Kapt3GradleSubplugin.isEnabled(project)
private fun Kapt3SubpluginContext.getKaptStubsDir() = createAndReturnTemporaryKaptDirectory("stubs")
private fun Kapt3SubpluginContext.getKaptIncrementalDataDir() = createAndReturnTemporaryKaptDirectory("incrementalData")
private fun Kapt3SubpluginContext.createAndReturnTemporaryKaptDirectory(name: String): File {
val dir = File(project.buildDir, "tmp/kapt3/$name/$sourceSetName")
dir.mkdirs()
return dir
}
private inner class Kapt3SubpluginContext(
val project: Project,
val kotlinCompile: KotlinCompile,
val javaCompile: AbstractCompile,
val kaptVariantData: KaptVariantData<*>?,
val sourceSetName: String,
val javaSourceSet: SourceSet?,
val kaptExtension: KaptExtension,
val kaptClasspath: MutableList<File>) {
val sourcesOutputDir = getKaptGeneratedSourcesDir(project, sourceSetName)
val kotlinSourcesOutputDir = getKaptGeneratedKotlinSourcesDir(project, sourceSetName)
val classesOutputDir = getKaptGeneratedClassesDir(project, sourceSetName)
val kaptClasspathArtifacts = project
.resolveSubpluginArtifacts(listOf(this@Kapt3KotlinGradleSubplugin))
.flatMap { it.value }
}
override fun apply(
project: Project,
kotlinCompile: KotlinCompile,
javaCompile: AbstractCompile,
variantData: Any?,
androidProjectHandler: Any?,
javaSourceSet: SourceSet?
): List<SubpluginOption> {
assert((variantData != null) xor (javaSourceSet != null))
val kaptClasspath = arrayListOf<File>()
val buildDependencies = arrayListOf<TaskDependency>()
val kaptConfigurations = arrayListOf<Configuration>()
fun handleSourceSet(sourceSetName: String) {
val kaptConfiguration = project.findKaptConfiguration(sourceSetName)
val filteredDependencies = kaptConfiguration?.dependencies?.filter {
it.group != getGroupName() || it.name != getArtifactName()
} ?: emptyList()
if (kaptConfiguration != null) {
kaptConfigurations += kaptConfiguration
buildDependencies += kaptConfiguration.buildDependencies
if (filteredDependencies.isNotEmpty()) {
kaptClasspath.addAll(kaptConfiguration.resolve())
}
}
}
val kaptVariantData = variantData as? KaptVariantData<*>
val sourceSetName = if (kaptVariantData != null) {
for (provider in kaptVariantData.sourceProviders) {
handleSourceSet((provider as AndroidSourceSet).name)
}
kaptVariantData.name
}
else {
if (javaSourceSet == null) error("Java source set should not be null")
handleSourceSet(javaSourceSet.name)
javaSourceSet.name
}
val kaptExtension = project.extensions.getByType(KaptExtension::class.java)
val context = Kapt3SubpluginContext(project, kotlinCompile, javaCompile,
kaptVariantData, sourceSetName, javaSourceSet, kaptExtension, kaptClasspath)
val kaptGenerateStubsTask = context.createKaptGenerateStubsTask()
val kaptTask = context.createKaptKotlinTask()
kaptGenerateStubsTask.source(*kaptConfigurations.toTypedArray())
kaptGenerateStubsTask.dependsOn(*buildDependencies.toTypedArray())
kaptGenerateStubsTask.dependsOn(*kotlinCompile.dependsOn.toTypedArray())
kaptTask.dependsOn(kaptGenerateStubsTask)
kotlinCompile.dependsOn(kaptTask)
/** Plugin options are applied to kapt*Compile inside [createKaptKotlinTask] */
return emptyList()
}
override fun getSubpluginKotlinTasks(project: Project, kotlinCompile: KotlinCompile): List<AbstractCompile> {
val kaptGenerateStubsTask = kotlinToKaptGenerateStubsTasksMap[kotlinCompile]
return if (kaptGenerateStubsTask == null) emptyList() else listOf(kaptGenerateStubsTask)
}
// This method should be called no more than once for each Kapt3SubpluginContext
private fun Kapt3SubpluginContext.buildOptions(aptMode: String): List<SubpluginOption> {
val pluginOptions = mutableListOf<SubpluginOption>()
val generatedFilesDir = getKaptGeneratedSourcesDir(project, sourceSetName)
kaptVariantData?.addJavaSourceFoldersToModel(generatedFilesDir)
pluginOptions += SubpluginOption("aptMode", aptMode)
disableAnnotationProcessingInJavaTask()
kaptClasspath.forEach { pluginOptions += FilesSubpluginOption("apclasspath", FileOptionKind.INTERNAL, listOf(it)) }
javaCompile.source(generatedFilesDir)
pluginOptions += FilesSubpluginOption("sources", FileOptionKind.INTERNAL, listOf(generatedFilesDir))
pluginOptions += FilesSubpluginOption("classes", FileOptionKind.INTERNAL, listOf(getKaptGeneratedClassesDir(project, sourceSetName)))
pluginOptions += FilesSubpluginOption("incrementalData", FileOptionKind.INTERNAL, listOf(getKaptIncrementalDataDir()))
val annotationProcessors = kaptExtension.processors
if (annotationProcessors.isNotEmpty()) {
pluginOptions += SubpluginOption("processors", annotationProcessors)
}
val androidPlugin = kaptVariantData?.let {
project.extensions.findByName("android") as? BaseExtension
}
val androidOptions = kaptVariantData?.annotationProcessorOptions ?: emptyMap()
kotlinSourcesOutputDir.mkdirs()
val apOptions =
(kaptExtension.getAdditionalArguments(project, kaptVariantData?.variantData, androidPlugin) + androidOptions)
.map { SubpluginOption(it.key, it.value) } +
FilesSubpluginOption("kapt.kotlin.generated", FileOptionKind.INTERNAL, listOf(kotlinSourcesOutputDir))
pluginOptions += WrapperSubpluginOption("apoptions", encodeList(apOptions.associate { it.key to it.value }), apOptions)
pluginOptions += SubpluginOption("javacArguments", encodeList(kaptExtension.getJavacOptions()))
addMiscOptions(pluginOptions)
return pluginOptions
}
private fun Kapt3SubpluginContext.buildAndAddOptionsTo(task: Task, container: CompilerPluginOptions, aptMode: String) {
val compilerPluginId = getCompilerPluginId()
for (option in wrapPluginOptions(buildOptions(aptMode), "configuration")) {
container.addPluginArgument(compilerPluginId, option)
task.registerSubpluginOptionAsInput(compilerPluginId, option)
}
// Also register all the subplugin options from the Kotlin task:
project.afterEvaluate {
kotlinCompile.pluginOptions.subpluginOptionsByPluginId.forEach { (pluginId, options) ->
options.forEach { option ->
task.registerSubpluginOptionAsInput("kotlinCompile.$pluginId", option)
}
}
}
}
private fun encodeList(options: Map<String, String>): String {
val os = ByteArrayOutputStream()
val oos = ObjectOutputStream(os)
oos.writeInt(options.size)
for ((key, value) in options.entries) {
oos.writeUTF(key)
oos.writeUTF(value)
}
oos.flush()
return Base64.getEncoder().encodeToString(os.toByteArray())
}
private fun Kapt3SubpluginContext.addMiscOptions(pluginOptions: MutableList<SubpluginOption>) {
if (kaptExtension.generateStubs) {
project.logger.warn("'kapt.generateStubs' is not used by the 'kotlin-kapt' plugin")
}
pluginOptions += SubpluginOption("useLightAnalysis", "${kaptExtension.useLightAnalysis}")
pluginOptions += SubpluginOption("correctErrorTypes", "${kaptExtension.correctErrorTypes}")
pluginOptions += FilesSubpluginOption("stubs", FileOptionKind.INTERNAL, listOf(getKaptStubsDir()))
if (project.hasProperty(VERBOSE_OPTION_NAME) && project.property(VERBOSE_OPTION_NAME) == "true") {
pluginOptions += SubpluginOption("verbose", "true")
}
}
private fun Kapt3SubpluginContext.createKaptKotlinTask(): KaptTask {
val kaptTask = project.tasks.create(
getKaptTaskName("kapt"),
KaptTask::class.java)
kaptTask.useBuildCacheIfSupported()
if (shouldEnableGradleCache()) {
val reason = "Caching is disabled by default for kapt because of arbitrary behavior of external " +
"annotation processors. You can enable it by adding 'kapt.useBuildCache = true' to the build script."
kaptTask.outputs.doNotCacheIf(reason) { !kaptTask.useBuildCache }
}
kaptTask.useBuildCache = kaptExtension.useBuildCache
kaptTask.kotlinCompileTask = kotlinCompile
kaptClasspathArtifacts.forEach { kaptTask.pluginOptions.addClasspathEntry(it) }
kaptTask.stubsDir = getKaptStubsDir()
kaptTask.destinationDir = sourcesOutputDir
kaptTask.kotlinSourcesDestinationDir = kotlinSourcesOutputDir
kaptTask.classesDir = classesOutputDir
javaSourceSet?.output?.apply {
if (tryAddClassesDir { project.files(classesOutputDir).builtBy(kaptTask) }) {
kotlinCompile.attachClassesDir { classesOutputDir }
}
}
kotlinCompile.source(sourcesOutputDir, kotlinSourcesOutputDir)
if (kaptVariantData != null) {
kaptVariantData.registerGeneratedJavaSource(project, kaptTask, javaCompile)
}
else {
registerGeneratedJavaSource(kaptTask, javaCompile)
}
kaptTask.kaptClasspath = kaptClasspath
buildAndAddOptionsTo(kaptTask, kaptTask.pluginOptions, aptMode = "apt")
return kaptTask
}
private fun Kapt3SubpluginContext.createKaptGenerateStubsTask(): KaptGenerateStubsTask {
val kaptTask = project.tasks.create(
getKaptTaskName("kaptGenerateStubs"),
KaptGenerateStubsTask::class.java)
kaptTask.useBuildCacheIfSupported()
kaptTask.sourceSetName = sourceSetName
kaptTask.kotlinCompileTask = kotlinCompile
kotlinToKaptGenerateStubsTasksMap[kotlinCompile] = kaptTask
kaptClasspathArtifacts.forEach { kaptTask.pluginOptions.addClasspathEntry(it) }
kaptTask.stubsDir = getKaptStubsDir()
kaptTask.destinationDir = getKaptIncrementalDataDir()
kaptTask.mapClasspath { kotlinCompile.classpath }
kaptTask.generatedSourcesDir = sourcesOutputDir
mapKotlinTaskProperties(project, kaptTask)
kaptTask.kaptClasspath = kaptClasspath
buildAndAddOptionsTo(kaptTask, kaptTask.pluginOptions, aptMode = "stubs")
return kaptTask
}
private fun Kapt3SubpluginContext.getKaptTaskName(prefix: String): String {
// Replace compile*Kotlin to kapt*Kotlin
val baseName = kotlinCompile.name
assert(baseName.startsWith("compile"))
return baseName.replaceFirst("compile", prefix)
}
private fun Kapt3SubpluginContext.disableAnnotationProcessingInJavaTask() {
(javaCompile as? JavaCompile)?.let { javaCompile ->
val options = javaCompile.options
// 'android-apt' (com.neenbedankt) adds a File instance to compilerArgs (List<String>).
// Although it's not our problem, we need to handle this case properly.
val oldCompilerArgs: List<Any> = options.compilerArgs
val newCompilerArgs = oldCompilerArgs.filterTo(mutableListOf()) {
it !is CharSequence || !it.toString().startsWith("-proc:")
}
newCompilerArgs.add("-proc:none")
@Suppress("UNCHECKED_CAST")
options.compilerArgs = newCompilerArgs as List<String>
}
}
override fun getCompilerPluginId() = "org.jetbrains.kotlin.kapt3"
override fun getGroupName() = KAPT_GROUP_NAME
override fun getArtifactName() = KAPT_ARTIFACT_NAME
}
internal fun registerGeneratedJavaSource(kaptTask: KaptTask, javaTask: AbstractCompile) {
javaTask.source(kaptTask.destinationDir)
}
internal fun Configuration.getNamedDependencies(): List<Dependency> = allDependencies.filter { it.group != null && it.name != null }
internal fun checkAndroidAnnotationProcessorDependencyUsage(project: Project) {
val isKapt3Enabled = Kapt3GradleSubplugin.isEnabled(project)
val androidAPDependencies = project.configurations
.filter { it.name == "annotationProcessor"
|| (it.name.endsWith("AnnotationProcessor") && !it.name.startsWith("_")) }
.flatMap { it.getNamedDependencies() }
if (androidAPDependencies.isNotEmpty()) {
val artifactsRendered = androidAPDependencies.joinToString { "'${it.group}:${it.name}:${it.version}'" }
val andApplyKapt = if (isKapt3Enabled) "" else " and apply the kapt plugin: \"apply plugin: 'kotlin-kapt'\""
project.logger.warn("${project.name}: " +
"'annotationProcessor' dependencies won't be recognized as kapt annotation processors. " +
"Please change the configuration name to 'kapt' for these artifacts: $artifactsRendered$andApplyKapt.")
}
}
@@ -17,6 +17,7 @@
package org.jetbrains.kotlin.gradle.internal
import org.jetbrains.kotlin.gradle.plugin.SubpluginOption
import org.jetbrains.kotlin.gradle.plugin.WrapperSubpluginOption
import java.io.ByteArrayOutputStream
import java.io.ObjectOutputStream
import java.util.*
@@ -42,6 +43,6 @@ fun encodePluginOptions(options: Map<String, List<String>>): String {
fun wrapPluginOptions(options: List<SubpluginOption>, newOptionName: String): List<SubpluginOption> {
val groupedOptions = options.groupBy { it.key }.mapValues { opt -> opt.value.map { it.value } }
val encodedOptions = encodePluginOptions(groupedOptions)
val singleOption = SubpluginOption(newOptionName, encodedOptions)
val singleOption = WrapperSubpluginOption(newOptionName, encodedOptions, options)
return listOf(singleOption)
}
@@ -20,9 +20,7 @@ import org.gradle.api.plugins.InvalidPluginException
import org.gradle.api.plugins.JavaBasePlugin
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.plugins.JavaPluginConvention
import org.gradle.api.tasks.Delete
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.SourceSetOutput
import org.gradle.api.tasks.*
import org.gradle.api.tasks.compile.AbstractCompile
import org.gradle.api.tasks.compile.JavaCompile
import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments
@@ -822,7 +820,9 @@ internal class SubpluginEnvironment(
subpluginClasspath.forEach { pluginOptions.addClasspathEntry(it) }
for (option in subplugin.apply(project, kotlinTask, javaTask, variantData, androidProjectHandler, javaSourceSet)) {
pluginOptions.addPluginArgument(subplugin.getCompilerPluginId(), option.key, option.value)
val pluginId = subplugin.getCompilerPluginId()
pluginOptions.addPluginArgument(pluginId, option)
kotlinTask.registerSubpluginOptionAsInput(pluginId, option)
}
}
@@ -830,6 +830,43 @@ internal class SubpluginEnvironment(
}
}
private val fileOptionsPathSensitivity = PathSensitivity.ABSOLUTE
internal fun Task.registerSubpluginOptionAsInput(subpluginId: String, option: SubpluginOption) {
when (option) {
is WrapperSubpluginOption -> {
val subpluginIdWithWrapperKey = "$subpluginId.${option.key}"
option.originalOptions.forEach { registerSubpluginOptionAsInput(subpluginIdWithWrapperKey, it) }
}
is FilesSubpluginOption -> {
when (option.kind) {
FileOptionKind.INTERNAL -> Unit
FileOptionKind.OUTPUT_FILES -> outputsCompatible.filesCompatible(*option.files.toTypedArray())
FileOptionKind.OUTPUT_DIRS -> option.files.forEach { outputsCompatible.dirCompatible(it) }
FileOptionKind.INPUT_FILES, FileOptionKind.CLASSPATH_INPUT -> {
if (!shouldEnableGradleCache()) {
// Normalization makes no sense when we don't mean to use the cache,
// and moreover, Gradle lacks some of the APIs in the earlier versions.
inputsCompatible.filesCompatible(option.files)
}
else {
if (option.kind == FileOptionKind.CLASSPATH_INPUT)
inputs.files(option.files).withNormalizer(ClasspathNormalizer::class.java)
else
inputs.files(option.files).withPathSensitivity(fileOptionsPathSensitivity)
}
}
}
}
else -> {
// Since there might be multiple subplugin options with the same key,
// use the properties count to resolve duplication:
val propertyInputsCount = inputsCompatible.properties.size
inputsCompatible.propertyCompatible("$subpluginId." + option.key + ".$propertyInputsCount", option.value)
}
}
}
private fun Project.getAptDirsForSourceSet(sourceSetName: String): Pair<File, File> {
val aptOutputDir = File(buildDir, "generated/source/kapt")
val aptOutputDirForVariant = File(aptOutputDir, sourceSetName)
@@ -16,12 +16,16 @@
package org.jetbrains.kotlin.gradle.tasks
import org.jetbrains.kotlin.gradle.plugin.SubpluginOption
import java.io.File
internal class CompilerPluginOptions {
private val mutableClasspath = arrayListOf<String>()
private val mutableArguments = arrayListOf<String>()
internal val subpluginOptionsByPluginId =
mutableMapOf<String, MutableList<SubpluginOption>>()
val classpath: List<String>
get() = mutableClasspath
@@ -38,7 +42,8 @@ internal class CompilerPluginOptions {
mutableClasspath.remove(file.canonicalPath)
}
fun addPluginArgument(pluginId: String, key: String, value: String) {
mutableArguments.add("plugin:$pluginId:$key=$value")
fun addPluginArgument(pluginId: String, option: SubpluginOption) {
mutableArguments.add("plugin:$pluginId:${option.key}=${option.value}")
subpluginOptionsByPluginId.getOrPut(pluginId) { mutableListOf() }.add(option)
}
}
@@ -38,8 +38,7 @@ import org.jetbrains.kotlin.compilerRunner.*
import org.jetbrains.kotlin.gradle.dsl.*
import org.jetbrains.kotlin.gradle.internal.CompilerArgumentAware
import org.jetbrains.kotlin.gradle.internal.prepareCompilerArguments
import org.jetbrains.kotlin.gradle.plugin.kotlinDebug
import org.jetbrains.kotlin.gradle.plugin.kotlinInfo
import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.utils.ParsedGradleVersion
import org.jetbrains.kotlin.incremental.*
import org.jetbrains.kotlin.incremental.multiproject.ArtifactDifferenceRegistry
@@ -413,15 +412,15 @@ open class KotlinCompile : AbstractKotlinCompile<K2JVMCompilerArguments>(), Kotl
kaptAnnotationsFileUpdater = AnnotationFileUpdaterImpl(kaptAnnotationsFile)
}
addPluginArgument(ANNOTATIONS_PLUGIN_NAME, "output", kaptAnnotationsFile.canonicalPath)
addPluginArgument(ANNOTATIONS_PLUGIN_NAME, FilesSubpluginOption("output", FileOptionKind.INTERNAL, listOf(kaptAnnotationsFile)))
}
if (kaptOptions.generateStubs) {
addPluginArgument(ANNOTATIONS_PLUGIN_NAME, "stubs", destinationDir.canonicalPath)
addPluginArgument(ANNOTATIONS_PLUGIN_NAME, FilesSubpluginOption("stubs", FileOptionKind.INTERNAL, listOf(destinationDir)))
}
if (kaptOptions.supportInheritedAnnotations) {
addPluginArgument(ANNOTATIONS_PLUGIN_NAME, "inherited", true.toString())
addPluginArgument(ANNOTATIONS_PLUGIN_NAME, SubpluginOption("inherited", true.toString()))
}
}