1 /*
<lambda>null2  * Copyright 2021 The Android Open Source Project
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 androidx.room.compiler.processing.util.compiler
18 
19 import androidx.room.compiler.processing.util.compiler.DelegatingTestRegistrar.runCompilation
20 import java.net.URI
21 import kotlin.io.path.absolute
22 import kotlin.io.path.toPath
23 import org.jetbrains.kotlin.cli.common.ExitCode
24 import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
25 import org.jetbrains.kotlin.cli.common.messages.MessageCollector
26 import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
27 import org.jetbrains.kotlin.com.intellij.mock.MockProject
28 import org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar
29 import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
30 import org.jetbrains.kotlin.config.CompilerConfiguration
31 import org.jetbrains.kotlin.config.Services
32 import org.jetbrains.kotlin.util.ServiceLoaderLite
33 
34 /**
35  * A utility object for setting up Kotlin Compiler plugins that delegate to a list of thread local
36  * plugins.
37  *
38  * see [runCompilation] for usages.
39  */
40 @OptIn(ExperimentalCompilerApi::class)
41 object DelegatingTestRegistrar {
42 
43     @Suppress("DEPRECATION")
44     private val k1Delegates =
45         ThreadLocal<List<org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar>>()
46 
47     private val k2Delegates = ThreadLocal<List<CompilerPluginRegistrar>>()
48 
49     class K1Registrar :
50         @Suppress("DEPRECATION") org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar {
51         override fun registerProjectComponents(
52             project: MockProject,
53             configuration: CompilerConfiguration
54         ) {
55             k1Delegates.get()?.forEach { it.registerProjectComponents(project, configuration) }
56         }
57 
58         // FirKotlinToJvmBytecodeCompiler throws an error when it sees an incompatible plugin.
59         override val supportsK2: Boolean
60             get() = true
61     }
62 
63     class K2Registrar : CompilerPluginRegistrar() {
64         override fun ExtensionStorage.registerExtensions(configuration: CompilerConfiguration) {
65             k2Delegates.get()?.forEach { with(it) { registerExtensions(configuration) } }
66         }
67 
68         override val supportsK2: Boolean
69             get() = true
70     }
71 
72     private const val K1_SERVICES_REGISTRAR_PATH =
73         "META-INF/services/org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar"
74 
75     private const val K2_SERVICES_REGISTRAR_PATH =
76         "META-INF/services/org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar"
77 
78     private val k1ResourcePathForSelfClassLoader by lazy {
79         getResourcePathForClassLoader(K1_SERVICES_REGISTRAR_PATH)
80     }
81 
82     private val k2ResourcePathForSelfClassLoader by lazy {
83         getResourcePathForClassLoader(K2_SERVICES_REGISTRAR_PATH)
84     }
85 
86     private fun getResourcePathForClassLoader(servicesRegistrarPath: String): String {
87         val registrarClassToLoad =
88             when (servicesRegistrarPath) {
89                 K1_SERVICES_REGISTRAR_PATH ->
90                     @Suppress("DEPRECATION")
91                     org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar::class
92                 K2_SERVICES_REGISTRAR_PATH -> CompilerPluginRegistrar::class
93                 else -> error("Unknown services registrar path: $servicesRegistrarPath")
94             }
95         val expectedRegistrarClass =
96             when (servicesRegistrarPath) {
97                 K1_SERVICES_REGISTRAR_PATH -> K1Registrar::class
98                 K2_SERVICES_REGISTRAR_PATH -> K2Registrar::class
99                 else -> error("Unknown services registrar path: $servicesRegistrarPath")
100             }
101         val classpath =
102             this::class
103                 .java
104                 .classLoader
105                 .getResources(servicesRegistrarPath)
106                 .asSequence()
107                 .mapNotNull { url ->
108                     val uri = URI.create(url.toString().removeSuffix("/$servicesRegistrarPath"))
109                     when (uri.scheme) {
110                         "jar" -> URI.create(uri.schemeSpecificPart.removeSuffix("!")).toPath()
111                         "file" -> uri.toPath()
112                         else -> return@mapNotNull null
113                     }.absolute()
114                 }
115                 .find { resourcesPath ->
116                     ServiceLoaderLite.findImplementations(
117                             registrarClassToLoad.java,
118                             listOf(resourcesPath.toFile())
119                         )
120                         .any { implementation ->
121                             implementation == expectedRegistrarClass.java.name
122                         }
123                 }
124         if (classpath == null) {
125             throw AssertionError(
126                 """
127                 Could not find the $registrarClassToLoad class loader that should load
128                 $expectedRegistrarClass
129                 """
130                     .trimIndent()
131             )
132         }
133         return classpath.toString()
134     }
135 
136     internal fun runCompilation(
137         compiler: K2JVMCompiler,
138         messageCollector: MessageCollector,
139         arguments: K2JVMCompilerArguments,
140         registrars: PluginRegistrarArguments
141     ): ExitCode {
142         try {
143             k1Delegates.set(registrars.k1Registrars)
144             k2Delegates.set(registrars.k2Registrars)
145             arguments.addDelegatingTestRegistrars()
146             return compiler.exec(
147                 messageCollector = messageCollector,
148                 services = Services.EMPTY,
149                 arguments = arguments
150             )
151         } finally {
152             k1Delegates.remove()
153             k2Delegates.remove()
154         }
155     }
156 
157     private fun K2JVMCompilerArguments.addDelegatingTestRegistrars() {
158         pluginClasspaths =
159             buildList {
160                     pluginClasspaths?.let { addAll(it) }
161                     add(k1ResourcePathForSelfClassLoader)
162                     add(k2ResourcePathForSelfClassLoader)
163                 }
164                 .toTypedArray()
165     }
166 }
167