Skip to content

Commit 5b95782

Browse files
Fosotschuchortdev
authored andcommitted
15: enable adding list of ComponentRegistrar instances to the compilation
1 parent 88e61c3 commit 5b95782

File tree

6 files changed

+200
-28
lines changed

6 files changed

+200
-28
lines changed

src/main/kotlin/com/tschuchort/compiletesting/KaptComponentRegistrar.kt

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -47,17 +47,19 @@ import org.jetbrains.kotlin.psi.KtFile
4747
import org.jetbrains.kotlin.resolve.BindingTrace
4848
import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension
4949
import java.io.File
50-
import javax.annotation.processing.Processor
5150

52-
internal class KaptComponentRegistrar : ComponentRegistrar {
51+
internal class KaptComponentRegistrar(
52+
private val processors: List<IncrementalProcessor>,
53+
private val kaptOptions: KaptOptions.Builder
54+
) : ComponentRegistrar {
5355

5456
override fun registerProjectComponents(project: MockProject, configuration: CompilerConfiguration) {
55-
if (threadLocalParameters.get().processors.isEmpty())
57+
if (processors.isEmpty())
5658
return
5759

5860
val contentRoots = configuration[CLIConfigurationKeys.CONTENT_ROOTS] ?: emptyList()
5961

60-
val optionsBuilder = threadLocalParameters.get().kaptOptions.apply {
62+
val optionsBuilder = kaptOptions.apply {
6163
projectBaseDir = project.basePath?.let(::File)
6264
compileClasspath.addAll(contentRoots.filterIsInstance<JvmClasspathRoot>().map { it.file })
6365
javaSourceRoots.addAll(contentRoots.filterIsInstance<JavaSourceRoot>().map { it.file })
@@ -88,7 +90,7 @@ internal class KaptComponentRegistrar : ComponentRegistrar {
8890
val kapt3AnalysisCompletedHandlerExtension =
8991
object : AbstractKapt3Extension(options, logger, configuration) {
9092
override fun loadProcessors() = LoadedProcessors(
91-
processors = threadLocalParameters.get().processors,
93+
processors = processors,
9294
classLoader = this::class.java.classLoader
9395
)
9496
}
@@ -161,17 +163,4 @@ internal class KaptComponentRegistrar : ComponentRegistrar {
161163
}
162164
}
163165

164-
companion object {
165-
/** This kapt compiler plugin is instantiated by K2JVMCompiler using
166-
* a service locator. So we can't just pass parameters to it easily.
167-
* Instead we need to use a thread-local global variable to pass
168-
* any parameters that change between compilations
169-
*/
170-
val threadLocalParameters: ThreadLocal<Parameters> = ThreadLocal()
171-
}
172-
173-
data class Parameters(
174-
val processors: List<IncrementalProcessor>,
175-
val kaptOptions: KaptOptions.Builder
176-
)
177166
}

src/main/kotlin/com/tschuchort/compiletesting/KotlinCompilation.kt

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ class KotlinCompilation {
7070
*/
7171
var pluginClasspaths: List<File> = emptyList()
7272

73+
/**
74+
* Compiler plugins that should be added the compilation
75+
*/
76+
var compilerPlugins: List<ComponentRegistrar> = emptyList()
77+
7378
/** Source files to be compiled */
7479
var sources: List<SourceFile> = emptyList()
7580

@@ -454,16 +459,18 @@ class KotlinCompilation {
454459
it.flags.addAll(KaptFlag.MAP_DIAGNOSTIC_LOCATIONS, KaptFlag.VERBOSE)
455460
}
456461

457-
/* The kapt compiler plugin (KaptComponentRegistrar)
462+
/** The main compiler plugin (MainComponentRegistrar)
458463
* is instantiated by K2JVMCompiler using
459464
* a service locator. So we can't just pass parameters to it easily.
460465
* Instead we need to use a thread-local global variable to pass
461466
* any parameters that change between compilations
467+
*
462468
*/
463-
KaptComponentRegistrar.threadLocalParameters.set(
464-
KaptComponentRegistrar.Parameters(
469+
MainComponentRegistrar.threadLocalParameters.set(
470+
MainComponentRegistrar.Parameters(
465471
annotationProcessors.map { IncrementalProcessor(it, DeclaredProcType.NON_INCREMENTAL) },
466-
kaptOptions
472+
kaptOptions,
473+
compilerPlugins
467474
)
468475
)
469476

@@ -534,23 +541,40 @@ class KotlinCompilation {
534541
}
535542
.find { resourcesPath ->
536543
ServiceLoaderLite.findImplementations(ComponentRegistrar::class.java, listOf(resourcesPath.toFile()))
537-
.any { implementation -> implementation == KaptComponentRegistrar::class.java.name }
544+
.any { implementation -> implementation == MainComponentRegistrar::class.java.name }
538545
}?.toString() ?: throw AssertionError("Could not get path to ComponentRegistrar service from META-INF")
539546
}
540547

541548
/** Performs the 3rd compilation step to compile Kotlin source files */
542549
private fun compileKotlin(sourceFiles: List<File>): ExitCode {
550+
551+
/**
552+
* Here the list of compiler plugins is set
553+
*
554+
* To avoid that the annotation processors are executed twice,
555+
* the list is set to empty
556+
*/
557+
MainComponentRegistrar.threadLocalParameters.set(
558+
MainComponentRegistrar.Parameters(
559+
listOf(),
560+
KaptOptions.Builder(),
561+
compilerPlugins
562+
)
563+
)
564+
565+
543566
val sources = sourceFiles +
544567
kaptKotlinGeneratedDir.listFilesRecursively() +
545568
kaptSourceDir.listFilesRecursively()
546569

547570
// if no Kotlin sources are available, skip the compileKotlin step
548-
if(sources.filter<File>(File::hasKotlinFileExtension).isEmpty())
571+
if(sources.none(File::hasKotlinFileExtension))
549572
return ExitCode.OK
550573

551574
// in this step also include source files generated by kapt in the previous step
552-
val k2JvmArgs = commonK2JVMArgs().also {
553-
it.freeArgs = sources.map(File::getAbsolutePath).distinct()
575+
val k2JvmArgs = commonK2JVMArgs().also {jvmArgs->
576+
jvmArgs.pluginClasspaths = (jvmArgs.pluginClasspaths ?: emptyArray()) + arrayOf(getResourcesPath())
577+
jvmArgs.freeArgs = sources.map(File::getAbsolutePath).distinct()
554578
}
555579

556580
val compilerMessageCollector = PrintingMessageCollector(
@@ -727,7 +751,7 @@ class KotlinCompilation {
727751
return makeResult(exitCode)
728752
}
729753
} finally {
730-
KaptComponentRegistrar.threadLocalParameters.remove()
754+
MainComponentRegistrar.threadLocalParameters.remove()
731755
}
732756

733757
// step 3: compile Kotlin files
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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.base.kapt3.KaptOptions
20+
import org.jetbrains.kotlin.com.intellij.mock.MockProject
21+
import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
22+
import org.jetbrains.kotlin.config.CompilerConfiguration
23+
import org.jetbrains.kotlin.kapt3.base.incremental.IncrementalProcessor
24+
25+
internal class MainComponentRegistrar : ComponentRegistrar {
26+
27+
override fun registerProjectComponents(project: MockProject, configuration: CompilerConfiguration) {
28+
val parameters = threadLocalParameters.get()
29+
KaptComponentRegistrar(parameters.processors,parameters.kaptOptions).registerProjectComponents(project,configuration)
30+
31+
parameters.compilerPlugins.forEach { componentRegistrar->
32+
componentRegistrar.registerProjectComponents(project,configuration)
33+
}
34+
}
35+
36+
companion object {
37+
/** This compiler plugin is instantiated by K2JVMCompiler using
38+
* a service locator. So we can't just pass parameters to it easily.
39+
* Instead we need to use a thread-local global variable to pass
40+
* any parameters that change between compilations
41+
*/
42+
val threadLocalParameters: ThreadLocal<Parameters> = ThreadLocal()
43+
}
44+
45+
data class Parameters(
46+
val processors: List<IncrementalProcessor>,
47+
val kaptOptions: KaptOptions.Builder,
48+
val compilerPlugins: List<ComponentRegistrar>
49+
)
50+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
com.tschuchort.compiletesting.KaptComponentRegistrar
1+
com.tschuchort.compiletesting.MainComponentRegistrar
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package com.tschuchort.compiletesting
2+
3+
import com.nhaarman.mockitokotlin2.any
4+
import com.nhaarman.mockitokotlin2.atLeastOnce
5+
import com.nhaarman.mockitokotlin2.verify
6+
import org.assertj.core.api.Assertions
7+
import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
8+
import org.junit.Assert
9+
import org.junit.Test
10+
import org.mockito.Mockito
11+
import javax.annotation.processing.AbstractProcessor
12+
import javax.annotation.processing.RoundEnvironment
13+
import javax.lang.model.element.TypeElement
14+
15+
class CompilerPluginsTest {
16+
17+
@Test
18+
fun `when compiler plugins are added they get executed`() {
19+
20+
val mockPlugin = Mockito.mock(ComponentRegistrar::class.java)
21+
22+
val result = defaultCompilerConfig().apply {
23+
sources = listOf(SourceFile.new("emptyKotlinFile.kt", ""))
24+
compilerPlugins = listOf(mockPlugin)
25+
inheritClassPath = true
26+
}.compile()
27+
28+
verify(mockPlugin, atLeastOnce()).registerProjectComponents(any(), any())
29+
30+
Assertions.assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK)
31+
}
32+
33+
@Test
34+
fun `when compiler plugins and annotation processors are added they get executed`() {
35+
36+
val annotationProcessor = object : AbstractProcessor() {
37+
override fun getSupportedAnnotationTypes(): Set<String> = setOf(ProcessElem::class.java.canonicalName)
38+
39+
override fun process(p0: MutableSet<out TypeElement>?, p1: RoundEnvironment?): Boolean {
40+
p1?.getElementsAnnotatedWith(ProcessElem::class.java)?.forEach {
41+
Assert.assertEquals("JSource", it?.simpleName.toString())
42+
}
43+
return false
44+
}
45+
}
46+
47+
val mockPlugin = Mockito.mock(ComponentRegistrar::class.java)
48+
49+
val jSource = SourceFile.kotlin(
50+
"JSource.kt", """
51+
package com.tschuchort.compiletesting;
52+
53+
@ProcessElem
54+
class JSource {
55+
fun foo() { }
56+
}
57+
"""
58+
)
59+
60+
val result = defaultCompilerConfig().apply {
61+
sources = listOf(jSource)
62+
annotationProcessors = listOf(annotationProcessor)
63+
compilerPlugins = listOf(mockPlugin)
64+
inheritClassPath = true
65+
}.compile()
66+
67+
verify(mockPlugin, atLeastOnce()).registerProjectComponents(any(), any())
68+
69+
Assertions.assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK)
70+
}
71+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.tschuchort.compiletesting
2+
3+
import io.github.classgraph.ClassGraph
4+
import org.assertj.core.api.Assertions
5+
import java.io.File
6+
7+
fun defaultCompilerConfig(): KotlinCompilation {
8+
return KotlinCompilation( ).apply {
9+
inheritClassPath = false
10+
skipRuntimeVersionCheck = true
11+
correctErrorTypes = true
12+
verbose = true
13+
reportOutputFiles = false
14+
messageOutputStream = System.out
15+
}
16+
}
17+
18+
19+
fun assertClassLoadable(compileResult: KotlinCompilation.Result, className: String): Class<*> {
20+
try {
21+
val clazz = compileResult.classLoader.loadClass(className)
22+
Assertions.assertThat(clazz).isNotNull
23+
return clazz
24+
}
25+
catch(e: ClassNotFoundException) {
26+
return Assertions.fail<Nothing>("Class $className could not be loaded")
27+
}
28+
}
29+
30+
/**
31+
* Returns the classpath for a dependency (format $name-$version).
32+
* This is necessary to know the actual location of a dependency
33+
* which has been included in test runtime (build.gradle).
34+
*/
35+
fun classpathOf(dependency: String): File {
36+
val regex = Regex(".*$dependency\\.jar")
37+
return ClassGraph().classpathFiles.first { classpath -> classpath.name.matches(regex) }
38+
}

0 commit comments

Comments
 (0)