[KAPT4] KT-51982 Implement Kapt4AnalysisHandlerExtension, add KAPT CLI and Gradle IT
Co-authored-by: Alexander Udalov <Alexander.Udalov@jetbrains.com>
This commit is contained in:
committed by
Space Team
parent
d50d36f16c
commit
5c19cb3fcb
@@ -11,6 +11,7 @@ dependencies {
|
||||
compileOnly(project(":compiler:frontend.java"))
|
||||
compileOnly(project(":compiler:plugin-api"))
|
||||
implementation(project(":kotlin-annotation-processing-compiler"))
|
||||
compileOnly(project(":kotlin-annotation-processing-base"))
|
||||
compileOnly(project(":analysis:analysis-api-standalone"))
|
||||
compileOnly(toolsJarApi())
|
||||
|
||||
|
||||
-5
@@ -1,6 +1 @@
|
||||
#
|
||||
# Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
# Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
#
|
||||
|
||||
org.jetbrains.kotlin.kapt4.Kapt4CompilerPluginRegistrar
|
||||
|
||||
@@ -5,19 +5,171 @@
|
||||
|
||||
package org.jetbrains.kotlin.kapt4
|
||||
|
||||
import com.sun.tools.javac.tree.JCTree
|
||||
import org.jetbrains.kotlin.analysis.api.KtAnalysisApiInternals
|
||||
import org.jetbrains.kotlin.analysis.api.lifetime.KtLifetimeTokenProvider
|
||||
import org.jetbrains.kotlin.analysis.api.session.KtAnalysisSessionProvider
|
||||
import org.jetbrains.kotlin.analysis.api.standalone.KtAlwaysAccessibleLifetimeTokenProvider
|
||||
import org.jetbrains.kotlin.analysis.api.standalone.buildStandaloneAnalysisAPISession
|
||||
import org.jetbrains.kotlin.base.kapt3.*
|
||||
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
|
||||
import org.jetbrains.kotlin.cli.jvm.config.JavaSourceRoot
|
||||
import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot
|
||||
import org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar
|
||||
import org.jetbrains.kotlin.config.CommonConfigurationKeys
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.config.*
|
||||
import org.jetbrains.kotlin.config.CommonConfigurationKeys.USE_FIR
|
||||
import org.jetbrains.kotlin.fir.extensions.FirAnalysisHandlerExtension
|
||||
import org.jetbrains.kotlin.kapt3.EfficientProcessorLoader
|
||||
import org.jetbrains.kotlin.kapt3.KAPT_OPTIONS
|
||||
import org.jetbrains.kotlin.kapt3.base.Kapt
|
||||
import org.jetbrains.kotlin.kapt3.base.doAnnotationProcessing
|
||||
import org.jetbrains.kotlin.kapt3.base.util.KaptLogger
|
||||
import org.jetbrains.kotlin.kapt3.base.util.getPackageNameJava9Aware
|
||||
import org.jetbrains.kotlin.kapt3.util.MessageCollectorBackedKaptLogger
|
||||
import org.jetbrains.kotlin.kapt3.util.prettyPrint
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import java.io.File
|
||||
|
||||
|
||||
class Kapt4AnalysisHandlerExtension : FirAnalysisHandlerExtension() {
|
||||
private class Kapt4AnalysisHandlerExtension : FirAnalysisHandlerExtension() {
|
||||
override fun isApplicable(configuration: CompilerConfiguration): Boolean =
|
||||
configuration.getBoolean(CommonConfigurationKeys.USE_FIR) && configuration[KAPT_OPTIONS] != null
|
||||
configuration.getBoolean(USE_FIR) && configuration[KAPT_OPTIONS] != null
|
||||
|
||||
@OptIn(KtAnalysisApiInternals::class)
|
||||
override fun doAnalysis(configuration: CompilerConfiguration): Boolean {
|
||||
TODO("Not yet implemented")
|
||||
val optionsBuilder = configuration[KAPT_OPTIONS]!!
|
||||
val logger = MessageCollectorBackedKaptLogger(
|
||||
KaptFlag.VERBOSE in optionsBuilder.flags,
|
||||
KaptFlag.INFO_AS_WARNINGS in optionsBuilder.flags,
|
||||
configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)!!
|
||||
)
|
||||
|
||||
if (optionsBuilder.mode == AptMode.WITH_COMPILATION) {
|
||||
logger.error("KAPT \"compile\" mode is not supported in Kotlin 2.x. Run kapt with -Kapt-mode=stubsAndApt and use kotlinc for the final compilation step.")
|
||||
return false
|
||||
}
|
||||
|
||||
val oldLanguageVersionSettings = configuration.languageVersionSettings
|
||||
val updatedConfiguration = configuration.copy().apply {
|
||||
languageVersionSettings = object : LanguageVersionSettings by oldLanguageVersionSettings {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T> getFlag(flag: AnalysisFlag<T>): T =
|
||||
when (flag) {
|
||||
JvmAnalysisFlags.generatePropertyAnnotationsMethods -> true as T
|
||||
else -> oldLanguageVersionSettings.getFlag(flag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val standaloneAnalysisAPISession =
|
||||
buildStandaloneAnalysisAPISession(classLoader = Kapt4AnalysisHandlerExtension::class.java.classLoader) {
|
||||
@Suppress("DEPRECATION") // TODO: KT-61319 Kapt: remove usages of deprecated buildKtModuleProviderByCompilerConfiguration
|
||||
buildKtModuleProviderByCompilerConfiguration(updatedConfiguration)
|
||||
|
||||
registerProjectService(KtLifetimeTokenProvider::class.java, KtAlwaysAccessibleLifetimeTokenProvider())
|
||||
}
|
||||
|
||||
val (module, psiFiles) = standaloneAnalysisAPISession.modulesWithFiles.entries.single()
|
||||
|
||||
optionsBuilder.apply {
|
||||
projectBaseDir = projectBaseDir ?: module.project.basePath?.let(::File)
|
||||
val contentRoots = configuration[CLIConfigurationKeys.CONTENT_ROOTS] ?: emptyList()
|
||||
compileClasspath.addAll(contentRoots.filterIsInstance<JvmClasspathRoot>().map { it.file })
|
||||
javaSourceRoots.addAll(contentRoots.filterIsInstance<JavaSourceRoot>().map { it.file })
|
||||
classesOutputDir = classesOutputDir ?: configuration.get(JVMConfigurationKeys.OUTPUT_DIRECTORY)
|
||||
}
|
||||
|
||||
if (!optionsBuilder.checkOptions(logger, configuration)) return false
|
||||
val options = optionsBuilder.build()
|
||||
if (options[KaptFlag.VERBOSE]) {
|
||||
logger.info(options.logString())
|
||||
}
|
||||
|
||||
var context: Kapt4ContextForStubGeneration? = null
|
||||
return try {
|
||||
KtAnalysisSessionProvider.getInstance(module.project).analyze(module) {
|
||||
context = Kapt4ContextForStubGeneration(options, withJdk = false, logger, this, psiFiles.filterIsInstance<KtFile>())
|
||||
|
||||
if (options.mode.generateStubs)
|
||||
generateStubs(context!!)
|
||||
|
||||
if (options.mode.runAnnotationProcessing)
|
||||
runProcessors(context!!, options, logger)
|
||||
true
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logger.exception(e)
|
||||
false
|
||||
} finally {
|
||||
context?.close()
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateStubs(context: Kapt4ContextForStubGeneration) {
|
||||
val generator = with(context) { Kapt4StubGenerator() }
|
||||
val stubs = generator.generateStubs().values.filterNotNull().toList()
|
||||
for (kaptStub in stubs) {
|
||||
val stub = kaptStub.file
|
||||
val className = (stub.defs.first { it is JCTree.JCClassDecl } as JCTree.JCClassDecl).simpleName.toString()
|
||||
|
||||
val packageName = stub.getPackageNameJava9Aware()?.toString() ?: ""
|
||||
val stubsOutputDir = context.options.stubsOutputDir
|
||||
val packageDir = if (packageName.isEmpty()) stubsOutputDir else File(stubsOutputDir, packageName.replace('.', '/'))
|
||||
packageDir.mkdirs()
|
||||
|
||||
val sourceFile = File(packageDir, "$className.java")
|
||||
sourceFile.writeText(stub.prettyPrint(context.context))
|
||||
|
||||
kaptStub.writeMetadataIfNeeded(forSource = sourceFile)
|
||||
}
|
||||
File(context.options.stubsOutputDir, "error").apply { mkdirs() }.resolve("NonExistentClass.java")
|
||||
.writeText("package error;\npublic class NonExistentClass {}\n")
|
||||
}
|
||||
|
||||
private fun runProcessors(
|
||||
context: Kapt4ContextForStubGeneration,
|
||||
options: KaptOptions,
|
||||
logger: KaptLogger,
|
||||
) {
|
||||
val sources = options.collectJavaSourceFiles(context.sourcesToReprocess)
|
||||
if (sources.isEmpty()) return
|
||||
EfficientProcessorLoader(options, logger).use {
|
||||
context.doAnnotationProcessing(sources, it.loadProcessors().processors)
|
||||
}
|
||||
}
|
||||
|
||||
private fun KaptOptions.Builder.checkOptions(logger: KaptLogger, configuration: CompilerConfiguration): Boolean {
|
||||
if (classesOutputDir == null && configuration.get(JVMConfigurationKeys.OUTPUT_JAR) != null) {
|
||||
logger.error("Kapt does not support specifying JAR file outputs. Please specify the classes output directory explicitly.")
|
||||
return false
|
||||
}
|
||||
|
||||
if (processingClasspath.isEmpty()) {
|
||||
// Skip annotation processing if no annotation processors were provided
|
||||
logger.info("No annotation processors provided. Skip KAPT processing.")
|
||||
return false
|
||||
}
|
||||
|
||||
if (sourcesOutputDir == null || classesOutputDir == null || stubsOutputDir == null) {
|
||||
if (mode != AptMode.WITH_COMPILATION) {
|
||||
val nonExistentOptionName = when {
|
||||
sourcesOutputDir == null -> "Sources output directory"
|
||||
classesOutputDir == null -> "Classes output directory"
|
||||
stubsOutputDir == null -> "Stubs output directory"
|
||||
else -> throw IllegalStateException()
|
||||
}
|
||||
val moduleName = configuration.get(CommonConfigurationKeys.MODULE_NAME)
|
||||
?: configuration.get(JVMConfigurationKeys.MODULES).orEmpty().joinToString()
|
||||
|
||||
logger.warn("$nonExistentOptionName is not specified for $moduleName, skipping annotation processing")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if (!Kapt.checkJavacComponentsAccess(logger)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,20 +9,33 @@ import com.sun.tools.javac.tree.TreeMaker
|
||||
import com.sun.tools.javac.util.Context
|
||||
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
|
||||
import org.jetbrains.kotlin.asJava.classes.KtLightClass
|
||||
import org.jetbrains.kotlin.asJava.findFacadeClass
|
||||
import org.jetbrains.kotlin.asJava.toLightClass
|
||||
import org.jetbrains.kotlin.base.kapt3.KaptOptions
|
||||
import org.jetbrains.kotlin.kapt3.base.KaptContext
|
||||
import org.jetbrains.kotlin.kapt3.base.util.KaptLogger
|
||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
|
||||
internal class Kapt4ContextForStubGeneration(
|
||||
options: KaptOptions,
|
||||
withJdk: Boolean,
|
||||
logger: KaptLogger,
|
||||
val analysisSession: KtAnalysisSession,
|
||||
val classes: Iterable<KtLightClass>
|
||||
val files: List<KtFile>,
|
||||
) : KaptContext(options, withJdk, logger) {
|
||||
val classes: Iterable<KtLightClass> = buildSet {
|
||||
files.flatMapTo(this) { file ->
|
||||
file.children.filterIsInstance<KtClassOrObject>().mapNotNull {
|
||||
it.toLightClass()
|
||||
}
|
||||
}
|
||||
files.mapNotNullTo(this) { ktFile -> ktFile.findFacadeClass() }.distinct()
|
||||
}
|
||||
|
||||
internal val treeMaker = TreeMaker.instance(context) as Kapt4TreeMaker
|
||||
|
||||
override fun preregisterTreeMaker(context: Context) {
|
||||
Kapt4TreeMaker.preRegister(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,16 +13,12 @@ import org.jetbrains.kotlin.analysis.api.lifetime.KtReadActionConfinementLifetim
|
||||
import org.jetbrains.kotlin.analysis.api.session.KtAnalysisSessionProvider
|
||||
import org.jetbrains.kotlin.analysis.api.standalone.buildStandaloneAnalysisAPISession
|
||||
import org.jetbrains.kotlin.asJava.classes.KtLightClass
|
||||
import org.jetbrains.kotlin.asJava.findFacadeClass
|
||||
import org.jetbrains.kotlin.asJava.toLightClass
|
||||
import org.jetbrains.kotlin.base.kapt3.KaptOptions
|
||||
import org.jetbrains.kotlin.cli.common.config.addKotlinSourceRoots
|
||||
import org.jetbrains.kotlin.cli.jvm.config.addJavaSourceRoots
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.kapt3.base.util.WriterBackedKaptLogger
|
||||
import org.jetbrains.kotlin.kapt3.test.KaptMessageCollectorProvider
|
||||
import org.jetbrains.kotlin.kapt3.test.kaptOptionsProvider
|
||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.test.model.*
|
||||
import org.jetbrains.kotlin.test.services.*
|
||||
@@ -78,16 +74,6 @@ private fun run(
|
||||
buildKtModuleProviderByCompilerConfiguration(configuration)
|
||||
}
|
||||
val (module, psiFiles) = standaloneAnalysisAPISession.modulesWithFiles.entries.single()
|
||||
val ktFiles = psiFiles.filterIsInstance<KtFile>()
|
||||
|
||||
val lightClasses = buildSet {
|
||||
ktFiles.flatMapTo(this) { file ->
|
||||
file.children.filterIsInstance<KtClassOrObject>().mapNotNull {
|
||||
it.toLightClass()
|
||||
}
|
||||
}
|
||||
ktFiles.mapNotNullTo(this) { ktFile -> ktFile.findFacadeClass() }.distinct()
|
||||
}
|
||||
|
||||
return KtAnalysisSessionProvider.getInstance(module.project).analyze(module) {
|
||||
val context = Kapt4ContextForStubGeneration(
|
||||
@@ -95,7 +81,7 @@ private fun run(
|
||||
withJdk = false,
|
||||
WriterBackedKaptLogger(isVerbose = false),
|
||||
this@analyze,
|
||||
lightClasses
|
||||
psiFiles.filterIsInstance<KtFile>()
|
||||
)
|
||||
val generator = with(context) { Kapt4StubGenerator() }
|
||||
context to generator.generateStubs()
|
||||
@@ -111,4 +97,3 @@ internal data class Kapt4ContextBinaryArtifact(
|
||||
override val kind: BinaryKind<Kapt4ContextBinaryArtifact>
|
||||
get() = Kind
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user