Fix annotation processors outputting to classes dir with kapt 3

#KT-15915 fixed

 Annotation processor can access a classes directory provided by AP environment.
Previously kapt 3 was using kotlin-classes directory as a a classes directory.
However compileKotlin task does not expect this.
Also having multiple tasks with the same output dir is a bad practise in Gradle.

This change introduces a separate directory for classes generated by kapt 3.
Its output is copied to a resulting classes dir (just as a kotlin-classes dir).
This commit is contained in:
Alexey Tsvetkov
2017-01-31 14:08:41 +03:00
parent 34f04f3482
commit 9b5129a0f3
13 changed files with 149 additions and 18 deletions
@@ -270,4 +270,26 @@ class Kapt3IT : BaseGradleIT() {
assertCompiledKotlinSources(project.relativize(allMainKotlinSrc))
}
}
@Test
fun testKaptClassesDirSync() {
val project = Project("autoService", GRADLE_VERSION, directoryPrefix = "kapt2")
project.build("build") {
assertSuccessful()
assertKaptSuccessful()
assertFileExists("processor/build/classes/main/META-INF/services/javax.annotation.processing.Processor")
assertFileExists("processor/build/classes/main/processor/MyProcessor.class")
}
project.projectDir.getFileByName("MyProcessor.kt").modify {
it.replace("@AutoService(Processor::class)", "")
}
project.build(":processor:build") {
assertSuccessful()
assertNoSuchFile("processor/build/classes/main/META-INF/services/javax.annotation.processing.Processor")
assertFileExists("processor/build/classes/main/processor/MyProcessor.class")
}
}
}
@@ -0,0 +1,25 @@
buildscript {
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin'
allprojects {
repositories {
mavenLocal()
mavenCentral()
}
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
compile project(":processor")
kapt project(":processor")
}
@@ -0,0 +1,9 @@
apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin'
dependencies {
ext.autoServiceVersion = '1.0-rc2'
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
compile "com.google.auto.service:auto-service:$autoServiceVersion"
kapt "com.google.auto.service:auto-service:$autoServiceVersion"
}
@@ -0,0 +1,28 @@
package processor
import com.google.auto.service.AutoService
import javax.annotation.processing.*
import javax.lang.model.SourceVersion
import javax.lang.model.element.TypeElement
import javax.tools.Diagnostic
@AutoService(Processor::class)
@SupportedAnnotationTypes("annotation.ProcessThis")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
class MyProcessor() : AbstractProcessor() {
private var fileCreated = false
override fun process(annotations: Set<TypeElement>,
roundEnv: RoundEnvironment): Boolean {
processingEnv.messager.printMessage(Diagnostic.Kind.NOTE, "Working!")
if (fileCreated) return true
fileCreated = true
val file = processingEnv.filer.createSourceFile("Check")
file.openWriter().use {
it.appendln("// $annotations")
it.appendln("public class Check {}")
}
return true
}
}
@@ -0,0 +1,17 @@
package model
import annotation.ProcessThis
@ProcessThis
interface Model {
var a: Int
var b: Int
}
@ProcessThis
class Class {
var a = 0
}
@@ -60,12 +60,6 @@ internal class GradleCompilerRunner(private val project: Project) : KotlinCompil
): ExitCode {
val outputDir = args.destinationAsFile
if (environment !is GradleIncrementalCompilerEnvironment) {
log.debug("Removing all kotlin classes in $outputDir")
// we're free to delete all classes since only we know about that directory
outputDir.deleteRecursively()
}
val moduleFile = makeModuleFile(
args.moduleName,
isTest = false,
@@ -29,6 +29,7 @@ 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.KotlinCompile
import org.jetbrains.kotlin.gradle.tasks.SyncOutputTask
import java.io.File
// apply plugin: 'kotlin-kapt'
@@ -59,6 +60,9 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
}
fun findMainKaptConfiguration(project: Project) = project.findKaptConfiguration(MAIN_KAPT_CONFIGURATION_NAME)
fun getKaptClasssesDir(project: Project, sourceSetName: String): File =
File(project.project.buildDir, "tmp/kapt3/classes/$sourceSetName")
}
private val kotlinToKaptTasksMap = mutableMapOf<KotlinCompile, KaptTask>()
@@ -156,7 +160,7 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
javaCompile.source(generatedFilesDir)
pluginOptions += SubpluginOption("sources", generatedFilesDir.canonicalPath)
pluginOptions += SubpluginOption("classes", kotlinCompile.destinationDir.canonicalPath)
pluginOptions += SubpluginOption("classes", getKaptClasssesDir(project, sourceSetName).canonicalPath)
val androidPlugin = variantData?.let {
project.extensions.findByName("android") as? BaseExtension
@@ -193,6 +197,7 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
private fun Kapt3SubpluginContext.createKaptKotlinTask() {
val sourcesOutputDir = getKaptGeneratedDir(project, sourceSetName)
val classesOutputDir = getKaptClasssesDir(project, sourceSetName)
// Replace compile*Kotlin to kapt*Kotlin
assert(kotlinCompile.name.startsWith("compile"))
@@ -207,6 +212,7 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
kaptTask.mapClasspath { kotlinCompile.classpath }
kaptTask.destinationDir = sourcesOutputDir
kaptTask.classesDir = classesOutputDir
kaptTask.dependsOn(*(javaCompile.dependsOn.filter { it !== kotlinCompile }.toTypedArray()))
kotlinCompile.dependsOn(kaptTask)
@@ -39,12 +39,17 @@ open class KaptTask : AbstractCompile() {
return FileUtil.isAncestor(destinationDir, this, /* strict = */ false)
}
lateinit var classesDir: File
@TaskAction
override fun compile() {
/** Delete everything inside the [destinationDir] */
destinationDir.deleteRecursively()
destinationDir.mkdirs()
classesDir.deleteRecursively()
classesDir.mkdirs()
val sourceRoots = SourceRoots.ForJvm.create(getSource(), rawSourceRoots)
val compileClasspath = classpath.toList().filter(File::exists)
@@ -21,15 +21,15 @@ import org.gradle.api.tasks.bundling.Zip
import org.gradle.api.tasks.compile.AbstractCompile
import org.gradle.api.tasks.compile.JavaCompile
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptionsImpl
import org.jetbrains.kotlin.gradle.internal.AnnotationProcessingManager
import org.jetbrains.kotlin.gradle.internal.Kapt3GradleSubplugin
import org.jetbrains.kotlin.gradle.internal.Kapt3KotlinGradleSubplugin
import org.jetbrains.kotlin.gradle.internal.initKapt
import org.jetbrains.kotlin.gradle.internal.*
import org.jetbrains.kotlin.gradle.internal.Kapt3KotlinGradleSubplugin.Companion.getKaptClasssesDir
import org.jetbrains.kotlin.gradle.plugin.android.AndroidGradleWrapper
import org.jetbrains.kotlin.gradle.plugin.android.KotlinJillTask
import org.jetbrains.kotlin.gradle.tasks.*
import org.jetbrains.kotlin.incremental.configureMultiProjectIncrementalCompilation
import org.jetbrains.kotlin.incremental.multiproject.ArtifactDifferenceRegistryProviderAndroidWrapper
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import java.io.File
import java.net.URL
import java.util.*
@@ -540,6 +540,7 @@ private fun createSyncOutputTask(
syncTask.kotlinTask = kotlinCompile
kotlinTask.javaOutputDir = javaDir
kotlinAfterJavaTask?.javaOutputDir = javaDir
syncTask.kaptClassesDir = getKaptClasssesDir(project, variantName)
// copying should be executed after a latter task
val previousTask = kotlinAfterJavaTask ?: javaTask
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.gradle.tasks
import ch.qos.logback.core.util.FileUtil
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.OutputDirectory
@@ -23,6 +24,7 @@ import org.gradle.api.tasks.OutputFiles
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.incremental.IncrementalTaskInputs
import org.gradle.api.tasks.incremental.InputFileDetails
import org.jetbrains.kotlin.com.intellij.openapi.util.io.FileUtil.*
import org.jetbrains.kotlin.gradle.plugin.kotlinDebug
import java.io.File
import java.io.ObjectInputStream
@@ -55,6 +57,13 @@ import kotlin.properties.Delegates
internal open class SyncOutputTask : DefaultTask() {
@get:InputFiles
var kotlinOutputDir: File by Delegates.notNull()
@get:InputFiles
var kaptClassesDir: File by Delegates.notNull()
private val classesDirs: List<File>
get() = listOf(kotlinOutputDir, kaptClassesDir).filter(File::exists)
var javaOutputDir: File by Delegates.notNull()
var kotlinTask: KotlinCompile by Delegates.notNull()
@@ -78,13 +87,14 @@ internal open class SyncOutputTask : DefaultTask() {
@Suppress("unused")
@TaskAction
fun execute(inputs: IncrementalTaskInputs): Unit {
val sourceDirs = classesDirs.joinToString()
if (inputs.isIncremental) {
logger.kotlinDebug { "Incremental copying files from $kotlinOutputDir to $javaOutputDir" }
logger.kotlinDebug { "Incremental copying files from $sourceDirs to $javaOutputDir" }
inputs.outOfDate { processIncrementally(it) }
inputs.removed { processIncrementally(it) }
}
else {
logger.kotlinDebug { "Non-incremental copying files from $kotlinOutputDir to $javaOutputDir" }
logger.kotlinDebug { "Non-incremental copying files from $sourceDirs to $javaOutputDir" }
processNonIncrementally()
}
@@ -103,14 +113,16 @@ internal open class SyncOutputTask : DefaultTask() {
timestampsFile.delete()
timestamps.clear()
kotlinOutputDir.walkTopDown().forEach {
copy(it, it.siblingInJavaDir)
for (dir in classesDirs) {
dir.walkTopDown().forEach {
copy(it, it.siblingInJavaDir(baseDir = dir))
}
}
}
private fun processIncrementally(input: InputFileDetails) {
val fileInKotlinDir = input.file
val fileInJavaDir = fileInKotlinDir.siblingInJavaDir
val fileInJavaDir = fileInKotlinDir.siblingInJavaDir()
if (input.isRemoved) {
// file was removed in kotlin dir, remove from java as well
@@ -146,8 +158,10 @@ internal open class SyncOutputTask : DefaultTask() {
}
}
private val File.siblingInJavaDir: File
get() = File(javaOutputDir, this.relativeTo(kotlinOutputDir).path)
private fun File.siblingInJavaDir(baseDir: File? = null): File {
val base = baseDir ?: classesDirs.find { isAncestor(it, this, true) }!!
return File(javaOutputDir, this.relativeTo(base).path)
}
}
private val TIMESTAMP_FILE_NAME = "kotlin-files-in-java-timestamps.bin"
@@ -231,6 +231,12 @@ open class KotlinCompile : AbstractKotlinCompile<K2JVMCompilerArguments>(), Kotl
}
}
if (!incremental) {
logger.kotlinDebug { "Removing all kotlin classes in $destinationDir" }
destinationDir.deleteRecursively()
destinationDir.mkdirs()
}
try {
val exitCode = compilerRunner.runJvmCompiler(sourceRoots.kotlinSourceFiles, sourceRoots.javaSourceRoots, args, environment)
processCompilerExitCode(exitCode)