Skip to content

Commit 0c46408

Browse files
committed
Provide instances of processors directly instead of creating them with service locator
1 parent 23c62ab commit 0c46408

File tree

5 files changed

+287
-109
lines changed

5 files changed

+287
-109
lines changed
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
/*
2+
* Copyright 2010-2016 JetBrains s.r.o.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.tschuchort.compiletesting
18+
19+
import org.jetbrains.kotlin.analyzer.AnalysisResult
20+
import org.jetbrains.kotlin.base.kapt3.AptMode
21+
import org.jetbrains.kotlin.base.kapt3.KaptFlag
22+
import org.jetbrains.kotlin.base.kapt3.KaptOptions
23+
import org.jetbrains.kotlin.base.kapt3.logString
24+
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
25+
import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
26+
import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector
27+
import org.jetbrains.kotlin.cli.jvm.config.JavaSourceRoot
28+
import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot
29+
import org.jetbrains.kotlin.com.intellij.mock.MockProject
30+
import org.jetbrains.kotlin.com.intellij.openapi.project.Project
31+
import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
32+
import org.jetbrains.kotlin.config.CommonConfigurationKeys
33+
import org.jetbrains.kotlin.config.CompilerConfiguration
34+
import org.jetbrains.kotlin.config.JVMConfigurationKeys
35+
import org.jetbrains.kotlin.container.ComponentProvider
36+
import org.jetbrains.kotlin.context.ProjectContext
37+
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
38+
import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor
39+
import org.jetbrains.kotlin.kapt3.AbstractKapt3Extension
40+
import org.jetbrains.kotlin.kapt3.Kapt3ComponentRegistrar
41+
import org.jetbrains.kotlin.kapt3.base.Kapt
42+
import org.jetbrains.kotlin.kapt3.base.LoadedProcessors
43+
import org.jetbrains.kotlin.kapt3.base.util.KaptLogger
44+
import org.jetbrains.kotlin.kapt3.util.MessageCollectorBackedKaptLogger
45+
import org.jetbrains.kotlin.psi.KtFile
46+
import org.jetbrains.kotlin.resolve.BindingTrace
47+
import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension
48+
import java.io.File
49+
import javax.annotation.processing.Processor
50+
51+
@Suppress("RemoveEmptyPrimaryConstructor")
52+
internal class KaptComponentRegistrar() : ComponentRegistrar {
53+
54+
override fun registerProjectComponents(project: MockProject, configuration: CompilerConfiguration) {
55+
println("registerProjectComponents called")
56+
if (threadLocalParameters.get().processors.isEmpty())
57+
return
58+
59+
val contentRoots = configuration[CLIConfigurationKeys.CONTENT_ROOTS] ?: emptyList()
60+
61+
val optionsBuilder = threadLocalParameters.get().kaptOptions.apply {
62+
projectBaseDir = project.basePath?.let(::File)
63+
compileClasspath.addAll(contentRoots.filterIsInstance<JvmClasspathRoot>().map { it.file })
64+
javaSourceRoots.addAll(contentRoots.filterIsInstance<JavaSourceRoot>().map { it.file })
65+
classesOutputDir = classesOutputDir ?: configuration.get(JVMConfigurationKeys.OUTPUT_DIRECTORY)
66+
}
67+
68+
val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
69+
?: PrintingMessageCollector(System.err, MessageRenderer.PLAIN_FULL_PATHS, optionsBuilder.flags.contains(KaptFlag.VERBOSE))
70+
71+
val logger = MessageCollectorBackedKaptLogger(
72+
optionsBuilder.flags.contains(KaptFlag.VERBOSE),
73+
optionsBuilder.flags.contains(KaptFlag.INFO_AS_WARNINGS),
74+
messageCollector
75+
)
76+
77+
if (!optionsBuilder.checkOptions(project, logger, configuration)) {
78+
return
79+
}
80+
81+
val options = optionsBuilder.build()
82+
83+
options.sourcesOutputDir.mkdirs()
84+
85+
if (options[KaptFlag.VERBOSE]) {
86+
logger.info(options.logString())
87+
}
88+
89+
val kapt3AnalysisCompletedHandlerExtension =
90+
object : AbstractKapt3Extension(options, logger, configuration) {
91+
override fun loadProcessors() = LoadedProcessors(
92+
processors = threadLocalParameters.get().processors,
93+
classLoader = this::class.java.classLoader
94+
)
95+
}
96+
97+
AnalysisHandlerExtension.registerExtension(project, kapt3AnalysisCompletedHandlerExtension)
98+
StorageComponentContainerContributor.registerExtension(project, Kapt3ComponentRegistrar.KaptComponentContributor())
99+
}
100+
101+
private fun KaptOptions.Builder.checkOptions(project: MockProject, logger: KaptLogger, configuration: CompilerConfiguration): Boolean {
102+
fun abortAnalysis() = AnalysisHandlerExtension.registerExtension(project, AbortAnalysisHandlerExtension())
103+
104+
if (classesOutputDir == null) {
105+
if (configuration.get(JVMConfigurationKeys.OUTPUT_JAR) != null) {
106+
logger.error("Kapt does not support specifying JAR file outputs. Please specify the classes output directory explicitly.")
107+
abortAnalysis()
108+
return false
109+
}
110+
else {
111+
classesOutputDir = configuration.get(JVMConfigurationKeys.OUTPUT_DIRECTORY)
112+
}
113+
}
114+
115+
if (sourcesOutputDir == null || classesOutputDir == null || stubsOutputDir == null) {
116+
if (mode != AptMode.WITH_COMPILATION) {
117+
val nonExistentOptionName = when {
118+
sourcesOutputDir == null -> "Sources output directory"
119+
classesOutputDir == null -> "Classes output directory"
120+
stubsOutputDir == null -> "Stubs output directory"
121+
else -> throw IllegalStateException()
122+
}
123+
val moduleName = configuration.get(CommonConfigurationKeys.MODULE_NAME)
124+
?: configuration.get(JVMConfigurationKeys.MODULES).orEmpty().joinToString()
125+
126+
logger.warn("$nonExistentOptionName is not specified for $moduleName, skipping annotation processing")
127+
abortAnalysis()
128+
}
129+
return false
130+
}
131+
132+
if (!Kapt.checkJavacComponentsAccess(logger)) {
133+
abortAnalysis()
134+
return false
135+
}
136+
137+
return true
138+
}
139+
140+
/* This extension simply disables both code analysis and code generation.
141+
* When aptOnly is true, and any of required kapt options was not passed, we just abort compilation by providing this extension.
142+
* */
143+
private class AbortAnalysisHandlerExtension : AnalysisHandlerExtension {
144+
override fun doAnalysis(
145+
project: Project,
146+
module: ModuleDescriptor,
147+
projectContext: ProjectContext,
148+
files: Collection<KtFile>,
149+
bindingTrace: BindingTrace,
150+
componentProvider: ComponentProvider
151+
): AnalysisResult? {
152+
return AnalysisResult.success(bindingTrace.bindingContext, module, shouldGenerateCode = false)
153+
}
154+
155+
override fun analysisCompleted(
156+
project: Project,
157+
module: ModuleDescriptor,
158+
bindingTrace: BindingTrace,
159+
files: Collection<KtFile>
160+
): AnalysisResult? {
161+
return AnalysisResult.success(bindingTrace.bindingContext, module, shouldGenerateCode = false)
162+
}
163+
}
164+
165+
companion object {
166+
/** This kapt compiler plugin is instantiated by K2JVMCompiler using
167+
* a service locator. So we can't just pass parameters to it easily.
168+
* Instead we need to use a thread-local global variable to pass
169+
* any parameters that change between compilations
170+
*/
171+
val threadLocalParameters: ThreadLocal<Parameters> = ThreadLocal()
172+
}
173+
174+
data class Parameters(
175+
val processors: List<Processor>,
176+
val kaptOptions: KaptOptions.Builder
177+
)
178+
}

0 commit comments

Comments
 (0)