From c18bf5088d77b3df2298f38404b6a68201c5e61d Mon Sep 17 00:00:00 2001 From: Yan Zhulanow Date: Wed, 12 Apr 2017 19:45:01 +0300 Subject: [PATCH] Kapt3: Support incremental compilation of Java stubs (KT-15151) --- .../internal/Kapt3KotlinGradleSubplugin.kt | 89 +++++++++++++------ .../gradle/internal/KaptGenerateStubsTask.kt | 76 ++++++++++++++++ .../kotlin/gradle/internal/KaptTask.kt | 5 +- .../kotlin/gradle/plugin/gradleUtils.kt | 2 +- .../kotlin/gradle/tasks/SourceRoots.kt | 2 + .../jetbrains/kotlin/gradle/tasks/Tasks.kt | 4 +- 6 files changed, 146 insertions(+), 32 deletions(-) create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/KaptGenerateStubsTask.kt 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 0da66bbed45..8481d3a7571 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 @@ -28,6 +28,7 @@ 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.plugin.android.AndroidGradleWrapper +import org.jetbrains.kotlin.gradle.tasks.CompilerPluginOptions import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import java.io.ByteArrayOutputStream import java.io.File @@ -68,6 +69,7 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin { } private val kotlinToKaptTasksMap = mutableMapOf() + private val kotlinToKaptGenerateStubsTasksMap = mutableMapOf() override fun isApplicable(project: Project, task: KotlinCompile) = Kapt3GradleSubplugin.isEnabled(project) @@ -79,8 +81,12 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin { return File(project.project.buildDir, "generated/source/kaptKotlin/$sourceSetName") } - fun getKaptStubsDir(project: Project, sourceSetName: String): File { - val dir = File(project.project.buildDir, "tmp/kapt3/stubs/$sourceSetName") + 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 } @@ -96,6 +102,10 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin { val sourcesOutputDir = getKaptGeneratedDir(project, sourceSetName) val kotlinSourcesOutputDir = getKaptGeneratedDirForKotlin(project, sourceSetName) val classesOutputDir = getKaptClasssesDir(project, sourceSetName) + + val kaptClasspathArtifacts = project + .resolveSubpluginArtifacts(listOf(this@Kapt3KotlinGradleSubplugin)) + .flatMap { it.value } } override fun apply( @@ -140,18 +150,21 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin { val context = Kapt3SubpluginContext(project, kotlinCompile, javaCompile, variantData, sourceSetName, kaptExtension, kaptClasspath) - context.createKaptKotlinTask() + val kaptGenerateStubsTask = context.createKaptGenerateStubsTask() + context.createKaptKotlinTask(kaptGenerateStubsTask) /** Plugin options are applied to kapt*Compile inside [createKaptKotlinTask] */ return emptyList() } - override fun getSubpluginKotlinTasks(project: Project, kotlinCompile: KotlinCompile): List { - return kotlinToKaptTasksMap[kotlinCompile]?.let { listOf(it) } ?: emptyList() + override fun getSubpluginKotlinTasks(project: Project, kotlinCompile: KotlinCompile): List { + val kaptTask = kotlinToKaptTasksMap[kotlinCompile] + val kaptGenerateStubsTask = kotlinToKaptGenerateStubsTasksMap[kotlinCompile] + return listOf(kaptTask, kaptGenerateStubsTask).filterNotNull() } // This method should be called no more than once for each Kapt3SubpluginContext - private fun Kapt3SubpluginContext.buildOptions(): List { + private fun Kapt3SubpluginContext.buildOptions(aptMode: String): List { val pluginOptions = mutableListOf() val generatedFilesDir = getKaptGeneratedDir(project, sourceSetName) @@ -159,7 +172,7 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin { (variantData as BaseVariantData<*>).addJavaSourceFoldersToModel(generatedFilesDir) } - pluginOptions += SubpluginOption("aptOnly", "true") + pluginOptions += SubpluginOption("aptMode", aptMode) disableAnnotationProcessingInJavaTask() // Skip annotation processing in kotlinc if no kapt dependencies were provided @@ -172,6 +185,8 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin { pluginOptions += SubpluginOption("sources", generatedFilesDir.canonicalPath) pluginOptions += SubpluginOption("classes", getKaptClasssesDir(project, sourceSetName).canonicalPath) + pluginOptions += SubpluginOption("incrementalData", getKaptIncrementalDataDir().canonicalPath) + val annotationProcessors = kaptExtension.processors if (annotationProcessors.isNotEmpty()) { pluginOptions += SubpluginOption("processors", annotationProcessors) @@ -201,6 +216,13 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin { return pluginOptions } + private fun Kapt3SubpluginContext.buildAndAddOptionsTo(container: CompilerPluginOptions, aptMode: String) { + val compilerPluginId = getCompilerPluginId() + for (option in buildOptions(aptMode)) { + container.addPluginArgument(compilerPluginId, option.key, option.value) + } + } + fun encodeOptions(options: Map): String { val os = ByteArrayOutputStream() val oos = ObjectOutputStream(os) @@ -222,42 +244,59 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin { pluginOptions += SubpluginOption("useLightAnalysis", "${kaptExtension.useLightAnalysis}") pluginOptions += SubpluginOption("correctErrorTypes", "${kaptExtension.correctErrorTypes}") - pluginOptions += SubpluginOption("stubs", getKaptStubsDir(project, sourceSetName).canonicalPath) + 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() { - // Replace compile*Kotlin to kapt*Kotlin - assert(kotlinCompile.name.startsWith("compile")) - val kaptTaskName = kotlinCompile.name.replaceFirst("compile", "kapt") - val kaptTask = project.tasks.create(kaptTaskName, KaptTask::class.java) + private fun Kapt3SubpluginContext.createKaptKotlinTask(kaptGenerateStubsTask: KaptGenerateStubsTask) { + val kaptTask = project.tasks.create(getKaptTaskName("kapt"), KaptTask::class.java) kaptTask.kotlinCompileTask = kotlinCompile kotlinToKaptTasksMap[kotlinCompile] = kaptTask - project.resolveSubpluginArtifacts(listOf(this@Kapt3KotlinGradleSubplugin)).flatMap { it.value }.forEach { - kaptTask.pluginOptions.addClasspathEntry(it) - } + kaptClasspathArtifacts.forEach { kaptTask.pluginOptions.addClasspathEntry(it) } - kaptTask.stubsDir = getKaptStubsDir(project, sourceSetName) - - kaptTask.mapClasspath { kotlinCompile.classpath } + kaptTask.stubsDir = getKaptStubsDir() kaptTask.destinationDir = sourcesOutputDir + kaptTask.mapClasspath { kotlinCompile.classpath } kaptTask.classesDir = classesOutputDir - kaptTask.dependsOn(*(javaCompile.dependsOn.filter { it !== kotlinCompile }.toTypedArray())) + + kaptTask.dependsOn(kaptGenerateStubsTask) kotlinCompile.dependsOn(kaptTask) // Add generated source dir as a source root for kotlinCompile and javaCompile kotlinCompile.source(sourcesOutputDir, kotlinSourcesOutputDir) javaCompile.source(sourcesOutputDir) - val pluginOptions = kaptTask.pluginOptions - val compilerPluginId = getCompilerPluginId() - for (option in buildOptions()) { - pluginOptions.addPluginArgument(compilerPluginId, option.key, option.value) - } + buildAndAddOptionsTo(kaptTask.pluginOptions, aptMode = "apt") + } + + private fun Kapt3SubpluginContext.createKaptGenerateStubsTask(): KaptGenerateStubsTask { + val kaptTask = project.tasks.create(getKaptTaskName("kaptGenerateStubs"), KaptGenerateStubsTask::class.java) + 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 + + kaptTask.dependsOn(*(javaCompile.dependsOn.filter { it !== kotlinCompile }.toTypedArray())) + + 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() { diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/KaptGenerateStubsTask.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/KaptGenerateStubsTask.kt new file mode 100644 index 00000000000..3fbb1d5790c --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/KaptGenerateStubsTask.kt @@ -0,0 +1,76 @@ +/* + * Copyright 2010-2017 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 org.gradle.api.tasks.SourceTask +import org.gradle.api.tasks.incremental.IncrementalTaskInputs +import org.jetbrains.kotlin.com.intellij.openapi.util.io.FileUtil +import org.jetbrains.kotlin.gradle.plugin.kotlinDebug +import org.jetbrains.kotlin.gradle.tasks.FilteringSourceRootsContainer +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import org.jetbrains.kotlin.incremental.ChangedFiles +import org.jetbrains.kotlin.incremental.pathsAsStringRelativeTo +import java.io.File + +open class KaptGenerateStubsTask : KotlinCompile() { + override val sourceRootsContainer = FilteringSourceRootsContainer({ isSourceRootAllowed(it) }) + + internal lateinit var kotlinCompileTask: KotlinCompile + + lateinit var stubsDir: File + lateinit var generatedSourcesDir: File + + override fun source(vararg sources: Any?): SourceTask? { + return super.source(sourceRootsContainer.add(sources)) + } + override fun setSource(sources: Any?) { + super.setSource(sourceRootsContainer.set(sources)) + } + + private fun isSourceRootAllowed(source: File): Boolean { + fun File.isInside(parent: File) = FileUtil.isAncestor(parent, this, /* strict = */ false) + + return !source.isInside(destinationDir) && + !source.isInside(stubsDir) && + !source.isInside(generatedSourcesDir) + } + + override fun execute(inputs: IncrementalTaskInputs) { + val sourceRoots = kotlinCompileTask.getSourceRoots() + val allKotlinSources = sourceRoots.kotlinSourceFiles + + generatedSourcesDir.deleteRecursively() + + logger.kotlinDebug { "All kotlin sources: ${allKotlinSources.pathsAsStringRelativeTo(project.rootProject.projectDir)}" } + + if (allKotlinSources.isEmpty()) { + logger.kotlinDebug { "No Kotlin files found, skipping KaptGenerateStubs task" } + return + } + + sourceRoots.log(this.name, logger) + val args = createCompilerArgs() + + kotlinCompileTask.setupCompilerArgs(args) + args.pluginClasspaths = (pluginOptions.classpath + args.pluginClasspaths).toSet().toTypedArray() + args.pluginOptions = (pluginOptions.arguments + args.pluginOptions).toTypedArray() + args.verbose = project.hasProperty("kapt.verbose") && project.property("kapt.verbose").toString().toBoolean() == true + + compilerCalled = true + callCompiler(args, sourceRoots, ChangedFiles(inputs)) + } +} \ No newline at end of file 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 b93c51cec51..7b6bae2e59a 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 @@ -43,14 +43,11 @@ open class KaptTask : AbstractCompile() { @TaskAction override fun compile() { - /** Delete everything inside the [destinationDir] */ + /** Delete everything inside the [destinationDir] (sources output dir) */ destinationDir.clearDirectory() classesDir.clearDirectory() - // Kapt3 doesn't support incremental compilation so we should delete the existing stubs - stubsDir.clearDirectory() - val sourceRoots = SourceRoots.ForJvm.create(getSource(), rawSourceRoots) val args = K2JVMCompilerArguments() diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/gradleUtils.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/gradleUtils.kt index 9f375764096..181af3fdf9a 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/gradleUtils.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/gradleUtils.kt @@ -46,7 +46,7 @@ internal fun Task.finalizedByIfNotFailed(finalizer: Task) { this.finalizedBy(finalizer) } -internal fun AbstractCompile.mapClasspath(fn: ()->FileCollection) { +internal fun AbstractCompile.mapClasspath(fn: () -> FileCollection) { conventionMapping.map("classpath", fn) } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/SourceRoots.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/SourceRoots.kt index aceab1a1a47..aea6d4e1a3e 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/SourceRoots.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/SourceRoots.kt @@ -79,6 +79,8 @@ internal class FilteringSourceRootsContainer(val filter: (File) -> Boolean = { t when (source) { is SourceDirectorySet -> filteredDirs += source.srcDirs.filter { filter(it) } is File -> if (filter(source)) filteredDirs.add(source) + is Collection<*> -> source.forEach { filteredDirs += add(it) } + is Array<*> -> source.forEach { filteredDirs += add(it) } } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt index 0d3280d258f..9ffcfb1900a 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt @@ -122,7 +122,7 @@ abstract class AbstractKotlinCompile() : AbstractCo } @TaskAction - fun execute(inputs: IncrementalTaskInputs): Unit { + open fun execute(inputs: IncrementalTaskInputs): Unit { val sourceRoots = getSourceRoots() val allKotlinSources = sourceRoots.kotlinSourceFiles @@ -162,7 +162,7 @@ open class KotlinCompile : AbstractKotlinCompile(), Kotl private val kotlinOptionsImpl = KotlinJvmOptionsImpl() override val kotlinOptions: KotlinJvmOptions get() = kotlinOptionsImpl - internal val sourceRootsContainer = FilteringSourceRootsContainer() + internal open val sourceRootsContainer = FilteringSourceRootsContainer() internal val taskBuildDirectory: File get() = File(File(project.buildDir, KOTLIN_BUILD_DIR_NAME), name).apply { mkdirs() }