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