Kapt3: Extract annotation processing to its own task in Gradle. Now the kotlinCompile task should know nothing about kapt, for the main task it's just a regular Java source root.

This commit is contained in:
Yan Zhulanow
2016-11-03 18:15:38 +03:00
committed by Yan Zhulanow
parent 92282072fc
commit 95d1210317
11 changed files with 335 additions and 180 deletions
@@ -39,7 +39,7 @@ class ExampleSubplugin : KotlinGradleSubplugin<AbstractCompile> {
return listOf(SubpluginOption("exampleKey", "exampleValue")) return listOf(SubpluginOption("exampleKey", "exampleValue"))
} }
override fun getPluginName(): String { override fun getCompilerPluginId(): String {
return "example.plugin" return "example.plugin"
} }
@@ -32,8 +32,13 @@ interface KotlinGradleSubplugin<KotlinCompile : AbstractCompile> {
variantData: Any?, variantData: Any?,
javaSourceSet: SourceSet? javaSourceSet: SourceSet?
): List<SubpluginOption> ): List<SubpluginOption>
fun getPluginName(): String fun getSubpluginKotlinTasks(
project: Project,
kotlinCompile: KotlinCompile
): List<AbstractCompile> = emptyList()
fun getCompilerPluginId(): String
fun getGroupName(): String fun getGroupName(): String
fun getArtifactName(): String fun getArtifactName(): String
} }
@@ -105,7 +105,7 @@ class AndroidSubplugin : KotlinGradleSubplugin<KotlinCompile> {
} }
} }
override fun getPluginName() = "org.jetbrains.kotlin.android" override fun getCompilerPluginId() = "org.jetbrains.kotlin.android"
override fun getGroupName() = "org.jetbrains.kotlin" override fun getGroupName() = "org.jetbrains.kotlin"
@@ -63,7 +63,7 @@ internal fun Project.initKapt(
kotlinAfterJavaTask.source(kaptManager.generatedKotlinSourceDir) kotlinAfterJavaTask.source(kaptManager.generatedKotlinSourceDir)
kotlinAfterJavaTask.source(kaptManager.aptOutputDir) kotlinAfterJavaTask.source(kaptManager.aptOutputDir)
subpluginEnvironment.addSubpluginArguments(this, kotlinAfterJavaTask, javaTask, null, null) subpluginEnvironment.addSubpluginOptions(this, kotlinAfterJavaTask, javaTask, null, null)
javaTask.doLast { javaTask.doLast {
moveGeneratedJavaFilesToCorrespondingDirectories(kaptManager.aptOutputDir) moveGeneratedJavaFilesToCorrespondingDirectories(kaptManager.aptOutputDir)
@@ -26,10 +26,7 @@ import org.gradle.api.artifacts.Configuration
import org.gradle.api.tasks.SourceSet import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.compile.AbstractCompile 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.annotation.SourceAnnotationsRegistry import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.plugin.KaptExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinGradleSubplugin
import org.jetbrains.kotlin.gradle.plugin.SubpluginOption
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import java.io.File import java.io.File
@@ -51,6 +48,8 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
return if (sourceSetName != "main") "kapt${sourceSetName.capitalize()}" else "kapt" return if (sourceSetName != "main") "kapt${sourceSetName.capitalize()}" else "kapt"
} }
} }
private val kotlinToKaptTasksMap = mutableMapOf<KotlinCompile, KaptTask>()
override fun isApplicable(project: Project, task: KotlinCompile) = Kapt3GradleSubplugin.isEnabled(project) override fun isApplicable(project: Project, task: KotlinCompile) = Kapt3GradleSubplugin.isEnabled(project)
@@ -73,7 +72,6 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
val kotlinCompile: KotlinCompile, val kotlinCompile: KotlinCompile,
val javaCompile: AbstractCompile, val javaCompile: AbstractCompile,
val variantData: Any?, val variantData: Any?,
val javaSourceSet: SourceSet?,
val sourceSetName: String, val sourceSetName: String,
val kaptExtension: KaptExtension, val kaptExtension: KaptExtension,
val kaptClasspath: MutableList<File>) val kaptClasspath: MutableList<File>)
@@ -114,9 +112,16 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
val kaptExtension = project.extensions.getByType(KaptExtension::class.java) val kaptExtension = project.extensions.getByType(KaptExtension::class.java)
val context = Kapt3SubpluginContext(project, kotlinCompile, javaCompile, val context = Kapt3SubpluginContext(project, kotlinCompile, javaCompile,
variantData, javaSourceSet, sourceSetName, kaptExtension, kaptClasspath) variantData, sourceSetName, kaptExtension, kaptClasspath)
return context.buildOptions() context.createKaptKotlinTask()
/** 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()
} }
// This method should be called no more than once for each Kapt3SubpluginContext // This method should be called no more than once for each Kapt3SubpluginContext
@@ -146,9 +151,6 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
pluginOptions += SubpluginOption("apoption", "$key:$value") pluginOptions += SubpluginOption("apoption", "$key:$value")
} }
val annotationsFile = File(kotlinCompile.taskBuildDirectory, "source-annotations.txt")
kotlinCompile.sourceAnnotationsRegistry = SourceAnnotationsRegistry(annotationsFile)
addMiscOptions(pluginOptions) addMiscOptions(pluginOptions)
return pluginOptions return pluginOptions
@@ -159,12 +161,43 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
project.logger.warn("'kapt.generateStubs' is not used by the 'kotlin-kapt' plugin") project.logger.warn("'kapt.generateStubs' is not used by the 'kotlin-kapt' plugin")
} }
pluginOptions += SubpluginOption("aptOnly", "true")
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")
pluginOptions += SubpluginOption("stubs", getKaptStubsDir(project, sourceSetName).canonicalPath) pluginOptions += SubpluginOption("stubs", getKaptStubsDir(project, sourceSetName).canonicalPath)
} }
} }
private fun Kapt3SubpluginContext.createKaptKotlinTask() {
val sourcesOutputDir = getKaptGeneratedDir(project, sourceSetName)
// 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)
kaptTask.kotlinCompileTask = kotlinCompile
kotlinToKaptTasksMap[kotlinCompile] = kaptTask
project.resolveSubpluginArtifacts(listOf(this@Kapt3KotlinGradleSubplugin)).flatMap { it.value }.forEach {
kaptTask.pluginOptions.addClasspathEntry(it)
}
kaptTask.mapClasspath { kotlinCompile.classpath }
kaptTask.destinationDir = sourcesOutputDir
kotlinCompile.dependsOn(kaptTask)
// Add generated source dir as a source root for kotlinCompile and javaCompile
kotlinCompile.source(sourcesOutputDir)
javaCompile.source(sourcesOutputDir)
val pluginOptions = kaptTask.pluginOptions
val compilerPluginId = getCompilerPluginId()
for (option in buildOptions()) {
pluginOptions.addPluginArgument(compilerPluginId, option.key, option.value)
}
}
private fun Kapt3SubpluginContext.disableAnnotationProcessingInJavaTask() { private fun Kapt3SubpluginContext.disableAnnotationProcessingInJavaTask() {
(javaCompile as? JavaCompile)?.let { javaCompile -> (javaCompile as? JavaCompile)?.let { javaCompile ->
val options = javaCompile.options val options = javaCompile.options
@@ -175,7 +208,7 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
private val BaseVariantData<*>.sourceProviders: List<SourceProvider> private val BaseVariantData<*>.sourceProviders: List<SourceProvider>
get() = variantConfiguration.sortedSourceProviders get() = variantConfiguration.sortedSourceProviders
override fun getPluginName() = "org.jetbrains.kotlin.kapt3" override fun getCompilerPluginId() = "org.jetbrains.kotlin.kapt3"
override fun getGroupName() = "org.jetbrains.kotlin" override fun getGroupName() = "org.jetbrains.kotlin"
override fun getArtifactName() = "kotlin-annotation-processing" override fun getArtifactName() = "kotlin-annotation-processing"
} }
@@ -0,0 +1,65 @@
package org.jetbrains.kotlin.gradle.internal
import org.gradle.api.GradleException
import org.gradle.api.tasks.SourceTask
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.compile.AbstractCompile
import org.jetbrains.kotlin.cli.common.ExitCode
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
import org.jetbrains.kotlin.com.intellij.openapi.util.io.FileUtil
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptionsImpl
import org.jetbrains.kotlin.gradle.dsl.fillDefaultValues
import org.jetbrains.kotlin.gradle.tasks.*
import java.io.File
open class KaptTask : AbstractCompile() {
private val rawSourceRoots = FilteringSourceRootsContainer({ !it.isInsideDestinationDir() })
private val args = K2JVMCompilerArguments().apply { fillDefaultValues() }
internal val pluginOptions = CompilerPluginOptions()
internal lateinit var kotlinCompileTask: KotlinCompile
override fun setSource(sources: Any?) {
val filteredSources = rawSourceRoots.set(sources)
super.setSource(filteredSources)
}
override fun source(vararg sources: Any?): SourceTask? {
val filteredSources = rawSourceRoots.add(*sources)
return super.source(filteredSources)
}
private fun File.isInsideDestinationDir(): Boolean {
return FileUtil.isAncestor(destinationDir, this, /* strict = */ false)
}
private fun processCompilerExitCode(exitCode: ExitCode) {
when (exitCode) {
ExitCode.COMPILATION_ERROR -> throw GradleException("Annotation processing error. See log for more details")
ExitCode.INTERNAL_ERROR -> throw GradleException("Annotation processing internal error. See log for more details")
else -> {}
}
}
@TaskAction
override fun compile() {
/** Delete everything inside the [destinationDir] */
destinationDir.deleteRecursively()
destinationDir.mkdirs()
val compiler = K2JVMCompiler()
val sourceRoots = SourceRoots.ForJvm.create(getSource(), rawSourceRoots)
val compileClasspath = classpath.filter(File::exists)
args.moduleName = kotlinCompileTask.moduleName
args.pluginClasspaths = pluginOptions.classpath.toTypedArray()
args.pluginOptions = pluginOptions.arguments.toTypedArray()
kotlinCompileTask.parentKotlinOptionsImpl?.updateArguments(args)
KotlinJvmOptionsImpl().updateArguments(args)
processCompilerExitCode(compileJvmNotIncrementally(compiler, logger,
sourceRoots.kotlinSourceFiles, sourceRoots.javaSourceRoots, compileClasspath,
destinationDir, args))
}
}
@@ -118,8 +118,8 @@ internal class Kotlin2JvmSourceSetProcessor(
val javaTask = project.tasks.findByName(sourceSet.compileJavaTaskName) val javaTask = project.tasks.findByName(sourceSet.compileJavaTaskName)
val subpluginEnvironment = loadSubplugins(project) val subpluginEnvironment = loadSubplugins(project)
subpluginEnvironment.addSubpluginArguments(project, kotlinTask, val appliedPlugins = subpluginEnvironment.addSubpluginOptions(
javaTask as JavaCompile, null, sourceSet) project, kotlinTask, javaTask as JavaCompile, null, sourceSet)
var kotlinAfterJavaTask: KotlinCompile? = null var kotlinAfterJavaTask: KotlinCompile? = null
@@ -135,10 +135,15 @@ internal class Kotlin2JvmSourceSetProcessor(
} }
sourceSet.java.srcDirs.forEach { kotlinSourceSet.kotlin.srcDir(it) } sourceSet.java.srcDirs.forEach { kotlinSourceSet.kotlin.srcDir(it) }
// KotlinCompile.source(kotlinDirSet) should be called only after all java roots are added to kotlinDirSet // KotlinCompile.source(kotlinDirSet) should be called only after all java roots are added to kotlinDirSet
// otherwise some java roots can be ignored // otherwise some java roots can be ignored
kotlinTask.source(kotlinSourceSet.kotlin) kotlinTask.source(kotlinSourceSet.kotlin)
kotlinAfterJavaTask?.let { it.source(kotlinSourceSet.kotlin) } kotlinAfterJavaTask?.source(kotlinSourceSet.kotlin)
appliedPlugins
.flatMap { it.getSubpluginKotlinTasks(project, kotlinTask) }
.forEach { it.source(kotlinSourceSet.kotlin) }
configureJavaTask(kotlinTask, javaTask, logger) configureJavaTask(kotlinTask, javaTask, logger)
createSyncOutputTask(project, kotlinTask, javaTask, kotlinAfterJavaTask, sourceSetName) createSyncOutputTask(project, kotlinTask, javaTask, kotlinAfterJavaTask, sourceSetName)
val artifactFile = project.tryGetSingleArtifact() val artifactFile = project.tryGetSingleArtifact()
@@ -355,7 +360,8 @@ internal open class KotlinAndroidPlugin(
} }
} }
subpluginEnvironment.addSubpluginArguments(project, kotlinTask, javaTask, variantData, null) val appliedPlugins = subpluginEnvironment.addSubpluginOptions(
project, kotlinTask, javaTask, variantData, null)
kotlinTask.mapClasspath { kotlinTask.mapClasspath {
javaTask.classpath + project.files(AndroidGradleWrapper.getRuntimeJars(androidPlugin, androidExt)) javaTask.classpath + project.files(AndroidGradleWrapper.getRuntimeJars(androidPlugin, androidExt))
@@ -379,6 +385,9 @@ internal open class KotlinAndroidPlugin(
if (kotlinAfterJavaTask != null) { if (kotlinAfterJavaTask != null) {
configureSources(kotlinAfterJavaTask, variantData) configureSources(kotlinAfterJavaTask, variantData)
} }
appliedPlugins
.flatMap { it.getSubpluginKotlinTasks(project, kotlinTask) }
.forEach { configureSources(it, variantData) }
configureJavaTask(kotlinTask, javaTask, logger) configureJavaTask(kotlinTask, javaTask, logger)
createSyncOutputTask(project, kotlinTask, javaTask, kotlinAfterJavaTask, variantDataName) createSyncOutputTask(project, kotlinTask, javaTask, kotlinAfterJavaTask, variantDataName)
@@ -393,17 +402,17 @@ internal open class KotlinAndroidPlugin(
} }
} }
private fun configureSources(kotlinTask: KotlinCompile, variantData: BaseVariantData<out BaseVariantOutputData>) { private fun configureSources(compileTask: AbstractCompile, variantData: BaseVariantData<out BaseVariantOutputData>) {
val logger = kotlinTask.project.logger val logger = compileTask.project.logger
for (provider in variantData.sourceProviders) { for (provider in variantData.sourceProviders) {
val kotlinSourceSet = provider.getConvention(KOTLIN_DSL_NAME) as? KotlinSourceSet ?: continue val kotlinSourceSet = provider.getConvention(KOTLIN_DSL_NAME) as? KotlinSourceSet ?: continue
kotlinTask.source(kotlinSourceSet.kotlin) compileTask.source(kotlinSourceSet.kotlin)
} }
for (javaSrcDir in AndroidGradleWrapper.getJavaSources(variantData)) { for (javaSrcDir in AndroidGradleWrapper.getJavaSources(variantData)) {
kotlinTask.source(javaSrcDir) compileTask.source(javaSrcDir)
logger.kotlinDebug("Source directory $javaSrcDir was added to kotlin source for ${kotlinTask.name}") logger.kotlinDebug("Source directory $javaSrcDir was added to kotlin source for ${compileTask.name}")
} }
} }
@@ -482,29 +491,7 @@ private fun loadSubplugins(project: Project): SubpluginEnvironment {
val subplugins = ServiceLoader.load(KotlinGradleSubplugin::class.java, project.buildscript.classLoader) val subplugins = ServiceLoader.load(KotlinGradleSubplugin::class.java, project.buildscript.classLoader)
.map { @Suppress("UNCHECKED_CAST") (it as KotlinGradleSubplugin<KotlinCompile>) } .map { @Suppress("UNCHECKED_CAST") (it as KotlinGradleSubplugin<KotlinCompile>) }
fun Project.getResolvedArtifacts() = buildscript.configurations.getByName("classpath") return SubpluginEnvironment(project.resolveSubpluginArtifacts(subplugins), subplugins)
.resolvedConfiguration.resolvedArtifacts
val resolvedClasspathArtifacts = project.getResolvedArtifacts().toMutableList()
val rootProject = project.rootProject
if (rootProject != project) {
resolvedClasspathArtifacts += rootProject.getResolvedArtifacts()
}
val subpluginClasspaths = hashMapOf<KotlinGradleSubplugin<KotlinCompile>, List<File>>()
for (subplugin in subplugins) {
val file = resolvedClasspathArtifacts
.firstOrNull {
val id = it.moduleVersion.id
subplugin.getGroupName() == id.group && subplugin.getArtifactName() == id.name
}?.file
if (file != null) {
subpluginClasspaths.put(subplugin, listOf(file))
}
}
return SubpluginEnvironment(subpluginClasspaths, subplugins)
} catch (e: NoClassDefFoundError) { } catch (e: NoClassDefFoundError) {
// Skip plugin loading if KotlinGradleSubplugin is not defined. // Skip plugin loading if KotlinGradleSubplugin is not defined.
// It is true now for tests in kotlin-gradle-plugin-core. // It is true now for tests in kotlin-gradle-plugin-core.
@@ -512,33 +499,65 @@ private fun loadSubplugins(project: Project): SubpluginEnvironment {
} }
} }
internal fun Project.resolveSubpluginArtifacts(
subplugins: List<KotlinGradleSubplugin<KotlinCompile>>
): Map<KotlinGradleSubplugin<KotlinCompile>, List<File>> {
fun Project.getResolvedArtifacts() = buildscript.configurations.getByName("classpath")
.resolvedConfiguration.resolvedArtifacts
val resolvedClasspathArtifacts = getResolvedArtifacts().toMutableList()
val rootProject = rootProject
if (rootProject != this) {
resolvedClasspathArtifacts += rootProject.getResolvedArtifacts()
}
val subpluginClasspaths = hashMapOf<KotlinGradleSubplugin<KotlinCompile>, List<File>>()
for (subplugin in subplugins) {
val file = resolvedClasspathArtifacts
.firstOrNull {
val id = it.moduleVersion.id
subplugin.getGroupName() == id.group && subplugin.getArtifactName() == id.name
}?.file
if (file != null) {
subpluginClasspaths.put(subplugin, listOf(file))
}
}
return subpluginClasspaths
}
internal class SubpluginEnvironment( internal class SubpluginEnvironment(
val subpluginClasspaths: Map<KotlinGradleSubplugin<KotlinCompile>, List<File>>, val subpluginClasspaths: Map<KotlinGradleSubplugin<KotlinCompile>, List<File>>,
val subplugins: List<KotlinGradleSubplugin<KotlinCompile>> val subplugins: List<KotlinGradleSubplugin<KotlinCompile>>
) { ) {
fun addSubpluginArguments( fun addSubpluginOptions(
project: Project, project: Project,
kotlinTask: KotlinCompile, kotlinTask: KotlinCompile,
javaTask: AbstractCompile, javaTask: AbstractCompile,
variantData: Any?, variantData: Any?,
javaSourceSet: SourceSet?) { javaSourceSet: SourceSet?
): List<KotlinGradleSubplugin<KotlinCompile>> {
val pluginOptions = kotlinTask.pluginOptions val pluginOptions = kotlinTask.pluginOptions
for (subplugin in subplugins) { val appliedSubplugins = subplugins.filter { it.isApplicable(project, kotlinTask) }
for (subplugin in appliedSubplugins) {
if (!subplugin.isApplicable(project, kotlinTask)) continue if (!subplugin.isApplicable(project, kotlinTask)) continue
with(subplugin) { with(subplugin) {
project.logger.kotlinDebug("Subplugin ${getPluginName()} (${getGroupName()}:${getArtifactName()}) loaded.") project.logger.kotlinDebug("Subplugin ${getCompilerPluginId()} (${getGroupName()}:${getArtifactName()}) loaded.")
} }
val subpluginClasspath = subpluginClasspaths[subplugin] ?: continue val subpluginClasspath = subpluginClasspaths[subplugin] ?: continue
subpluginClasspath.forEach { pluginOptions.addClasspathEntry(it) } subpluginClasspath.forEach { pluginOptions.addClasspathEntry(it) }
for (arg in subplugin.apply(project, kotlinTask, javaTask, variantData, javaSourceSet)) { for (option in subplugin.apply(project, kotlinTask, javaTask, variantData, javaSourceSet)) {
pluginOptions.addPluginArgument(subplugin.getPluginName(), arg.key, arg.value) pluginOptions.addPluginArgument(subplugin.getCompilerPluginId(), option.key, option.value)
} }
} }
return appliedSubplugins
} }
} }
@@ -0,0 +1,88 @@
package org.jetbrains.kotlin.gradle.tasks
import org.gradle.api.file.FileTree
import org.gradle.api.file.SourceDirectorySet
import org.gradle.api.logging.Logger
import org.jetbrains.kotlin.com.intellij.openapi.util.io.FileUtil
import org.jetbrains.kotlin.gradle.plugin.kotlinDebug
import org.jetbrains.kotlin.incremental.isJavaFile
import org.jetbrains.kotlin.incremental.isKotlinFile
import java.io.File
import java.util.*
internal sealed class SourceRoots(val kotlinSourceFiles: List<File>) {
private companion object {
fun dumpPaths(files: Iterable<File>): String =
"[${files.map { it.canonicalPath }.sorted().joinToString(prefix = "\n\t", separator = ",\n\t")}]"
}
open fun log(taskName: String, logger: Logger) {
logger.kotlinDebug { "$taskName source roots: ${dumpPaths(kotlinSourceFiles)}" }
}
class ForJvm(kotlinSourceFiles: List<File>, val javaSourceRoots: Set<File>) : SourceRoots(kotlinSourceFiles) {
companion object {
fun create(taskSource: FileTree, sourceRoots: FilteringSourceRootsContainer): ForJvm {
val kotlinSourceFiles = (taskSource as Iterable<File>).filter(File::isKotlinFile)
val javaSourceRoots = findRootsForSources(
sourceRoots.sourceRoots, taskSource.filter(File::isJavaFile))
return ForJvm(kotlinSourceFiles, javaSourceRoots)
}
private fun findRootsForSources(allSourceRoots: Iterable<File>, sources: Iterable<File>): Set<File> {
val resultRoots = HashSet<File>()
val sourceDirs = sources.mapTo(HashSet()) { it.parentFile }
for (sourceDir in sourceDirs) {
for (sourceRoot in allSourceRoots) {
if (FileUtil.isAncestor(sourceRoot, sourceDir, /* strict = */false)) {
resultRoots.add(sourceRoot)
}
}
}
return resultRoots
}
}
override fun log(taskName: String, logger: Logger) {
super.log(taskName, logger)
logger.kotlinDebug { "$taskName java source roots: ${dumpPaths(javaSourceRoots)}" }
}
}
class ForJs(kotlinSourceFiles: List<File>) : SourceRoots(kotlinSourceFiles) {
companion object {
fun create(taskSource: FileTree) = ForJs((taskSource as Iterable<File>).filter(File::isKotlinFile))
}
}
}
internal class FilteringSourceRootsContainer(val filter: (File) -> Boolean = { true }) {
private val mutableSourceRoots = mutableListOf<File>()
val sourceRoots: List<File>
get() = mutableSourceRoots
fun clear() {
mutableSourceRoots.clear()
}
fun set(source: Any?): List<File> {
clear()
return add(source)
}
fun add(vararg sources: Any?): List<File> {
val filteredDirs = mutableListOf<File>()
for (source in sources) {
when (source) {
is SourceDirectorySet -> filteredDirs += source.srcDirs.filter { filter(it) }
is File -> if (filter(source)) filteredDirs.add(source)
}
}
mutableSourceRoots += filteredDirs
return filteredDirs
}
}
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.gradle.tasks
import org.codehaus.groovy.runtime.MethodClosure import org.codehaus.groovy.runtime.MethodClosure
import org.gradle.api.GradleException import org.gradle.api.GradleException
import org.gradle.api.Task import org.gradle.api.Task
import org.gradle.api.file.FileTree
import org.gradle.api.file.SourceDirectorySet import org.gradle.api.file.SourceDirectorySet
import org.gradle.api.logging.Logger import org.gradle.api.logging.Logger
import org.gradle.api.tasks.SourceTask import org.gradle.api.tasks.SourceTask
@@ -28,6 +29,7 @@ import org.gradle.api.tasks.incremental.IncrementalTaskInputs
import org.gradle.util.GradleVersion import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.annotation.AnnotationFileUpdater import org.jetbrains.kotlin.annotation.AnnotationFileUpdater
import org.jetbrains.kotlin.annotation.SourceAnnotationsRegistry import org.jetbrains.kotlin.annotation.SourceAnnotationsRegistry
import org.jetbrains.kotlin.build.JvmSourceRoot
import org.jetbrains.kotlin.cli.common.CLICompiler import org.jetbrains.kotlin.cli.common.CLICompiler
import org.jetbrains.kotlin.cli.common.ExitCode import org.jetbrains.kotlin.cli.common.ExitCode
import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments
@@ -38,7 +40,6 @@ import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.MessageCollector import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.js.K2JSCompiler import org.jetbrains.kotlin.cli.js.K2JSCompiler
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
import org.jetbrains.kotlin.com.intellij.openapi.util.io.FileUtil
import org.jetbrains.kotlin.compilerRunner.ArgumentUtils import org.jetbrains.kotlin.compilerRunner.ArgumentUtils
import org.jetbrains.kotlin.config.Services import org.jetbrains.kotlin.config.Services
import org.jetbrains.kotlin.gradle.dsl.* import org.jetbrains.kotlin.gradle.dsl.*
@@ -46,7 +47,6 @@ import org.jetbrains.kotlin.gradle.plugin.kotlinDebug
import org.jetbrains.kotlin.gradle.plugin.kotlinInfo import org.jetbrains.kotlin.gradle.plugin.kotlinInfo
import org.jetbrains.kotlin.gradle.utils.ParsedGradleVersion import org.jetbrains.kotlin.gradle.utils.ParsedGradleVersion
import org.jetbrains.kotlin.incremental.* import org.jetbrains.kotlin.incremental.*
import org.jetbrains.kotlin.incremental.components.SourceRetentionAnnotationHandler
import org.jetbrains.kotlin.incremental.multiproject.ArtifactDifferenceRegistryProvider import org.jetbrains.kotlin.incremental.multiproject.ArtifactDifferenceRegistryProvider
import org.jetbrains.kotlin.utils.LibraryUtils import org.jetbrains.kotlin.utils.LibraryUtils
import java.io.File import java.io.File
@@ -79,7 +79,7 @@ abstract class AbstractKotlinCompile<T : CommonCompilerArguments>() : AbstractCo
internal var friendTaskName: String? = null internal var friendTaskName: String? = null
internal var javaOutputDir: File? = null internal var javaOutputDir: File? = null
internal var sourceSetName: String by Delegates.notNull() internal var sourceSetName: String by Delegates.notNull()
protected val moduleName: String internal val moduleName: String
get() = "${project.name}_$sourceSetName" get() = "${project.name}_$sourceSetName"
override fun compile() { override fun compile() {
@@ -88,7 +88,9 @@ abstract class AbstractKotlinCompile<T : CommonCompilerArguments>() : AbstractCo
@TaskAction @TaskAction
fun execute(inputs: IncrementalTaskInputs): Unit { fun execute(inputs: IncrementalTaskInputs): Unit {
val allKotlinSources = getKotlinSources() val sourceRoots = getSourceRoots()
val allKotlinSources = sourceRoots.kotlinSourceFiles
logger.kotlinDebug { "all kotlin sources: ${allKotlinSources.pathsAsStringRelativeTo(project.rootProject.projectDir)}" } logger.kotlinDebug { "all kotlin sources: ${allKotlinSources.pathsAsStringRelativeTo(project.rootProject.projectDir)}" }
if (allKotlinSources.isEmpty()) { if (allKotlinSources.isEmpty()) {
@@ -96,14 +98,14 @@ abstract class AbstractKotlinCompile<T : CommonCompilerArguments>() : AbstractCo
return return
} }
sourceRoots.log(this.name, logger)
val args = populateCompilerArguments() val args = populateCompilerArguments()
compilerCalled = true compilerCalled = true
callCompiler(args, allKotlinSources, ChangedFiles(inputs)) callCompiler(args, sourceRoots, ChangedFiles(inputs))
} }
private fun getKotlinSources(): List<File> = (getSource() as Iterable<File>).filter(File::isKotlinFile) internal abstract fun getSourceRoots(): SourceRoots
internal abstract fun callCompiler(args: T, sourceRoots: SourceRoots, changedFiles: ChangedFiles)
internal abstract fun callCompiler(args: T, allKotlinSources: List<File>, changedFiles: ChangedFiles)
} }
open class KotlinCompile : AbstractKotlinCompile<K2JVMCompilerArguments>(), KotlinJvmCompile { open class KotlinCompile : AbstractKotlinCompile<K2JVMCompilerArguments>(), KotlinJvmCompile {
@@ -113,8 +115,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()
private val sourceRoots = HashSet<File>()
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() }
@@ -136,9 +137,6 @@ open class KotlinCompile : AbstractKotlinCompile<K2JVMCompilerArguments>(), Kotl
private val compileClasspath: Iterable<File> private val compileClasspath: Iterable<File>
get() = (classpath + additionalClasspath) get() = (classpath + additionalClasspath)
.filterTo(LinkedHashSet(), File::exists) .filterTo(LinkedHashSet(), File::exists)
private val kapt2GeneratedSourcesDir: File
get() = File(project.buildDir, "generated/source/kapt2")
internal val kaptOptions = KaptOptions() internal val kaptOptions = KaptOptions()
internal val pluginOptions = CompilerPluginOptions() internal val pluginOptions = CompilerPluginOptions()
@@ -171,21 +169,30 @@ open class KotlinCompile : AbstractKotlinCompile<K2JVMCompilerArguments>(), Kotl
parentKotlinOptionsImpl?.updateArguments(args) parentKotlinOptionsImpl?.updateArguments(args)
kotlinOptionsImpl.updateArguments(args) kotlinOptionsImpl.updateArguments(args)
fun dumpPaths(files: Iterable<File>): String =
"[${files.map { it.canonicalPath }.sorted().joinToString(prefix = "\n\t", separator = ",\n\t")}]"
logger.kotlinDebug { "$name destinationDir = $destinationDir" } logger.kotlinDebug { "$name destinationDir = $destinationDir" }
logger.kotlinDebug { "$name source roots: ${dumpPaths(sourceRoots)}" }
logger.kotlinDebug { "$name java source roots: ${dumpPaths(getJavaSourceRoots())}" }
return args return args
} }
override fun callCompiler(args: K2JVMCompilerArguments, allKotlinSources: List<File>, changedFiles: ChangedFiles) { internal fun addFriendPathForTestTask(friendKotlinTaskName: String, args: K2JVMCompilerArguments) {
val friendTask = project.getTasksByName(friendKotlinTaskName, /* recursive = */false).firstOrNull() as? KotlinCompile ?: return
args.friendPaths = arrayOf(friendTask.javaOutputDir!!.absolutePath)
args.moduleName = friendTask.moduleName
logger.kotlinDebug("java destination directory for production = ${friendTask.javaOutputDir}")
}
override fun getSourceRoots() = SourceRoots.ForJvm.create(getSource(), sourceRootsContainer)
override fun callCompiler(args: K2JVMCompilerArguments, sourceRoots: SourceRoots, changedFiles: ChangedFiles) {
sourceRoots as SourceRoots.ForJvm
val outputDir = destinationDir val outputDir = destinationDir
if (!incremental) { if (!incremental) {
anyClassesCompiled = true anyClassesCompiled = true
processCompilerExitCode(compileNotIncremental(allKotlinSources, outputDir, args)) processCompilerExitCode(compileJvmNotIncrementally(
compiler, logger,
sourceRoots.kotlinSourceFiles, sourceRoots.javaSourceRoots,
compileClasspath, outputDir, args))
return return
} }
@@ -194,19 +201,18 @@ open class KotlinCompile : AbstractKotlinCompile<K2JVMCompilerArguments>(), Kotl
val messageCollector = GradleMessageCollector(logger) val messageCollector = GradleMessageCollector(logger)
val compiler = IncrementalJvmCompilerRunner( val compiler = IncrementalJvmCompilerRunner(
taskBuildDirectory, taskBuildDirectory,
getJavaSourceRoots(), sourceRoots.javaSourceRoots,
cacheVersions, cacheVersions,
reporter, reporter,
kaptAnnotationsFileUpdater, kaptAnnotationsFileUpdater,
sourceAnnotationsRegistry, sourceAnnotationsRegistry,
kapt2GeneratedSourcesDir,
artifactDifferenceRegistryProvider, artifactDifferenceRegistryProvider,
artifactFile artifactFile
) )
args.classpathAsList = classpath.toList() args.classpathAsList = classpath.toList()
args.destinationAsFile = destinationDir args.destinationAsFile = destinationDir
try { try {
val exitCode = compiler.compile(allKotlinSources, args, messageCollector, { changedFiles }) val exitCode = compiler.compile(sourceRoots.kotlinSourceFiles, args, messageCollector, { changedFiles })
processCompilerExitCode(exitCode) processCompilerExitCode(exitCode)
} }
catch (e: Throwable) { catch (e: Throwable) {
@@ -236,45 +242,6 @@ open class KotlinCompile : AbstractKotlinCompile<K2JVMCompilerArguments>(), Kotl
} }
} }
private fun compileNotIncremental(
sourcesToCompile: List<File>,
outputDir: File,
args: K2JVMCompilerArguments
): ExitCode {
logger.kotlinDebug("Removing all kotlin classes in $outputDir")
// we're free to delete all classes since only we know about that directory
// todo: can be optimized -- compile and remove only files that were not generated
listClassFiles(outputDir.canonicalPath).forEach { it.delete() }
val moduleFile = makeModuleFile(
args.moduleName,
isTest = false,
outputDir = outputDir,
sourcesToCompile = sourcesToCompile,
javaSourceRoots = getJavaSourceRoots(),
classpath = compileClasspath,
friendDirs = listOf())
args.module = moduleFile.absolutePath
val messageCollector = GradleMessageCollector(logger)
sourceAnnotationsRegistry?.clear()
val services = with (Services.Builder()) {
sourceAnnotationsRegistry?.let { handler ->
register(SourceRetentionAnnotationHandler::class.java, handler)
}
build()
}
try {
logger.kotlinDebug("compiling with args: ${ArgumentUtils.convertArgumentsToStringList(args)}")
logger.kotlinDebug("compiling with classpath: ${compileClasspath.toList().sorted().joinToString()}")
return compiler.exec(messageCollector, services, args)
}
finally {
moduleFile.delete()
}
}
private fun handleKaptProperties() { private fun handleKaptProperties() {
kaptOptions.annotationsFile?.let { kaptAnnotationsFile -> kaptOptions.annotationsFile?.let { kaptAnnotationsFile ->
if (incremental) { if (incremental) {
@@ -293,61 +260,51 @@ open class KotlinCompile : AbstractKotlinCompile<K2JVMCompilerArguments>(), Kotl
} }
} }
private fun getJavaSourceRoots(): Set<File> =
findRootsForSources(getSource().filter { it.isJavaFile() })
private fun File.isKapt2GeneratedDirectory(): Boolean {
if (!kapt2GeneratedSourcesDir.isDirectory) return false
return FileUtil.isAncestor(kapt2GeneratedSourcesDir, this, /* strict = */ false)
}
private fun filterOutKapt2Directories(vararg sources: Any?): Array<Any> {
return sources.flatMap { source ->
when (source) {
is File -> if (source.isKapt2GeneratedDirectory()) emptyList<File>() else listOf(source)
is SourceDirectorySet -> source.srcDirs.filter { !it.isKapt2GeneratedDirectory() }
else -> emptyList<File>()
}
}.toTypedArray()
}
// override setSource to track source directory sets and files (for generated android folders) // override setSource to track source directory sets and files (for generated android folders)
override fun setSource(sources: Any?) { override fun setSource(sources: Any?) {
sourceRoots.clear() sourceRootsContainer.set(sources)
val sourcesToAdd = filterOutKapt2Directories(sources) super.setSource(sources)
addSourceRoots(*sourcesToAdd)
super.setSource(sourcesToAdd.firstOrNull())
} }
// override source to track source directory sets and files (for generated android folders) // override source to track source directory sets and files (for generated android folders)
override fun source(vararg sources: Any?): SourceTask? { override fun source(vararg sources: Any?): SourceTask? {
val sourcesToAdd = filterOutKapt2Directories(*sources) sourceRootsContainer.add(*sources)
addSourceRoots(*sourcesToAdd) return super.source(*sources)
return super.source(*sourcesToAdd)
} }
}
internal fun findRootsForSources(sources: Iterable<File>): Set<File> { internal fun compileJvmNotIncrementally(
val resultRoots = HashSet<File>() compiler: K2JVMCompiler,
val sourceDirs = sources.mapTo(HashSet()) { it.parentFile } logger: Logger,
sourcesToCompile: List<File>,
javaSourceRoots: Iterable<File>,
compileClasspath: Iterable<File>,
outputDir: File,
args: K2JVMCompilerArguments
): ExitCode {
logger.kotlinDebug("Removing all kotlin classes in $outputDir")
// we're free to delete all classes since only we know about that directory
// todo: can be optimized -- compile and remove only files that were not generated
listClassFiles(outputDir.canonicalPath).forEach { it.delete() }
for (sourceDir in sourceDirs) { val moduleFile = makeModuleFile(
for (sourceRoot in sourceRoots) { args.moduleName,
if (FileUtil.isAncestor(sourceRoot, sourceDir, /* strict = */false)) { isTest = false,
resultRoots.add(sourceRoot) outputDir = outputDir,
} sourcesToCompile = sourcesToCompile,
} javaSourceRoots = javaSourceRoots,
} classpath = compileClasspath,
friendDirs = listOf())
args.module = moduleFile.absolutePath
val messageCollector = GradleMessageCollector(logger)
return resultRoots try {
logger.kotlinDebug("compiling with args: ${ArgumentUtils.convertArgumentsToStringList(args)}")
logger.kotlinDebug("compiling with classpath: ${compileClasspath.toList().sorted().joinToString()}")
return compiler.exec(messageCollector, Services.EMPTY, args)
} }
finally {
private fun addSourceRoots(vararg sources: Any?) { moduleFile.delete()
for (source in sources) {
when (source) {
is SourceDirectorySet -> sourceRoots.addAll(source.srcDirs)
is File -> sourceRoots.add(source)
}
}
} }
} }
@@ -378,10 +335,15 @@ open class Kotlin2JsCompile() : AbstractKotlinCompile<K2JSCompilerArguments>(),
return args return args
} }
override fun callCompiler(args: K2JSCompilerArguments, allKotlinSources: List<File>, changedFiles: ChangedFiles) { override fun getSourceRoots() = SourceRoots.ForJs.create(getSource())
override fun callCompiler(args: K2JSCompilerArguments, sourceRoots: SourceRoots, changedFiles: ChangedFiles) {
sourceRoots as SourceRoots.ForJs
val messageCollector = GradleMessageCollector(logger)
logger.debug("Calling compiler") logger.debug("Calling compiler")
destinationDir.mkdirs() destinationDir.mkdirs()
args.freeArgs = args.freeArgs + allKotlinSources.map { it.absolutePath } args.freeArgs = args.freeArgs + sourceRoots.kotlinSourceFiles.map { it.absolutePath }
if (args.outputFile == null) { if (args.outputFile == null) {
throw GradleException("$name.kotlinOptions.outputFile should be specified.") throw GradleException("$name.kotlinOptions.outputFile should be specified.")
@@ -98,7 +98,6 @@ internal class IncrementalJvmCompilerRunner(
private val reporter: IncReporter, private val reporter: IncReporter,
private var kaptAnnotationsFileUpdater: AnnotationFileUpdater? = null, private var kaptAnnotationsFileUpdater: AnnotationFileUpdater? = null,
private val sourceAnnotationsRegistry: SourceAnnotationsRegistry? = null, private val sourceAnnotationsRegistry: SourceAnnotationsRegistry? = null,
private val kapt2GeneratedSourcesDir: File? = null,
private val artifactDifferenceRegistryProvider: ArtifactDifferenceRegistryProvider? = null, private val artifactDifferenceRegistryProvider: ArtifactDifferenceRegistryProvider? = null,
private val artifactFile: File? = null private val artifactFile: File? = null
) { ) {
@@ -356,9 +355,6 @@ internal class IncrementalJvmCompilerRunner(
caches.lookupCache.update(lookupTracker, sourcesToCompile, removedKotlinSources) caches.lookupCache.update(lookupTracker, sourcesToCompile, removedKotlinSources)
val generatedJavaFiles = (kapt2GeneratedSourcesDir?.walk() ?: emptySequence<File>()).filter(File::isJavaFile).toList()
val generatedJavaFilesDiff = caches.incrementalCache.generatedSourceSnapshotMap.compareAndUpdate(generatedJavaFiles)
if (compilationMode is CompilationMode.Rebuild) { if (compilationMode is CompilationMode.Rebuild) {
artifactFile?.let { artifactFile -> artifactFile?.let { artifactFile ->
artifactDifferenceRegistryProvider?.withRegistry(reporter) { registry -> artifactDifferenceRegistryProvider?.withRegistry(reporter) { registry ->
@@ -369,23 +365,10 @@ internal class IncrementalJvmCompilerRunner(
} }
val (dirtyLookupSymbols, dirtyClassFqNames) = compilationResult.getDirtyData(listOf(caches.incrementalCache), reporter) val (dirtyLookupSymbols, dirtyClassFqNames) = compilationResult.getDirtyData(listOf(caches.incrementalCache), reporter)
val generatedJavaFilesChanges = javaFilesProcessor.process(generatedJavaFilesDiff)
val compiledInThisIterationSet = sourcesToCompile.toHashSet() val compiledInThisIterationSet = sourcesToCompile.toHashSet()
val dirtyKotlinFilesFromJava = when (generatedJavaFilesChanges) {
is ChangesEither.Unknown -> {
reporter.report { "Could not get changes for generated java files, recompiling all kotlin" }
compilationMode = CompilationMode.Rebuild
allKotlinSources.toSet()
}
is ChangesEither.Known -> {
mapLookupSymbolsToFiles(caches.lookupCache, generatedJavaFilesChanges.lookupSymbols, reporter, excludes = compiledInThisIterationSet)
}
else -> throw IllegalStateException("Unknown ChangesEither implementation: $generatedJavaFiles")
}
with (dirtySources) { with (dirtySources) {
clear() clear()
addAll(dirtyKotlinFilesFromJava)
addAll(mapLookupSymbolsToFiles(caches.lookupCache, dirtyLookupSymbols, reporter, excludes = compiledInThisIterationSet)) addAll(mapLookupSymbolsToFiles(caches.lookupCache, dirtyLookupSymbols, reporter, excludes = compiledInThisIterationSet))
addAll(mapClassesFqNamesToFiles(listOf(caches.incrementalCache), dirtyClassFqNames, reporter, excludes = compiledInThisIterationSet)) addAll(mapClassesFqNamesToFiles(listOf(caches.incrementalCache), dirtyClassFqNames, reporter, excludes = compiledInThisIterationSet))
} }
@@ -90,7 +90,7 @@ class Kapt3CommandLineProcessor : CommandLineProcessor {
override val pluginOptions: Collection<CliOption> = override val pluginOptions: Collection<CliOption> =
listOf(SOURCE_OUTPUT_DIR_OPTION, ANNOTATION_PROCESSOR_CLASSPATH_OPTION, APT_OPTIONS_OPTION, listOf(SOURCE_OUTPUT_DIR_OPTION, ANNOTATION_PROCESSOR_CLASSPATH_OPTION, APT_OPTIONS_OPTION,
CLASS_OUTPUT_DIR_OPTION, VERBOSE_MODE_OPTION, STUBS_OUTPUT_DIR_OPTION) CLASS_OUTPUT_DIR_OPTION, VERBOSE_MODE_OPTION, STUBS_OUTPUT_DIR_OPTION, APT_ONLY_OPTION)
private fun <T> CompilerConfiguration.appendList(option: CompilerConfigurationKey<List<T>>, value: T) { private fun <T> CompilerConfiguration.appendList(option: CompilerConfigurationKey<List<T>>, value: T) {
val paths = getList(option).toMutableList() val paths = getList(option).toMutableList()