From c0e373e852ef23f077bace44346900c4e706303d Mon Sep 17 00:00:00 2001 From: Ilya Kirillov Date: Fri, 14 Nov 2025 23:12:02 +0100 Subject: [PATCH] LSP-272 Support Kotlin compiler plugins in LSP GitOrigin-RevId: 82f7d4342816f1d9ab5e7ac4573038c91f012842 --- .../fir/extensions/KtCompilerPluginsCache.kt | 304 ++++++++++++++++++ .../KtCompilerPluginsProviderIdeImpl.kt | 287 ++--------------- 2 files changed, 326 insertions(+), 265 deletions(-) create mode 100644 plugins/kotlin/fir/src/org/jetbrains/kotlin/idea/fir/extensions/KtCompilerPluginsCache.kt diff --git a/plugins/kotlin/fir/src/org/jetbrains/kotlin/idea/fir/extensions/KtCompilerPluginsCache.kt b/plugins/kotlin/fir/src/org/jetbrains/kotlin/idea/fir/extensions/KtCompilerPluginsCache.kt new file mode 100644 index 000000000000..f7fa2e08aec8 --- /dev/null +++ b/plugins/kotlin/fir/src/org/jetbrains/kotlin/idea/fir/extensions/KtCompilerPluginsCache.kt @@ -0,0 +1,304 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +@file:Suppress("IO_FILE_USAGE") + +package org.jetbrains.kotlin.idea.fir.extensions + +import com.intellij.openapi.components.PathMacroManager +import com.intellij.openapi.diagnostic.logger +import com.intellij.openapi.diagnostic.runAndLogException +import com.intellij.openapi.module.Module +import com.intellij.openapi.module.ModuleManager +import com.intellij.openapi.progress.ProcessCanceledException +import com.intellij.openapi.progress.ProgressManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.CompilerModuleExtension +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.platform.eel.provider.getEelDescriptor +import com.intellij.platform.eel.provider.utils.Path +import com.intellij.util.concurrency.SynchronizedClearableLazy +import com.intellij.util.containers.ContainerUtil +import com.intellij.util.containers.orNull +import com.intellij.util.lang.UrlClassLoader +import org.jetbrains.annotations.ApiStatus +import org.jetbrains.kotlin.analysis.api.KaExperimentalApi +import org.jetbrains.kotlin.analysis.api.platform.projectStructure.KotlinCompilerPluginsProvider +import org.jetbrains.kotlin.analysis.api.platform.projectStructure.areCompilerPluginsSupported +import org.jetbrains.kotlin.analysis.api.projectStructure.KaModule +import org.jetbrains.kotlin.analysis.api.projectStructure.KaScriptModule +import org.jetbrains.kotlin.analysis.api.projectStructure.KaSourceModule +import org.jetbrains.kotlin.analysis.low.level.api.fir.LLFirInternals +import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments +import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments +import org.jetbrains.kotlin.cli.plugins.processCompilerPluginsOptions +import org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor +import org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar +import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi +import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.config.JVMConfigurationKeys +import org.jetbrains.kotlin.config.JvmTarget +import org.jetbrains.kotlin.extensions.ProjectExtensionDescriptor +import org.jetbrains.kotlin.fir.extensions.FirAssignExpressionAltererExtension +import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrar +import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrarAdapter +import org.jetbrains.kotlin.idea.base.projectStructure.KaSourceModuleKind +import org.jetbrains.kotlin.idea.base.projectStructure.openapiModule +import org.jetbrains.kotlin.idea.base.projectStructure.sourceModuleKind +import org.jetbrains.kotlin.idea.compiler.configuration.KotlinCommonCompilerArgumentsHolder +import org.jetbrains.kotlin.idea.facet.KotlinFacet +import org.jetbrains.kotlin.idea.util.getOriginalOrDelegateFileOrSelf +import org.jetbrains.kotlin.scripting.compiler.plugin.impl.makeScriptCompilerArguments +import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition +import org.jetbrains.kotlin.util.ServiceLoaderLite +import java.io.File +import java.nio.file.Path +import java.util.Optional +import java.util.concurrent.ConcurrentMap +import kotlin.script.experimental.api.ScriptCompilationConfiguration +import kotlin.script.experimental.api.compilerOptions + +@OptIn(ExperimentalCompilerApi::class) +@ApiStatus.Internal +class KtCompilerPluginsCache private constructor( + private val project: Project, + private val pluginsClassLoader: UrlClassLoader, + private val registrarForSourceModule: ConcurrentMap>, + /** + * As scripts might be associated with injections, + * it's better to have a more stable anchor such as a top-level file. + */ + private val registrarForScriptModule: SynchronizedClearableLazy>>, + private val onlyBundledPluginsEnabled: Boolean +) { + + fun isPluginOfTypeRegistered( + module: KaModule, + pluginType: KotlinCompilerPluginsProvider.CompilerPluginType, + ): Boolean { + @Suppress("REDUNDANT_ELSE_IN_WHEN") + val extension = when (pluginType) { + KotlinCompilerPluginsProvider.CompilerPluginType.ASSIGNMENT -> FirAssignExpressionAltererExtension::class + else -> return false + } + return getRegisteredExtensions( + module = module, + extensionType = FirExtensionRegistrarAdapter.Companion + ) + .map { (it as FirExtensionRegistrar).configure() } + .any { it.extensions[extension]?.isNotEmpty() == true } + } + + @OptIn(KaExperimentalApi::class) + fun getRegisteredExtensions( + module: KaModule, + extensionType: ProjectExtensionDescriptor + ): List { + if (!module.areCompilerPluginsSupported()) return emptyList() + val classLoader = pluginsClassLoader + return when (module) { + is KaSourceModule -> { + val registrarForModule = registrarForSourceModule + module.getExtensionsForModule(classLoader, registrarForModule, module, extensionType) + } + + is KaScriptModule -> { + val registrarForModule = registrarForScriptModule.value + val cacheKey = module.file.virtualFile.getOriginalOrDelegateFileOrSelf() + + module.getExtensionsForModule(classLoader, registrarForModule, cacheKey, extensionType) + } + + else -> emptyList() + } + } + + @OptIn(KaExperimentalApi::class) + private fun KaModule.getExtensionsForModule( + classLoader: ClassLoader, + registrarForModule: ConcurrentMap>, + cacheKey: K, + extensionType: ProjectExtensionDescriptor + ): List { + val extensionStorage = registrarForModule.computeIfAbsent(cacheKey) { + Optional.ofNullable(computeExtensionStorage(classLoader, this)) + }.orNull() ?: return emptyList() + val registrars = extensionStorage.registeredExtensions[extensionType] ?: return emptyList() + @Suppress("UNCHECKED_CAST") return registrars as List + } + + @OptIn(KaExperimentalApi::class, LLFirInternals::class) + private fun computeExtensionStorage( + classLoader: ClassLoader, + module: KaModule + ): CompilerPluginRegistrar.ExtensionStorage? { + val compilerArguments = when (module) { + is KaSourceModule -> module.openapiModule.getCompilerArguments() + is KaScriptModule -> { + val scriptDefinition = module.file.findScriptDefinition() ?: return null + val scriptConfiguration = scriptDefinition.compilationConfiguration + + val providedOptions = scriptConfiguration[ScriptCompilationConfiguration.compilerOptions] ?: return null + makeScriptCompilerArguments(providedOptions) + } + + else -> { + return null + } + } + val pluginClasspaths = collectSubstitutedPluginClasspaths(project, onlyBundledPluginsEnabled, listOf(compilerArguments)).map { it.toFile() } + if (pluginClasspaths.isEmpty()) return null + + val logger = logger() + + ProgressManager.checkCanceled() + + val pluginRegistrars = + logger.runAndLogException { ServiceLoaderLite.loadImplementations(pluginClasspaths, classLoader) } + ?.takeIf { it.isNotEmpty() } ?: return null + + ProgressManager.checkCanceled() + + val commandLineProcessors = logger.runAndLogException { + ServiceLoaderLite.loadImplementations(pluginClasspaths, classLoader) + } ?: return null + + ProgressManager.checkCanceled() + + val compilerConfiguration = + CompilerConfiguration().apply { // Temporary work-around for KTIJ-24320. Calls to 'setupCommonArguments()' and 'setupJvmSpecificArguments()' + // (or even a platform-agnostic alternative) should be added. + if (compilerArguments is K2JVMCompilerArguments && module is KaSourceModule) { + val compilerExtension = CompilerModuleExtension.getInstance(module.openapiModule) + val outputUrl = when (module.sourceModuleKind) { + KaSourceModuleKind.TEST -> compilerExtension?.compilerOutputUrlForTests + KaSourceModuleKind.PRODUCTION -> compilerExtension?.compilerOutputUrl + } + + putIfNotNull(JVMConfigurationKeys.JVM_TARGET, compilerArguments.jvmTarget?.let(JvmTarget::fromString)) + putIfNotNull(JVMConfigurationKeys.OUTPUT_DIRECTORY, outputUrl?.let { File(it) }) + } + + processCompilerPluginsOptions(this, compilerArguments.pluginOptions?.toList(), commandLineProcessors) + } + + val storage = CompilerPluginRegistrar.ExtensionStorage() + for (pluginRegistrar in pluginRegistrars) { + ProgressManager.checkCanceled() + + with(pluginRegistrar) { + try { + val configuration = KotlinFirCompilerPluginConfigurationForIdeProvider.getCompilerConfigurationWithCustomOptions( + pluginRegistrar, compilerConfiguration + ) ?: compilerConfiguration + storage.registerExtensions(configuration) + } catch (e: ProcessCanceledException) { + throw e + } catch (e: Throwable) { + LOG.error(e) + } + } + } + return storage + } + + /** + * Throws away only part of the cache related to the scripts, leaving other storage intact. + */ + fun resetScriptCache() { + val scriptsCache = this.registrarForScriptModule.drop() ?: return + + scriptsCache.values.mapNotNull { it.orNull() }.disposeAll() + } + + fun dispose() { + registrarForSourceModule.values.mapNotNull { it.orNull() }.disposeAll() + resetScriptCache() + } + + companion object { + + private val LOG = logger() + + fun new(project: Project, onlyBundledPluginsEnabled: Boolean): KtCompilerPluginsCache { + val pluginsClassLoader: UrlClassLoader = UrlClassLoader.build().apply { + parent(KaModule::class.java.classLoader) + val compilerArguments = ModuleManager.getInstance(project).modules.map { it.getCompilerArguments() } + val pluginClasspaths = collectSubstitutedPluginClasspaths(project, onlyBundledPluginsEnabled, compilerArguments) + files(pluginClasspaths) + }.get() + return KtCompilerPluginsCache( + pluginsClassLoader = pluginsClassLoader, + registrarForSourceModule = ContainerUtil.createConcurrentWeakMap(), + registrarForScriptModule = SynchronizedClearableLazy { ContainerUtil.createConcurrentWeakMap() }, + onlyBundledPluginsEnabled = onlyBundledPluginsEnabled, + project = project + ) + } + + private fun Collection.disposeAll() { + for (storage in this) { + for (disposable in storage.disposables) { + LOG.runAndLogException { + disposable.dispose() + } + } + } + } + + /** + * Returns the paths defined in [org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments.pluginClasspaths] + * in the absolute form with the expansion of the present path macros + * (like 'KOTLIN_BUNDLED'). + */ + private fun CommonCompilerArguments.getOriginalPluginClasspaths(project: Project): List { + val pluginClassPaths = this.pluginClasspaths + + if (pluginClassPaths.isNullOrEmpty()) return emptyList() + + val pathMacroManager = PathMacroManager.getInstance(project) + val expandedPluginClassPaths = pluginClassPaths.map { pathMacroManager.expandPath(it) } + val eel = project.getEelDescriptor() + return expandedPluginClassPaths.map { Path(it, eel).toAbsolutePath() } + } + + /** + * We have the following logic for plugins' substitution: + * 1. Always replace our own plugins (like "allopen", "noarg", etc.) with bundled ones to avoid binary incompatibility. + * 2. Allow using other compiler plugins only if [onlyBundledPluginsEnabled] is set to false; otherwise, filter them. + */ + private fun substitutePluginJar(project: Project, onlyBundledPluginsEnabled: Boolean, userSuppliedPluginJar: Path): Path? { + ProgressManager.checkCanceled() + + val bundledPlugin = KotlinBundledFirCompilerPluginProvider.provideBundledPluginJar(project, userSuppliedPluginJar) + if (bundledPlugin != null) return bundledPlugin + + return userSuppliedPluginJar.takeUnless { onlyBundledPluginsEnabled } + } + + /** + * Collects the substituted plugin classpaths for the given [compilerArguments] list. + * + * We process the whole [compilerArguments] list at once, because it gives us opportunity + * to not call [substitutePluginJar] multiple times if [compilerArguments] use the same + * compiler plugins jars. + */ + private fun collectSubstitutedPluginClasspaths( + project: Project, + onlyBundledPluginsEnabled: Boolean, + compilerArguments: List + ): List { + val combinedOriginalClasspaths = compilerArguments.asSequence().flatMap { it.getOriginalPluginClasspaths(project) }.distinct() + + val substitutedClasspaths = combinedOriginalClasspaths.mapNotNull { userSuppliedPluginJar -> + substitutePluginJar(project, onlyBundledPluginsEnabled, userSuppliedPluginJar) + }.distinct() + + return substitutedClasspaths.toList() + } + + @Suppress("MISSING_DEPENDENCY_SUPERCLASS_IN_TYPE_ARGUMENT") + private fun Module.getCompilerArguments(): CommonCompilerArguments { + return KotlinFacet.get(this)?.configuration?.settings?.mergedCompilerArguments + ?: KotlinCommonCompilerArgumentsHolder.getInstance(project).settings + } + } +} \ No newline at end of file diff --git a/plugins/kotlin/fir/src/org/jetbrains/kotlin/idea/fir/extensions/KtCompilerPluginsProviderIdeImpl.kt b/plugins/kotlin/fir/src/org/jetbrains/kotlin/idea/fir/extensions/KtCompilerPluginsProviderIdeImpl.kt index 181f1af7c72c..61023805370a 100644 --- a/plugins/kotlin/fir/src/org/jetbrains/kotlin/idea/fir/extensions/KtCompilerPluginsProviderIdeImpl.kt +++ b/plugins/kotlin/fir/src/org/jetbrains/kotlin/idea/fir/extensions/KtCompilerPluginsProviderIdeImpl.kt @@ -3,84 +3,45 @@ package org.jetbrains.kotlin.idea.fir.extensions import com.intellij.ide.trustedProjects.TrustedProjects import com.intellij.openapi.Disposable -import com.intellij.openapi.components.PathMacroManager -import com.intellij.openapi.diagnostic.logger -import com.intellij.openapi.diagnostic.runAndLogException -import com.intellij.openapi.module.Module -import com.intellij.openapi.module.ModuleManager -import com.intellij.openapi.progress.ProcessCanceledException -import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.project.Project -import com.intellij.openapi.roots.CompilerModuleExtension import com.intellij.openapi.util.registry.Registry import com.intellij.openapi.util.registry.RegistryValue import com.intellij.openapi.util.registry.RegistryValueListener -import com.intellij.openapi.vfs.VirtualFile import com.intellij.platform.backend.workspace.workspaceModel -import com.intellij.platform.eel.provider.getEelDescriptor -import com.intellij.platform.eel.provider.utils.Path import com.intellij.platform.workspace.jps.entities.FacetEntity import com.intellij.util.concurrency.SynchronizedClearableLazy -import com.intellij.util.containers.ContainerUtil -import com.intellij.util.containers.orNull -import com.intellij.util.lang.UrlClassLoader import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import org.jetbrains.annotations.ApiStatus import org.jetbrains.kotlin.analysis.api.KaExperimentalApi import org.jetbrains.kotlin.analysis.api.platform.projectStructure.KotlinCompilerPluginsProvider import org.jetbrains.kotlin.analysis.api.platform.projectStructure.KotlinCompilerPluginsProvider.CompilerPluginType -import org.jetbrains.kotlin.analysis.api.platform.projectStructure.areCompilerPluginsSupported import org.jetbrains.kotlin.analysis.api.projectStructure.KaModule -import org.jetbrains.kotlin.analysis.api.projectStructure.KaScriptModule -import org.jetbrains.kotlin.analysis.api.projectStructure.KaSourceModule -import org.jetbrains.kotlin.analysis.low.level.api.fir.LLFirInternals -import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments -import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments -import org.jetbrains.kotlin.cli.plugins.processCompilerPluginsOptions -import org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor import org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi -import org.jetbrains.kotlin.config.CompilerConfiguration -import org.jetbrains.kotlin.config.JVMConfigurationKeys -import org.jetbrains.kotlin.config.JvmTarget import org.jetbrains.kotlin.extensions.ProjectExtensionDescriptor -import org.jetbrains.kotlin.fir.extensions.FirAssignExpressionAltererExtension -import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrar -import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrarAdapter -import org.jetbrains.kotlin.idea.base.projectStructure.KaSourceModuleKind -import org.jetbrains.kotlin.idea.base.projectStructure.openapiModule -import org.jetbrains.kotlin.idea.base.projectStructure.sourceModuleKind import org.jetbrains.kotlin.idea.base.util.caching.getChanges -import org.jetbrains.kotlin.idea.compiler.configuration.KotlinCommonCompilerArgumentsHolder import org.jetbrains.kotlin.idea.compiler.configuration.KotlinCompilerPluginsScriptConfigurationListener import org.jetbrains.kotlin.idea.compiler.configuration.KotlinCompilerSettingsListener -import org.jetbrains.kotlin.idea.facet.KotlinFacet import org.jetbrains.kotlin.idea.facet.isKotlinFacet -import org.jetbrains.kotlin.idea.util.getOriginalOrDelegateFileOrSelf import org.jetbrains.kotlin.idea.workspaceModel.KotlinSettingsEntity -import org.jetbrains.kotlin.scripting.compiler.plugin.impl.makeScriptCompilerArguments -import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition -import org.jetbrains.kotlin.util.ServiceLoaderLite -import java.io.File -import java.nio.file.Path -import java.util.* -import java.util.concurrent.ConcurrentMap -import kotlin.script.experimental.api.ScriptCompilationConfiguration -import kotlin.script.experimental.api.compilerOptions @OptIn(ExperimentalCompilerApi::class) +@ApiStatus.Internal internal class KtCompilerPluginsProviderIdeImpl( private val project: Project, cs: CoroutineScope, ) : KotlinCompilerPluginsProvider, Disposable { - private val pluginsCacheCachedValue: SynchronizedClearableLazy = SynchronizedClearableLazy { createNewCache() } - private val pluginsCache: PluginsCache? - get() = pluginsCacheCachedValue.value private val onlyBundledPluginsEnabledRegistryValue: RegistryValue = Registry.get("kotlin.k2.only.bundled.compiler.plugins.enabled") - private val onlyBundledPluginsEnabled: Boolean - get() = onlyBundledPluginsEnabledRegistryValue.asBoolean() + private val pluginsCacheCachedValue: SynchronizedClearableLazy = SynchronizedClearableLazy { + if (TrustedProjects.isProjectTrusted(project)) { + KtCompilerPluginsCache.new(project, onlyBundledPluginsEnabledRegistryValue.asBoolean()) + } else { + null + } + } init { cs.launch { @@ -106,10 +67,9 @@ internal class KtCompilerPluginsProviderIdeImpl( messageBusConnection.subscribe( KotlinCompilerPluginsScriptConfigurationListener.TOPIC, object : KotlinCompilerPluginsScriptConfigurationListener { override fun scriptConfigurationsChanged() { - resetScriptCache(pluginsCacheCachedValue.valueIfInitialized ?: return) + (pluginsCacheCachedValue.valueIfInitialized ?: return).resetScriptCache() } }) - onlyBundledPluginsEnabledRegistryValue.addListener( object : RegistryValueListener { override fun afterValueChanged(value: RegistryValue) { @@ -119,200 +79,21 @@ internal class KtCompilerPluginsProviderIdeImpl( ) } - private fun createNewCache(): PluginsCache? { - if (!TrustedProjects.isProjectTrusted(project)) return null - val pluginsClassLoader: UrlClassLoader = UrlClassLoader.build().apply { - parent(KaModule::class.java.classLoader) - - val allModules = ModuleManager.getInstance(project).modules - val pluginClasspaths = collectSubstitutedPluginClasspaths(allModules.map { it.getCompilerArguments() }) - - files(pluginClasspaths) - }.get() - return PluginsCache( - pluginsClassLoader, - ContainerUtil.createConcurrentWeakMap(), - SynchronizedClearableLazy { ContainerUtil.createConcurrentWeakMap() }) - } - - private class PluginsCache( - val pluginsClassLoader: UrlClassLoader, - val registrarForSourceModule: ConcurrentMap>, - /** - * As scripts might be associated with injections, - * it's better to have a more stable anchor such as a top-level file. - */ - val registrarForScriptModule: SynchronizedClearableLazy>> - ) - @OptIn(KaExperimentalApi::class) override fun getRegisteredExtensions(module: KaModule, extensionType: ProjectExtensionDescriptor): List { - if (!module.areCompilerPluginsSupported()) return emptyList() - - return when (module) { - is KaSourceModule -> { - val registrarForModule = pluginsCache?.registrarForSourceModule ?: return emptyList() - module.getExtensionsForModule(registrarForModule, module, extensionType) - } - - is KaScriptModule -> { - val registrarForModule = pluginsCache?.registrarForScriptModule?.value ?: return emptyList() - val cacheKey = module.file.virtualFile.getOriginalOrDelegateFileOrSelf() - - module.getExtensionsForModule(registrarForModule, cacheKey, extensionType) - } - - else -> emptyList() - } - } - - @OptIn(KaExperimentalApi::class) - private fun KaModule.getExtensionsForModule( - registrarForModule: ConcurrentMap>, - cacheKey: K, - extensionType: ProjectExtensionDescriptor - ): List { - val extensionStorage = registrarForModule.computeIfAbsent(cacheKey) { - Optional.ofNullable(computeExtensionStorage(this)) - }.orNull() ?: return emptyList() - val registrars = extensionStorage.registeredExtensions[extensionType] ?: return emptyList() - @Suppress("UNCHECKED_CAST") return registrars as List + val pluginsCache = pluginsCacheCachedValue.value ?: return emptyList() + return pluginsCache.getRegisteredExtensions( + module = module, + extensionType = extensionType + ) } override fun isPluginOfTypeRegistered(module: KaModule, pluginType: CompilerPluginType): Boolean { - val extension = when (pluginType) { - CompilerPluginType.ASSIGNMENT -> FirAssignExpressionAltererExtension::class - else -> return false - } - - return getRegisteredExtensions(module, FirExtensionRegistrarAdapter).map { (it as FirExtensionRegistrar).configure() } - .any { it.extensions[extension]?.isNotEmpty() == true } - } - - @OptIn(KaExperimentalApi::class, LLFirInternals::class) - private fun computeExtensionStorage(module: KaModule): CompilerPluginRegistrar.ExtensionStorage? { - val classLoader = pluginsCache?.pluginsClassLoader ?: return null - - val compilerArguments = when (module) { - is KaSourceModule -> module.openapiModule.getCompilerArguments() - is KaScriptModule -> { - val scriptDefinition = module.file.findScriptDefinition() ?: return null - val scriptConfiguration = scriptDefinition.compilationConfiguration - - val providedOptions = scriptConfiguration[ScriptCompilationConfiguration.compilerOptions] ?: return null - makeScriptCompilerArguments(providedOptions) - } - - else -> { - return null - } - } - val pluginClasspaths = collectSubstitutedPluginClasspaths(listOf(compilerArguments)).map { it.toFile() } - if (pluginClasspaths.isEmpty()) return null - - val logger = logger() - - ProgressManager.checkCanceled() - - val pluginRegistrars = - logger.runAndLogException { ServiceLoaderLite.loadImplementations(pluginClasspaths, classLoader) } - ?.takeIf { it.isNotEmpty() } ?: return null - - ProgressManager.checkCanceled() - - val commandLineProcessors = logger.runAndLogException { - ServiceLoaderLite.loadImplementations(pluginClasspaths, classLoader) - } ?: return null - - ProgressManager.checkCanceled() - - val compilerConfiguration = - CompilerConfiguration().apply { // Temporary work-around for KTIJ-24320. Calls to 'setupCommonArguments()' and 'setupJvmSpecificArguments()' - // (or even a platform-agnostic alternative) should be added. - if (compilerArguments is K2JVMCompilerArguments && module is KaSourceModule) { - val compilerExtension = CompilerModuleExtension.getInstance(module.openapiModule) - val outputUrl = when (module.sourceModuleKind) { - KaSourceModuleKind.TEST -> compilerExtension?.compilerOutputUrlForTests - KaSourceModuleKind.PRODUCTION, null -> compilerExtension?.compilerOutputUrl - } - - putIfNotNull(JVMConfigurationKeys.JVM_TARGET, compilerArguments.jvmTarget?.let(JvmTarget::fromString)) - putIfNotNull(JVMConfigurationKeys.OUTPUT_DIRECTORY, outputUrl?.let { File(it) }) - } - - processCompilerPluginsOptions(this, compilerArguments.pluginOptions?.toList(), commandLineProcessors) - } - - val storage = CompilerPluginRegistrar.ExtensionStorage() - for (pluginRegistrar in pluginRegistrars) { - ProgressManager.checkCanceled() - - with(pluginRegistrar) { - try { - val configuration = KotlinFirCompilerPluginConfigurationForIdeProvider.getCompilerConfigurationWithCustomOptions( - pluginRegistrar, compilerConfiguration - ) ?: compilerConfiguration - storage.registerExtensions(configuration) - } catch (e: ProcessCanceledException) { - throw e - } catch (e: Throwable) { - LOG.error(e) - } - } - } - return storage - } - - /** - * Returns the paths defined in [CommonCompilerArguments.pluginClasspaths] - * in the absolute form with the expansion of the present path macros - * (like 'KOTLIN_BUNDLED'). - */ - private fun CommonCompilerArguments.getOriginalPluginClasspaths(): List { - val pluginClassPaths = this.pluginClasspaths - - if (pluginClassPaths.isNullOrEmpty()) return emptyList() - - val pathMacroManager = PathMacroManager.getInstance(project) - val expandedPluginClassPaths = pluginClassPaths.map { pathMacroManager.expandPath(it) } - val eel = project.getEelDescriptor() - return expandedPluginClassPaths.map { Path(it, eel).toAbsolutePath() } - } - - /** - * Collects the substituted plugin classpaths for the given [compilerArguments] list. - * - * We process the whole [compilerArguments] list at once, because it gives us opportunity - * to not call [substitutePluginJar] multiple times if [compilerArguments] use the same - * compiler plugins jars. - */ - private fun collectSubstitutedPluginClasspaths(compilerArguments: List): List { - val combinedOriginalClasspaths = compilerArguments.asSequence().flatMap { it.getOriginalPluginClasspaths() }.distinct() - - val substitutedClasspaths = combinedOriginalClasspaths.mapNotNull(::substitutePluginJar).distinct() - - return substitutedClasspaths.toList() - } - - /** - * We have the following logic for plugins' substitution: - * 1. Always replace our own plugins (like "allopen", "noarg", etc.) with bundled ones to avoid binary incompatibility. - * 2. Allow to use other compiler plugins only if [onlyBundledPluginsEnabled] is set to false; otherwise, filter them. - */ - private fun substitutePluginJar(userSuppliedPluginJar: Path): Path? { - ProgressManager.checkCanceled() - - val bundledPlugin = KotlinBundledFirCompilerPluginProvider.provideBundledPluginJar(project, userSuppliedPluginJar) - if (bundledPlugin != null) return bundledPlugin - - return userSuppliedPluginJar.takeUnless { onlyBundledPluginsEnabled } - } - - - private fun Module.getCompilerArguments(): CommonCompilerArguments { - return KotlinFacet.get(this)?.configuration?.settings?.mergedCompilerArguments ?: KotlinCommonCompilerArgumentsHolder.getInstance( - project - ).settings + val pluginsCache = pluginsCacheCachedValue.value ?: return false + return pluginsCache.isPluginOfTypeRegistered( + module = module, + pluginType = pluginType + ) } override fun dispose() { @@ -324,37 +105,13 @@ internal class KtCompilerPluginsProviderIdeImpl( * registered in the corresponding [CompilerPluginRegistrar.ExtensionStorage]s. */ private fun resetPluginsCache() { - val droppedCache = pluginsCacheCachedValue.drop() ?: return - - droppedCache.registrarForSourceModule.values.mapNotNull { it.orNull() }.disposeAll() - - resetScriptCache(droppedCache) - } - - /** - * Throws away only part of the cache related to the scripts, leaving other storage intact. - */ - private fun resetScriptCache(pluginsCache: PluginsCache) { - val scriptsCache = pluginsCache.registrarForScriptModule.drop() ?: return - - scriptsCache.values.mapNotNull { it.orNull() }.disposeAll() - } - - private fun Collection.disposeAll() { - for (storage in this) { - for (disposable in storage.disposables) { - LOG.runAndLogException { - disposable.dispose() - } - } - } + pluginsCacheCachedValue.drop()?.dispose() } companion object { fun getInstance(project: Project): KtCompilerPluginsProviderIdeImpl { return KotlinCompilerPluginsProvider.getInstance(project) as KtCompilerPluginsProviderIdeImpl } - - private val LOG = logger() } } +