From 57f710c9315bd30917b271d122d968187b79e508 Mon Sep 17 00:00:00 2001 From: Sergey Igushkin Date: Thu, 30 Nov 2017 20:50:44 +0300 Subject: [PATCH] Explicit opt-in switch for Kapt caching. --- .../internal/Kapt3KotlinGradleSubplugin.kt | 797 +++++++++--------- .../kotlin/gradle/internal/KaptTask.kt | 237 +++--- .../kotlin/gradle/plugin/KaptExtension.kt | 8 + 3 files changed, 531 insertions(+), 511 deletions(-) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/Kapt3KotlinGradleSubplugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/Kapt3KotlinGradleSubplugin.kt index 4e183888745..c819fe2558d 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/Kapt3KotlinGradleSubplugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/Kapt3KotlinGradleSubplugin.kt @@ -1,394 +1,403 @@ -/* - * 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.useBuildCacheIfSupported -import java.io.ByteArrayOutputStream -import java.io.ObjectOutputStream -import java.util.* - -// apply plugin: 'kotlin-kapt' -class Kapt3GradleSubplugin : Plugin { - 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(val variantData: T) { - abstract val name: String - abstract val sourceProviders: Iterable - abstract fun addJavaSourceFoldersToModel(generatedFilesDir: File) - abstract val annotationProcessorOptions: Map? - abstract fun registerGeneratedJavaSource( - project: Project, - kaptTask: KaptTask, - javaTask: AbstractCompile) -} - -// Subplugin for the Kotlin Gradle plugin -class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin { - 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() - - 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) { - 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 { - assert((variantData != null) xor (javaSourceSet != null)) - - val kaptClasspath = arrayListOf() - val buildDependencies = arrayListOf() - val kaptConfigurations = arrayListOf() - - 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 { - 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 { - val pluginOptions = mutableListOf() - - 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 { - 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) { - 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 - - 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). - // Although it's not our problem, we need to handle this case properly. - val oldCompilerArgs: List = 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 - } - } - - 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 = 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.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 { + 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(val variantData: T) { + abstract val name: String + abstract val sourceProviders: Iterable + abstract fun addJavaSourceFoldersToModel(generatedFilesDir: File) + abstract val annotationProcessorOptions: Map? + abstract fun registerGeneratedJavaSource( + project: Project, + kaptTask: KaptTask, + javaTask: AbstractCompile) +} + +// Subplugin for the Kotlin Gradle plugin +class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin { + 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() + + 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) { + 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 { + assert((variantData != null) xor (javaSourceSet != null)) + + val kaptClasspath = arrayListOf() + val buildDependencies = arrayListOf() + val kaptConfigurations = arrayListOf() + + 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 { + 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 { + val pluginOptions = mutableListOf() + + 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 { + 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) { + 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). + // Although it's not our problem, we need to handle this case properly. + val oldCompilerArgs: List = 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 + } + } + + 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 = 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.") + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/KaptTask.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/KaptTask.kt index 80fb6112438..4675a06a335 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/KaptTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/KaptTask.kt @@ -1,118 +1,121 @@ -package org.jetbrains.kotlin.gradle.internal - -import com.intellij.openapi.util.io.FileUtil -import com.intellij.openapi.util.text.StringUtil.compareVersionNumbers -import org.gradle.api.GradleException -import org.gradle.api.file.FileCollection -import org.gradle.api.internal.ConventionTask -import org.gradle.api.tasks.* -import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments -import org.jetbrains.kotlin.compilerRunner.GradleCompilerEnvironment -import org.jetbrains.kotlin.compilerRunner.GradleCompilerRunner -import org.jetbrains.kotlin.compilerRunner.OutputItemsCollectorImpl -import org.jetbrains.kotlin.gradle.tasks.* -import java.io.File - -@CacheableTask -open class KaptTask : ConventionTask(), CompilerArgumentAware { - - @get:Internal - internal val pluginOptions = CompilerPluginOptions() - - @get:Internal - internal lateinit var kotlinCompileTask: KotlinCompile - - @get:Internal - internal lateinit var stubsDir: File - - private fun isInsideDestinationDirs(file: File): Boolean { - return FileUtil.isAncestor(destinationDir, file, /* strict = */ false) - || FileUtil.isAncestor(classesDir, file, /* strict = */ false) - } - - @get:Classpath @get:InputFiles - lateinit var kaptClasspath: List - - @get:OutputDirectory - internal lateinit var classesDir: File - - @get:OutputDirectory - lateinit var destinationDir: File - - @get:OutputDirectory - lateinit var kotlinSourcesDestinationDir: File - - override fun createCompilerArgs(): K2JVMCompilerArguments = K2JVMCompilerArguments() - - override fun setupCompilerArgs(args: K2JVMCompilerArguments, defaultsOnly: Boolean) { - kotlinCompileTask.setupCompilerArgs(args) - - args.pluginClasspaths = (pluginClasspath + args.pluginClasspaths!!).toSet().toTypedArray() - args.pluginOptions = (pluginOptions.arguments + args.pluginOptions!!).toTypedArray() - args.verbose = project.hasProperty("kapt.verbose") && project.property("kapt.verbose").toString().toBoolean() == true - } - - @get:Classpath @get:InputFiles - val classpath: FileCollection - get() = kotlinCompileTask.classpath - - @get:Classpath @get:InputFiles @Suppress("unused") - internal val kotlinTaskPluginClasspaths get() = kotlinCompileTask.pluginClasspath - - @get:Classpath @get:InputFiles - internal val pluginClasspath get() = pluginOptions.classpath - - @get:InputFiles @get:PathSensitive(PathSensitivity.ABSOLUTE) - val source: FileCollection - get() { - val sourcesFromKotlinTask = kotlinCompileTask.source - .filter { it.extension == "java" && !isInsideDestinationDirs(it) } - - val stubSources = project.fileTree(stubsDir) - return sourcesFromKotlinTask + stubSources - } - - @TaskAction - fun compile() { - /** Delete everything inside generated sources and classes output directory - * (annotation processing is not incremental) */ - destinationDir.clearDirectory() - classesDir.clearDirectory() - kotlinSourcesDestinationDir.clearDirectory() - - val sourceRootsFromKotlin = kotlinCompileTask.sourceRootsContainer.sourceRoots - val rawSourceRoots = FilteringSourceRootsContainer(sourceRootsFromKotlin, { !isInsideDestinationDirs(it) }) - val sourceRoots = SourceRoots.ForJvm.create(kotlinCompileTask.source, rawSourceRoots) - - val args = prepareCompilerArguments() - - val messageCollector = GradleMessageCollector(logger) - val outputItemCollector = OutputItemsCollectorImpl() - val environment = GradleCompilerEnvironment(kotlinCompileTask.computedCompilerClasspath, messageCollector, outputItemCollector, args) - if (environment.toolsJar == null && !isAtLeastJava9) { - throw GradleException("Could not find tools.jar in system classpath, which is required for kapt to work") - } - - val compilerRunner = GradleCompilerRunner(project) - val exitCode = compilerRunner.runJvmCompiler( - sourceRoots.kotlinSourceFiles, - sourceRoots.javaSourceRoots, - kotlinCompileTask.javaPackagePrefix, - args, - environment) - throwGradleExceptionIfError(exitCode) - } - - private fun File.clearDirectory() { - deleteRecursively() - mkdirs() - } - - private val isAtLeastJava9: Boolean - get() = compareVersionNumbers(getJavaRuntimeVersion(), "9") >= 0 - - private fun getJavaRuntimeVersion(): String { - val rtVersion = System.getProperty("java.runtime.version") - return if (Character.isDigit(rtVersion[0])) rtVersion else System.getProperty("java.version") - } +package org.jetbrains.kotlin.gradle.internal + +import com.intellij.openapi.util.io.FileUtil +import com.intellij.openapi.util.text.StringUtil.compareVersionNumbers +import org.gradle.api.GradleException +import org.gradle.api.file.FileCollection +import org.gradle.api.internal.ConventionTask +import org.gradle.api.tasks.* +import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments +import org.jetbrains.kotlin.compilerRunner.GradleCompilerEnvironment +import org.jetbrains.kotlin.compilerRunner.GradleCompilerRunner +import org.jetbrains.kotlin.compilerRunner.OutputItemsCollectorImpl +import org.jetbrains.kotlin.gradle.tasks.* +import java.io.File + +@CacheableTask +open class KaptTask : ConventionTask(), CompilerArgumentAware { + + @get:Internal + internal val pluginOptions = CompilerPluginOptions() + + @get:Internal + internal lateinit var kotlinCompileTask: KotlinCompile + + @get:Internal + internal lateinit var stubsDir: File + + private fun isInsideDestinationDirs(file: File): Boolean { + return FileUtil.isAncestor(destinationDir, file, /* strict = */ false) + || FileUtil.isAncestor(classesDir, file, /* strict = */ false) + } + + @get:Classpath @get:InputFiles + lateinit var kaptClasspath: List + + @get:OutputDirectory + internal lateinit var classesDir: File + + @get:OutputDirectory + lateinit var destinationDir: File + + @get:OutputDirectory + lateinit var kotlinSourcesDestinationDir: File + + override fun createCompilerArgs(): K2JVMCompilerArguments = K2JVMCompilerArguments() + + override fun setupCompilerArgs(args: K2JVMCompilerArguments, defaultsOnly: Boolean) { + kotlinCompileTask.setupCompilerArgs(args) + + args.pluginClasspaths = (pluginClasspath + args.pluginClasspaths!!).toSet().toTypedArray() + args.pluginOptions = (pluginOptions.arguments + args.pluginOptions!!).toTypedArray() + args.verbose = project.hasProperty("kapt.verbose") && project.property("kapt.verbose").toString().toBoolean() == true + } + + @get:Classpath @get:InputFiles + val classpath: FileCollection + get() = kotlinCompileTask.classpath + + @get:Internal + var useBuildCache: Boolean = false + + @get:Classpath @get:InputFiles @Suppress("unused") + internal val kotlinTaskPluginClasspaths get() = kotlinCompileTask.pluginClasspath + + @get:Classpath @get:InputFiles + internal val pluginClasspath get() = pluginOptions.classpath + + @get:InputFiles @get:PathSensitive(PathSensitivity.ABSOLUTE) + val source: FileCollection + get() { + val sourcesFromKotlinTask = kotlinCompileTask.source + .filter { it.extension == "java" && !isInsideDestinationDirs(it) } + + val stubSources = project.fileTree(stubsDir) + return sourcesFromKotlinTask + stubSources + } + + @TaskAction + fun compile() { + /** Delete everything inside generated sources and classes output directory + * (annotation processing is not incremental) */ + destinationDir.clearDirectory() + classesDir.clearDirectory() + kotlinSourcesDestinationDir.clearDirectory() + + val sourceRootsFromKotlin = kotlinCompileTask.sourceRootsContainer.sourceRoots + val rawSourceRoots = FilteringSourceRootsContainer(sourceRootsFromKotlin, { !isInsideDestinationDirs(it) }) + val sourceRoots = SourceRoots.ForJvm.create(kotlinCompileTask.source, rawSourceRoots) + + val args = prepareCompilerArguments() + + val messageCollector = GradleMessageCollector(logger) + val outputItemCollector = OutputItemsCollectorImpl() + val environment = GradleCompilerEnvironment(kotlinCompileTask.computedCompilerClasspath, messageCollector, outputItemCollector, args) + if (environment.toolsJar == null && !isAtLeastJava9) { + throw GradleException("Could not find tools.jar in system classpath, which is required for kapt to work") + } + + val compilerRunner = GradleCompilerRunner(project) + val exitCode = compilerRunner.runJvmCompiler( + sourceRoots.kotlinSourceFiles, + sourceRoots.javaSourceRoots, + kotlinCompileTask.javaPackagePrefix, + args, + environment) + throwGradleExceptionIfError(exitCode) + } + + private fun File.clearDirectory() { + deleteRecursively() + mkdirs() + } + + private val isAtLeastJava9: Boolean + get() = compareVersionNumbers(getJavaRuntimeVersion(), "9") >= 0 + + private fun getJavaRuntimeVersion(): String { + val rtVersion = System.getProperty("java.runtime.version") + return if (Character.isDigit(rtVersion[0])) rtVersion else System.getProperty("java.version") + } } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KaptExtension.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KaptExtension.kt index 9907afcd02d..3e52bde5a30 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KaptExtension.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KaptExtension.kt @@ -32,12 +32,20 @@ open class KaptExtension { open var processors: String = "" + /** Explicit opt-in switch for Kapt caching. Should be used when annotation processors used by this project are + * certain NOT to use anything aside from the task inputs in their logic and are guaranteed to produce the same + * output on subsequent runs without input changes. */ + var useBuildCache: Boolean = false + private val apOptionsActions = mutableListOf<(KaptAnnotationProcessorOptions) -> Unit>() private val javacOptionsActions = mutableListOf<(KaptJavacOptionsDelegate) -> Unit>() + private var apOptionsClosure: Closure<*>? = null + private var javacOptionsClosure: Closure<*>? = null + open fun arguments(closure: Closure<*>) { apOptionsActions += { apOptions -> apOptions.execute(closure)