diff --git a/compiler/daemon/daemon-common/src/org/jetbrains/kotlin/daemon/common/CompilationOptions.kt b/compiler/daemon/daemon-common/src/org/jetbrains/kotlin/daemon/common/CompilationOptions.kt index eaf004fe7bd..66419a67145 100644 --- a/compiler/daemon/daemon-common/src/org/jetbrains/kotlin/daemon/common/CompilationOptions.kt +++ b/compiler/daemon/daemon-common/src/org/jetbrains/kotlin/daemon/common/CompilationOptions.kt @@ -64,7 +64,8 @@ class IncrementalCompilationOptions( */ val outputFiles: List, 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" + ")" } } diff --git a/compiler/daemon/src/org/jetbrains/kotlin/daemon/CompileServiceImpl.kt b/compiler/daemon/src/org/jetbrains/kotlin/daemon/CompileServiceImpl.kt index 3c3d7fef32b..2fc8211ed0d 100644 --- a/compiler/daemon/src/org/jetbrains/kotlin/daemon/CompileServiceImpl.kt +++ b/compiler/daemon/src/org/jetbrains/kotlin/daemon/CompileServiceImpl.kt @@ -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) diff --git a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalCompilerRunner.kt b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalCompilerRunner.kt index 844c890787f..fff8690a757 100644 --- a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalCompilerRunner.kt +++ b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalCompilerRunner.kt @@ -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 diff --git a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalJvmCompilerRunner.kt b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalJvmCompilerRunner.kt index f9abe3dd809..e60f8a7a7a1 100644 --- a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalJvmCompilerRunner.kt +++ b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalJvmCompilerRunner.kt @@ -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, private val modulesApiHistory: ModulesApiHistory, - override val kotlinSourceFilesExtensions: List = DEFAULT_KOTLIN_SOURCE_FILES_EXTENSIONS + override val kotlinSourceFilesExtensions: List = DEFAULT_KOTLIN_SOURCE_FILES_EXTENSIONS, + private val classpathFqNamesHistory: File? = null ) : IncrementalCompilerRunner( workingDir, "caches-jvm", @@ -126,6 +128,8 @@ class IncrementalJvmCompilerRunner( override fun destinationDir(args: K2JVMCompilerArguments): File = args.destinationAsFile + private var dirtyClasspathChanges: Collection = emptySet() + 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( diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/java/org/jetbrains/kotlin/gradle/incapt/IncrementalProcessor.java b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/java/org/jetbrains/kotlin/gradle/incapt/IncrementalProcessor.java new file mode 100644 index 00000000000..4d48157a802 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/java/org/jetbrains/kotlin/gradle/incapt/IncrementalProcessor.java @@ -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 getSupportedAnnotationTypes() { + return Collections.singleton("example.ExampleAnnotation"); + } + + @Override + public boolean process(Set 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; + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BaseGradleIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BaseGradleIT.kt index 788b1500f2d..bf424e676e8 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BaseGradleIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BaseGradleIT.kt @@ -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 { diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KaptIncrementalIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KaptIncrementalIT.kt index 9caf1998f6b..1191415305c 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KaptIncrementalIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KaptIncrementalIT.kt @@ -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") diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KaptIncrementalWithAggregatingApt.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KaptIncrementalWithAggregatingApt.kt new file mode 100644 index 00000000000..dcf1383dfe9 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KaptIncrementalWithAggregatingApt.kt @@ -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) + ) + } + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KaptIncrementalWithIsolatingApt.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KaptIncrementalWithIsolatingApt.kt new file mode 100644 index 00000000000..06e01eadb15 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KaptIncrementalWithIsolatingApt.kt @@ -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 { + 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) +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/GradleKotlinCompilerWork.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/GradleKotlinCompilerWork.kt index a76e3f30d6a..953f8506ad8 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/GradleKotlinCompilerWork.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/GradleKotlinCompilerWork.kt @@ -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") diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/IncrementalCompilationEnvironment.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/IncrementalCompilationEnvironment.kt index c1a9df5a0e7..525ff7e3ab1 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/IncrementalCompilationEnvironment.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/IncrementalCompilationEnvironment.kt @@ -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 diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/Kapt3KotlinGradleSubplugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/Kapt3KotlinGradleSubplugin.kt index 6d845dea9f2..e466a9df50f 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/Kapt3KotlinGradleSubplugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/Kapt3KotlinGradleSubplugin.kt @@ -90,6 +90,7 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin { 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 { 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 { 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 { ) 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 { } } - 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 { 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 { } 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") } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/KaptGenerateStubsTask.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/KaptGenerateStubsTask.kt index 61beb0bc253..58144b1b20b 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/KaptGenerateStubsTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/KaptGenerateStubsTask.kt @@ -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) && diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/KaptTask.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/KaptTask.kt index d185724bdbb..3ed7c9a35bc 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/KaptTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/KaptTask.kt @@ -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 + /** 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 { + return if (!isIncremental || !inputs.isIncremental || !getCompiledSources().all { it.exists() }) { + clearLocalState() + emptyList() + } else { + with(mutableSetOf()) { + 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" diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/KaptWithKotlincTask.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/KaptWithKotlincTask.kt index 807fbbcb94f..74db041d8f6 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/KaptWithKotlincTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/KaptWithKotlincTask.kt @@ -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 { @get:Internal @@ -46,17 +51,28 @@ open class KaptWithKotlincTask : KaptTask(), CompilerArgumentAwareWithInput = 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() diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/KaptWithoutKotlincTask.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/KaptWithoutKotlincTask.kt index aef44800cb4..c44d2b0cb31 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/KaptWithoutKotlincTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/KaptWithoutKotlincTask.kt @@ -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 @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, val javaSourceRoots: List, + val changedFiles: List, + val compiledSources: List, + val incAptCache: File?, + val classpathFqNamesHistory: File?, + val sourcesOutputDir: File, val classesOutputDir: File, val stubsOutputDir: File, diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/subpluginUtils.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/subpluginUtils.kt index fbddd3c33d1..d1fd3df9d2f 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/subpluginUtils.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/subpluginUtils.kt @@ -43,15 +43,24 @@ fun encodePluginOptions(options: Map>): String { return Base64.getEncoder().encodeToString(os.toByteArray()) } -internal fun CompilerPluginOptions.withWrappedKaptOptions(withApClasspath: Iterable): CompilerPluginOptions { +internal fun CompilerPluginOptions.withWrappedKaptOptions( + withApClasspath: Iterable, changedFiles: List = emptyList(), compiledSourcesDir: List = emptyList() +): CompilerPluginOptions { val resultOptionsByPluginId: MutableMap> = 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() diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/android/Android25ProjectHandler.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/android/Android25ProjectHandler.kt index 0e84e7dfe66..0117da6bfea 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/android/Android25ProjectHandler.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/android/Android25ProjectHandler.kt @@ -79,6 +79,7 @@ class Android25ProjectHandler( .map { it.file.absolutePath } .toTypedArray() } + kotlinTask.javaOutputDir = javaTask.destinationDir } override fun getSourceProviders(variantData: BaseVariant): Iterable = diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt index 5776e242743..1df0010af5d 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt @@ -433,7 +433,8 @@ open class KotlinCompile : AbstractKotlinCompile(), Kotl taskBuildDirectory, usePreciseJavaTracking = usePreciseJavaTracking, disableMultiModuleIC = disableMultiModuleIC(), - multiModuleICSettings = multiModuleICSettings + multiModuleICSettings = multiModuleICSettings, + classpathFqNamesHistory = getClasspathFqNamesHistoryDir() ) } else null @@ -475,6 +476,10 @@ open class KotlinCompile : AbstractKotlinCompile(), 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) diff --git a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/Kapt.kt b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/Kapt.kt index e8efcaeb7e7..c6a5b8134d3 100644 --- a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/Kapt.kt +++ b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/Kapt.kt @@ -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) diff --git a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/KaptContext.kt b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/KaptContext.kt index 77321a80323..43329afc6b8 100644 --- a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/KaptContext.kt +++ b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/KaptContext.kt @@ -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() } diff --git a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/KaptOptions.kt b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/KaptOptions.kt index 32b7460e3bd..f0bbaef8f7d 100644 --- a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/KaptOptions.kt +++ b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/KaptOptions.kt @@ -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, val javaSourceRoots: List, + val changedFiles: List, + val compiledSources: List, + val incrementalCache: File?, + val classpathFqNamesHistory: File?, + val sourcesOutputDir: File, val classesOutputDir: File, val stubsOutputDir: File, @@ -36,6 +43,11 @@ class KaptOptions( val compileClasspath: MutableList = mutableListOf() val javaSourceRoots: MutableList = mutableListOf() + val changedFiles: MutableList = mutableListOf() + val compiledSources: MutableList = 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 { - 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 { + fun allSources(): List { + 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") } \ No newline at end of file diff --git a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/ProcessorLoader.kt b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/ProcessorLoader.kt index 7a5bea20c65..73228ce6a9f 100644 --- a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/ProcessorLoader.kt +++ b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/ProcessorLoader.kt @@ -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, val classLoader: ClassLoader) +class LoadedProcessors(val processors: List, 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, classpath: Iterable): List { + val processorNames = processors.map {it.javaClass.name}.toSet() + + val processorsInfo: Map = 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 { diff --git a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/annotationProcessing.kt b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/annotationProcessing.kt index b44efe19fcd..5562e79e95f 100644 --- a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/annotationProcessing.kt +++ b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/annotationProcessing.kt @@ -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, - processors: List, + processors: List, additionalSources: JavacList = 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 diff --git a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/incremental/IncrementalAptCache.kt b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/incremental/IncrementalAptCache.kt new file mode 100644 index 00000000000..2a4dea1795c --- /dev/null +++ b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/incremental/IncrementalAptCache.kt @@ -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 = mutableSetOf() + private val isolatingMapping: MutableMap = mutableMapOf() + // Annotations claimed by aggregating annotation processors + private val aggregatingClaimedAnnotations: MutableSet = mutableSetOf() + + var isIncremental = true + private set + + fun updateCache(processors: List): Boolean { + val aggregating = mutableListOf() + val isolating = mutableListOf() + val nonIncremental = mutableListOf() + 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 { + aggregatingGenerated.forEach { it.delete() } + aggregatingGenerated.clear() + + return aggregatingClaimedAnnotations + } + + fun invalidateIsolatingGenerated(fromSources: Set) { + 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() + } +} \ No newline at end of file diff --git a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/incremental/cache.kt b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/incremental/cache.kt new file mode 100644 index 00000000000..41d0e18e5a0 --- /dev/null +++ b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/incremental/cache.kt @@ -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() + after.forEach{ file -> + ObjectInputStream(file.inputStream().buffered()).use { + @Suppress("UNCHECKED_CAST") + dirtyFqNames.addAll(it.readObject() as Collection) + } + } + + 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) { + 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): 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): SourcesToReprocess() + object FullRebuild : SourcesToReprocess() +} + +sealed class ClasspathChanged { + class Incremental(val dirtyFqNames: Set) : ClasspathChanged() + object FullRebuild : ClasspathChanged() +} \ No newline at end of file diff --git a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/incremental/classStructureCache.kt b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/incremental/classStructureCache.kt new file mode 100644 index 00000000000..6a15ba59dab --- /dev/null +++ b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/incremental/classStructureCache.kt @@ -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() + + /** Map from types to files they are mentioned in. */ + @Transient + private var dependencyCache = mutableMapOf>() + @Transient + private var nonTransitiveCache = mutableMapOf>() + + fun addSourceStructure(sourceStructure: SourceFileStructure) { + sourceCache[sourceStructure.sourceFile] = sourceStructure + } + + private fun readObject(input: java.io.ObjectInputStream) { + @Suppress("UNCHECKED_CAST") + sourceCache = input.readObject() as MutableMap + + 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() + 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() + 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): Set { + val patterns = annotations.map { JavacProcessingEnvironment.validImportStringToPattern(it) } + val matchesAnyPattern = { name: String -> patterns.any { it.matcher(name).matches() } } + + val toReprocess = mutableSetOf() + + 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 = mutableSetOf() + + private val mentionedTypes: MutableSet = mutableSetOf() + private val privateTypes: MutableSet = mutableSetOf() + + private val definedConstants: MutableMap = mutableMapOf() + private val mentionedAnnotations: MutableSet = mutableSetOf() + + fun getDeclaredTypes(): Set = declaredTypes + fun getMentionedTypes(): Set = mentionedTypes + fun getPrivateTypes(): Set = privateTypes + fun getDefinedConstants(): Map = definedConstants + fun getMentionedAnnotations(): Set = 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, val dirtyFqNamesFromClasspath: Set) \ No newline at end of file diff --git a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/incremental/incrementalProcessorDiscovery.kt b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/incremental/incrementalProcessorDiscovery.kt new file mode 100644 index 00000000000..acebea39cd8 --- /dev/null +++ b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/incremental/incrementalProcessorDiscovery.kt @@ -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, classpath: Iterable): Map { + val finalValues = mutableMapOf() + + 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 { + val text: List = 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() + 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 +} \ No newline at end of file diff --git a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/incremental/incrementalProcessors.kt b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/incremental/incrementalProcessors.kt new file mode 100644 index 00000000000..e99bc228fe9 --- /dev/null +++ b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/incremental/incrementalProcessors.kt @@ -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() + private var isFullRebuild = !runtimeProcType.isIncremental + + internal fun add(createdFile: URI, originatingElements: Array) { + 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 = if (isFullRebuild) emptyMap() else generatedToSource + internal fun getRuntimeType(): RuntimeProcType { + return if (isFullRebuild) { + RuntimeProcType.NON_INCREMENTAL + } else { + runtimeProcType + } + } +} + +private fun getSrcFiles(elements: Array): List { + 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), +} \ No newline at end of file diff --git a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/incremental/javacVisitors.kt b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/incremental/javacVisitors.kt new file mode 100644 index 00000000000..53fbe30c40c --- /dev/null +++ b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/incremental/javacVisitors.kt @@ -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() { + + 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 + } +} \ No newline at end of file diff --git a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/javac/KaptJavaCompiler.kt b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/javac/KaptJavaCompiler.kt index 5c83bd19e5b..55b7610693a 100644 --- a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/javac/KaptJavaCompiler.kt +++ b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/javac/KaptJavaCompiler.kt @@ -17,6 +17,8 @@ class KaptJavaCompiler(context: Context) : JavaCompiler(context) { return if (shouldStop(cs)) JavacList.nil() else list } + fun getTaskListeners() = this.taskListener + companion object { internal fun preRegister(context: Context) { context.put(compilerKey, Context.Factory(::KaptJavaCompiler)) diff --git a/plugins/kapt3/kapt3-base/test/JavaKaptContextTest.kt b/plugins/kapt3/kapt3-base/test/JavaKaptContextTest.kt index 9ea8532bcfe..d8fbcb07eb4 100644 --- a/plugins/kapt3/kapt3-base/test/JavaKaptContextTest.kt +++ b/plugins/kapt3/kapt3-base/test/JavaKaptContextTest.kt @@ -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, 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, 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) diff --git a/plugins/kapt3/kapt3-base/test/org/jetbrains/kotlin/kapt3/base/incremental/AggregatingIncrementalProcessorTest.kt b/plugins/kapt3/kapt3-base/test/org/jetbrains/kotlin/kapt3/base/incremental/AggregatingIncrementalProcessorTest.kt new file mode 100644 index 00000000000..544ac579df8 --- /dev/null +++ b/plugins/kapt3/kapt3-base/test/org/jetbrains/kotlin/kapt3/base/incremental/AggregatingIncrementalProcessorTest.kt @@ -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(), 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() + ) + } +} \ No newline at end of file diff --git a/plugins/kapt3/kapt3-base/test/org/jetbrains/kotlin/kapt3/base/incremental/AnnotationProcessorDependencyCollectorTest.kt b/plugins/kapt3/kapt3-base/test/org/jetbrains/kotlin/kapt3/base/incremental/AnnotationProcessorDependencyCollectorTest.kt new file mode 100644 index 00000000000..c95ec020d66 --- /dev/null +++ b/plugins/kapt3/kapt3-base/test/org/jetbrains/kotlin/kapt3/base/incremental/AnnotationProcessorDependencyCollectorTest.kt @@ -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()) + } + + @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()) + } +} + diff --git a/plugins/kapt3/kapt3-base/test/org/jetbrains/kotlin/kapt3/base/incremental/DynamicIncrementalProcessorTest.kt b/plugins/kapt3/kapt3-base/test/org/jetbrains/kotlin/kapt3/base/incremental/DynamicIncrementalProcessorTest.kt new file mode 100644 index 00000000000..1210210ad25 --- /dev/null +++ b/plugins/kapt3/kapt3-base/test/org/jetbrains/kotlin/kapt3/base/incremental/DynamicIncrementalProcessorTest.kt @@ -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(), + dynamic.getGeneratedToSources() + ) + } +} \ No newline at end of file diff --git a/plugins/kapt3/kapt3-base/test/org/jetbrains/kotlin/kapt3/base/incremental/IncrementalKaptTest.kt b/plugins/kapt3/kapt3-base/test/org/jetbrains/kotlin/kapt3/base/incremental/IncrementalKaptTest.kt new file mode 100644 index 00000000000..92f04c930b6 --- /dev/null +++ b/plugins/kapt3/kapt3-base/test/org/jetbrains/kotlin/kapt3/base/incremental/IncrementalKaptTest.kt @@ -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()) + } +} \ No newline at end of file diff --git a/plugins/kapt3/kapt3-base/test/org/jetbrains/kotlin/kapt3/base/incremental/IncrementalProcessorDiscoveryTest.kt b/plugins/kapt3/kapt3-base/test/org/jetbrains/kotlin/kapt3/base/incremental/IncrementalProcessorDiscoveryTest.kt new file mode 100644 index 00000000000..361d91f3887 --- /dev/null +++ b/plugins/kapt3/kapt3-base/test/org/jetbrains/kotlin/kapt3/base/incremental/IncrementalProcessorDiscoveryTest.kt @@ -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 + ) + } +} \ No newline at end of file diff --git a/plugins/kapt3/kapt3-base/test/org/jetbrains/kotlin/kapt3/base/incremental/IsolatingIncrementalProcessorTest.kt b/plugins/kapt3/kapt3-base/test/org/jetbrains/kotlin/kapt3/base/incremental/IsolatingIncrementalProcessorTest.kt new file mode 100644 index 00000000000..e74c1614dd2 --- /dev/null +++ b/plugins/kapt3/kapt3-base/test/org/jetbrains/kotlin/kapt3/base/incremental/IsolatingIncrementalProcessorTest.kt @@ -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(), 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(), 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() + ) + } +} diff --git a/plugins/kapt3/kapt3-base/test/org/jetbrains/kotlin/kapt3/base/incremental/JavaClassCacheManagerTest.kt b/plugins/kapt3/kapt3-base/test/org/jetbrains/kotlin/kapt3/base/incremental/JavaClassCacheManagerTest.kt new file mode 100644 index 00000000000..b9e75828d99 --- /dev/null +++ b/plugins/kapt3/kapt3-base/test/org/jetbrains/kotlin/kapt3/base/incremental/JavaClassCacheManagerTest.kt @@ -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(), 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) + } +} diff --git a/plugins/kapt3/kapt3-base/test/org/jetbrains/kotlin/kapt3/base/incremental/TestComplexIncrementalAptCache.kt b/plugins/kapt3/kapt3-base/test/org/jetbrains/kotlin/kapt3/base/incremental/TestComplexIncrementalAptCache.kt new file mode 100644 index 00000000000..07f6fac57c6 --- /dev/null +++ b/plugins/kapt3/kapt3-base/test/org/jetbrains/kotlin/kapt3/base/incremental/TestComplexIncrementalAptCache.kt @@ -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(), myEnum.getMentionedAnnotations()) + assertEquals(emptySet(), myEnum.getPrivateTypes()) + assertEquals(setOf("test.MyEnum", "test.TypeGeneratedByApt"), myEnum.getMentionedTypes()) + assertEquals(emptyMap(), 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(), 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(), numberAnnotation.getPrivateTypes()) + assertEquals( + setOf( + "test.BaseAnnotation", + "test.NumberAnnotation", + "java.lang.Class", + "test.MyEnum", + "test.NumberManager" + ), numberAnnotation.getMentionedTypes() + ) + assertEquals(emptyMap(), numberAnnotation.getDefinedConstants()) + } + + @Test + fun testNumberException() { + val numberException = cache.javaCache.getStructure(MY_TEST_DIR.resolve("NumberException.java"))!! + + assertEquals(setOf("test.NumberException"), numberException.getDeclaredTypes()) + assertEquals(emptySet(), numberException.getMentionedAnnotations()) + assertEquals(emptySet(), numberException.getPrivateTypes()) + assertEquals(setOf("test.NumberException", "java.lang.RuntimeException"), numberException.getMentionedTypes()) + assertEquals(emptyMap(), 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(), numberHolder.getDefinedConstants()) + } + + @Test + fun testNumberManager() { + val numberManager = cache.javaCache.getStructure(MY_TEST_DIR.resolve("NumberManager.java"))!! + + assertEquals(setOf("test.NumberManager"), numberManager.getDeclaredTypes()) + assertEquals(emptySet(), 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(), genericNumber.getMentionedAnnotations()) + assertEquals(emptySet(), 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(), genericNumber.getDefinedConstants()) + } +} \ No newline at end of file diff --git a/plugins/kapt3/kapt3-base/test/org/jetbrains/kotlin/kapt3/base/incremental/TestSimpleIncrementalAptCache.kt b/plugins/kapt3/kapt3-base/test/org/jetbrains/kotlin/kapt3/base/incremental/TestSimpleIncrementalAptCache.kt new file mode 100644 index 00000000000..5dfa2846833 --- /dev/null +++ b/plugins/kapt3/kapt3-base/test/org/jetbrains/kotlin/kapt3/base/incremental/TestSimpleIncrementalAptCache.kt @@ -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)) + } +} \ No newline at end of file diff --git a/plugins/kapt3/kapt3-base/test/org/jetbrains/kotlin/kapt3/base/incremental/fixtures.kt b/plugins/kapt3/kapt3-base/test/org/jetbrains/kotlin/kapt3/base/incremental/fixtures.kt new file mode 100644 index 00000000000..404ff7e260b --- /dev/null +++ b/plugins/kapt3/kapt3-base/test/org/jetbrains/kotlin/kapt3/base/incremental/fixtures.kt @@ -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, + processor: List, + 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, 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 = mutableSetOf("test.Observable") + + override fun process(annotations: MutableSet, 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 { + return mutableSetOf("org.gradle.annotation.processing.${kind.name}") + } +} + +class SimpleCreatingClassFilesAndResources : SimpleProcessor() { + override fun process(annotations: MutableSet, 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 + } +} \ No newline at end of file diff --git a/plugins/kapt3/kapt3-base/testData/runner/incremental/Address.java b/plugins/kapt3/kapt3-base/testData/runner/incremental/Address.java new file mode 100644 index 00000000000..ee7a24473db --- /dev/null +++ b/plugins/kapt3/kapt3-base/testData/runner/incremental/Address.java @@ -0,0 +1,4 @@ +package test; + +@Observable +public class Address{} \ No newline at end of file diff --git a/plugins/kapt3/kapt3-base/testData/runner/incremental/Observable.java b/plugins/kapt3/kapt3-base/testData/runner/incremental/Observable.java new file mode 100644 index 00000000000..8f128af5ac8 --- /dev/null +++ b/plugins/kapt3/kapt3-base/testData/runner/incremental/Observable.java @@ -0,0 +1,3 @@ +package test; + +public @interface Observable {} \ No newline at end of file diff --git a/plugins/kapt3/kapt3-base/testData/runner/incremental/User.java b/plugins/kapt3/kapt3-base/testData/runner/incremental/User.java new file mode 100644 index 00000000000..da888cd209e --- /dev/null +++ b/plugins/kapt3/kapt3-base/testData/runner/incremental/User.java @@ -0,0 +1,4 @@ +package test; + +@Observable +public class User {} \ No newline at end of file diff --git a/plugins/kapt3/kapt3-base/testData/runner/incremental/complex/GenericNumber.java b/plugins/kapt3/kapt3-base/testData/runner/incremental/complex/GenericNumber.java new file mode 100644 index 00000000000..12c60551279 --- /dev/null +++ b/plugins/kapt3/kapt3-base/testData/runner/incremental/complex/GenericNumber.java @@ -0,0 +1,9 @@ +package test; + +import java.util.HashSet; + +public class GenericNumber { + java.util.HashSet usingGenerics(HashSet set) { + return null; + } +} \ No newline at end of file diff --git a/plugins/kapt3/kapt3-base/testData/runner/incremental/complex/MyEnum.java b/plugins/kapt3/kapt3-base/testData/runner/incremental/complex/MyEnum.java new file mode 100644 index 00000000000..939449f5738 --- /dev/null +++ b/plugins/kapt3/kapt3-base/testData/runner/incremental/complex/MyEnum.java @@ -0,0 +1,8 @@ +package test; + +public enum MyEnum { + VALUE; + + public void process(test.TypeGeneratedByApt toBeGenerated) { + } +} \ No newline at end of file diff --git a/plugins/kapt3/kapt3-base/testData/runner/incremental/complex/MyNumber.java b/plugins/kapt3/kapt3-base/testData/runner/incremental/complex/MyNumber.java new file mode 100644 index 00000000000..06d9a6e4fa8 --- /dev/null +++ b/plugins/kapt3/kapt3-base/testData/runner/incremental/complex/MyNumber.java @@ -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 {} \ No newline at end of file diff --git a/plugins/kapt3/kapt3-base/testData/runner/incremental/complex/NumberAnnotation.java b/plugins/kapt3/kapt3-base/testData/runner/incremental/complex/NumberAnnotation.java new file mode 100644 index 00000000000..035215d31c3 --- /dev/null +++ b/plugins/kapt3/kapt3-base/testData/runner/incremental/complex/NumberAnnotation.java @@ -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 {} \ No newline at end of file diff --git a/plugins/kapt3/kapt3-base/testData/runner/incremental/complex/NumberException.java b/plugins/kapt3/kapt3-base/testData/runner/incremental/complex/NumberException.java new file mode 100644 index 00000000000..678ad780ef5 --- /dev/null +++ b/plugins/kapt3/kapt3-base/testData/runner/incremental/complex/NumberException.java @@ -0,0 +1,4 @@ +package test; + +public class NumberException extends RuntimeException { +} \ No newline at end of file diff --git a/plugins/kapt3/kapt3-base/testData/runner/incremental/complex/NumberHolder.java b/plugins/kapt3/kapt3-base/testData/runner/incremental/complex/NumberHolder.java new file mode 100644 index 00000000000..40345cc73f3 --- /dev/null +++ b/plugins/kapt3/kapt3-base/testData/runner/incremental/complex/NumberHolder.java @@ -0,0 +1,16 @@ +package test; + +@NumberAnnotation +public class NumberHolder extends java.util.HashSet implements Runnable { + + private NumberManager manager; + + String getStringValue(NumberManager usingManager) { + return null; + } + + public void run() throws NumberException { + } + + class MyInnerClass {} +} \ No newline at end of file diff --git a/plugins/kapt3/kapt3-base/testData/runner/incremental/complex/NumberManager.java b/plugins/kapt3/kapt3-base/testData/runner/incremental/complex/NumberManager.java new file mode 100644 index 00000000000..cac3fa1b5e2 --- /dev/null +++ b/plugins/kapt3/kapt3-base/testData/runner/incremental/complex/NumberManager.java @@ -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[] getAllHolders() { + return null; + } + + private MyEnum getMyEnum() { + return null; + } +} \ No newline at end of file diff --git a/plugins/kapt3/kapt3-cli/src/KaptCliOption.kt b/plugins/kapt3/kapt3-cli/src/KaptCliOption.kt index aa1bf735bb4..628fb854421 100644 --- a/plugins/kapt3/kapt3-cli/src/KaptCliOption.kt +++ b/plugins/kapt3/kapt3-cli/src/KaptCliOption.kt @@ -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", "", "Output path for incremental data"), + CHANGED_FILES( + "changedFile", + "", + "Use only in apt mode. Changed java source file that should be processed when using incremental annotation processing.", + true + ), + + COMPILED_SOURCES_DIR( + "compiledSourcesDir", + "", + "Use only in apt mode. Compiled sources (.class files) from previous compilation. This is typically a kotlinc or javac output.", + true + ), + + INCREMENTAL_CACHE( + "incrementalCache", + "", + "Use only in apt mode. Output directory for cache necessary to support incremental annotation processing." + ), + + CLASSPATH_FQ_NAMES_HISTORY( + "classpathFqNamesHistory", + "", + "Use only in apt mode. Directory containing history of classpath fq name changes." + ), + ANNOTATION_PROCESSOR_CLASSPATH_OPTION( "apclasspath", "", diff --git a/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/Kapt3Extension.kt b/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/Kapt3Extension.kt index 75884465025..31e083c3796 100644 --- a/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/Kapt3Extension.kt +++ b/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/Kapt3Extension.kt @@ -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 { diff --git a/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/Kapt3Plugin.kt b/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/Kapt3Plugin.kt index b4a5372cfb2..198c6e11337 100644 --- a/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/Kapt3Plugin.kt +++ b/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/Kapt3Plugin.kt @@ -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() }) diff --git a/plugins/kapt3/kapt3-compiler/test/org/jetbrains/kotlin/kapt3/test/AbstractKotlinKapt3IntegrationTest.kt b/plugins/kapt3/kapt3-compiler/test/org/jetbrains/kotlin/kapt3/test/AbstractKotlinKapt3IntegrationTest.kt index 66bd700f034..34f1092ca19 100644 --- a/plugins/kapt3/kapt3-compiler/test/org/jetbrains/kotlin/kapt3/test/AbstractKotlinKapt3IntegrationTest.kt +++ b/plugins/kapt3/kapt3-compiler/test/org/jetbrains/kotlin/kapt3/test/AbstractKotlinKapt3IntegrationTest.kt @@ -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? = 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) { if (this.savedStubs != null) {