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