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:
committed by
Yan Zhulanow
parent
743be477ec
commit
32d77e5226
+61
-7
@@ -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)
|
||||
}
|
||||
|
||||
+13
-2
@@ -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)
|
||||
}
|
||||
}
|
||||
+28
-4
@@ -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
|
||||
}
|
||||
}
|
||||
+3
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user