Incremental annotation processing with KAPT

Add support for incremental annotation processors in KAPT. These
processors conform to https://docs.gradle.org/current/userguide/java_plugin.html#sec:incremental_annotation_processing
specification.

Support is provided by using javac compiler APIs and
recording the source file structure. At runtime, processors
are instrumented with custom Filer that is used to keep track of generated
files. In order to support classpath changes, stub generation task is
used to generated a list of changed FQCNs, and this is simply used by KAPT.
Both worker and non-worker mode are supported.

 #KT-23880
This commit is contained in:
Ivan Gavrilovic
2019-03-02 12:58:45 +00:00
committed by Alexey Tsvetkov
parent 600a955a51
commit 9f14daa682
56 changed files with 2649 additions and 58 deletions
@@ -64,7 +64,8 @@ class IncrementalCompilationOptions(
*/
val outputFiles: List<File>,
val multiModuleICSettings: MultiModuleICSettings,
val modulesInfo: IncrementalModuleInfo
val modulesInfo: IncrementalModuleInfo,
val classpathFqNamesHistory: File? = null
) : CompilationOptions(compilerMode, targetPlatform, reportCategories, reportSeverity, requestedCompilationResults) {
companion object {
const val serialVersionUID: Long = 0
@@ -80,6 +81,7 @@ class IncrementalCompilationOptions(
"multiModuleICSettings=$multiModuleICSettings, " +
"usePreciseJavaTracking=$usePreciseJavaTracking" +
"outputFiles=$outputFiles" +
"classpathFqNamesHistory=$classpathFqNamesHistory" +
")"
}
}
@@ -41,7 +41,10 @@ import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.metadata.K2MetadataCompiler
import org.jetbrains.kotlin.config.Services
import org.jetbrains.kotlin.daemon.common.*
import org.jetbrains.kotlin.daemon.report.*
import org.jetbrains.kotlin.daemon.report.CompileServicesFacadeMessageCollector
import org.jetbrains.kotlin.daemon.report.DaemonMessageReporter
import org.jetbrains.kotlin.daemon.report.DaemonMessageReporterPrintStreamAdapter
import org.jetbrains.kotlin.daemon.report.getICReporter
import org.jetbrains.kotlin.incremental.*
import org.jetbrains.kotlin.incremental.components.ExpectActualTracker
import org.jetbrains.kotlin.incremental.components.LookupTracker
@@ -580,15 +583,19 @@ class CompileServiceImpl(
}
}
val outputFiles = incrementalCompilationOptions.outputFiles.toMutableList()
incrementalCompilationOptions.classpathFqNamesHistory?.let { outputFiles.add(it) }
val compiler = IncrementalJvmCompilerRunner(
workingDir,
javaSourceRoots,
reporter,
buildHistoryFile = incrementalCompilationOptions.multiModuleICSettings.buildHistoryFile,
outputFiles = incrementalCompilationOptions.outputFiles,
outputFiles = outputFiles,
usePreciseJavaTracking = incrementalCompilationOptions.usePreciseJavaTracking,
modulesApiHistory = modulesApiHistory,
kotlinSourceFilesExtensions = allKotlinExtensions
kotlinSourceFilesExtensions = allKotlinExtensions,
classpathFqNamesHistory = incrementalCompilationOptions.classpathFqNamesHistory
)
return try {
compiler.compile(allKotlinFiles, k2jvmArgs, compilerMessageCollector, changedFiles)
@@ -313,7 +313,7 @@ abstract class IncrementalCompilerRunner<
open fun runWithNoDirtyKotlinSources(caches: CacheManager): Boolean = false
private fun processChangesAfterBuild(
protected open fun processChangesAfterBuild(
compilationMode: CompilationMode,
currentBuildInfo: BuildInfo,
dirtyData: DirtyData
@@ -47,6 +47,7 @@ import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import java.io.File
import java.io.ObjectOutputStream
fun makeIncrementally(
cachesDir: File,
@@ -109,7 +110,8 @@ class IncrementalJvmCompilerRunner(
buildHistoryFile: File,
outputFiles: Collection<File>,
private val modulesApiHistory: ModulesApiHistory,
override val kotlinSourceFilesExtensions: List<String> = DEFAULT_KOTLIN_SOURCE_FILES_EXTENSIONS
override val kotlinSourceFilesExtensions: List<String> = DEFAULT_KOTLIN_SOURCE_FILES_EXTENSIONS,
private val classpathFqNamesHistory: File? = null
) : IncrementalCompilerRunner<K2JVMCompilerArguments, IncrementalJvmCachesManager>(
workingDir,
"caches-jvm",
@@ -126,6 +128,8 @@ class IncrementalJvmCompilerRunner(
override fun destinationDir(args: K2JVMCompilerArguments): File =
args.destinationAsFile
private var dirtyClasspathChanges: Collection<FqName> = emptySet<FqName>()
private val psiFileFactory: PsiFileFactory by lazy {
val rootDisposable = Disposer.newDisposable()
val configuration = CompilerConfiguration()
@@ -163,6 +167,7 @@ class IncrementalJvmCompilerRunner(
}
is ChangesEither.Known -> {
dirtyFiles.addByDirtySymbols(classpathChanges.lookupSymbols)
dirtyClasspathChanges = classpathChanges.fqNames
dirtyFiles.addByDirtyClasses(classpathChanges.fqNames)
}
}
@@ -253,6 +258,28 @@ class IncrementalJvmCompilerRunner(
}
}
override fun processChangesAfterBuild(compilationMode: CompilationMode, currentBuildInfo: BuildInfo, dirtyData: DirtyData) {
super.processChangesAfterBuild(compilationMode, currentBuildInfo, dirtyData)
classpathFqNamesHistory ?: return
classpathFqNamesHistory.mkdirs()
val historyFiles = classpathFqNamesHistory.listFiles()
if (dirtyClasspathChanges.isEmpty() && historyFiles.isNotEmpty()) {
// Don't write an empty file. We check there is at least one file so that downstream task can mark what it has processed.
return
}
if (historyFiles.size > 10) {
historyFiles.minBy { it.lastModified() }!!.delete()
}
val newHistoryFile = classpathFqNamesHistory.resolve(System.currentTimeMillis().toString())
ObjectOutputStream(newHistoryFile.outputStream().buffered()).use {
val listOfNames = dirtyClasspathChanges.map { it.toString() }.toList()
it.writeObject(listOfNames)
}
}
override fun postCompilationHook(exitCode: ExitCode) {}
override fun updateCaches(
@@ -0,0 +1,55 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.gradle.incapt;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import java.io.IOException;
import java.io.Writer;
import java.util.Collections;
import java.util.Set;
/** Simple processor that generates a class for every annotated element (class, field, method). */
public class IncrementalProcessor extends AbstractProcessor {
@Override
public Set<String> getSupportedAnnotationTypes() {
return Collections.singleton("example.ExampleAnnotation");
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (annotations.isEmpty()) return true;
for (Element element : roundEnv.getElementsAnnotatedWith(annotations.iterator().next())) {
if (element instanceof TypeElement || element instanceof ExecutableElement || element instanceof VariableElement) {
String name = element.getSimpleName().toString();
name = name.substring(0, 1).toUpperCase() + name.substring(1) + "Generated";
String packageName;
if (element instanceof TypeElement) {
packageName = element.getEnclosingElement().getSimpleName().toString();
}
else {
packageName = element.getEnclosingElement().getEnclosingElement().getSimpleName().toString();
}
try (Writer writer = processingEnv.getFiler().createSourceFile(packageName + "." + name, element).openWriter()) {
writer.append("package ").append(packageName).append(";");
writer.append("\npublic class ").append(name).append(" {}");
}
catch (IOException ignored) {
}
}
}
return false;
}
}
@@ -197,7 +197,7 @@ abstract class BaseGradleIT {
val parallelTasksInProject: Boolean? = null
)
data class KaptOptions(val verbose: Boolean, val useWorkers: Boolean)
data class KaptOptions(val verbose: Boolean, val useWorkers: Boolean, val incrementalKapt: Boolean = false, val includeCompileClasspath: Boolean = true)
open inner class Project(
val projectName: String,
@@ -619,6 +619,8 @@ abstract class BaseGradleIT {
options.kaptOptions?.also { kaptOptions ->
add("-Pkapt.verbose=${kaptOptions.verbose}")
add("-Pkapt.use.worker.api=${kaptOptions.useWorkers}")
add("-Pkapt.incremental.apt=${kaptOptions.incrementalKapt}")
add("-Pkapt.include.compile.classpath=${kaptOptions.includeCompileClasspath}")
}
options.parallelTasksInProject?.let {
@@ -6,12 +6,12 @@ import org.jetbrains.kotlin.gradle.util.getFileByName
import org.jetbrains.kotlin.gradle.util.modify
import org.junit.Test
class KaptIncrementalIT : BaseGradleIT() {
open class KaptIncrementalIT : BaseGradleIT() {
companion object {
private val EXAMPLE_ANNOTATION_REGEX = "@(field:)?example.ExampleAnnotation".toRegex()
}
private fun getProject() =
open fun getProject() =
Project(
"kaptIncrementalCompilationProject",
GradleVersionRequired.None
@@ -25,13 +25,13 @@ class KaptIncrementalIT : BaseGradleIT() {
@Test
fun testAddNewLine() {
val project = Project("simple", directoryPrefix = "kapt2")
val project = getProject()
project.build("clean", "build") {
assertSuccessful()
}
project.projectFile("test.kt").modify { "\n$it" }
project.projectFile("useB.kt").modify { "\n$it" }
project.build("build") {
assertSuccessful()
assertTasksExecuted(":kaptGenerateStubsKotlin", ":compileKotlin")
@@ -0,0 +1,86 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.gradle
import org.jetbrains.kotlin.gradle.util.modify
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
class KaptIncrementalWithAggregatingApt : KaptIncrementalIT() {
override fun getProject() =
Project(
"kaptIncrementalCompilationProject",
GradleVersionRequired.None
).apply {
setupIncrementalAptProject("AGGREGATING")
}
override fun defaultBuildOptions(): BuildOptions =
super.defaultBuildOptions().copy(
incremental = true,
kaptOptions = KaptOptions(
verbose = true,
useWorkers = true,
incrementalKapt = true,
includeCompileClasspath = false
)
)
@Test
fun testIncrementalChanges() {
val project = getProject()
var aptTimestamp = 0L
project.build("clean", "build") {
assertSuccessful()
val classpathHistory =
fileInWorkingDir("build/kotlin/kaptGenerateStubsKotlin/classpath-fq-history").listFiles().asList().single()
val stubsTimestamp = classpathHistory.name.toLong()
aptTimestamp = fileInWorkingDir("build/tmp/kapt3/incApCache/main/last-build-ts.bin").readText().toLong()
assertTrue(stubsTimestamp < aptTimestamp)
}
project.projectFile("useB.kt").modify { current -> "$current\nfun otherFunction() {}" }
project.build("build") {
assertSuccessful()
val newAptTimestamp = fileInWorkingDir("build/tmp/kapt3/incApCache/main/last-build-ts.bin").readText().toLong()
assertTrue(aptTimestamp < newAptTimestamp)
assertEquals(
setOf(
fileInWorkingDir("build/tmp/kapt3/stubs/main/bar/UseBKt.java").absolutePath,
fileInWorkingDir("build/tmp/kapt3/stubs/main/bar/B.java").absolutePath,
fileInWorkingDir("build/tmp/kapt3/stubs/main/baz/UtilKt.java").absolutePath,
fileInWorkingDir("build/tmp/kapt3/stubs/main/foo/A.java").absolutePath
), getProcessedSources(output)
)
}
project.projectFile("JavaClass.java").modify { current ->
val lastBrace = current.lastIndexOf("}")
current.substring(0, lastBrace) + "private void anotherFun() {}\n }"
}
project.build("build") {
assertSuccessful()
assertEquals(
setOf(
project.projectFile("JavaClass.java").absolutePath,
fileInWorkingDir("build/tmp/kapt3/stubs/main/bar/UseBKt.java").absolutePath,
fileInWorkingDir("build/tmp/kapt3/stubs/main/bar/B.java").absolutePath,
fileInWorkingDir("build/tmp/kapt3/stubs/main/baz/UtilKt.java").absolutePath,
fileInWorkingDir("build/tmp/kapt3/stubs/main/foo/A.java").absolutePath
),
getProcessedSources(output)
)
}
}
}
@@ -0,0 +1,113 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.gradle
import org.jetbrains.kotlin.gradle.incapt.IncrementalProcessor
import org.jetbrains.kotlin.gradle.util.modify
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
class KaptIncrementalWithIsolatingApt : KaptIncrementalIT() {
override fun getProject() =
Project(
"kaptIncrementalCompilationProject",
GradleVersionRequired.None
).apply {
setupIncrementalAptProject("ISOLATING")
}
override fun defaultBuildOptions(): BuildOptions =
super.defaultBuildOptions().copy(
incremental = true,
kaptOptions = KaptOptions(
verbose = true,
useWorkers = true,
incrementalKapt = true,
includeCompileClasspath = false
)
)
@Test
fun testIncrementalChanges() {
val project = getProject()
var aptTimestamp = 0L
project.build("clean", "build") {
assertSuccessful()
val classpathHistory =
fileInWorkingDir("build/kotlin/kaptGenerateStubsKotlin/classpath-fq-history").listFiles().asList().single()
val stubsTimestamp = classpathHistory.name.toLong()
aptTimestamp = fileInWorkingDir("build/tmp/kapt3/incApCache/main/last-build-ts.bin").readText().toLong()
assertTrue(stubsTimestamp < aptTimestamp)
}
project.projectFile("useB.kt").modify { current -> "$current\nfun otherFunction() {}" }
project.build("build") {
assertSuccessful()
val newAptTimestamp = fileInWorkingDir("build/tmp/kapt3/incApCache/main/last-build-ts.bin").readText().toLong()
assertTrue(aptTimestamp < newAptTimestamp)
assertEquals(setOf(fileInWorkingDir("build/tmp/kapt3/stubs/main/bar/UseBKt.java").absolutePath), getProcessedSources(output))
}
project.projectFile("B.kt").modify { current ->
val lastBrace = current.lastIndexOf("}")
current.substring(0, lastBrace) + "fun anotherFun() {}\n }"
}
project.build("build") {
assertSuccessful()
assertEquals(
setOf(
fileInWorkingDir("build/tmp/kapt3/stubs/main/bar/B.java").absolutePath,
fileInWorkingDir("build/tmp/kapt3/stubs/main/bar/UseBKt.java").absolutePath
),
getProcessedSources(output)
)
}
}
}
private const val patternApt = "Processing java sources with annotation processors:"
fun getProcessedSources(output: String): Set<String> {
val logging = output.lines().single { it.contains(patternApt) }
val indexOf = logging.indexOf(patternApt) + patternApt.length
return logging.drop(indexOf).split(",").map { it.trim() }.toSet()
}
fun BaseGradleIT.Project.setupIncrementalAptProject(procType: String) {
setupWorkingDir()
val buildFile = projectDir.resolve("build.gradle")
val content = buildFile.readText()
val processorPath = projectDir.resolve("incrementalProcessor.jar")
ZipOutputStream(processorPath.outputStream()).use {
val path = IncrementalProcessor::class.java.name.replace(".", "/") + ".class"
val inputStream = IncrementalProcessor::class.java.classLoader.getResourceAsStream(path)
it.putNextEntry(ZipEntry(path))
it.write(inputStream.readBytes())
it.closeEntry()
it.putNextEntry(ZipEntry("META-INF/gradle/incremental.annotation.processors"))
it.write("${IncrementalProcessor::class.java.name},$procType".toByteArray())
it.closeEntry()
it.putNextEntry(ZipEntry("META-INF/services/javax.annotation.processing.Processor"))
it.write(IncrementalProcessor::class.java.name.toByteArray())
it.closeEntry()
}
val updatedContent = content.replace(
Regex("^\\s*kapt\\s\"org\\.jetbrain.*$", RegexOption.MULTILINE),
" kapt files(\"$processorPath\")"
)
buildFile.writeText(updatedContent)
}
@@ -273,7 +273,8 @@ internal class GradleKotlinCompilerWork @Inject constructor(
usePreciseJavaTracking = icEnv.usePreciseJavaTracking,
outputFiles = outputFiles,
multiModuleICSettings = icEnv.multiModuleICSettings,
modulesInfo = incrementalModuleInfo!!
modulesInfo = incrementalModuleInfo!!,
classpathFqNamesHistory = icEnv.classpathFqNamesHistory
)
log.info("Options for KOTLIN DAEMON: $compilationOptions")
@@ -15,7 +15,8 @@ internal class IncrementalCompilationEnvironment(
val workingDir: File,
val usePreciseJavaTracking: Boolean = false,
val disableMultiModuleIC: Boolean = false,
val multiModuleICSettings: MultiModuleICSettings
val multiModuleICSettings: MultiModuleICSettings,
val classpathFqNamesHistory: File? = null
) : Serializable {
companion object {
const val serialVersionUID: Long = 0
@@ -90,6 +90,7 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
private val USE_WORKER_API = "kapt.use.worker.api"
private val INFO_AS_WARNINGS = "kapt.info.as.warnings"
private val INCLUDE_COMPILE_CLASSPATH = "kapt.include.compile.classpath"
private val INCREMENTAL_APT = "kapt.incremental.apt"
const val KAPT_WORKER_DEPENDENCIES_CONFIGURATION_NAME = "kotlinKaptWorkerDependencies"
@@ -119,6 +120,10 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
return isWorkerAPISupported() && hasProperty(USE_WORKER_API) && property(USE_WORKER_API) == "true"
}
fun Project.isIncrementalKapt(): Boolean {
return hasProperty(INCREMENTAL_APT) && property(INCREMENTAL_APT) == "true"
}
fun Project.isInfoAsWarnings(): Boolean {
return hasProperty(INFO_AS_WARNINGS) && property(INFO_AS_WARNINGS) == "true"
}
@@ -157,6 +162,8 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
private fun Kapt3SubpluginContext.getKaptIncrementalDataDir() = createAndReturnTemporaryKaptDirectory("incrementalData")
private fun Kapt3SubpluginContext.getKaptIncrementalAnnotationProcessingCache() = createAndReturnTemporaryKaptDirectory("incApCache")
private fun Kapt3SubpluginContext.createAndReturnTemporaryKaptDirectory(name: String): File {
val dir = File(project.buildDir, "tmp/kapt3/$name/$sourceSetName")
dir.mkdirs()
@@ -227,7 +234,9 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
)
val kaptGenerateStubsTask = context.createKaptGenerateStubsTask()
val kaptTask = context.createKaptKotlinTask(useWorkerApi = project.isUseWorkerApi())
val kaptTask = context.createKaptKotlinTask(
useWorkerApi = project.isUseWorkerApi(),
classpathHistoryDir = kaptGenerateStubsTask.getClasspathFqNamesHistoryDir())
kaptGenerateStubsTask.source(*kaptConfigurations.toTypedArray())
@@ -365,7 +374,7 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
}
}
private fun Kapt3SubpluginContext.createKaptKotlinTask(useWorkerApi: Boolean): KaptTask {
private fun Kapt3SubpluginContext.createKaptKotlinTask(useWorkerApi: Boolean, classpathHistoryDir: File? = null): KaptTask {
val taskClass = if (useWorkerApi) KaptWithoutKotlincTask::class.java else KaptWithKotlincTask::class.java
val kaptTask = project.tasks.create(getKaptTaskName("kapt"), taskClass)
@@ -379,6 +388,12 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
kaptTask.classesDir = classesOutputDir
kaptTask.includeCompileClasspath = includeCompileClasspath
kaptTask.isIncremental = project.isIncrementalKapt()
if (kaptTask.isIncremental) {
kaptTask.incAptCache = getKaptIncrementalAnnotationProcessingCache()
kaptTask.classpathDirtyFqNamesHistoryDir = project.files(classpathHistoryDir)
}
kotlinCompilation?.run {
output.apply {
addClassesDir { project.files(classesOutputDir).builtBy(kaptTask) }
@@ -403,6 +418,16 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
}
if (kaptTask is KaptWithKotlincTask) {
if (kaptTask.isIncremental) {
kaptTask.pluginOptions.addPluginArgument(
getCompilerPluginId(),
SubpluginOption("incrementalCache", kaptTask.incAptCache!!.absolutePath))
kaptTask.pluginOptions.addPluginArgument(
getCompilerPluginId(),
SubpluginOption("classpathFqNamesHistory", kaptTask.classpathDirtyFqNamesHistoryDir.singleFile!!.absolutePath))
}
buildAndAddOptionsTo(kaptTask, kaptTask.pluginOptions, aptMode = "apt")
}
@@ -74,6 +74,11 @@ open class KaptGenerateStubsTask : KotlinCompile() {
super.setSource(sourceRootsContainer.set(sources))
}
@Internal
override fun getClasspathFqNamesHistoryDir(): File? {
return taskBuildDirectory.resolve("classpath-fq-history")
}
private fun isSourceRootAllowed(source: File): Boolean =
!destinationDir.isParentOf(source) &&
!stubsDir.isParentOf(source) &&
@@ -5,9 +5,11 @@ import org.gradle.api.artifacts.Configuration
import org.gradle.api.file.FileCollection
import org.gradle.api.internal.ConventionTask
import org.gradle.api.tasks.*
import org.gradle.api.tasks.incremental.IncrementalTaskInputs
import org.jetbrains.kotlin.gradle.internal.tasks.TaskWithLocalState
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jetbrains.kotlin.gradle.tasks.cacheOnlyIfEnabledForKotlin
import org.jetbrains.kotlin.gradle.tasks.clearLocalState
import org.jetbrains.kotlin.gradle.tasks.isBuildCacheSupported
import org.jetbrains.kotlin.gradle.utils.isJavaFile
import java.io.File
@@ -45,6 +47,11 @@ abstract class KaptTask : ConventionTask(), TaskWithLocalState {
@get:Internal
internal lateinit var kaptClasspathConfigurations: List<Configuration>
/** Output directory that contains caches necessary to support incremental annotation processing. */
@get:OutputDirectory
@get:Optional
var incAptCache: File? = null
@get:OutputDirectory
internal lateinit var classesDir: File
@@ -60,6 +67,12 @@ abstract class KaptTask : ConventionTask(), TaskWithLocalState {
@get:Input
internal var includeCompileClasspath: Boolean = true
@get:InputFiles
internal var classpathDirtyFqNamesHistoryDir: FileCollection = project.files()
@get:Input
internal var isIncremental = true
// @Internal because _abiClasspath and _nonAbiClasspath are used for actual checks
@get:Internal
val classpath: FileCollection
@@ -142,6 +155,23 @@ abstract class KaptTask : ConventionTask(), TaskWithLocalState {
}
}
// TODO(gavra): Here we assume that kotlinc and javac output is available for incremental runs. We should insert some checks.
@Internal
protected fun getCompiledSources() = listOfNotNull(kotlinCompileTask.destinationDir, kotlinCompileTask.javaOutputDir)
protected fun getChangedFiles(inputs: IncrementalTaskInputs): List<File> {
return if (!isIncremental || !inputs.isIncremental || !getCompiledSources().all { it.exists() }) {
clearLocalState()
emptyList()
} else {
with(mutableSetOf<File>()) {
inputs.outOfDate { this.add(it.file) }
inputs.removed { this.add(it.file) }
return@with this.toList()
}
}
}
private fun hasAnnotationProcessors(file: File): Boolean {
val processorEntryPath = "META-INF/services/javax.annotation.processing.Processor"
@@ -12,17 +12,22 @@ import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.incremental.IncrementalTaskInputs
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
import org.jetbrains.kotlin.compilerRunner.GradleCompilerEnvironment
import org.jetbrains.kotlin.compilerRunner.GradleCompilerRunner
import org.jetbrains.kotlin.compilerRunner.IncrementalCompilationEnvironment
import org.jetbrains.kotlin.gradle.logging.GradleKotlinLogger
import org.jetbrains.kotlin.compilerRunner.OutputItemsCollectorImpl
import org.jetbrains.kotlin.gradle.incremental.ChangedFiles
import org.jetbrains.kotlin.gradle.internal.tasks.allOutputFiles
import org.jetbrains.kotlin.gradle.plugin.PLUGIN_CLASSPATH_CONFIGURATION_NAME
import org.jetbrains.kotlin.gradle.tasks.CompilerPluginOptions
import org.jetbrains.kotlin.gradle.logging.GradlePrintingMessageCollector
import org.jetbrains.kotlin.gradle.tasks.clearLocalState
import org.jetbrains.kotlin.gradle.utils.toSortedPathsArray
import org.jetbrains.kotlin.incremental.ChangedFiles
import java.io.File
open class KaptWithKotlincTask : KaptTask(), CompilerArgumentAwareWithInput<K2JVMCompilerArguments> {
@get:Internal
@@ -46,17 +51,28 @@ open class KaptWithKotlincTask : KaptTask(), CompilerArgumentAwareWithInput<K2JV
args.pluginClasspaths = pluginClasspath.toSortedPathsArray()
val pluginOptionsWithKapt: CompilerPluginOptions = pluginOptions.withWrappedKaptOptions(withApClasspath = kaptClasspath)
val pluginOptionsWithKapt: CompilerPluginOptions = pluginOptions.withWrappedKaptOptions(
withApClasspath = kaptClasspath,
changedFiles = changedFiles,
compiledSourcesDir = getCompiledSources())
args.pluginOptions = (pluginOptionsWithKapt.arguments + args.pluginOptions!!).toTypedArray()
args.verbose = project.hasProperty("kapt.verbose") && project.property("kapt.verbose").toString().toBoolean() == true
}
/**
* This will be part of the subplugin options that is not part of the input snapshotting, so just initialize it. Actual value is set
* in the task action.
*/
private var changedFiles: List<File> = emptyList()
@TaskAction
fun compile() {
fun compile(inputs: IncrementalTaskInputs) {
logger.debug("Running kapt annotation processing using the Kotlin compiler")
checkAnnotationProcessorClasspath()
clearLocalState()
changedFiles = getChangedFiles(inputs)
val args = prepareCompilerArguments()
@@ -9,8 +9,10 @@ import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.incremental.IncrementalTaskInputs
import org.gradle.workers.IsolationMode
import org.gradle.workers.WorkerExecutor
import org.jetbrains.kotlin.gradle.incremental.ChangedFiles
import org.jetbrains.kotlin.gradle.internal.Kapt3KotlinGradleSubplugin.Companion.KAPT_WORKER_DEPENDENCIES_CONFIGURATION_NAME
import org.jetbrains.kotlin.gradle.plugin.KotlinAndroidPluginWrapper
import org.jetbrains.kotlin.gradle.tasks.clearLocalState
@@ -45,10 +47,9 @@ open class KaptWithoutKotlincTask @Inject constructor(private val workerExecutor
lateinit var javacOptions: Map<String, String>
@TaskAction
fun compile() {
fun compile(inputs: IncrementalTaskInputs) {
logger.info("Running kapt annotation processing using the Gradle Worker API")
checkAnnotationProcessorClasspath()
clearLocalState()
val compileClasspath = classpath.files.toMutableList()
if (project.plugins.none { it is KotlinAndroidPluginWrapper }) {
@@ -66,6 +67,11 @@ open class KaptWithoutKotlincTask @Inject constructor(private val workerExecutor
compileClasspath,
javaSourceRoots.toList(),
getChangedFiles(inputs),
getCompiledSources(),
incAptCache,
classpathDirtyFqNamesHistoryDir.singleOrNull(),
destinationDir,
classesDir,
stubsDir,
@@ -155,6 +161,11 @@ private class KaptExecution @Inject constructor(
compileClasspath,
javaSourceRoots,
changedFiles,
compiledSources,
incAptCache,
classpathFqNamesHistory,
sourcesOutputDir,
classesOutputDir,
stubsOutputDir,
@@ -186,6 +197,11 @@ private data class KaptOptionsForWorker(
val compileClasspath: List<File>,
val javaSourceRoots: List<File>,
val changedFiles: List<File>,
val compiledSources: List<File>,
val incAptCache: File?,
val classpathFqNamesHistory: File?,
val sourcesOutputDir: File,
val classesOutputDir: File,
val stubsOutputDir: File,
@@ -43,15 +43,24 @@ fun encodePluginOptions(options: Map<String, List<String>>): String {
return Base64.getEncoder().encodeToString(os.toByteArray())
}
internal fun CompilerPluginOptions.withWrappedKaptOptions(withApClasspath: Iterable<File>): CompilerPluginOptions {
internal fun CompilerPluginOptions.withWrappedKaptOptions(
withApClasspath: Iterable<File>, changedFiles: List<File> = emptyList(), compiledSourcesDir: List<File> = emptyList()
): CompilerPluginOptions {
val resultOptionsByPluginId: MutableMap<String, List<SubpluginOption>> =
subpluginOptionsByPluginId.toMutableMap()
resultOptionsByPluginId.compute(Kapt3KotlinGradleSubplugin.KAPT_SUBPLUGIN_ID) { _, kaptOptions ->
val kaptOptionsWithClasspath =
kaptOptions.orEmpty() + withApClasspath.map { FilesSubpluginOption("apclasspath", listOf(it)) }
val changedFilesOption = FilesSubpluginOption("changedFile", changedFiles).takeIf { changedFiles.isNotEmpty() }
val compiledSourcesOption =
FilesSubpluginOption("compiledSourcesDir", compiledSourcesDir).takeIf { compiledSourcesDir.isNotEmpty() }
wrapPluginOptions(kaptOptionsWithClasspath, "configuration")
val kaptOptionsWithClasspath =
kaptOptions.orEmpty() +
withApClasspath.map { FilesSubpluginOption("apclasspath", listOf(it)) } +
changedFilesOption +
compiledSourcesOption
wrapPluginOptions(kaptOptionsWithClasspath.filterNotNull(), "configuration")
}
val result = CompilerPluginOptions()
@@ -79,6 +79,7 @@ class Android25ProjectHandler(
.map { it.file.absolutePath }
.toTypedArray()
}
kotlinTask.javaOutputDir = javaTask.destinationDir
}
override fun getSourceProviders(variantData: BaseVariant): Iterable<SourceProvider> =
@@ -433,7 +433,8 @@ open class KotlinCompile : AbstractKotlinCompile<K2JVMCompilerArguments>(), Kotl
taskBuildDirectory,
usePreciseJavaTracking = usePreciseJavaTracking,
disableMultiModuleIC = disableMultiModuleIC(),
multiModuleICSettings = multiModuleICSettings
multiModuleICSettings = multiModuleICSettings,
classpathFqNamesHistory = getClasspathFqNamesHistoryDir()
)
} else null
@@ -475,6 +476,10 @@ open class KotlinCompile : AbstractKotlinCompile<K2JVMCompilerArguments>(), Kotl
return false
}
@Optional
@Internal
internal open fun getClasspathFqNamesHistoryDir(): File? = null
// override setSource to track source directory sets and files (for generated android folders)
override fun setSource(sources: Any?) {
sourceRootsContainer.set(sources)
@@ -34,7 +34,7 @@ object Kapt {
logger.info { options.logString("stand-alone mode") }
val javaSourceFiles = options.collectJavaSourceFiles()
val javaSourceFiles = options.collectJavaSourceFiles(kaptContext.cacheManager)
val processorLoader = ProcessorLoader(options, logger)
@@ -13,6 +13,7 @@ import com.sun.tools.javac.util.Log
import com.sun.tools.javac.util.Options
import org.jetbrains.kotlin.base.kapt3.KaptFlag
import org.jetbrains.kotlin.base.kapt3.KaptOptions
import org.jetbrains.kotlin.kapt3.base.incremental.JavaClassCacheManager
import org.jetbrains.kotlin.kapt3.base.javac.KaptJavaCompiler
import org.jetbrains.kotlin.kapt3.base.javac.KaptJavaFileManager
import org.jetbrains.kotlin.kapt3.base.javac.KaptJavaLog
@@ -29,6 +30,7 @@ open class KaptContext(val options: KaptOptions, val withJdk: Boolean, val logge
val fileManager: KaptJavaFileManager
private val javacOptions: Options
val javaLog: KaptJavaLog
val cacheManager: JavaClassCacheManager?
protected open fun preregisterTreeMaker(context: Context) {}
@@ -76,8 +78,14 @@ open class KaptContext(val options: KaptOptions, val withJdk: Boolean, val logge
put("accessInternalAPI", "true")
}
val compileClasspath = if (options.changedFiles.isEmpty()) {
options.compileClasspath
} else {
options.compileClasspath + options.compiledSources
}
putJavacOption("CLASSPATH", "CLASS_PATH",
options.compileClasspath.joinToString(File.pathSeparator) { it.canonicalPath })
compileClasspath.joinToString(File.pathSeparator) { it.canonicalPath })
@Suppress("SpellCheckingInspection")
putJavacOption("PROCESSORPATH", "PROCESSOR_PATH",
@@ -106,10 +114,15 @@ open class KaptContext(val options: KaptOptions, val withJdk: Boolean, val logge
ClassReader.instance(context).saveParameterNames = true
cacheManager = options.incrementalCache?.let {
JavaClassCacheManager(it, options.classpathFqNamesHistory!!)
}
javaLog = compiler.log as KaptJavaLog
}
override fun close() {
cacheManager?.close()
compiler.close()
fileManager.close()
}
@@ -5,6 +5,8 @@
package org.jetbrains.kotlin.base.kapt3
import org.jetbrains.kotlin.kapt3.base.incremental.JavaClassCacheManager
import org.jetbrains.kotlin.kapt3.base.incremental.SourcesToReprocess
import java.io.File
import java.nio.file.Files
@@ -13,6 +15,11 @@ class KaptOptions(
val compileClasspath: List<File>,
val javaSourceRoots: List<File>,
val changedFiles: List<File>,
val compiledSources: List<File>,
val incrementalCache: File?,
val classpathFqNamesHistory: File?,
val sourcesOutputDir: File,
val classesOutputDir: File,
val stubsOutputDir: File,
@@ -36,6 +43,11 @@ class KaptOptions(
val compileClasspath: MutableList<File> = mutableListOf()
val javaSourceRoots: MutableList<File> = mutableListOf()
val changedFiles: MutableList<File> = mutableListOf()
val compiledSources: MutableList<File> = mutableListOf()
var incrementalCache: File? = null
var classpathFqNamesHistory: File? = null
var sourcesOutputDir: File? = null
var classesOutputDir: File? = null
var stubsOutputDir: File? = null
@@ -62,6 +74,7 @@ class KaptOptions(
return KaptOptions(
projectBaseDir, compileClasspath, javaSourceRoots,
changedFiles, compiledSources, incrementalCache, classpathFqNamesHistory,
sourcesOutputDir, classesOutputDir, stubsOutputDir, incrementalDataOutputDir,
processingClasspath, processors, processingOptions, javacOptions, KaptFlags.fromSet(flags),
mode, detectMemoryLeaks
@@ -116,12 +129,25 @@ enum class AptMode(override val stringValue: String) : KaptSelector {
get() = this != APT_ONLY
}
fun KaptOptions.collectJavaSourceFiles(): List<File> {
return (javaSourceRoots + stubsOutputDir)
.sortedBy { Files.isSymbolicLink(it.toPath()) } // Get non-symbolic paths first
.flatMap { root -> root.walk().filter { it.isFile && it.extension == "java" }.toList() }
.sortedBy { Files.isSymbolicLink(it.toPath()) } // This time is for .java files
.distinctBy { it.canonicalPath }
fun KaptOptions.collectJavaSourceFiles(cacheManager: JavaClassCacheManager? = null): List<File> {
fun allSources(): List<File> {
return (javaSourceRoots + stubsOutputDir)
.sortedBy { Files.isSymbolicLink(it.toPath()) } // Get non-symbolic paths first
.flatMap { root -> root.walk().filter { it.isFile && it.extension == "java" }.toList() }
.sortedBy { Files.isSymbolicLink(it.toPath()) } // This time is for .java files
.distinctBy { it.canonicalPath }
}
return if (cacheManager != null && changedFiles.isNotEmpty()) {
val toReprocess = cacheManager.invalidateAndGetDirtyFiles(changedFiles.filter { it.extension == "java" })
when (toReprocess) {
is SourcesToReprocess.FullRebuild -> allSources()
is SourcesToReprocess.Incremental -> toReprocess.toReprocess.filter { it.exists() }
}
} else {
allSources()
}
}
fun KaptOptions.logString(additionalInfo: String = "") = buildString {
@@ -146,4 +172,9 @@ fun KaptOptions.logString(additionalInfo: String = "") = buildString {
appendln("AP options: $processingOptions")
appendln("Javac options: $javacOptions")
appendln("[incremental apt] Changed files: $changedFiles")
appendln("[incremental apt] Compiled sources directories: ${compiledSources.joinToString()}")
appendln("[incremental apt] Cache directory for incremental compilation: $incrementalCache")
appendln("[incremental apt] Classpath fq names history dir: $classpathFqNamesHistory")
}
@@ -7,6 +7,9 @@ package org.jetbrains.kotlin.kapt3.base
import org.jetbrains.kotlin.base.kapt3.KaptFlag
import org.jetbrains.kotlin.base.kapt3.KaptOptions
import org.jetbrains.kotlin.kapt3.base.incremental.DeclaredProcType
import org.jetbrains.kotlin.kapt3.base.incremental.IncrementalProcessor
import org.jetbrains.kotlin.kapt3.base.incremental.getIncrementalProcessorsFromClasspath
import org.jetbrains.kotlin.kapt3.base.util.KaptLogger
import org.jetbrains.kotlin.kapt3.base.util.info
import java.io.Closeable
@@ -17,7 +20,7 @@ import java.net.URLClassLoader
import java.util.*
import javax.annotation.processing.Processor
class LoadedProcessors(val processors: List<Processor>, val classLoader: ClassLoader)
class LoadedProcessors(val processors: List<IncrementalProcessor>, val classLoader: ClassLoader)
open class ProcessorLoader(private val options: KaptOptions, private val logger: KaptLogger) : Closeable {
private var annotationProcessingClassLoader: URLClassLoader? = null
@@ -48,7 +51,21 @@ open class ProcessorLoader(private val options: KaptOptions, private val logger:
logger.info { "Annotation processors: " + processors.joinToString { it::class.java.canonicalName } }
}
return LoadedProcessors(processors, classLoader)
return LoadedProcessors(wrapInIncrementalProcessor(processors, classpath), classLoader)
}
private fun wrapInIncrementalProcessor(processors: List<Processor>, classpath: Iterable<File>): List<IncrementalProcessor> {
val processorNames = processors.map {it.javaClass.name}.toSet()
val processorsInfo: Map<String, DeclaredProcType> = getIncrementalProcessorsFromClasspath(processorNames, classpath)
val nonIncremental = processorNames.filter { !processorsInfo.containsKey(it) }
return if (nonIncremental.isNotEmpty()) {
logger.info("Incremental KAPT support is disabled. Processors that are not incremental: ${nonIncremental.joinToString()}.")
processors.map { IncrementalProcessor(it, DeclaredProcType.NON_INCREMENTAL) }
} else {
processors.map { IncrementalProcessor(it, processorsInfo.getValue(it.javaClass.name)) }
}
}
open fun doLoadProcessors(classLoader: URLClassLoader): List<Processor> {
@@ -5,13 +5,16 @@
package org.jetbrains.kotlin.kapt3.base
import com.sun.tools.javac.comp.CompileStates.*
import com.sun.source.util.Trees
import com.sun.tools.javac.comp.CompileStates.CompileState
import com.sun.tools.javac.main.JavaCompiler
import com.sun.tools.javac.processing.AnnotationProcessingError
import com.sun.tools.javac.processing.JavacFiler
import com.sun.tools.javac.processing.JavacProcessingEnvironment
import com.sun.tools.javac.tree.JCTree
import org.jetbrains.kotlin.base.kapt3.KaptFlag
import org.jetbrains.kotlin.base.kapt3.collectJavaSourceFiles
import org.jetbrains.kotlin.kapt3.base.incremental.*
import org.jetbrains.kotlin.kapt3.base.util.KaptBaseError
import org.jetbrains.kotlin.kapt3.base.util.isJava9OrLater
import org.jetbrains.kotlin.kapt3.base.util.measureTimeMillisWithResult
@@ -27,10 +30,11 @@ import com.sun.tools.javac.util.List as JavacList
fun KaptContext.doAnnotationProcessing(
javaSourceFiles: List<File>,
processors: List<Processor>,
processors: List<IncrementalProcessor>,
additionalSources: JavacList<JCTree.JCCompilationUnit> = JavacList.nil()
) {
val processingEnvironment = JavacProcessingEnvironment.instance(context)
val wrappedProcessors = processors.map { ProcessorWrapper(it) }
val compilerAfterAP: JavaCompiler
@@ -42,25 +46,42 @@ fun KaptContext.doAnnotationProcessing(
compiler.initProcessAnnotations(wrappedProcessors)
}
if (logger.isVerbose) {
logger.info("Processing java sources with annotation processors: ${javaSourceFiles.joinToString()}")
}
val parsedJavaFiles = parseJavaFiles(javaSourceFiles)
val listener = cacheManager?.let {
if (processors.any { it.kind == DeclaredProcType.NON_INCREMENTAL}) return@let null
val recordTypesListener = MentionedTypesTaskListener(cacheManager.javaCache, Trees.instance(processingEnvironment))
compiler.getTaskListeners().add(recordTypesListener)
recordTypesListener
}
compilerAfterAP = try {
javaLog.interceptorData.files = parsedJavaFiles.map { it.sourceFile to it }.toMap()
val analyzedFiles = compiler.stopIfErrorOccurred(
CompileState.PARSE, compiler.enterTrees(parsedJavaFiles + additionalSources)
)
listener?.let { compiler.getTaskListeners().remove(it) }
val l = System.currentTimeMillis()
if (isJava9OrLater()) {
val processAnnotationsMethod = compiler.javaClass.getMethod("processAnnotations", JavacList::class.java)
processAnnotationsMethod.invoke(compiler, analyzedFiles)
compiler
} else {
compiler.processAnnotations(analyzedFiles)
val processAnnotations = compiler.processAnnotations(analyzedFiles)
processAnnotations
}
} catch (e: AnnotationProcessingError) {
throw KaptBaseError(KaptBaseError.Kind.EXCEPTION, e.cause ?: e)
}
cacheManager?.updateCache(processors)
val log = compilerAfterAP.log
val filer = processingEnvironment.filer as JavacFiler
@@ -0,0 +1,78 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.kapt3.base.incremental
import java.io.File
import java.io.Serializable
class IncrementalAptCache : Serializable {
private val aggregatingGenerated: MutableSet<File> = mutableSetOf()
private val isolatingMapping: MutableMap<File, File> = mutableMapOf()
// Annotations claimed by aggregating annotation processors
private val aggregatingClaimedAnnotations: MutableSet<String> = mutableSetOf()
var isIncremental = true
private set
fun updateCache(processors: List<IncrementalProcessor>): Boolean {
val aggregating = mutableListOf<IncrementalProcessor>()
val isolating = mutableListOf<IncrementalProcessor>()
val nonIncremental = mutableListOf<IncrementalProcessor>()
processors.forEach {
when (it.getRuntimeType()) {
RuntimeProcType.AGGREGATING -> aggregating.add(it)
RuntimeProcType.ISOLATING -> isolating.add(it)
RuntimeProcType.NON_INCREMENTAL -> nonIncremental.add(it)
}
}
if (nonIncremental.isNotEmpty()) {
invalidateCache()
return false
}
aggregatingGenerated.clear()
aggregatingGenerated.addAll(aggregating.flatMap { it.getGeneratedToSources().keys })
aggregatingClaimedAnnotations.clear()
aggregatingClaimedAnnotations.addAll(aggregating.flatMap { it.supportedAnnotationTypes })
for (isolatingProcessor in isolating) {
isolatingProcessor.getGeneratedToSources().forEach {
isolatingMapping[it.key] = it.value!!
}
}
return true
}
fun invalidateAggregatingAndGetAnnotations(): Set<String> {
aggregatingGenerated.forEach { it.delete() }
aggregatingGenerated.clear()
return aggregatingClaimedAnnotations
}
fun invalidateIsolatingGenerated(fromSources: Set<File>) {
var changedSources = fromSources.toSet()
// We need to do it in a loop because mapping could be: [AGenerated.java -> A.java, AGeneratedGenerated.java -> AGenerated.java]
while(changedSources.isNotEmpty()) {
val generated = isolatingMapping.filter { changedSources.contains(it.value) }.keys
generated.forEach {
it.delete()
isolatingMapping.remove(it)
}
changedSources = generated
}
}
private fun invalidateCache() {
isIncremental = false
aggregatingGenerated.clear()
isolatingMapping.clear()
}
}
@@ -0,0 +1,157 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.kapt3.base.incremental
import java.io.*
// TODO(gavra): switch away from Java serialization
class JavaClassCacheManager(private val file: File, private val classpathFqNamesHistory: File) : Closeable {
internal val javaCacheFile = file.resolve("java-cache.bin")
internal val javaCache = maybeGetJavaCacheFromFile()
internal val aptCacheFile = file.resolve("apt-cache.bin")
internal val aptCache = maybeGetAptCacheFromFile()
internal val lastBuildTimestamp = file.resolve("last-build-ts.bin")
private var closed = false
private fun getDirtyFqNamesFromClasspath(): ClasspathChanged {
if (!lastBuildTimestamp.exists()) return ClasspathChanged.FullRebuild
val lastTimestamp = lastBuildTimestamp.readText()
val (before, after) = classpathFqNamesHistory.listFiles().partition { it.name < lastTimestamp }
if (before.isEmpty()) {
return ClasspathChanged.FullRebuild
}
val dirtyFqNames = mutableSetOf<String>()
after.forEach{ file ->
ObjectInputStream(file.inputStream().buffered()).use {
@Suppress("UNCHECKED_CAST")
dirtyFqNames.addAll(it.readObject() as Collection<String>)
}
}
return if (dirtyFqNames.isNotEmpty()) {
// TODO(gavra): We need to handle constants from classpath that might change between runs being incremental. One solution
// would be to fetch the changed symbols alongside changed fqNames, and to check if the symbol is a constant using ASM.
ClasspathChanged.FullRebuild
} else {
ClasspathChanged.Incremental(dirtyFqNames)
}
}
fun updateCache(processors: List<IncrementalProcessor>) {
if (!aptCache.updateCache(processors)) {
javaCache.invalidateAll()
}
}
/**
* From set of changed sources, get list of files to recompile using structural information and dependency information from
* annotation processing.
*/
fun invalidateAndGetDirtyFiles(changedSources: Collection<File>): SourcesToReprocess {
if (!aptCache.isIncremental) {
return SourcesToReprocess.FullRebuild
}
val dirtyFqNamesFromClasspath = getDirtyFqNamesFromClasspath()
return when (dirtyFqNamesFromClasspath) {
is ClasspathChanged.FullRebuild -> SourcesToReprocess.FullRebuild
is ClasspathChanged.Incremental -> {
val changes = Changes(changedSources, dirtyFqNamesFromClasspath.dirtyFqNames)
val filesToReprocess = javaCache.invalidateEntriesForChangedFiles(changes)
when (filesToReprocess) {
is SourcesToReprocess.FullRebuild -> SourcesToReprocess.FullRebuild
is SourcesToReprocess.Incremental -> {
val toReprocess = filesToReprocess.toReprocess.toMutableSet()
aptCache.invalidateIsolatingGenerated(toReprocess)
if (!toReprocess.isEmpty()) {
// only if there are some files to reprocess we should invalidate the aggregating ones
toReprocess.addAll(
javaCache.invalidateEntriesAnnotatedWith(aptCache.invalidateAggregatingAndGetAnnotations())
)
}
SourcesToReprocess.Incremental(toReprocess.toList())
}
}
}
}
}
private fun maybeGetAptCacheFromFile(): IncrementalAptCache{
return if (aptCacheFile.exists()) {
try {
ObjectInputStream(BufferedInputStream(aptCacheFile.inputStream())).use {
it.readObject() as IncrementalAptCache
}
} catch (e: Throwable) {
// cache corrupt
IncrementalAptCache()
}
} else {
IncrementalAptCache()
}
}
private fun maybeGetJavaCacheFromFile(): JavaClassCache {
return if (javaCacheFile.exists()) {
try {
ObjectInputStream(BufferedInputStream(javaCacheFile.inputStream())).use {
it.readObject() as JavaClassCache
}
} catch (e: Throwable) {
JavaClassCache()
}
} else {
JavaClassCache()
}
}
override fun close() {
if (closed) return
with(javaCacheFile) {
delete()
parentFile.mkdirs()
ObjectOutputStream(BufferedOutputStream(outputStream())).use {
it.writeObject(javaCache)
}
}
with(aptCacheFile) {
delete()
parentFile.mkdirs()
ObjectOutputStream(BufferedOutputStream(outputStream())).use {
it.writeObject(aptCache)
}
}
with(lastBuildTimestamp) {
writeText(System.currentTimeMillis().toString())
}
closed = true
}
}
sealed class SourcesToReprocess {
class Incremental(val toReprocess: List<File>): SourcesToReprocess()
object FullRebuild : SourcesToReprocess()
}
sealed class ClasspathChanged {
class Incremental(val dirtyFqNames: Set<String>) : ClasspathChanged()
object FullRebuild : ClasspathChanged()
}
@@ -0,0 +1,170 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.kapt3.base.incremental
import com.sun.tools.javac.processing.JavacProcessingEnvironment
import java.io.File
import java.io.Serializable
import java.net.URI
class JavaClassCache() : Serializable {
private var sourceCache = mutableMapOf<URI, SourceFileStructure>()
/** Map from types to files they are mentioned in. */
@Transient
private var dependencyCache = mutableMapOf<String, MutableSet<URI>>()
@Transient
private var nonTransitiveCache = mutableMapOf<String, MutableSet<URI>>()
fun addSourceStructure(sourceStructure: SourceFileStructure) {
sourceCache[sourceStructure.sourceFile] = sourceStructure
}
private fun readObject(input: java.io.ObjectInputStream) {
@Suppress("UNCHECKED_CAST")
sourceCache = input.readObject() as MutableMap<URI, SourceFileStructure>
dependencyCache = HashMap(sourceCache.size * 4)
for (sourceInfo in sourceCache.values) {
for (mentionedType in sourceInfo.getMentionedTypes()) {
val dependants = dependencyCache[mentionedType] ?: mutableSetOf()
dependants.add(sourceInfo.sourceFile)
dependencyCache[mentionedType] = dependants
}
}
nonTransitiveCache = HashMap(sourceCache.size * 2)
for (sourceInfo in sourceCache.values) {
for (privateType in sourceInfo.getPrivateTypes()) {
val dependants = nonTransitiveCache[privateType] ?: mutableSetOf()
dependants.add(sourceInfo.sourceFile)
nonTransitiveCache[privateType] = dependants
}
}
}
fun isAlreadyProcessed(sourceFile: URI) = sourceCache.containsKey(sourceFile)
/** Used for testing only. */
internal fun getStructure(sourceFile: File) = sourceCache[sourceFile.toURI()]
/**
* Invalidate cache entires for the specified files, and any files that depend on the changed ones. It returns the set of files that
* should be re-processed.
* */
fun invalidateEntriesForChangedFiles(changes: Changes): SourcesToReprocess {
val allDirtyFiles = mutableSetOf<URI>()
var currentDirtyFiles = changes.sourceChanges.map { it.toURI() }
// check for constants first because they cause full rebuilt
for (sourceChange in currentDirtyFiles) {
val structure = sourceCache[sourceChange] ?: continue
if (structure.getDefinedConstants().isNotEmpty()) {
// TODO(gavra): compare constant values, and only full rebuild if the value changes
invalidateAll()
return SourcesToReprocess.FullRebuild
}
}
while (currentDirtyFiles.isNotEmpty()) {
val nextRound = mutableSetOf<URI>()
for (dirtyFile in currentDirtyFiles) {
allDirtyFiles.add(dirtyFile)
val structure = sourceCache.remove(dirtyFile) ?: continue
val dirtyTypes = structure.getDeclaredTypes()
dirtyTypes.forEach { type ->
nonTransitiveCache[type]?.let {
allDirtyFiles.addAll(it)
}
dependencyCache[type]?.let {
nextRound.addAll(it)
}
}
}
currentDirtyFiles = nextRound.filter { !allDirtyFiles.contains(it) }
}
return SourcesToReprocess.Incremental(allDirtyFiles.map { File(it) })
}
/**
* For aggregating annotation processors, we always need to reprocess all files annotated with an annotation claimed by the aggregating
* annotation processor. This search is not transitive.
*/
fun invalidateEntriesAnnotatedWith(annotations: Set<String>): Set<File> {
val patterns = annotations.map { JavacProcessingEnvironment.validImportStringToPattern(it) }
val matchesAnyPattern = { name: String -> patterns.any { it.matcher(name).matches() } }
val toReprocess = mutableSetOf<URI>()
for (cacheEntry in sourceCache) {
if (cacheEntry.value.getMentionedAnnotations().any(matchesAnyPattern)) {
toReprocess.add(cacheEntry.key)
}
}
toReprocess.forEach {
sourceCache.remove(it)
}
return toReprocess.map { File(it) }.toSet()
}
internal fun invalidateAll() {
sourceCache.clear()
}
}
private val IGNORE_TYPES = { name: String -> name == "java.lang.Object" }
class SourceFileStructure(
val sourceFile: URI
) : Serializable {
private val declaredTypes: MutableSet<String> = mutableSetOf()
private val mentionedTypes: MutableSet<String> = mutableSetOf()
private val privateTypes: MutableSet<String> = mutableSetOf()
private val definedConstants: MutableMap<String, Any> = mutableMapOf()
private val mentionedAnnotations: MutableSet<String> = mutableSetOf()
fun getDeclaredTypes(): Set<String> = declaredTypes
fun getMentionedTypes(): Set<String> = mentionedTypes
fun getPrivateTypes(): Set<String> = privateTypes
fun getDefinedConstants(): Map<String, Any> = definedConstants
fun getMentionedAnnotations(): Set<String> = mentionedAnnotations
fun addDeclaredType(declaredType: String) {
declaredTypes.add(declaredType)
}
fun addMentionedType(mentionedType: String) {
mentionedType.takeUnless(IGNORE_TYPES)?.let {
mentionedTypes.add(it)
}
}
fun addDefinedConstant(name: String, value: Any) {
definedConstants[name] = value
}
fun addMentionedAnnotations(name: String) {
mentionedAnnotations.add(name)
}
fun addPrivateType(name: String) {
privateTypes.add(name)
}
}
class Changes(val sourceChanges: Collection<File>, val dirtyFqNamesFromClasspath: Set<String>)
@@ -0,0 +1,62 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.kapt3.base.incremental
import java.io.File
import java.io.InputStream
import java.util.*
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
private val INCREMENTAL_DECLARED_TYPES =
setOf(DeclaredProcType.AGGREGATING.name, DeclaredProcType.ISOLATING.name, DeclaredProcType.DYNAMIC.name)
private const val INCREMENTAL_ANNOTATION_FLAG = "META-INF/gradle/incremental.annotation.processors"
/** Checks the incremental annotation processor information for the annotation processor classpath. */
fun getIncrementalProcessorsFromClasspath(
names: Set<String>, classpath: Iterable<File>): Map<String, DeclaredProcType> {
val finalValues = mutableMapOf<String, DeclaredProcType>()
classpath.forEach { entry ->
val fromEntry = processSingleClasspathEntry(entry)
fromEntry.filter { names.contains(it.key) }.forEach { finalValues[it.key] = it.value }
if (finalValues.size == names.size) return finalValues
}
return finalValues
}
private fun processSingleClasspathEntry(rootFile: File): Map<String, DeclaredProcType> {
val text: List<String> = when {
rootFile.isDirectory -> {
val markerFile = rootFile.resolve(INCREMENTAL_ANNOTATION_FLAG)
if (markerFile.exists()) {
markerFile.bufferedReader().readLines()
} else {
emptyList()
}
}
rootFile.extension == "jar" -> ZipFile(rootFile).use { zipFile ->
val content: InputStream? = zipFile.getInputStream(ZipEntry(INCREMENTAL_ANNOTATION_FLAG))
content?.bufferedReader()?.readLines() ?: emptyList()
}
else -> emptyList()
}
val nameToType = mutableMapOf<String, DeclaredProcType>()
for (line in text) {
val parts = line.split(",")
if (parts.size == 2) {
val kind = parts[1].toUpperCase(Locale.ENGLISH)
if (INCREMENTAL_DECLARED_TYPES.contains(kind)) {
nameToType[parts[0]] = enumValueOf(kind)
}
}
}
return nameToType
}
@@ -0,0 +1,163 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.kapt3.base.incremental
import com.sun.tools.javac.code.Symbol
import java.io.File
import java.io.InputStream
import java.net.URI
import java.util.*
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import javax.annotation.processing.Filer
import javax.annotation.processing.ProcessingEnvironment
import javax.annotation.processing.Processor
import javax.lang.model.element.Element
import javax.lang.model.element.PackageElement
import javax.tools.FileObject
import javax.tools.JavaFileManager
import javax.tools.JavaFileObject
private val ALLOWED_RUNTIME_TYPES = setOf(RuntimeProcType.AGGREGATING.name, RuntimeProcType.ISOLATING.name)
class IncrementalProcessor(private val processor: Processor, val kind: DeclaredProcType) : Processor by processor {
private var dependencyCollector = lazy { createDependencyCollector() }
override fun init(processingEnv: ProcessingEnvironment) {
if (kind == DeclaredProcType.NON_INCREMENTAL) {
processor.init(processingEnv)
} else {
val originalFiler = processingEnv.filer
val incrementalFiler = IncrementalFiler(originalFiler)
val incProcEnvironment = IncrementalProcessingEnvironment(processingEnv, incrementalFiler)
processor.init(incProcEnvironment)
incrementalFiler.dependencyCollector = dependencyCollector.value
}
}
/** This has to invoked only once the processors has been initialized, because this accesses Processor.getSupportedOptions(). */
private fun createDependencyCollector(): AnnotationProcessorDependencyCollector {
val type = if (kind == DeclaredProcType.DYNAMIC) {
val fromOptions = supportedOptions.singleOrNull { it.startsWith("org.gradle.annotation.processing.") }
if (fromOptions == null) {
RuntimeProcType.NON_INCREMENTAL
} else {
val declaredType = fromOptions.drop("org.gradle.annotation.processing.".length).toUpperCase()
if (ALLOWED_RUNTIME_TYPES.contains(declaredType)) {
enumValueOf(declaredType)
} else {
RuntimeProcType.NON_INCREMENTAL
}
}
} else {
kind.toRuntimeType()
}
return AnnotationProcessorDependencyCollector(type)
}
fun getGeneratedToSources() = dependencyCollector.value.getGeneratedToSources()
fun getRuntimeType(): RuntimeProcType = dependencyCollector.value.getRuntimeType()
}
internal class IncrementalProcessingEnvironment(private val processingEnv: ProcessingEnvironment, private val incFiler: IncrementalFiler) :
ProcessingEnvironment by processingEnv {
override fun getFiler(): Filer = incFiler
}
internal class IncrementalFiler(private val filer: Filer) : Filer by filer {
internal var dependencyCollector: AnnotationProcessorDependencyCollector? = null
override fun createSourceFile(name: CharSequence?, vararg originatingElements: Element?): JavaFileObject {
val createdSourceFile = filer.createSourceFile(name, *originatingElements)
dependencyCollector!!.add(createdSourceFile.toUri(), originatingElements)
return createdSourceFile
}
override fun createClassFile(name: CharSequence?, vararg originatingElements: Element?): JavaFileObject {
val createdClassFile = filer.createClassFile(name, *originatingElements)
dependencyCollector!!.add(createdClassFile.toUri(), originatingElements)
return createdClassFile
}
override fun createResource(
location: JavaFileManager.Location?,
pkg: CharSequence?,
relativeName: CharSequence?,
vararg originatingElements: Element?
): FileObject {
val createdResource = filer.createResource(location, pkg, relativeName, *originatingElements)
dependencyCollector!!.add(createdResource.toUri(), originatingElements)
return createdResource
}
}
internal class AnnotationProcessorDependencyCollector(private val runtimeProcType: RuntimeProcType) {
private val generatedToSource = mutableMapOf<File, File?>()
private var isFullRebuild = !runtimeProcType.isIncremental
internal fun add(createdFile: URI, originatingElements: Array<out Element?>) {
if (isFullRebuild) return
val generatedFile = File(createdFile)
if (runtimeProcType == RuntimeProcType.AGGREGATING) {
generatedToSource[generatedFile] = null
} else {
val srcFiles = getSrcFiles(originatingElements)
if (srcFiles.size != 1) {
isFullRebuild = true
} else {
generatedToSource[generatedFile] = srcFiles.single()
}
}
}
internal fun getGeneratedToSources(): Map<File, File?> = if (isFullRebuild) emptyMap() else generatedToSource
internal fun getRuntimeType(): RuntimeProcType {
return if (isFullRebuild) {
RuntimeProcType.NON_INCREMENTAL
} else {
runtimeProcType
}
}
}
private fun getSrcFiles(elements: Array<out Element?>): List<File> {
return elements.filterNotNull().mapNotNull {
var origin = it
while (origin.enclosingElement != null && origin.enclosingElement !is PackageElement) {
origin = origin.enclosingElement
}
(origin as? Symbol.ClassSymbol)?.sourcefile?.let { src -> File(src.toUri()) }
}
}
enum class DeclaredProcType {
AGGREGATING {
override fun toRuntimeType() = RuntimeProcType.AGGREGATING
},
ISOLATING {
override fun toRuntimeType() = RuntimeProcType.ISOLATING
},
DYNAMIC {
override fun toRuntimeType() = throw IllegalStateException("This should not be used")
},
NON_INCREMENTAL {
override fun toRuntimeType() = RuntimeProcType.NON_INCREMENTAL
};
abstract fun toRuntimeType(): RuntimeProcType
}
enum class RuntimeProcType(val isIncremental: Boolean) {
AGGREGATING(true),
ISOLATING(true),
NON_INCREMENTAL(false),
}
@@ -0,0 +1,182 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.kapt3.base.incremental
import com.sun.source.tree.*
import com.sun.source.util.SimpleTreeVisitor
import com.sun.source.util.TaskEvent
import com.sun.source.util.TaskListener
import com.sun.source.util.Trees
import com.sun.tools.javac.code.Symbol
import com.sun.tools.javac.tree.JCTree
import javax.lang.model.element.ElementKind
import javax.lang.model.element.Modifier
class MentionedTypesTaskListener(
private val cache: JavaClassCache,
private val trees: Trees
) : TaskListener {
var time = 0L
override fun started(e: TaskEvent) {
// do nothing, we just process on finish
}
override fun finished(e: TaskEvent) {
if (e.kind != TaskEvent.Kind.ENTER || cache.isAlreadyProcessed(e.sourceFile.toUri())) return
val l = System.currentTimeMillis()
val compilationUnit = e.compilationUnit
val structure = SourceFileStructure(e.sourceFile.toUri())
val treeVisitor = TypeTreeVisitor(compilationUnit, trees, structure)
compilationUnit.typeDecls.forEach {
it.accept(treeVisitor, Visibility.ABI)
}
cache.addSourceStructure(structure)
time += System.currentTimeMillis() - l
}
}
private enum class Visibility {
ABI, NON_ABI
}
private class TypeTreeVisitor(val unit: CompilationUnitTree, val trees: Trees, val sourceStructure: SourceFileStructure) :
SimpleTreeVisitor<Void, Visibility>() {
override fun visitClass(node: ClassTree, visibility: Visibility): Void? {
node as JCTree.JCClassDecl
sourceStructure.addDeclaredType(node.sym.fullname.toString())
sourceStructure.addMentionedType(node.sym.fullname.toString())
node.modifiers.annotations.forEach { visit(it, visibility) }
node.typeParameters.forEach { visit(it, visibility) }
visit(node.extendsClause, visibility)
node.implementsClause.forEach { visit(it, visibility) }
node.members.forEach { visit(it, visibility) }
return null
}
override fun visitParameterizedType(node: ParameterizedTypeTree, visibility: Visibility): Void? {
visit(node.type, visibility)
node.typeArguments.forEach { visit(it, visibility) }
return null
}
override fun visitMethod(node: MethodTree, visibility: Visibility): Void? {
val methodVisibility = if (node.modifiers.flags.contains(Modifier.PRIVATE)) Visibility.NON_ABI else Visibility.ABI
node.modifiers.annotations.forEach { visit(it, methodVisibility) }
visit(node.returnType, methodVisibility)
node.parameters.forEach { visit(it, methodVisibility) }
visit(node.defaultValue, methodVisibility)
node.throws.forEach { visit(it, methodVisibility) }
node.typeParameters.forEach { visit(it, methodVisibility) }
return null
}
override fun visitAnnotatedType(node: AnnotatedTypeTree, visibility: Visibility): Void? {
node.annotations.forEach { visit(it, visibility) }
visit(node.underlyingType, visibility)
return null
}
override fun visitVariable(node: VariableTree, visibility: Visibility): Void? {
node as JCTree.JCVariableDecl
val newVisibility = if (node.sym.getKind() == ElementKind.FIELD) {
val flags = node.modifiers.getFlags()
node.sym.constValue?.let { constValue ->
if (flags.contains(Modifier.FINAL)
&& flags.contains(Modifier.STATIC)
&& !flags.contains(Modifier.PRIVATE)
) {
sourceStructure.addDefinedConstant(node.sym.simpleName.toString(), constValue)
}
}
if (flags.contains(Modifier.PRIVATE)) Visibility.NON_ABI else Visibility.ABI
} else {
visibility
}
visit(node.getType(), newVisibility)
node.modifiers.annotations.forEach { visit(it, newVisibility) }
return null
}
override fun visitIdentifier(node: IdentifierTree, visibility: Visibility): Void? {
node as JCTree.JCIdent
maybeAddToTracker(node.sym, visibility)
return null
}
override fun visitArrayType(node: ArrayTypeTree, visibility: Visibility): Void? {
visit(node.type, visibility)
return null
}
override fun visitNewArray(node: NewArrayTree, visibility: Visibility): Void? {
node.annotations.forEach { visit(it, visibility) }
node.dimAnnotations.forEach { visit(it, visibility) }
visit(node.type, visibility)
node.initializers.forEach { visit(it, visibility) }
return null
}
override fun visitAnnotation(node: AnnotationTree, visibility: Visibility): Void? {
visit(node.annotationType, visibility)
node.arguments.forEach { visit(it, visibility) }
return null
}
override fun visitTypeParameter(node: TypeParameterTree, visibility: Visibility): Void? {
node.annotations.forEach { visit(it, visibility) }
node.bounds.forEach { visit(it, visibility) }
return null
}
override fun visitMemberSelect(node: MemberSelectTree, visibility: Visibility): Void? {
// TODO (gavra): explore this for constant usage tracking
node as JCTree.JCFieldAccess
if (!maybeAddToTracker(node.sym, visibility)) {
visit(node.expression, visibility)
}
return null
}
override fun visitWildcard(node: WildcardTree, visibility: Visibility): Void? {
visit(node.bound, visibility)
return null
}
private fun maybeAddToTracker(symbol: Symbol, visibility: Visibility): Boolean {
val kind = symbol.getKind()
if (!kind.isInterface && !kind.isClass) {
return false
}
val qualifiedName = symbol.qualifiedName.toString()
if (symbol.getKind() == ElementKind.ANNOTATION_TYPE) {
sourceStructure.addMentionedAnnotations(qualifiedName)
}
when (visibility) {
Visibility.ABI -> sourceStructure.addMentionedType(qualifiedName)
Visibility.NON_ABI -> sourceStructure.addPrivateType(qualifiedName)
}
return true
}
}
@@ -17,6 +17,8 @@ class KaptJavaCompiler(context: Context) : JavaCompiler(context) {
return if (shouldStop(cs)) JavacList.nil<T>() else list
}
fun getTaskListeners() = this.taskListener
companion object {
internal fun preRegister(context: Context) {
context.put(compilerKey, Context.Factory<JavaCompiler>(::KaptJavaCompiler))
@@ -11,6 +11,8 @@ import org.jetbrains.kotlin.base.kapt3.KaptFlag
import org.jetbrains.kotlin.base.kapt3.KaptOptions
import org.jetbrains.kotlin.kapt3.base.KaptContext
import org.jetbrains.kotlin.kapt3.base.doAnnotationProcessing
import org.jetbrains.kotlin.kapt3.base.incremental.DeclaredProcType
import org.jetbrains.kotlin.kapt3.base.incremental.IncrementalProcessor
import org.jetbrains.kotlin.kapt3.base.util.KaptBaseError
import org.jetbrains.kotlin.kapt3.base.util.WriterBackedKaptLogger
import org.junit.Test
@@ -25,32 +27,36 @@ class JavaKaptContextTest : TestCase() {
companion object {
private val TEST_DATA_DIR = File("plugins/kapt3/kapt3-base/testData/runner")
fun simpleProcessor() = object : AbstractProcessor() {
override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean {
for (annotation in annotations) {
val annotationName = annotation.simpleName.toString()
val annotatedElements = roundEnv.getElementsAnnotatedWith(annotation)
fun simpleProcessor() = IncrementalProcessor(
object : AbstractProcessor() {
override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean {
for (annotation in annotations) {
val annotationName = annotation.simpleName.toString()
val annotatedElements = roundEnv.getElementsAnnotatedWith(annotation)
for (annotatedElement in annotatedElements) {
val generatedClassName = annotatedElement.simpleName.toString().capitalize() + annotationName.capitalize()
val file = processingEnv.filer.createSourceFile("generated." + generatedClassName)
file.openWriter().use {
it.write("""
for (annotatedElement in annotatedElements) {
val generatedClassName = annotatedElement.simpleName.toString().capitalize() + annotationName.capitalize()
val file = processingEnv.filer.createSourceFile("generated." + generatedClassName)
file.openWriter().use {
it.write(
"""
package generated;
class $generatedClassName {}
""".trimIndent())
""".trimIndent()
)
}
}
}
return true
}
return true
}
override fun getSupportedAnnotationTypes() = setOf("test.MyAnnotation")
}
override fun getSupportedAnnotationTypes() = setOf("test.MyAnnotation")
}, DeclaredProcType.NON_INCREMENTAL
)
}
private fun doAnnotationProcessing(javaSourceFile: File, processor: Processor, outputDir: File) {
private fun doAnnotationProcessing(javaSourceFile: File, processor: IncrementalProcessor, outputDir: File) {
val options = KaptOptions.Builder().apply {
projectBaseDir = javaSourceFile.parentFile
@@ -93,7 +99,7 @@ class JavaKaptContextTest : TestCase() {
}
try {
doAnnotationProcessing(File(TEST_DATA_DIR, "Simple.java"), processor, TEST_DATA_DIR)
doAnnotationProcessing(File(TEST_DATA_DIR, "Simple.java"), IncrementalProcessor(processor, DeclaredProcType.NON_INCREMENTAL), TEST_DATA_DIR)
} catch (e: KaptBaseError) {
assertEquals(KaptBaseError.Kind.EXCEPTION, e.kind)
assertEquals("Here we are!", e.cause!!.message)
@@ -0,0 +1,75 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.kapt.base.test.org.jetbrains.kotlin.kapt3.base.incremental
import org.jetbrains.kotlin.kapt3.base.incremental.RuntimeProcType
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.io.File
class AggregatingIncrementalProcessorTest {
@JvmField
@Rule
val tmp: TemporaryFolder = TemporaryFolder()
private lateinit var generatedSources: File
@Before
fun setUp() {
generatedSources = tmp.newFolder()
}
@Test
fun testDependenciesRecorded() {
val srcFiles = listOf("User.java", "Address.java", "Observable.java").map { File(TEST_DATA_DIR, it) }
val aggregating = SimpleProcessor().toAggregating()
runAnnotationProcessing(srcFiles, listOf(aggregating), generatedSources)
assertEquals(RuntimeProcType.AGGREGATING, aggregating.getRuntimeType())
assertEquals(
mapOf(
generatedSources.resolve("test/UserGenerated.java") to null,
generatedSources.resolve("test/AddressGenerated.java") to null
),
aggregating.getGeneratedToSources()
)
}
@Test
fun testNoSourcesToProcess() {
val srcFiles = listOf("Observable.java").map { File(TEST_DATA_DIR, it) }
val aggregating = SimpleProcessor().toAggregating()
runAnnotationProcessing(srcFiles, listOf(aggregating), generatedSources)
assertEquals(RuntimeProcType.AGGREGATING, aggregating.getRuntimeType())
assertEquals(emptyMap<File, File>(), aggregating.getGeneratedToSources())
}
@Test
fun testGeneratingSourcesClassesResources() {
val srcFiles = listOf("User.java", "Address.java", "Observable.java").map { File(TEST_DATA_DIR, it) }
val aggregating = SimpleCreatingClassFilesAndResources().toAggregating()
runAnnotationProcessing(srcFiles, listOf(aggregating), generatedSources)
assertEquals(RuntimeProcType.AGGREGATING, aggregating.getRuntimeType())
assertEquals(
mapOf(
generatedSources.resolve("test/UserGenerated.java") to null,
generatedSources.resolve("test/UserGeneratedClass.class") to null,
generatedSources.resolve("test/UserGeneratedResource") to null,
generatedSources.resolve("test/AddressGenerated.java") to null,
generatedSources.resolve("test/AddressGeneratedClass.class") to null,
generatedSources.resolve("test/AddressGeneratedResource") to null
),
aggregating.getGeneratedToSources()
)
}
}
@@ -0,0 +1,44 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.kapt.base.test.org.jetbrains.kotlin.kapt3.base.incremental
import org.jetbrains.kotlin.kapt3.base.incremental.AnnotationProcessorDependencyCollector
import org.jetbrains.kotlin.kapt3.base.incremental.RuntimeProcType
import org.junit.Assert.assertEquals
import org.junit.Test
import java.io.File
class AnnotationProcessorDependencyCollectorTest {
@Test
fun testAggregating() {
val aggregating = AnnotationProcessorDependencyCollector(RuntimeProcType.AGGREGATING)
val generated = listOf("GeneratedA.java", "GeneratedB.java", "GeneratedC.java").map { File(it).toURI() }
generated.forEach { aggregating.add(it, emptyArray()) }
assertEquals(aggregating.getGeneratedToSources(), generated.map { File(it) to null }.toMap())
assertEquals(aggregating.getRuntimeType(), RuntimeProcType.AGGREGATING)
}
@Test
fun testIsolatingWithoutOrigin() {
val isolating = AnnotationProcessorDependencyCollector(RuntimeProcType.ISOLATING)
isolating.add(File("GeneratedA.java").toURI(), emptyArray())
assertEquals(isolating.getRuntimeType(), RuntimeProcType.NON_INCREMENTAL)
assertEquals(isolating.getGeneratedToSources(), emptyMap<File, File?>())
}
@Test
fun testNonIncremental() {
val nonIncremental = AnnotationProcessorDependencyCollector(RuntimeProcType.NON_INCREMENTAL)
nonIncremental.add(File("GeneratedA.java").toURI(), emptyArray())
nonIncremental.add(File("GeneratedB.java").toURI(), emptyArray())
assertEquals(nonIncremental.getRuntimeType(), RuntimeProcType.NON_INCREMENTAL)
assertEquals(nonIncremental.getGeneratedToSources(), emptyMap<File, File?>())
}
}
@@ -0,0 +1,75 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.kapt.base.test.org.jetbrains.kotlin.kapt3.base.incremental
import org.jetbrains.kotlin.kapt3.base.incremental.RuntimeProcType
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.io.File
class DynamicIncrementalProcessorTest {
@JvmField
@Rule
val tmp: TemporaryFolder = TemporaryFolder()
private lateinit var generatedSources: File
@Before
fun setUp() {
generatedSources = tmp.newFolder()
}
@Test
fun testIfIsolating() {
val srcFiles = listOf("User.java", "Address.java", "Observable.java").map { File(TEST_DATA_DIR, it) }
val dynamic = DynamicProcessor(kind = RuntimeProcType.ISOLATING).toDynamic()
runAnnotationProcessing(srcFiles, listOf(dynamic), generatedSources)
assertEquals(RuntimeProcType.ISOLATING, dynamic.getRuntimeType())
assertEquals(
mapOf(
generatedSources.resolve("test/UserGenerated.java") to File("plugins/kapt3/kapt3-base/testData/runner/incremental/User.java").absoluteFile,
generatedSources.resolve("test/AddressGenerated.java") to File("plugins/kapt3/kapt3-base/testData/runner/incremental/Address.java").absoluteFile
),
dynamic.getGeneratedToSources()
)
}
@Test
fun testIfAggregating() {
val srcFiles = listOf("User.java", "Address.java", "Observable.java").map { File(TEST_DATA_DIR, it) }
val dynamic = DynamicProcessor(kind = RuntimeProcType.AGGREGATING).toDynamic()
runAnnotationProcessing(srcFiles, listOf(dynamic), generatedSources)
assertEquals(RuntimeProcType.AGGREGATING, dynamic.getRuntimeType())
assertEquals(
mapOf(
generatedSources.resolve("test/UserGenerated.java") to null,
generatedSources.resolve("test/AddressGenerated.java") to null
),
dynamic.getGeneratedToSources()
)
}
@Test
fun testIfNonIncremental() {
val srcFiles = listOf("User.java", "Address.java", "Observable.java").map { File(TEST_DATA_DIR, it) }
val dynamic = DynamicProcessor(kind = RuntimeProcType.NON_INCREMENTAL).toDynamic()
runAnnotationProcessing(srcFiles, listOf(dynamic), generatedSources)
assertEquals(RuntimeProcType.NON_INCREMENTAL, dynamic.getRuntimeType())
assertEquals(
emptyMap<File, File>(),
dynamic.getGeneratedToSources()
)
}
}
@@ -0,0 +1,101 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.kapt.base.test.org.jetbrains.kotlin.kapt3.base.incremental
import org.jetbrains.kotlin.base.kapt3.KaptOptions
import org.jetbrains.kotlin.base.kapt3.collectJavaSourceFiles
import org.jetbrains.kotlin.kapt3.base.KaptContext
import org.jetbrains.kotlin.kapt3.base.doAnnotationProcessing
import org.jetbrains.kotlin.kapt3.base.util.WriterBackedKaptLogger
import org.junit.Assert.*
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
class IncrementalKaptTest {
@Rule
@JvmField
var tmp = TemporaryFolder()
@Test
fun testIncrementalRun() {
val sourcesDir = tmp.newFolder().resolve("test").also { base ->
base.mkdir()
listOf("User.java", "Address.java", "Observable.java").map {
TEST_DATA_DIR.resolve(it).copyTo(base.resolve(it))
}
}
val outputDir = tmp.newFolder()
val incrementalCacheDir = tmp.newFolder()
val classpathHistory = tmp.newFolder().also {
it.resolve("0").createNewFile()
}
val options = KaptOptions.Builder().apply {
projectBaseDir = tmp.newFolder()
javaSourceRoots.add(sourcesDir)
sourcesOutputDir = outputDir
classesOutputDir = outputDir
stubsOutputDir = outputDir
incrementalDataOutputDir = outputDir
incrementalCache = incrementalCacheDir
classpathFqNamesHistory = classpathHistory
}.build()
val logger = WriterBackedKaptLogger(isVerbose = true)
KaptContext(options, true, logger).use {
it.doAnnotationProcessing(
options.collectJavaSourceFiles(it.cacheManager), listOf(SimpleProcessor().toIsolating())
)
}
val classesOutput = tmp.newFolder()
compileSources(sourcesDir.listFiles().asIterable(), classesOutput)
val addressTimestamp = outputDir.resolve("test/AddressGenerated.java").lastModified()
assertTrue(outputDir.resolve("test/UserGenerated.java").exists())
assertTrue(outputDir.resolve("test/AddressGenerated.java").exists())
val optionsForSecondRun = KaptOptions.Builder().apply {
projectBaseDir = tmp.newFolder()
sourcesOutputDir = outputDir
classesOutputDir = outputDir
stubsOutputDir = outputDir
incrementalDataOutputDir = outputDir
incrementalCache = incrementalCacheDir
classpathFqNamesHistory = classpathHistory
compiledSources.add(classesOutput)
changedFiles.add(sourcesDir.resolve("User.java"))
}.build()
KaptContext(optionsForSecondRun, true, logger).use {
val sourcesToReprocess = optionsForSecondRun.collectJavaSourceFiles(it.cacheManager)
assertFalse(outputDir.resolve("test/UserGenerated.java").exists())
it.doAnnotationProcessing(
sourcesToReprocess, listOf(SimpleProcessor().toIsolating())
)
}
assertEquals(addressTimestamp, outputDir.resolve("test/AddressGenerated.java").lastModified())
assertTrue(outputDir.resolve("test/UserGenerated.java").exists())
sourcesDir.resolve("User.java").delete()
KaptContext(optionsForSecondRun, true, logger).use {
it.doAnnotationProcessing(
optionsForSecondRun.collectJavaSourceFiles(it.cacheManager), listOf(SimpleProcessor().toIsolating())
)
}
assertEquals(addressTimestamp, outputDir.resolve("test/AddressGenerated.java").lastModified())
assertFalse(outputDir.resolve("test/UserGenerated.java").exists())
}
}
@@ -0,0 +1,105 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.kapt.base.test.org.jetbrains.kotlin.kapt3.base.incremental
import org.jetbrains.kotlin.kapt3.base.incremental.DeclaredProcType
import org.jetbrains.kotlin.kapt3.base.incremental.getIncrementalProcessorsFromClasspath
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
class IncrementalProcessorDiscoveryTest {
private val markerFileContent = """
Input1Processor1,AGGREGATING
Input1Processor2,ISOLATING
Input1Processor3,DYNAMIC
Input1Processor4,UNKNOWN
Input1Processor5 this is malformed input
""".trimIndent();
@Rule
@JvmField
var tmp = TemporaryFolder()
@Test
fun locateInJars() {
val inputJar = tmp.root.resolve("inputJar.jar")
ZipOutputStream(inputJar.outputStream()).use {
it.putNextEntry(ZipEntry("META-INF/gradle/incremental.annotation.processors"))
it.write(markerFileContent.toByteArray())
it.closeEntry()
}
val info = getIncrementalProcessorsFromClasspath(
setOf("Input1Processor4", "Input1Processor3", "Input1Processor2", "Input1Processor1"),
listOf(inputJar)
)
assertEquals(
mapOf(
"Input1Processor1" to DeclaredProcType.AGGREGATING,
"Input1Processor2" to DeclaredProcType.ISOLATING,
"Input1Processor3" to DeclaredProcType.DYNAMIC
),
info
)
}
@Test
fun locateInDir() {
val inputDir = tmp.root.resolve("inputDir")
inputDir.resolve("META-INF/gradle/incremental.annotation.processors").let {
it.parentFile.mkdirs()
it.writeText(markerFileContent)
}
val info = getIncrementalProcessorsFromClasspath(
setOf("Input1Processor4", "Input1Processor3", "Input1Processor2", "Input1Processor1"),
listOf(inputDir)
)
assertEquals(
mapOf(
"Input1Processor1" to DeclaredProcType.AGGREGATING,
"Input1Processor2" to DeclaredProcType.ISOLATING,
"Input1Processor3" to DeclaredProcType.DYNAMIC
),
info
)
}
@Test
fun locateInJarsAndDirs() {
val inputJar = tmp.root.resolve("inputJar.jar")
ZipOutputStream(inputJar.outputStream()).use {
it.putNextEntry(ZipEntry("META-INF/gradle/incremental.annotation.processors"))
it.write("InputJarProcessor,ISOLATING".toByteArray())
it.closeEntry()
}
val inputDir = tmp.root.resolve("inputDir")
inputDir.resolve("META-INF/gradle/incremental.annotation.processors").let {
it.parentFile.mkdirs()
it.writeText("InputDirProcessor,DYNAMIC")
}
val info = getIncrementalProcessorsFromClasspath(
setOf("InputJarNonIncrementalProcessor", "InputJarProcessor", "InputDirNonIncrementalProcessor", "InputDirProcessor"),
listOf(inputJar, inputDir)
)
assertEquals(
mapOf(
"InputJarProcessor" to DeclaredProcType.ISOLATING,
"InputDirProcessor" to DeclaredProcType.DYNAMIC
),
info
)
}
}
@@ -0,0 +1,110 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.kapt.base.test.org.jetbrains.kotlin.kapt3.base.incremental
import org.jetbrains.kotlin.kapt3.base.incremental.RuntimeProcType
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.io.File
class IsolationgIncrementalProcessorTest {
@JvmField
@Rule
val tmp: TemporaryFolder = TemporaryFolder()
private lateinit var generatedSources: File
@Before
fun setUp() {
generatedSources = tmp.newFolder()
}
@Test
fun testDependenciesRecorded() {
val srcFiles = listOf("User.java", "Address.java", "Observable.java").map { File(TEST_DATA_DIR, it) }
val isolating = SimpleProcessor().toIsolating()
runAnnotationProcessing(srcFiles, listOf(isolating), generatedSources)
assertEquals(RuntimeProcType.ISOLATING, isolating.getRuntimeType())
assertEquals(
mapOf(
generatedSources.resolve("test/UserGenerated.java") to File("plugins/kapt3/kapt3-base/testData/runner/incremental/User.java").absoluteFile,
generatedSources.resolve("test/AddressGenerated.java") to File("plugins/kapt3/kapt3-base/testData/runner/incremental/Address.java").absoluteFile
),
isolating.getGeneratedToSources()
)
}
@Test
fun testNoSourcesToProcess() {
val srcFiles = listOf("Observable.java").map { File(TEST_DATA_DIR, it) }
val isolating = SimpleProcessor().toIsolating()
runAnnotationProcessing(srcFiles, listOf(isolating), generatedSources)
assertEquals(RuntimeProcType.ISOLATING, isolating.getRuntimeType())
assertEquals(emptyMap<File, File>(), isolating.getGeneratedToSources())
}
@Test
fun testGeneratingSourcesClassesResources() {
val srcFiles = listOf("User.java", "Address.java", "Observable.java").map { File(TEST_DATA_DIR, it) }
val isolating = SimpleCreatingClassFilesAndResources().toIsolating()
runAnnotationProcessing(srcFiles, listOf(isolating), generatedSources)
assertEquals(RuntimeProcType.ISOLATING, isolating.getRuntimeType())
assertEquals(
mapOf(
generatedSources.resolve("test/UserGenerated.java") to TEST_DATA_DIR.resolve("User.java").absoluteFile,
generatedSources.resolve("test/UserGeneratedClass.class") to TEST_DATA_DIR.resolve("User.java").absoluteFile,
generatedSources.resolve("test/UserGeneratedResource") to TEST_DATA_DIR.resolve("User.java").absoluteFile,
generatedSources.resolve("test/AddressGenerated.java") to TEST_DATA_DIR.resolve("Address.java").absoluteFile,
generatedSources.resolve("test/AddressGeneratedClass.class") to TEST_DATA_DIR.resolve("Address.java").absoluteFile,
generatedSources.resolve("test/AddressGeneratedResource") to TEST_DATA_DIR.resolve("Address.java").absoluteFile
),
isolating.getGeneratedToSources()
)
}
@Test
fun testWrongOriginElement() {
val srcFiles = listOf("User.java", "Address.java", "Observable.java").map { File(TEST_DATA_DIR, it) }
val isolating = SimpleProcessor(wrongOrigin = true).toIsolating()
runAnnotationProcessing(srcFiles, listOf(isolating), generatedSources)
assertEquals(RuntimeProcType.NON_INCREMENTAL, isolating.getRuntimeType())
assertEquals(emptyMap<File, File>(), isolating.getGeneratedToSources())
}
@Test
fun testTwoIsolating() {
val srcFiles = listOf("User.java", "Address.java", "Observable.java").map { File(TEST_DATA_DIR, it) }
val isolating = listOf(
SimpleProcessor().toIsolating(),
SimpleProcessor(generatedSuffix = "Two").toIsolating()
)
runAnnotationProcessing(srcFiles, isolating, generatedSources)
isolating.forEach { assertEquals(RuntimeProcType.ISOLATING, it.getRuntimeType()) }
assertEquals(
mapOf(
generatedSources.resolve("test/UserGenerated.java") to File("plugins/kapt3/kapt3-base/testData/runner/incremental/User.java").absoluteFile,
generatedSources.resolve("test/AddressGenerated.java") to File("plugins/kapt3/kapt3-base/testData/runner/incremental/Address.java").absoluteFile
), isolating[0].getGeneratedToSources()
)
assertEquals(
mapOf(
generatedSources.resolve("test/UserGeneratedTwo.java") to File("plugins/kapt3/kapt3-base/testData/runner/incremental/User.java").absoluteFile,
generatedSources.resolve("test/AddressGeneratedTwo.java") to File("plugins/kapt3/kapt3-base/testData/runner/incremental/Address.java").absoluteFile
), isolating[1].getGeneratedToSources()
)
}
}
@@ -0,0 +1,223 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.kapt3.base.incremental;
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.io.File
import java.io.ObjectOutputStream
class JavaClassCacheManagerTest {
@Rule
@JvmField
var tmp = TemporaryFolder()
private lateinit var cache: JavaClassCacheManager
private lateinit var cacheDir: File
private lateinit var classpathHistory: File
@Before
fun setUp() {
cacheDir = tmp.newFolder()
classpathHistory = tmp.newFolder()
cache = JavaClassCacheManager(cacheDir, classpathHistory)
}
@Test
fun testClosingCache() {
cache.close()
assertTrue(cacheDir.resolve("java-cache.bin").exists())
assertTrue(cacheDir.resolve("apt-cache.bin").exists())
assertTrue(cacheDir.resolve("last-build-ts.bin").exists())
}
@Test
fun testMentionedTypes() {
SourceFileStructure(File("Mentioned.java").toURI()).also {
it.addDeclaredType("test.Mentioned")
cache.javaCache.addSourceStructure(it)
}
SourceFileStructure(File("Src.java").toURI()).also {
it.addDeclaredType("test.Src")
it.addMentionedType("test.Mentioned")
cache.javaCache.addSourceStructure(it)
}
SourceFileStructure(File("ReferencesSrc.java").toURI()).also {
it.addDeclaredType("test.ReferencesSrc")
it.addPrivateType("test.Src")
cache.javaCache.addSourceStructure(it)
}
prepareForIncremental()
val dirtyFiles = cache.invalidateAndGetDirtyFiles(listOf(File("Mentioned.java"))) as SourcesToReprocess.Incremental
assertEquals(
listOf(
File("Mentioned.java").absoluteFile,
File("Src.java").absoluteFile,
File("ReferencesSrc.java").absoluteFile
), dirtyFiles.toReprocess
)
}
@Test
fun testPrivateTypes() {
SourceFileStructure(File("Mentioned.java").toURI()).also {
it.addDeclaredType("test.Mentioned")
cache.javaCache.addSourceStructure(it)
}
SourceFileStructure(File("Src.java").toURI()).also {
it.addDeclaredType("test.Src")
it.addPrivateType("test.Mentioned")
cache.javaCache.addSourceStructure(it)
}
SourceFileStructure(File("ReferencesSrc.java").toURI()).also {
it.addDeclaredType("test.ReferencesSrc")
it.addPrivateType("test.Src")
cache.javaCache.addSourceStructure(it)
}
prepareForIncremental()
val dirtyFiles = cache.invalidateAndGetDirtyFiles(listOf(File("Mentioned.java"))) as SourcesToReprocess.Incremental
assertEquals(
listOf(
File("Mentioned.java").absoluteFile,
File("Src.java").absoluteFile
), dirtyFiles.toReprocess
)
}
@Test
fun testMultipleDeclared() {
SourceFileStructure(File("TwoTypes.java").toURI()).also {
it.addDeclaredType("test.TwoTypes")
it.addDeclaredType("test.AnotherType")
cache.javaCache.addSourceStructure(it)
}
SourceFileStructure(File("ReferencesTwoTypes.java").toURI()).also {
it.addDeclaredType("test.ReferencesTwoTypes")
it.addPrivateType("test.TwoTypes")
cache.javaCache.addSourceStructure(it)
}
SourceFileStructure(File("ReferencesAnotherType.java").toURI()).also {
it.addDeclaredType("test.ReferencesAnotherType")
it.addPrivateType("test.AnotherType")
cache.javaCache.addSourceStructure(it)
}
prepareForIncremental()
val dirtyFiles = cache.invalidateAndGetDirtyFiles(listOf(File("TwoTypes.java"))) as SourcesToReprocess.Incremental
assertEquals(
listOf(
File("TwoTypes.java").absoluteFile,
File("ReferencesTwoTypes.java").absoluteFile,
File("ReferencesAnotherType.java").absoluteFile
), dirtyFiles.toReprocess
)
}
@Test
fun testNoClasspathHistory() {
SourceFileStructure(File("Src.java").toURI()).also {
it.addDeclaredType("test.Src")
cache.javaCache.addSourceStructure(it)
}
cache.close()
classpathHistory.resolve(Long.MAX_VALUE.toString()).createNewFile()
val dirtyFiles = cache.invalidateAndGetDirtyFiles(listOf())
assertEquals(SourcesToReprocess.FullRebuild, dirtyFiles)
}
@Test
fun testWithClasspathHistoryButNoNewChanges() {
SourceFileStructure(File("Src.java").toURI()).also {
it.addDeclaredType("test.Src")
it.addMentionedType("test.Mentioned")
cache.javaCache.addSourceStructure(it)
}
prepareForIncremental()
ObjectOutputStream(classpathHistory.resolve("0").outputStream()).use {
it.writeObject(listOf("test.Mentioned"))
}
val dirtyFiles = cache.invalidateAndGetDirtyFiles(listOf()) as SourcesToReprocess.Incremental
assertEquals(emptyList<File>(), dirtyFiles.toReprocess)
}
@Test
fun testWithClasspathHistoryWithChanges() {
SourceFileStructure(File("Src.java").toURI()).also {
it.addDeclaredType("test.Src")
it.addMentionedType("test.Mentioned")
cache.javaCache.addSourceStructure(it)
}
prepareForIncremental()
ObjectOutputStream(classpathHistory.resolve(Long.MAX_VALUE.toString()).outputStream()).use {
it.writeObject(listOf("test.Mentioned"))
}
val dirtyFiles = cache.invalidateAndGetDirtyFiles(listOf())
assertEquals(SourcesToReprocess.FullRebuild, dirtyFiles)
}
@Test
fun testDefinesConstant() {
SourceFileStructure(File("Constants.java").toURI()).also {
it.addDeclaredType("test.Constants")
it.addDefinedConstant("CONST", 123)
cache.javaCache.addSourceStructure(it)
}
SourceFileStructure(File("Unrelated1.java").toURI()).also {
it.addDeclaredType("test.Unrelated1")
cache.javaCache.addSourceStructure(it)
}
SourceFileStructure(File("Unrelated2.java").toURI()).also {
it.addDeclaredType("test.Unrelated2")
cache.javaCache.addSourceStructure(it)
}
prepareForIncremental()
val dirtyFiles = cache.invalidateAndGetDirtyFiles(listOf(File("Constants.java")))
assertEquals(SourcesToReprocess.FullRebuild, dirtyFiles)
}
@Test
fun testWithAnnotations() {
SourceFileStructure(File("Annotated1.java").toURI()).also {
it.addDeclaredType("test.Annotated1")
it.addMentionedAnnotations("test.Annotation")
cache.javaCache.addSourceStructure(it)
}
SourceFileStructure(File("Annotated2.java").toURI()).also {
it.addDeclaredType("test.Annotated2")
it.addMentionedAnnotations("com.test.MyAnnotation")
cache.javaCache.addSourceStructure(it)
}
SourceFileStructure(File("Annotated3.java").toURI()).also {
it.addDeclaredType("test.Annotated3")
it.addMentionedAnnotations("Runnable")
cache.javaCache.addSourceStructure(it)
}
prepareForIncremental()
assertEquals(setOf(File("Annotated1.java").absoluteFile), cache.javaCache.invalidateEntriesAnnotatedWith(setOf("test.Annotation")))
assertEquals(setOf(File("Annotated2.java").absoluteFile), cache.javaCache.invalidateEntriesAnnotatedWith(setOf("com.test.*")))
assertEquals(setOf(File("Annotated3.java").absoluteFile), cache.javaCache.invalidateEntriesAnnotatedWith(setOf("*")))
}
private fun prepareForIncremental() {
cache.close()
classpathHistory.resolve("0").createNewFile()
cache = JavaClassCacheManager(cacheDir, classpathHistory)
}
}
@@ -0,0 +1,210 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.kapt.base.test.org.jetbrains.kotlin.kapt3.base.incremental
import org.jetbrains.kotlin.kapt3.base.incremental.JavaClassCacheManager
import org.jetbrains.kotlin.kapt3.base.incremental.MentionedTypesTaskListener
import org.junit.Assert.assertEquals
import org.junit.BeforeClass
import org.junit.ClassRule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.io.File
private val MY_TEST_DIR = File("plugins/kapt3/kapt3-base/testData/runner/incremental/complex")
class TestComplexIncrementalAptCache {
companion object {
@ClassRule
@JvmField
var tmp = TemporaryFolder()
private lateinit var cache: JavaClassCacheManager
private lateinit var generatedSources: File
@JvmStatic
@BeforeClass
fun setUp() {
val classpathHistory = tmp.newFolder()
cache = JavaClassCacheManager(tmp.newFolder(), classpathHistory)
generatedSources = tmp.newFolder()
cache.close()
classpathHistory.resolve("0").createNewFile()
val processor = SimpleProcessor().toAggregating()
val srcFiles = listOf(
"MyEnum.java",
"MyNumber.java",
"NumberAnnotation.java",
"NumberException.java",
"NumberHolder.java",
"NumberManager.java",
"GenericNumber.java"
).map { File(MY_TEST_DIR, it) }
runAnnotationProcessing(
srcFiles,
listOf(processor),
generatedSources
) { trees -> MentionedTypesTaskListener(cache.javaCache, trees) }
cache.updateCache(listOf(processor))
}
}
@Test
fun testEnum() {
val myEnum = cache.javaCache.getStructure(MY_TEST_DIR.resolve("MyEnum.java"))!!
assertEquals(setOf("test.MyEnum"), myEnum.getDeclaredTypes())
assertEquals(emptySet<String>(), myEnum.getMentionedAnnotations())
assertEquals(emptySet<String>(), myEnum.getPrivateTypes())
assertEquals(setOf("test.MyEnum", "test.TypeGeneratedByApt"), myEnum.getMentionedTypes())
assertEquals(emptyMap<String, Any>(), myEnum.getDefinedConstants())
}
@Test
fun testMyNumber() {
val myNumber = cache.javaCache.getStructure(MY_TEST_DIR.resolve("MyNumber.java"))!!
assertEquals(
setOf(
"test.MyNumber",
"test.FieldAnnotation",
"test.MethodAnnotation",
"test.ParameterAnnotation",
"test.TypeUseAnnotation",
"test.AnotherTypeUseAnnotation",
"test.ThrowTypeUseAnnotation"
), myNumber.getDeclaredTypes()
)
assertEquals(
setOf(
"java.lang.annotation.Target",
"test.FieldAnnotation",
"test.ParameterAnnotation",
"test.MethodAnnotation",
"test.TypeUseAnnotation",
"test.AnotherTypeUseAnnotation",
"test.ThrowTypeUseAnnotation"
), myNumber.getMentionedAnnotations()
)
assertEquals(
setOf(
"test.FieldAnnotation",
"java.lang.String",
"test.ParameterAnnotation",
"test.MethodAnnotation",
"test.AnotherTypeUseAnnotation",
"test.ThrowTypeUseAnnotation",
"java.lang.Number",
"java.lang.RuntimeException"
), myNumber.getPrivateTypes()
)
assertEquals(
setOf(
"test.MyNumber",
"java.lang.annotation.Target",
"test.FieldAnnotation",
"test.ParameterAnnotation",
"test.MethodAnnotation",
"test.TypeUseAnnotation",
"test.AnotherTypeUseAnnotation",
"test.ThrowTypeUseAnnotation",
"java.util.HashSet"
), myNumber.getMentionedTypes()
)
assertEquals(emptyMap<String, String>(), myNumber.getDefinedConstants())
}
@Test
fun testAnnotation() {
val numberAnnotation = cache.javaCache.getStructure(MY_TEST_DIR.resolve("NumberAnnotation.java"))!!
assertEquals(setOf("test.NumberAnnotation", "test.BaseAnnotation"), numberAnnotation.getDeclaredTypes())
assertEquals(setOf("test.BaseAnnotation"), numberAnnotation.getMentionedAnnotations())
assertEquals(emptySet<String>(), numberAnnotation.getPrivateTypes())
assertEquals(
setOf(
"test.BaseAnnotation",
"test.NumberAnnotation",
"java.lang.Class",
"test.MyEnum",
"test.NumberManager"
), numberAnnotation.getMentionedTypes()
)
assertEquals(emptyMap<String, String>(), numberAnnotation.getDefinedConstants())
}
@Test
fun testNumberException() {
val numberException = cache.javaCache.getStructure(MY_TEST_DIR.resolve("NumberException.java"))!!
assertEquals(setOf("test.NumberException"), numberException.getDeclaredTypes())
assertEquals(emptySet<String>(), numberException.getMentionedAnnotations())
assertEquals(emptySet<String>(), numberException.getPrivateTypes())
assertEquals(setOf("test.NumberException", "java.lang.RuntimeException"), numberException.getMentionedTypes())
assertEquals(emptyMap<String, String>(), numberException.getDefinedConstants())
}
@Test
fun testNumberHolder() {
val numberHolder = cache.javaCache.getStructure(MY_TEST_DIR.resolve("NumberHolder.java"))!!
assertEquals(setOf("test.NumberHolder", "test.NumberHolder.MyInnerClass"), numberHolder.getDeclaredTypes())
assertEquals(setOf("test.NumberAnnotation"), numberHolder.getMentionedAnnotations())
assertEquals(setOf("test.NumberManager"), numberHolder.getPrivateTypes())
assertEquals(
setOf(
"test.NumberHolder",
"test.NumberHolder.MyInnerClass",
"test.NumberAnnotation",
"test.NumberManager",
"test.MyNumber",
"java.util.HashSet",
"java.lang.Runnable",
"java.lang.String",
"test.NumberException"
), numberHolder.getMentionedTypes()
)
assertEquals(emptyMap<String, String>(), numberHolder.getDefinedConstants())
}
@Test
fun testNumberManager() {
val numberManager = cache.javaCache.getStructure(MY_TEST_DIR.resolve("NumberManager.java"))!!
assertEquals(setOf("test.NumberManager"), numberManager.getDeclaredTypes())
assertEquals(emptySet<String>(), numberManager.getMentionedAnnotations())
assertEquals(setOf("test.MyEnum"), numberManager.getPrivateTypes())
assertEquals(
setOf(
"test.NumberManager",
"java.lang.String",
"test.NumberHolder"
), numberManager.getMentionedTypes()
)
assertEquals(mapOf("CONST" to "STRING_CONST", "INT_CONST" to 246), numberManager.getDefinedConstants())
}
@Test
fun testGenericNumber() {
val genericNumber = cache.javaCache.getStructure(MY_TEST_DIR.resolve("GenericNumber.java"))!!
assertEquals(setOf("test.GenericNumber"), genericNumber.getDeclaredTypes())
assertEquals(emptySet<String>(), genericNumber.getMentionedAnnotations())
assertEquals(emptySet<String>(), genericNumber.getPrivateTypes())
assertEquals(
setOf(
"test.GenericNumber",
"java.util.HashSet",
"java.lang.Runnable",
"java.lang.Cloneable",
"java.lang.CharSequence",
"java.lang.Number"
), genericNumber.getMentionedTypes()
)
assertEquals(emptyMap<String, String>(), genericNumber.getDefinedConstants())
}
}
@@ -0,0 +1,79 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.kapt.base.test.org.jetbrains.kotlin.kapt3.base.incremental
import org.jetbrains.kotlin.kapt3.base.incremental.IncrementalProcessor
import org.jetbrains.kotlin.kapt3.base.incremental.JavaClassCacheManager
import org.jetbrains.kotlin.kapt3.base.incremental.MentionedTypesTaskListener
import org.jetbrains.kotlin.kapt3.base.incremental.SourcesToReprocess
import org.junit.Assert.*
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.io.File
class TestSimpleIncrementalAptCache {
@Rule
@JvmField
var tmp = TemporaryFolder()
private lateinit var cache: JavaClassCacheManager
private lateinit var generatedSources: File
@Before
fun setUp() {
val classpathHistory = tmp.newFolder()
cache = JavaClassCacheManager(tmp.newFolder(), classpathHistory)
generatedSources = tmp.newFolder()
cache.close()
classpathHistory.resolve("0").createNewFile()
}
@Test
fun testAggregatingAnnotations() {
runProcessor(SimpleProcessor().toAggregating())
val dirtyFiles = cache.invalidateAndGetDirtyFiles(listOf(TEST_DATA_DIR.resolve("User.java"))) as SourcesToReprocess.Incremental
assertEquals(
listOf(TEST_DATA_DIR.resolve("User.java").absoluteFile, TEST_DATA_DIR.resolve("Address.java").absoluteFile),
dirtyFiles.toReprocess
)
assertFalse(generatedSources.resolve("test/UserGenerated.java").exists())
assertFalse(generatedSources.resolve("test/AddressGenerated.java").exists())
}
@Test
fun testIsolatingAnnotations() {
runProcessor(SimpleProcessor().toIsolating())
val dirtyFiles = cache.invalidateAndGetDirtyFiles(listOf(TEST_DATA_DIR.resolve("User.java"))) as SourcesToReprocess.Incremental
assertFalse(generatedSources.resolve("test/UserGenerated.java").exists())
assertEquals(
listOf(TEST_DATA_DIR.resolve("User.java").absoluteFile),
dirtyFiles.toReprocess
)
}
@Test
fun testNonIncremental() {
runProcessor(SimpleProcessor().toNonIncremental())
val dirtyFiles = cache.invalidateAndGetDirtyFiles(listOf(TEST_DATA_DIR.resolve("User.java")))
assertTrue(dirtyFiles is SourcesToReprocess.FullRebuild)
}
private fun runProcessor(processor: IncrementalProcessor) {
val srcFiles = listOf("User.java", "Address.java", "Observable.java").map { File(TEST_DATA_DIR, it) }
runAnnotationProcessing(
srcFiles,
listOf(processor),
generatedSources
) { trees -> MentionedTypesTaskListener(cache.javaCache, trees) }
cache.updateCache(listOf(processor))
}
}
@@ -0,0 +1,124 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.kapt.base.test.org.jetbrains.kotlin.kapt3.base.incremental
import com.sun.source.util.TaskListener
import com.sun.source.util.Trees
import com.sun.tools.javac.api.JavacTaskImpl
import org.jetbrains.kotlin.kapt3.base.incremental.DeclaredProcType
import org.jetbrains.kotlin.kapt3.base.incremental.IncrementalProcessor
import org.jetbrains.kotlin.kapt3.base.incremental.RuntimeProcType
import java.io.File
import javax.annotation.processing.AbstractProcessor
import javax.annotation.processing.Filer
import javax.annotation.processing.ProcessingEnvironment
import javax.annotation.processing.RoundEnvironment
import javax.lang.model.SourceVersion
import javax.lang.model.element.TypeElement
import javax.tools.StandardLocation
import javax.tools.ToolProvider
val TEST_DATA_DIR = File("plugins/kapt3/kapt3-base/testData/runner/incremental")
fun runAnnotationProcessing(
srcFiles: List<File>,
processor: List<IncrementalProcessor>,
generatedSources: File,
listener: (Trees) -> TaskListener? = { null }
) {
val compiler = ToolProvider.getSystemJavaCompiler()
compiler.getStandardFileManager(null, null, null).use { fileManager ->
val javaSrcs = fileManager.getJavaFileObjectsFromFiles(srcFiles)
val compilationTask =
compiler.getTask(
null,
fileManager,
null,
listOf("-proc:only", "-s", generatedSources.absolutePath, "-d", generatedSources.absolutePath),
null,
javaSrcs
) as JavacTaskImpl
val taskListener = listener(Trees.instance(compilationTask))
taskListener?.let { compilationTask.addTaskListener(it) }
compilationTask.setProcessors(processor)
compilationTask.call()
}
}
fun compileSources(srcFiles: Iterable<File>, outputDir: File) {
val compiler = ToolProvider.getSystemJavaCompiler()
compiler.getStandardFileManager(null, null, null).use { fileManager ->
val compilationTask =
compiler.getTask(
null,
fileManager,
null,
listOf("-d", outputDir.absolutePath),
null,
fileManager.getJavaFileObjectsFromFiles(srcFiles)
) as JavacTaskImpl
compilationTask.call()
}
}
fun SimpleProcessor.toAggregating() = IncrementalProcessor(this, DeclaredProcType.AGGREGATING)
fun SimpleProcessor.toIsolating() = IncrementalProcessor(this, DeclaredProcType.ISOLATING)
fun SimpleProcessor.toNonIncremental() = IncrementalProcessor(this, DeclaredProcType.NON_INCREMENTAL)
fun DynamicProcessor.toDynamic() = IncrementalProcessor(this, DeclaredProcType.DYNAMIC)
open class SimpleProcessor(private val wrongOrigin: Boolean = false, private val generatedSuffix: String = "") : AbstractProcessor() {
lateinit var filer: Filer
override fun init(processingEnv: ProcessingEnvironment?) {
super.init(processingEnv)
filer = processingEnv!!.filer
}
override fun getSupportedAnnotationTypes(): MutableSet<String> = mutableSetOf("test.Observable")
override fun process(annotations: MutableSet<out TypeElement>, roundEnv: RoundEnvironment): Boolean {
if (annotations.isEmpty()) return false
roundEnv.getElementsAnnotatedWith(annotations.single()).forEach {
it as TypeElement
val generatedName = "${it.qualifiedName}Generated$generatedSuffix"
filer.createSourceFile(generatedName, it.takeUnless { wrongOrigin }).openWriter().use { it.write("") }
}
return false
}
override fun getSupportedSourceVersion(): SourceVersion {
return SourceVersion.latest()
}
}
class DynamicProcessor(private val kind: RuntimeProcType) : SimpleProcessor() {
override fun getSupportedOptions(): MutableSet<String> {
return mutableSetOf("org.gradle.annotation.processing.${kind.name}")
}
}
class SimpleCreatingClassFilesAndResources : SimpleProcessor() {
override fun process(annotations: MutableSet<out TypeElement>, roundEnv: RoundEnvironment): Boolean {
super.process(annotations, roundEnv)
if (annotations.isEmpty()) return false
roundEnv.getElementsAnnotatedWith(annotations.single()).forEach {
it as TypeElement
val generatedName = "${it.qualifiedName}Generated"
filer.createClassFile("${generatedName}Class", it).openWriter().use { it.write("") }
filer.createResource(StandardLocation.SOURCE_OUTPUT, "test", "${it.simpleName}GeneratedResource", it).openWriter().use { it.write("") }
}
return false
}
}
@@ -0,0 +1,4 @@
package test;
@Observable
public class Address{}
@@ -0,0 +1,3 @@
package test;
public @interface Observable {}
@@ -0,0 +1,4 @@
package test;
@Observable
public class User {}
@@ -0,0 +1,9 @@
package test;
import java.util.HashSet;
public class GenericNumber {
<T extends Runnable&Cloneable> java.util.HashSet<? super java.lang.Number> usingGenerics(HashSet<? extends CharSequence> set) {
return null;
}
}
@@ -0,0 +1,8 @@
package test;
public enum MyEnum {
VALUE;
public void process(test.TypeGeneratedByApt toBeGenerated) {
}
}
@@ -0,0 +1,32 @@
package test;
import java.lang.annotation.*;
import java.util.*;
import static java.lang.annotation.ElementType.*;
public class MyNumber extends @TypeUseAnnotation HashSet {
@FieldAnnotation
private String value;
@MethodAnnotation
private void getPrintedValue(@ParameterAnnotation String format) throws @ThrowTypeUseAnnotation RuntimeException{
}
private <@AnotherTypeUseAnnotation T extends Number> void accept(T visitor) {
}
}
@interface FieldAnnotation {}
@interface MethodAnnotation {}
@interface ParameterAnnotation {}
@Target(value={TYPE_PARAMETER, TYPE_USE})
@interface TypeUseAnnotation {}
@Target(value={TYPE_PARAMETER, TYPE_USE})
@interface AnotherTypeUseAnnotation {}
@Target(value={TYPE_PARAMETER, TYPE_USE})
@interface ThrowTypeUseAnnotation {}
@@ -0,0 +1,9 @@
package test;
public @interface NumberAnnotation {
Class[] classReferences() default { NumberManager.class};
MyEnum enumReference() default MyEnum.VALUE;
BaseAnnotation otherAnnotation() default @test.BaseAnnotation;
}
@interface BaseAnnotation {}
@@ -0,0 +1,4 @@
package test;
public class NumberException extends RuntimeException {
}
@@ -0,0 +1,16 @@
package test;
@NumberAnnotation
public class NumberHolder<T extends MyNumber> extends java.util.HashSet<T> implements Runnable {
private NumberManager manager;
String getStringValue(NumberManager usingManager) {
return null;
}
public void run() throws NumberException {
}
class MyInnerClass {}
}
@@ -0,0 +1,16 @@
package test;
public class NumberManager {
static final String CONST = "STRING_CONST";
static final int INT_CONST = 123 + 123;
static int NOT_A_CONST = 1000;
<T extends NumberHolder> T[] getAllHolders() {
return null;
}
private MyEnum getMyEnum() {
return null;
}
}
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.kapt.cli
import org.jetbrains.kotlin.compiler.plugin.AbstractCliOption
import org.jetbrains.kotlin.kapt.cli.CliToolOption.Format.*
import java.io.File
class CliToolOption(val name: String, val format: Format) {
enum class Format {
@@ -69,6 +70,32 @@ enum class KaptCliOption(
INCREMENTAL_DATA_OUTPUT_DIR_OPTION("incrementalData", "<path>", "Output path for incremental data"),
CHANGED_FILES(
"changedFile",
"<path>",
"Use only in apt mode. Changed java source file that should be processed when using incremental annotation processing.",
true
),
COMPILED_SOURCES_DIR(
"compiledSourcesDir",
"<path>",
"Use only in apt mode. Compiled sources (.class files) from previous compilation. This is typically a kotlinc or javac output.",
true
),
INCREMENTAL_CACHE(
"incrementalCache",
"<path>",
"Use only in apt mode. Output directory for cache necessary to support incremental annotation processing."
),
CLASSPATH_FQ_NAMES_HISTORY(
"classpathFqNamesHistory",
"<path>",
"Use only in apt mode. Directory containing history of classpath fq name changes."
),
ANNOTATION_PROCESSOR_CLASSPATH_OPTION(
"apclasspath",
"<classpath>",
@@ -216,7 +216,7 @@ abstract class AbstractKapt3Extension(
private fun runAnnotationProcessing(kaptContext: KaptContext, processors: LoadedProcessors) {
if (!options.mode.runAnnotationProcessing) return
val javaSourceFiles = options.collectJavaSourceFiles()
val javaSourceFiles = options.collectJavaSourceFiles(kaptContext.cacheManager)
logger.info { "Java source files: " + javaSourceFiles.joinToString { it.canonicalPath } }
val (annotationProcessingTime) = measureTimeMillis {
@@ -21,6 +21,7 @@ import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.analyzer.AnalysisResult
import org.jetbrains.kotlin.base.kapt3.*
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.config.KotlinSourceRoot
import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector
import org.jetbrains.kotlin.cli.jvm.config.JavaSourceRoot
@@ -96,6 +97,11 @@ class Kapt3CommandLineProcessor : CommandLineProcessor {
STUBS_OUTPUT_DIR_OPTION -> stubsOutputDir = File(value)
INCREMENTAL_DATA_OUTPUT_DIR_OPTION -> incrementalDataOutputDir = File(value)
CHANGED_FILES -> changedFiles.addAll(value.split(File.pathSeparator).map { File(it) })
COMPILED_SOURCES_DIR -> compiledSources.addAll(value.split(File.pathSeparator).map { File(it) })
INCREMENTAL_CACHE -> incrementalCache = File(value)
CLASSPATH_FQ_NAMES_HISTORY -> classpathFqNamesHistory = File(value)
ANNOTATION_PROCESSOR_CLASSPATH_OPTION -> processingClasspath += File(value)
ANNOTATION_PROCESSORS_OPTION -> processors.addAll(value.split(',').map { it.trim() }.filter { it.isNotEmpty() })
@@ -28,6 +28,8 @@ import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.kapt3.*
import org.jetbrains.kotlin.kapt3.base.KaptContext
import org.jetbrains.kotlin.kapt3.base.LoadedProcessors
import org.jetbrains.kotlin.kapt3.base.incremental.DeclaredProcType
import org.jetbrains.kotlin.kapt3.base.incremental.IncrementalProcessor
import org.jetbrains.kotlin.kapt3.javac.KaptJavaFileObject
import org.jetbrains.kotlin.kapt3.stubs.ClassFileToSourceStubConverter
import org.jetbrains.kotlin.kapt3.stubs.ClassFileToSourceStubConverter.KaptStub
@@ -171,7 +173,9 @@ abstract class AbstractKotlinKapt3IntegrationTest : CodegenTestCase() {
internal var savedStubs: String? = null
internal var savedBindings: Map<String, KaptJavaFileObject>? = null
override fun loadProcessors() = LoadedProcessors(processors, Kapt3ExtensionForTests::class.java.classLoader)
override fun loadProcessors() = LoadedProcessors(
processors.map { IncrementalProcessor(it, DeclaredProcType.NON_INCREMENTAL) },
Kapt3ExtensionForTests::class.java.classLoader)
override fun saveStubs(kaptContext: KaptContext, stubs: List<KaptStub>) {
if (this.savedStubs != null) {