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:
committed by
Alexey Tsvetkov
parent
600a955a51
commit
9f14daa682
+3
-1
@@ -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)
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+28
-1
@@ -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(
|
||||
|
||||
+55
@@ -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;
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -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 {
|
||||
|
||||
+4
-4
@@ -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")
|
||||
|
||||
+86
@@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+113
@@ -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)
|
||||
}
|
||||
+2
-1
@@ -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")
|
||||
|
||||
+2
-1
@@ -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
|
||||
|
||||
+27
-2
@@ -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")
|
||||
}
|
||||
|
||||
|
||||
+5
@@ -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) &&
|
||||
|
||||
+30
@@ -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"
|
||||
|
||||
|
||||
+19
-3
@@ -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()
|
||||
|
||||
|
||||
+18
-2
@@ -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,
|
||||
|
||||
+13
-4
@@ -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()
|
||||
|
||||
+1
@@ -79,6 +79,7 @@ class Android25ProjectHandler(
|
||||
.map { it.file.absolutePath }
|
||||
.toTypedArray()
|
||||
}
|
||||
kotlinTask.javaOutputDir = javaTask.destinationDir
|
||||
}
|
||||
|
||||
override fun getSourceProviders(variantData: BaseVariant): Iterable<SourceProvider> =
|
||||
|
||||
+6
-1
@@ -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> {
|
||||
|
||||
+24
-3
@@ -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
|
||||
|
||||
+78
@@ -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()
|
||||
}
|
||||
+170
@@ -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>)
|
||||
+62
@@ -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
|
||||
}
|
||||
+163
@@ -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),
|
||||
}
|
||||
+182
@@ -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)
|
||||
|
||||
+75
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
+44
@@ -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?>())
|
||||
}
|
||||
}
|
||||
|
||||
+75
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
+101
@@ -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())
|
||||
}
|
||||
}
|
||||
+105
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
+110
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
+223
@@ -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)
|
||||
}
|
||||
}
|
||||
+210
@@ -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())
|
||||
}
|
||||
}
|
||||
+79
@@ -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))
|
||||
}
|
||||
}
|
||||
+124
@@ -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 {}
|
||||
+9
@@ -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 {}
|
||||
+9
@@ -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 {}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
package test;
|
||||
|
||||
public class NumberException extends RuntimeException {
|
||||
}
|
||||
+16
@@ -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 {}
|
||||
}
|
||||
+16
@@ -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() })
|
||||
|
||||
|
||||
+5
-1
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user