Kapt: support incremental compilation in Gradle (KT-13500)

Kapt will process sources on each step of incremental compilation.
(cherry picked from commit 4cb2127)
This commit is contained in:
Yan Zhulanow
2016-08-31 21:08:33 +03:00
committed by Yan Zhulanow
parent 743be477ec
commit 32d77e5226
9 changed files with 208 additions and 19 deletions
@@ -32,6 +32,7 @@ import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisCompletedHandlerExtension
import java.io.File
import java.io.IOException
import java.net.URLClassLoader
import java.util.*
import javax.annotation.processing.Processor
@@ -42,8 +43,10 @@ class ClasspathBasedAnnotationProcessingExtension(
generatedSourcesOutputDir: File,
classesOutputDir: File,
javaSourceRoots: List<File>,
verboseOutput: Boolean
) : AbstractAnnotationProcessingExtension(generatedSourcesOutputDir, classesOutputDir, javaSourceRoots, verboseOutput) {
verboseOutput: Boolean,
incrementalDataFile: File?
) : AbstractAnnotationProcessingExtension(generatedSourcesOutputDir,
classesOutputDir, javaSourceRoots, verboseOutput, incrementalDataFile) {
override fun loadAnnotationProcessors(): List<Processor> {
val classLoader = URLClassLoader(annotationProcessingClasspath.map { it.toURI().toURL() }.toTypedArray())
return ServiceLoader.load(Processor::class.java, classLoader).toList()
@@ -54,8 +57,13 @@ abstract class AbstractAnnotationProcessingExtension(
val generatedSourcesOutputDir: File,
val classesOutputDir: File,
val javaSourceRoots: List<File>,
val verboseOutput: Boolean
val verboseOutput: Boolean,
val incrementalDataFile: File? = null
) : AnalysisCompletedHandlerExtension {
private companion object {
val LINE_SEPARATOR = System.getProperty("line.separator") ?: "\n"
}
private var annotationProcessingComplete = false
private val messager = KotlinMessager()
@@ -65,6 +73,10 @@ abstract class AbstractAnnotationProcessingExtension(
private fun Int.count(noun: String) = if (this == 1) "$this $noun" else "$this ${noun}s"
private inline fun <T, R> T.runIf(condition: Boolean, block: T.() -> R) {
if (condition) block()
}
override fun analysisCompleted(
project: Project,
module: ModuleDescriptor,
@@ -134,12 +146,13 @@ abstract class AbstractAnnotationProcessingExtension(
}
private fun KotlinProcessingEnvironment.doAnnotationProcessing(files: Collection<KtFile>): ProcessingResult {
run initializeProcessors@ {
val allSupportedAnnotationFqNames = run initializeProcessors@ {
processors.forEach { it.init(this) }
log { "Initialized processors: " + processors.joinToString { it.javaClass.name } }
processors.flatMapTo(mutableSetOf()) { it.supportedAnnotationTypes }
}
val firstRoundAnnotations = RoundAnnotations()
val firstRoundAnnotations = RoundAnnotations(allSupportedAnnotationFqNames)
run analyzeFilesForFirstRound@ {
log { "Analysing Kotlin files: " + files.map { it.virtualFile.path } }
@@ -159,13 +172,54 @@ abstract class AbstractAnnotationProcessingExtension(
}
}
runIf(incrementalDataFile != null) processIncrementalData@ {
val incrementalDataFile = incrementalDataFile!!
runIf(incrementalDataFile.exists()) analyzeFilesFromPreviousIncrementalData@ {
val incrementalData = try {
incrementalDataFile.readText()
} catch (e: IOException) {
log { "An exception occurred while processing incremental data file: $incrementalDataFile" }
null
}
if (incrementalData != null) {
val analyzedClasses = mutableListOf<String>()
for (line in incrementalData.lines()) {
if (line.length < 3 || !line.startsWith("i ")) continue
val fqName = line.drop(2)
val psiClass = javaPsiFacade.findClass(fqName, projectScope) ?: continue
if (firstRoundAnnotations.analyzeDeclaration(psiClass)) {
analyzedClasses += fqName
}
}
log { "Analysing files from incremental data: $analyzedClasses" }
}
}
run saveNewIncrementalData@ {
val analyzedClasses = firstRoundAnnotations.analyzedClasses
log { "Saving incremental data: ${analyzedClasses.size} class names" }
try {
incrementalDataFile.parentFile.mkdirs()
incrementalDataFile.writeText(analyzedClasses.map { "i $it" }.joinToString(LINE_SEPARATOR))
}
catch (e: IOException) {
log { "Unable to write $incrementalDataFile" }
}
}
}
val finalRoundNumber = run annotationProcessing@ {
val firstRoundEnvironment = KotlinRoundEnvironment(firstRoundAnnotations, false, 1)
process(firstRoundEnvironment)
} + 1
log { "Starting round $finalRoundNumber (final)" }
val finalRoundEnvironment = KotlinRoundEnvironment(RoundAnnotations(), true, finalRoundNumber)
val finalRoundEnvironment = KotlinRoundEnvironment(RoundAnnotations(allSupportedAnnotationFqNames), true, finalRoundNumber)
for (processor in processors) {
processor.process(emptySet(), finalRoundEnvironment)
}
@@ -200,7 +254,7 @@ abstract class AbstractAnnotationProcessingExtension(
}
// Start the next round
val nextRoundAnnotations = RoundAnnotations().apply { analyzeFiles(psiFiles) }
val nextRoundAnnotations = RoundAnnotations(roundEnvironment.supportedAnnotationFqNames).apply { analyzeFiles(psiFiles) }
val nextRoundEnvironment = KotlinRoundEnvironment(nextRoundAnnotations, false, roundEnvironment.roundNumber + 1)
return process(nextRoundEnvironment)
}
@@ -42,6 +42,9 @@ object AnnotationProcessingConfigurationKeys {
val ANNOTATION_PROCESSOR_CLASSPATH: CompilerConfigurationKey<List<String>> =
CompilerConfigurationKey.create<List<String>>("annotation processor classpath")
val INCREMENTAL_DATA_FILE: CompilerConfigurationKey<String> =
CompilerConfigurationKey.create<String>("data file for incremental compilation support")
val VERBOSE_MODE: CompilerConfigurationKey<String> =
CompilerConfigurationKey.create<String>("verbose mode")
}
@@ -60,6 +63,9 @@ class AnnotationProcessingCommandLineProcessor : CommandLineProcessor {
CliOption("apclasspath", "<classpath>", "Annotation processor classpath",
required = false, allowMultipleOccurrences = true)
val INCREMENTAL_DATA_FILE_OPTION: CliOption =
CliOption("incrementalData", "<path>", "Location of the incremental data file", required = false)
val VERBOSE_MODE_OPTION: CliOption =
CliOption("verbose", "true | false", "Enable verbose output", required = false)
}
@@ -67,7 +73,8 @@ class AnnotationProcessingCommandLineProcessor : CommandLineProcessor {
override val pluginId: String = ANNOTATION_PROCESSING_COMPILER_PLUGIN_ID
override val pluginOptions: Collection<CliOption> =
listOf(GENERATED_OUTPUT_DIR_OPTION, ANNOTATION_PROCESSOR_CLASSPATH_OPTION, CLASS_FILES_OUTPUT_DIR_OPTION, VERBOSE_MODE_OPTION)
listOf(GENERATED_OUTPUT_DIR_OPTION, ANNOTATION_PROCESSOR_CLASSPATH_OPTION,
CLASS_FILES_OUTPUT_DIR_OPTION, INCREMENTAL_DATA_FILE_OPTION, VERBOSE_MODE_OPTION)
override fun processOption(option: CliOption, value: String, configuration: CompilerConfiguration) {
when (option) {
@@ -78,6 +85,7 @@ class AnnotationProcessingCommandLineProcessor : CommandLineProcessor {
}
GENERATED_OUTPUT_DIR_OPTION -> configuration.put(AnnotationProcessingConfigurationKeys.GENERATED_OUTPUT_DIR, value)
CLASS_FILES_OUTPUT_DIR_OPTION -> configuration.put(AnnotationProcessingConfigurationKeys.CLASS_FILES_OUTPUT_DIR, value)
INCREMENTAL_DATA_FILE_OPTION -> configuration.put(AnnotationProcessingConfigurationKeys.INCREMENTAL_DATA_FILE, value)
VERBOSE_MODE_OPTION -> configuration.put(AnnotationProcessingConfigurationKeys.VERBOSE_MODE, value)
else -> throw CliOptionProcessingException("Unknown option: ${option.name}")
}
@@ -95,6 +103,8 @@ class AnnotationProcessingComponentRegistrar : ComponentRegistrar {
override fun registerProjectComponents(project: MockProject, configuration: CompilerConfiguration) {
val generatedOutputDir = configuration.get(AnnotationProcessingConfigurationKeys.GENERATED_OUTPUT_DIR) ?: return
val apClasspath = configuration.get(AnnotationProcessingConfigurationKeys.ANNOTATION_PROCESSOR_CLASSPATH)?.map(::File) ?: return
val incrementalDataFile = configuration.get(AnnotationProcessingConfigurationKeys.INCREMENTAL_DATA_FILE)?.let(::File)
val generatedOutputDirFile = File(generatedOutputDir)
generatedOutputDirFile.mkdirs()
@@ -117,7 +127,8 @@ class AnnotationProcessingComponentRegistrar : ComponentRegistrar {
.registerExtension(DefaultErrorMessagesAnnotationProcessing())
val annotationProcessingExtension = ClasspathBasedAnnotationProcessingExtension(
classpath, generatedOutputDirFile, classesOutputDir, javaRoots, verboseOutput)
classpath, generatedOutputDirFile, classesOutputDir, javaRoots, verboseOutput, incrementalDataFile)
AnalysisCompletedHandlerExtension.registerExtension(project, annotationProcessingExtension)
}
}
@@ -22,12 +22,19 @@ import org.jetbrains.kotlin.asJava.toLightClass
import org.jetbrains.kotlin.java.model.internal.getAnnotationsWithInherited
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
internal class RoundAnnotations() {
internal class RoundAnnotations(val supportedAnnotationFqNames: Set<String>) {
private val mutableAnnotationsMap = mutableMapOf<String, MutableList<PsiModifierListOwner>>()
private val mutableAnalyzedClasses = mutableSetOf<String>()
private val acceptsAnyAnnotation = "*" in supportedAnnotationFqNames
val annotationsMap: Map<String, List<PsiModifierListOwner>>
get() = mutableAnnotationsMap
val analyzedClasses: Set<String>
get() = mutableAnalyzedClasses
fun analyzeFiles(files: Collection<KtFile>) = files.forEach { analyzeFile(it) }
@@ -51,13 +58,28 @@ internal class RoundAnnotations() {
fun analyzeFile(file: PsiJavaFile) {
file.classes.forEach { analyzeDeclaration(it) }
}
fun PsiElement.getTopLevelClassParent(): PsiClass? = when (this) {
is PsiClass -> containingClass?.let { it.getTopLevelClassParent() } ?: this
else -> getParentOfType<PsiClass>(true)?.getTopLevelClassParent()
}
fun analyzeDeclaration(declaration: PsiElement) {
if (declaration !is PsiModifierListOwner) return
fun analyzeDeclaration(declaration: PsiElement): Boolean {
if (declaration !is PsiModifierListOwner) return false
// Do not analyze classes twice (for incremental compilation data)
if (declaration is PsiClass && declaration.qualifiedName in analyzedClasses) return false
for (annotation in declaration.getAnnotationsWithInherited()) {
val fqName = annotation.qualifiedName ?: return
val fqName = annotation.qualifiedName ?: continue
if (!acceptsAnyAnnotation && fqName !in supportedAnnotationFqNames) continue
mutableAnnotationsMap.getOrPut(fqName, { mutableListOf() }).add(declaration)
// Add only top-level classes
val topLevelClassQualifiedName = declaration.getTopLevelClassParent()?.qualifiedName
if (topLevelClassQualifiedName != null) {
mutableAnalyzedClasses += topLevelClassQualifiedName
}
}
if (declaration is PsiClass) {
@@ -71,5 +93,7 @@ internal class RoundAnnotations() {
analyzeDeclaration(parameter)
}
}
return true
}
}
@@ -33,6 +33,9 @@ internal class KotlinRoundEnvironment(
internal val annotationsMap: Map<String, List<PsiModifierListOwner>>
get() = roundAnnotations.annotationsMap
internal val supportedAnnotationFqNames: Set<String>
get() = roundAnnotations.supportedAnnotationFqNames
override fun getRootElements() = emptySet<Element>()
override fun processingOver() = isProcessingOver