Kapt3: Support incremental compilation of Java stubs (KT-15151)

This commit is contained in:
Yan Zhulanow
2017-04-12 19:45:01 +03:00
parent 3bf534b392
commit c18bf5088d
6 changed files with 146 additions and 32 deletions
@@ -28,6 +28,7 @@ import org.gradle.api.tasks.compile.AbstractCompile
import org.gradle.api.tasks.compile.JavaCompile import org.gradle.api.tasks.compile.JavaCompile
import org.jetbrains.kotlin.gradle.plugin.* import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.plugin.android.AndroidGradleWrapper import org.jetbrains.kotlin.gradle.plugin.android.AndroidGradleWrapper
import org.jetbrains.kotlin.gradle.tasks.CompilerPluginOptions
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.File import java.io.File
@@ -68,6 +69,7 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
} }
private val kotlinToKaptTasksMap = mutableMapOf<KotlinCompile, KaptTask>() private val kotlinToKaptTasksMap = mutableMapOf<KotlinCompile, KaptTask>()
private val kotlinToKaptGenerateStubsTasksMap = mutableMapOf<KotlinCompile, KaptGenerateStubsTask>()
override fun isApplicable(project: Project, task: KotlinCompile) = Kapt3GradleSubplugin.isEnabled(project) override fun isApplicable(project: Project, task: KotlinCompile) = Kapt3GradleSubplugin.isEnabled(project)
@@ -79,8 +81,12 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
return File(project.project.buildDir, "generated/source/kaptKotlin/$sourceSetName") return File(project.project.buildDir, "generated/source/kaptKotlin/$sourceSetName")
} }
fun getKaptStubsDir(project: Project, sourceSetName: String): File { private fun Kapt3SubpluginContext.getKaptStubsDir() = createAndReturnTemporaryKaptDirectory("stubs")
val dir = File(project.project.buildDir, "tmp/kapt3/stubs/$sourceSetName")
private fun Kapt3SubpluginContext.getKaptIncrementalDataDir() = createAndReturnTemporaryKaptDirectory("incrementalData")
private fun Kapt3SubpluginContext.createAndReturnTemporaryKaptDirectory(name: String): File {
val dir = File(project.buildDir, "tmp/kapt3/$name/$sourceSetName")
dir.mkdirs() dir.mkdirs()
return dir return dir
} }
@@ -96,6 +102,10 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
val sourcesOutputDir = getKaptGeneratedDir(project, sourceSetName) val sourcesOutputDir = getKaptGeneratedDir(project, sourceSetName)
val kotlinSourcesOutputDir = getKaptGeneratedDirForKotlin(project, sourceSetName) val kotlinSourcesOutputDir = getKaptGeneratedDirForKotlin(project, sourceSetName)
val classesOutputDir = getKaptClasssesDir(project, sourceSetName) val classesOutputDir = getKaptClasssesDir(project, sourceSetName)
val kaptClasspathArtifacts = project
.resolveSubpluginArtifacts(listOf(this@Kapt3KotlinGradleSubplugin))
.flatMap { it.value }
} }
override fun apply( override fun apply(
@@ -140,18 +150,21 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
val context = Kapt3SubpluginContext(project, kotlinCompile, javaCompile, val context = Kapt3SubpluginContext(project, kotlinCompile, javaCompile,
variantData, sourceSetName, kaptExtension, kaptClasspath) variantData, sourceSetName, kaptExtension, kaptClasspath)
context.createKaptKotlinTask() val kaptGenerateStubsTask = context.createKaptGenerateStubsTask()
context.createKaptKotlinTask(kaptGenerateStubsTask)
/** Plugin options are applied to kapt*Compile inside [createKaptKotlinTask] */ /** Plugin options are applied to kapt*Compile inside [createKaptKotlinTask] */
return emptyList() return emptyList()
} }
override fun getSubpluginKotlinTasks(project: Project, kotlinCompile: KotlinCompile): List<KaptTask> { override fun getSubpluginKotlinTasks(project: Project, kotlinCompile: KotlinCompile): List<AbstractCompile> {
return kotlinToKaptTasksMap[kotlinCompile]?.let { listOf(it) } ?: emptyList() 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 // This method should be called no more than once for each Kapt3SubpluginContext
private fun Kapt3SubpluginContext.buildOptions(): List<SubpluginOption> { private fun Kapt3SubpluginContext.buildOptions(aptMode: String): List<SubpluginOption> {
val pluginOptions = mutableListOf<SubpluginOption>() val pluginOptions = mutableListOf<SubpluginOption>()
val generatedFilesDir = getKaptGeneratedDir(project, sourceSetName) val generatedFilesDir = getKaptGeneratedDir(project, sourceSetName)
@@ -159,7 +172,7 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
(variantData as BaseVariantData<*>).addJavaSourceFoldersToModel(generatedFilesDir) (variantData as BaseVariantData<*>).addJavaSourceFoldersToModel(generatedFilesDir)
} }
pluginOptions += SubpluginOption("aptOnly", "true") pluginOptions += SubpluginOption("aptMode", aptMode)
disableAnnotationProcessingInJavaTask() disableAnnotationProcessingInJavaTask()
// Skip annotation processing in kotlinc if no kapt dependencies were provided // Skip annotation processing in kotlinc if no kapt dependencies were provided
@@ -172,6 +185,8 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
pluginOptions += SubpluginOption("sources", generatedFilesDir.canonicalPath) pluginOptions += SubpluginOption("sources", generatedFilesDir.canonicalPath)
pluginOptions += SubpluginOption("classes", getKaptClasssesDir(project, sourceSetName).canonicalPath) pluginOptions += SubpluginOption("classes", getKaptClasssesDir(project, sourceSetName).canonicalPath)
pluginOptions += SubpluginOption("incrementalData", getKaptIncrementalDataDir().canonicalPath)
val annotationProcessors = kaptExtension.processors val annotationProcessors = kaptExtension.processors
if (annotationProcessors.isNotEmpty()) { if (annotationProcessors.isNotEmpty()) {
pluginOptions += SubpluginOption("processors", annotationProcessors) pluginOptions += SubpluginOption("processors", annotationProcessors)
@@ -201,6 +216,13 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
return pluginOptions 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, String>): String { fun encodeOptions(options: Map<String, String>): String {
val os = ByteArrayOutputStream() val os = ByteArrayOutputStream()
val oos = ObjectOutputStream(os) val oos = ObjectOutputStream(os)
@@ -222,42 +244,59 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
pluginOptions += SubpluginOption("useLightAnalysis", "${kaptExtension.useLightAnalysis}") pluginOptions += SubpluginOption("useLightAnalysis", "${kaptExtension.useLightAnalysis}")
pluginOptions += SubpluginOption("correctErrorTypes", "${kaptExtension.correctErrorTypes}") 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") { if (project.hasProperty(VERBOSE_OPTION_NAME) && project.property(VERBOSE_OPTION_NAME) == "true") {
pluginOptions += SubpluginOption("verbose", "true") pluginOptions += SubpluginOption("verbose", "true")
} }
} }
private fun Kapt3SubpluginContext.createKaptKotlinTask() { private fun Kapt3SubpluginContext.createKaptKotlinTask(kaptGenerateStubsTask: KaptGenerateStubsTask) {
// Replace compile*Kotlin to kapt*Kotlin val kaptTask = project.tasks.create(getKaptTaskName("kapt"), KaptTask::class.java)
assert(kotlinCompile.name.startsWith("compile"))
val kaptTaskName = kotlinCompile.name.replaceFirst("compile", "kapt")
val kaptTask = project.tasks.create(kaptTaskName, KaptTask::class.java)
kaptTask.kotlinCompileTask = kotlinCompile kaptTask.kotlinCompileTask = kotlinCompile
kotlinToKaptTasksMap[kotlinCompile] = kaptTask kotlinToKaptTasksMap[kotlinCompile] = kaptTask
project.resolveSubpluginArtifacts(listOf(this@Kapt3KotlinGradleSubplugin)).flatMap { it.value }.forEach { kaptClasspathArtifacts.forEach { kaptTask.pluginOptions.addClasspathEntry(it) }
kaptTask.pluginOptions.addClasspathEntry(it)
}
kaptTask.stubsDir = getKaptStubsDir(project, sourceSetName) kaptTask.stubsDir = getKaptStubsDir()
kaptTask.mapClasspath { kotlinCompile.classpath }
kaptTask.destinationDir = sourcesOutputDir kaptTask.destinationDir = sourcesOutputDir
kaptTask.mapClasspath { kotlinCompile.classpath }
kaptTask.classesDir = classesOutputDir kaptTask.classesDir = classesOutputDir
kaptTask.dependsOn(*(javaCompile.dependsOn.filter { it !== kotlinCompile }.toTypedArray()))
kaptTask.dependsOn(kaptGenerateStubsTask)
kotlinCompile.dependsOn(kaptTask) kotlinCompile.dependsOn(kaptTask)
// Add generated source dir as a source root for kotlinCompile and javaCompile // Add generated source dir as a source root for kotlinCompile and javaCompile
kotlinCompile.source(sourcesOutputDir, kotlinSourcesOutputDir) kotlinCompile.source(sourcesOutputDir, kotlinSourcesOutputDir)
javaCompile.source(sourcesOutputDir) javaCompile.source(sourcesOutputDir)
val pluginOptions = kaptTask.pluginOptions buildAndAddOptionsTo(kaptTask.pluginOptions, aptMode = "apt")
val compilerPluginId = getCompilerPluginId() }
for (option in buildOptions()) {
pluginOptions.addPluginArgument(compilerPluginId, option.key, option.value) 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() { private fun Kapt3SubpluginContext.disableAnnotationProcessingInJavaTask() {
@@ -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))
}
}
@@ -43,14 +43,11 @@ open class KaptTask : AbstractCompile() {
@TaskAction @TaskAction
override fun compile() { override fun compile() {
/** Delete everything inside the [destinationDir] */ /** Delete everything inside the [destinationDir] (sources output dir) */
destinationDir.clearDirectory() destinationDir.clearDirectory()
classesDir.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 sourceRoots = SourceRoots.ForJvm.create(getSource(), rawSourceRoots)
val args = K2JVMCompilerArguments() val args = K2JVMCompilerArguments()
@@ -46,7 +46,7 @@ internal fun Task.finalizedByIfNotFailed(finalizer: Task) {
this.finalizedBy(finalizer) this.finalizedBy(finalizer)
} }
internal fun AbstractCompile.mapClasspath(fn: ()->FileCollection) { internal fun AbstractCompile.mapClasspath(fn: () -> FileCollection) {
conventionMapping.map("classpath", fn) conventionMapping.map("classpath", fn)
} }
@@ -79,6 +79,8 @@ internal class FilteringSourceRootsContainer(val filter: (File) -> Boolean = { t
when (source) { when (source) {
is SourceDirectorySet -> filteredDirs += source.srcDirs.filter { filter(it) } is SourceDirectorySet -> filteredDirs += source.srcDirs.filter { filter(it) }
is File -> if (filter(source)) filteredDirs.add(source) is File -> if (filter(source)) filteredDirs.add(source)
is Collection<*> -> source.forEach { filteredDirs += add(it) }
is Array<*> -> source.forEach { filteredDirs += add(it) }
} }
} }
@@ -122,7 +122,7 @@ abstract class AbstractKotlinCompile<T : CommonCompilerArguments>() : AbstractCo
} }
@TaskAction @TaskAction
fun execute(inputs: IncrementalTaskInputs): Unit { open fun execute(inputs: IncrementalTaskInputs): Unit {
val sourceRoots = getSourceRoots() val sourceRoots = getSourceRoots()
val allKotlinSources = sourceRoots.kotlinSourceFiles val allKotlinSources = sourceRoots.kotlinSourceFiles
@@ -162,7 +162,7 @@ open class KotlinCompile : AbstractKotlinCompile<K2JVMCompilerArguments>(), Kotl
private val kotlinOptionsImpl = KotlinJvmOptionsImpl() private val kotlinOptionsImpl = KotlinJvmOptionsImpl()
override val kotlinOptions: KotlinJvmOptions override val kotlinOptions: KotlinJvmOptions
get() = kotlinOptionsImpl get() = kotlinOptionsImpl
internal val sourceRootsContainer = FilteringSourceRootsContainer() internal open val sourceRootsContainer = FilteringSourceRootsContainer()
internal val taskBuildDirectory: File internal val taskBuildDirectory: File
get() = File(File(project.buildDir, KOTLIN_BUILD_DIR_NAME), name).apply { mkdirs() } get() = File(File(project.buildDir, KOTLIN_BUILD_DIR_NAME), name).apply { mkdirs() }