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.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<KotlinCompile> {
}
private val kotlinToKaptTasksMap = mutableMapOf<KotlinCompile, KaptTask>()
private val kotlinToKaptGenerateStubsTasksMap = mutableMapOf<KotlinCompile, KaptGenerateStubsTask>()
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")
}
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<KotlinCompile> {
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<KotlinCompile> {
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<KaptTask> {
return kotlinToKaptTasksMap[kotlinCompile]?.let { listOf(it) } ?: emptyList()
override fun getSubpluginKotlinTasks(project: Project, kotlinCompile: KotlinCompile): List<AbstractCompile> {
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<SubpluginOption> {
private fun Kapt3SubpluginContext.buildOptions(aptMode: String): List<SubpluginOption> {
val pluginOptions = mutableListOf<SubpluginOption>()
val generatedFilesDir = getKaptGeneratedDir(project, sourceSetName)
@@ -159,7 +172,7 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
(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<KotlinCompile> {
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<KotlinCompile> {
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 {
val os = ByteArrayOutputStream()
val oos = ObjectOutputStream(os)
@@ -222,42 +244,59 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
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() {
@@ -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
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()
@@ -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)
}
@@ -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) }
}
}
@@ -122,7 +122,7 @@ abstract class AbstractKotlinCompile<T : CommonCompilerArguments>() : 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<K2JVMCompilerArguments>(), 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() }