Skip to content

Commit

Permalink
Kapt3: Extract annotation processing to its own task in Gradle. Now t…
Browse files Browse the repository at this point in the history
…he kotlinCompile task should know nothing about kapt, for the main task it's just a regular Java source root.
  • Loading branch information
yanex committed Dec 5, 2016
1 parent 9228207 commit 95d1210
Show file tree
Hide file tree
Showing 11 changed files with 336 additions and 181 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class ExampleSubplugin : KotlinGradleSubplugin<AbstractCompile> {
return listOf(SubpluginOption("exampleKey", "exampleValue"))
}

override fun getPluginName(): String {
override fun getCompilerPluginId(): String {
return "example.plugin"
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,13 @@ interface KotlinGradleSubplugin<KotlinCompile : AbstractCompile> {
variantData: Any?,
javaSourceSet: SourceSet?
): List<SubpluginOption>

fun getPluginName(): String

fun getSubpluginKotlinTasks(
project: Project,
kotlinCompile: KotlinCompile
): List<AbstractCompile> = emptyList()

fun getCompilerPluginId(): String
fun getGroupName(): String
fun getArtifactName(): String
}
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ internal fun Project.initKapt(

kotlinAfterJavaTask.source(kaptManager.generatedKotlinSourceDir)
kotlinAfterJavaTask.source(kaptManager.aptOutputDir)
subpluginEnvironment.addSubpluginArguments(this, kotlinAfterJavaTask, javaTask, null, null)
subpluginEnvironment.addSubpluginOptions(this, kotlinAfterJavaTask, javaTask, null, null)

javaTask.doLast {
moveGeneratedJavaFilesToCorrespondingDirectories(kaptManager.aptOutputDir)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,7 @@ import org.gradle.api.artifacts.Configuration
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.compile.AbstractCompile
import org.gradle.api.tasks.compile.JavaCompile
import org.jetbrains.kotlin.annotation.SourceAnnotationsRegistry
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.plugin.*
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import java.io.File

Expand All @@ -51,6 +48,8 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
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)

Expand All @@ -73,7 +72,6 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
val kotlinCompile: KotlinCompile,
val javaCompile: AbstractCompile,
val variantData: Any?,
val javaSourceSet: SourceSet?,
val sourceSetName: String,
val kaptExtension: KaptExtension,
val kaptClasspath: MutableList<File>)
Expand Down Expand Up @@ -114,9 +112,16 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
val kaptExtension = project.extensions.getByType(KaptExtension::class.java)

val context = Kapt3SubpluginContext(project, kotlinCompile, javaCompile,
variantData, javaSourceSet, sourceSetName, kaptExtension, kaptClasspath)
variantData, sourceSetName, kaptExtension, kaptClasspath)

context.createKaptKotlinTask()

/** Plugin options are applied to kapt*Compile inside [createKaptKotlinTask] */
return emptyList()
}

return context.buildOptions()
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
Expand Down Expand Up @@ -146,9 +151,6 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
pluginOptions += SubpluginOption("apoption", "$key:$value")
}

val annotationsFile = File(kotlinCompile.taskBuildDirectory, "source-annotations.txt")
kotlinCompile.sourceAnnotationsRegistry = SourceAnnotationsRegistry(annotationsFile)

addMiscOptions(pluginOptions)

return pluginOptions
Expand All @@ -159,12 +161,43 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
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") {
pluginOptions += SubpluginOption("verbose", "true")
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() {
(javaCompile as? JavaCompile)?.let { javaCompile ->
val options = javaCompile.options
Expand All @@ -175,7 +208,7 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
private val BaseVariantData<*>.sourceProviders: List<SourceProvider>
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 getArtifactName() = "kotlin-annotation-processing"
}
Original file line number Diff line number Diff line change
@@ -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))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@ internal class Kotlin2JvmSourceSetProcessor(
val javaTask = project.tasks.findByName(sourceSet.compileJavaTaskName)

val subpluginEnvironment = loadSubplugins(project)
subpluginEnvironment.addSubpluginArguments(project, kotlinTask,
javaTask as JavaCompile, null, sourceSet)
val appliedPlugins = subpluginEnvironment.addSubpluginOptions(
project, kotlinTask, javaTask as JavaCompile, null, sourceSet)

var kotlinAfterJavaTask: KotlinCompile? = null

Expand All @@ -135,10 +135,15 @@ internal class Kotlin2JvmSourceSetProcessor(
}

sourceSet.java.srcDirs.forEach { kotlinSourceSet.kotlin.srcDir(it) }

// KotlinCompile.source(kotlinDirSet) should be called only after all java roots are added to kotlinDirSet
// otherwise some java roots can be ignored
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)
createSyncOutputTask(project, kotlinTask, javaTask, kotlinAfterJavaTask, sourceSetName)
val artifactFile = project.tryGetSingleArtifact()
Expand Down Expand Up @@ -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 {
javaTask.classpath + project.files(AndroidGradleWrapper.getRuntimeJars(androidPlugin, androidExt))
Expand All @@ -379,6 +385,9 @@ internal open class KotlinAndroidPlugin(
if (kotlinAfterJavaTask != null) {
configureSources(kotlinAfterJavaTask, variantData)
}
appliedPlugins
.flatMap { it.getSubpluginKotlinTasks(project, kotlinTask) }
.forEach { configureSources(it, variantData) }

configureJavaTask(kotlinTask, javaTask, logger)
createSyncOutputTask(project, kotlinTask, javaTask, kotlinAfterJavaTask, variantDataName)
Expand All @@ -393,17 +402,17 @@ internal open class KotlinAndroidPlugin(
}
}

private fun configureSources(kotlinTask: KotlinCompile, variantData: BaseVariantData<out BaseVariantOutputData>) {
val logger = kotlinTask.project.logger
private fun configureSources(compileTask: AbstractCompile, variantData: BaseVariantData<out BaseVariantOutputData>) {
val logger = compileTask.project.logger

for (provider in variantData.sourceProviders) {
val kotlinSourceSet = provider.getConvention(KOTLIN_DSL_NAME) as? KotlinSourceSet ?: continue
kotlinTask.source(kotlinSourceSet.kotlin)
compileTask.source(kotlinSourceSet.kotlin)
}

for (javaSrcDir in AndroidGradleWrapper.getJavaSources(variantData)) {
kotlinTask.source(javaSrcDir)
logger.kotlinDebug("Source directory $javaSrcDir was added to kotlin source for ${kotlinTask.name}")
compileTask.source(javaSrcDir)
logger.kotlinDebug("Source directory $javaSrcDir was added to kotlin source for ${compileTask.name}")
}
}

Expand Down Expand Up @@ -482,63 +491,73 @@ private fun loadSubplugins(project: Project): SubpluginEnvironment {
val subplugins = ServiceLoader.load(KotlinGradleSubplugin::class.java, project.buildscript.classLoader)
.map { @Suppress("UNCHECKED_CAST") (it as KotlinGradleSubplugin<KotlinCompile>) }

fun Project.getResolvedArtifacts() = buildscript.configurations.getByName("classpath")
.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)
return SubpluginEnvironment(project.resolveSubpluginArtifacts(subplugins), subplugins)
} catch (e: NoClassDefFoundError) {
// Skip plugin loading if KotlinGradleSubplugin is not defined.
// It is true now for tests in kotlin-gradle-plugin-core.
return SubpluginEnvironment(mapOf(), listOf())
}
}

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(
val subpluginClasspaths: Map<KotlinGradleSubplugin<KotlinCompile>, List<File>>,
val subplugins: List<KotlinGradleSubplugin<KotlinCompile>>
) {

fun addSubpluginArguments(
fun addSubpluginOptions(
project: Project,
kotlinTask: KotlinCompile,
javaTask: AbstractCompile,
variantData: Any?,
javaSourceSet: SourceSet?) {
javaSourceSet: SourceSet?
): List<KotlinGradleSubplugin<KotlinCompile>> {
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

with(subplugin) {
project.logger.kotlinDebug("Subplugin ${getPluginName()} (${getGroupName()}:${getArtifactName()}) loaded.")
project.logger.kotlinDebug("Subplugin ${getCompilerPluginId()} (${getGroupName()}:${getArtifactName()}) loaded.")
}

val subpluginClasspath = subpluginClasspaths[subplugin] ?: continue
subpluginClasspath.forEach { pluginOptions.addClasspathEntry(it) }

for (arg in subplugin.apply(project, kotlinTask, javaTask, variantData, javaSourceSet)) {
pluginOptions.addPluginArgument(subplugin.getPluginName(), arg.key, arg.value)
for (option in subplugin.apply(project, kotlinTask, javaTask, variantData, javaSourceSet)) {
pluginOptions.addPluginArgument(subplugin.getCompilerPluginId(), option.key, option.value)
}
}

return appliedSubplugins
}
}

Expand Down
Loading

0 comments on commit 95d1210

Please sign in to comment.