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