|
3 | 3 | */ |
4 | 4 | package com.tschuchort.compiletesting |
5 | 5 |
|
6 | | -import org.jetbrains.kotlin.ksp.KotlinSymbolProcessingCommandLineProcessor |
7 | | -import org.jetbrains.kotlin.ksp.KotlinSymbolProcessingComponentRegistrar |
| 6 | +import org.jetbrains.kotlin.com.intellij.mock.MockProject |
| 7 | +import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar |
| 8 | +import org.jetbrains.kotlin.config.CompilerConfiguration |
| 9 | +import org.jetbrains.kotlin.ksp.AbstractKotlinSymbolProcessingExtension |
| 10 | +import org.jetbrains.kotlin.ksp.KspOptions |
8 | 11 | import org.jetbrains.kotlin.ksp.processing.SymbolProcessor |
| 12 | +import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension |
| 13 | +import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull |
9 | 14 | import java.io.File |
10 | 15 |
|
11 | | -private const val KSP_PLUGIN_ID = "org.jetbrains.kotlin.ksp" |
12 | | - |
13 | | -private fun KotlinCompilation.initAndGetKspConfig(): KspConfiguration { |
14 | | - val config = KspConfiguration(workingDir.resolve("ksp")) |
15 | | - if (config.workingDir.exists()) { |
16 | | - // already configured, just return |
17 | | - return config |
| 16 | +/** |
| 17 | + * The list of symbol processors for the kotlin compilation. |
| 18 | + * https://goo.gle/ksp |
| 19 | + */ |
| 20 | +var KotlinCompilation.symbolProcessors: List<SymbolProcessor> |
| 21 | + get() = getKspRegistrar().processors |
| 22 | + set(value) { |
| 23 | + val registrar = getKspRegistrar() |
| 24 | + registrar.processors = value |
18 | 25 | } |
19 | | - config.classesOutDir.mkdirs() |
20 | | - config.sourcesOurDir.mkdirs() |
21 | | - config.syntheticSources.mkdirs() |
22 | | - val kspOptions = listOf( |
23 | | - PluginOption(KSP_PLUGIN_ID, "apclasspath", config.syntheticSources.path), |
24 | | - PluginOption(KSP_PLUGIN_ID, "classes", config.classesOutDir.path), |
25 | | - PluginOption(KSP_PLUGIN_ID, "sources", config.sourcesOurDir.path) |
26 | | - ) |
27 | | - compilerPlugins = compilerPlugins + KotlinSymbolProcessingComponentRegistrar() |
28 | | - commandLineProcessors = commandLineProcessors + listOf(KotlinSymbolProcessingCommandLineProcessor()) |
29 | | - pluginOptions = pluginOptions + kspOptions |
30 | | - return config |
31 | | -} |
32 | 26 |
|
33 | | -fun KotlinCompilation.symbolProcessors( |
34 | | - vararg processors: Class<out SymbolProcessor> |
| 27 | +/** |
| 28 | + * The directory where generates KSP sources are written |
| 29 | + */ |
| 30 | +val KotlinCompilation.kspSourcesDir: File |
| 31 | + get() = kspWorkingDir.resolve("sources") |
| 32 | + |
| 33 | +/** |
| 34 | + * The working directory for KSP |
| 35 | + */ |
| 36 | +private val KotlinCompilation.kspWorkingDir: File |
| 37 | + get() = workingDir.resolve("ksp") |
| 38 | + |
| 39 | +/** |
| 40 | + * The directory where compiled KSP classes are written |
| 41 | + */ |
| 42 | +// TODO this seems to be ignored by KSP and it is putting classes into regular classes directory |
| 43 | +// but we still need to provide it in the KSP options builder as it is required |
| 44 | +// once it works, we should make the property public. |
| 45 | +private val KotlinCompilation.kspClassesDir: File |
| 46 | + get() = kspWorkingDir.resolve("classes") |
| 47 | + |
| 48 | +/** |
| 49 | + * Custom subclass of [AbstractKotlinSymbolProcessingExtension] where processors are pre-defined instead of being |
| 50 | + * loaded via ServiceLocator. |
| 51 | + */ |
| 52 | +private class KspTestExtension( |
| 53 | + options: KspOptions, |
| 54 | + private val processors: List<SymbolProcessor> |
| 55 | +) : AbstractKotlinSymbolProcessingExtension( |
| 56 | + options = options, |
| 57 | + testMode = false |
35 | 58 | ) { |
36 | | - check(processors.isNotEmpty()) { |
37 | | - "Must provide at least 1 symbol processor" |
38 | | - } |
39 | | - val config = initAndGetKspConfig() |
| 59 | + override fun loadProcessors() = processors |
| 60 | +} |
40 | 61 |
|
41 | | - // create a fake classpath that references our symbol processor |
42 | | - config.syntheticSources.apply { |
43 | | - resolve("META-INF/services/org.jetbrains.kotlin.ksp.processing.SymbolProcessor").apply { |
44 | | - parentFile.mkdirs() |
45 | | - // keep existing ones in case this function is called multiple times |
46 | | - val existing = if (exists()) { |
47 | | - readLines(Charsets.UTF_8) |
48 | | - } else { |
49 | | - emptyList() |
| 62 | +/** |
| 63 | + * Registers the [KspTestExtension] to load the given list of processors. |
| 64 | + */ |
| 65 | +private class KspCompileTestingComponentRegistrar( |
| 66 | + private val compilation: KotlinCompilation |
| 67 | +) : ComponentRegistrar { |
| 68 | + var processors = emptyList<SymbolProcessor>() |
| 69 | + override fun registerProjectComponents(project: MockProject, configuration: CompilerConfiguration) { |
| 70 | + if (processors.isEmpty()) { |
| 71 | + return |
| 72 | + } |
| 73 | + val options = KspOptions.Builder().apply { |
| 74 | + this.classesOutputDir = compilation.kspClassesDir.also { |
| 75 | + it.deleteRecursively() |
| 76 | + it.mkdirs() |
50 | 77 | } |
51 | | - val processorNames = existing + processors.map { |
52 | | - it.typeName |
| 78 | + this.sourcesOutputDir = compilation.kspSourcesDir.also { |
| 79 | + it.deleteRecursively() |
| 80 | + it.mkdirs() |
53 | 81 | } |
54 | | - writeText( |
55 | | - processorNames.joinToString(System.lineSeparator()) |
56 | | - ) |
57 | | - } |
| 82 | + }.build() |
| 83 | + val registrar = KspTestExtension(options, processors) |
| 84 | + AnalysisHandlerExtension.registerExtension(project, registrar) |
58 | 85 | } |
59 | | - this.kaptSourceDir |
60 | 86 | } |
61 | 87 |
|
62 | | -private data class KspConfiguration( |
63 | | - val workingDir: File |
64 | | -) { |
65 | | - val classesOutDir = workingDir.resolve("classesOutput") |
66 | | - val sourcesOurDir = workingDir.resolve("sourcesOutput") |
67 | | - val syntheticSources = workingDir.resolve("synthetic-ksp-service") |
| 88 | +/** |
| 89 | + * Gets the test registrar from the plugin list or adds if it does not exist. |
| 90 | + */ |
| 91 | +private fun KotlinCompilation.getKspRegistrar(): KspCompileTestingComponentRegistrar { |
| 92 | + compilerPlugins.firstIsInstanceOrNull<KspCompileTestingComponentRegistrar>()?.let { |
| 93 | + return it |
| 94 | + } |
| 95 | + val kspRegistrar = KspCompileTestingComponentRegistrar(this) |
| 96 | + compilerPlugins = compilerPlugins + kspRegistrar |
| 97 | + return kspRegistrar |
68 | 98 | } |
0 commit comments