From 4625468de7621d17003e59adbc32603a16d0a1cc Mon Sep 17 00:00:00 2001 From: Ilya Chernikov Date: Thu, 21 Feb 2019 17:48:04 +0100 Subject: [PATCH] Expose project environment from core env, to allow usages of plugins... before core env construction. Refactor script evaluation extension to this scheme. Plus some refactorings of the logic of script/repl evaluation in K2JVMCompiler --- .../extensions/ScriptEvaluationExtension.kt | 9 +- .../jetbrains/kotlin/cli/jvm/K2JVMCompiler.kt | 35 ++-- .../cli/jvm/compiler/KotlinCoreEnvironment.kt | 166 +++++++++++------- .../plugin/JvmCliScriptEvaluationExtension.kt | 18 +- 4 files changed, 140 insertions(+), 88 deletions(-) diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/common/extensions/ScriptEvaluationExtension.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/common/extensions/ScriptEvaluationExtension.kt index e3c534701d6..2b921676d98 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/common/extensions/ScriptEvaluationExtension.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/common/extensions/ScriptEvaluationExtension.kt @@ -5,9 +5,10 @@ package org.jetbrains.kotlin.cli.common.extensions +import com.intellij.core.JavaCoreProjectEnvironment import org.jetbrains.kotlin.cli.common.ExitCode import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments -import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment +import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.extensions.ProjectExtensionDescriptor interface ScriptEvaluationExtension { @@ -19,5 +20,9 @@ interface ScriptEvaluationExtension { fun isAccepted(arguments: CommonCompilerArguments): Boolean // TODO: it would be nice to split KotlinCoreEnvironment to actual environment and compilation/project configuration - fun eval(arguments: CommonCompilerArguments, coreEnvironment: KotlinCoreEnvironment): ExitCode + fun eval( + arguments: CommonCompilerArguments, + configuration: CompilerConfiguration, + projectEnvironment: JavaCoreProjectEnvironment + ): ExitCode } diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/K2JVMCompiler.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/K2JVMCompiler.kt index 0560ce1a585..4dc09835e13 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/K2JVMCompiler.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/K2JVMCompiler.kt @@ -79,36 +79,37 @@ class K2JVMCompiler : CLICompiler() { configuration.configureExplicitContentRoots(arguments) configuration.configureStandardLibs(paths, arguments) + configuration.configureAdvancedJvmOptions(arguments) - if (arguments.buildFile == null && arguments.freeArgs.isEmpty() && !arguments.version && !arguments.allowNoSourceFiles) { - if (arguments.script) { + if (arguments.buildFile == null && !arguments.version && !arguments.allowNoSourceFiles && (arguments.script || arguments.freeArgs.isEmpty())) { + // script or repl + if (arguments.script && arguments.freeArgs.isEmpty()) { messageCollector.report(ERROR, "Specify script source path to evaluate") return COMPILATION_ERROR } - ReplFromTerminal.run(rootDisposable, configuration) - return ExitCode.OK - } - configuration.configureAdvancedJvmOptions(arguments) + val projectEnvironment = + KotlinCoreEnvironment.ProjectEnvironment( + rootDisposable, + KotlinCoreEnvironment.getOrCreateApplicationEnvironmentForProduction(rootDisposable, configuration) + ) + projectEnvironment.registerExtensionsFromPlugins(configuration) - messageCollector.report(LOGGING, "Configuring the compilation environment") - try { if (arguments.script) { - val sourcePath = arguments.freeArgs.first() - configuration.addKotlinSourceRoot(sourcePath) - - val environment = createCoreEnvironment(rootDisposable, configuration, messageCollector) - ?: return COMPILATION_ERROR - - val scriptingEvaluator = ScriptEvaluationExtension.getInstances(environment.project).find { it.isAccepted(arguments) } + val scriptingEvaluator = ScriptEvaluationExtension.getInstances(projectEnvironment.project).find { it.isAccepted(arguments) } if (scriptingEvaluator == null) { messageCollector.report(ERROR, "Unable to evaluate script, no scripting plugin found") return COMPILATION_ERROR } - - return scriptingEvaluator.eval(arguments, environment) + return scriptingEvaluator.eval(arguments, configuration, projectEnvironment) + } else { + ReplFromTerminal.run(rootDisposable, configuration) + return ExitCode.OK } + } + messageCollector.report(LOGGING, "Configuring the compilation environment") + try { val destination = arguments.destination?.let { File(it) } val buildFile = arguments.buildFile?.let { File(it) } diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt index 3ff6474e74e..28b05fb0e33 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt @@ -121,34 +121,47 @@ import java.util.zip.ZipFile import javax.xml.stream.XMLInputFactory class KotlinCoreEnvironment private constructor( - parentDisposable: Disposable, - applicationEnvironment: JavaCoreApplicationEnvironment, + private val projectEnvironment: JavaCoreProjectEnvironment, initialConfiguration: CompilerConfiguration, configFiles: EnvironmentConfigFiles ) { - private val projectEnvironment: JavaCoreProjectEnvironment = - object : KotlinCoreProjectEnvironment(parentDisposable, applicationEnvironment) { - override fun preregisterServices() { - registerProjectExtensionPoints(Extensions.getArea(project)) - } - override fun registerJavaPsiFacade() { - with(project) { - registerService( - CoreJavaFileManager::class.java, - ServiceManager.getService(this, JavaFileManager::class.java) as CoreJavaFileManager - ) + class ProjectEnvironment( + disposable: Disposable, applicationEnvironment: JavaCoreApplicationEnvironment + ) : + KotlinCoreProjectEnvironment(disposable, applicationEnvironment) { - registerKotlinLightClassSupport(project) + private var extensionRegistered = false - registerService(ExternalAnnotationsManager::class.java, MockExternalAnnotationsManager()) - registerService(InferredAnnotationsManager::class.java, MockInferredAnnotationsManager()) - } + override fun preregisterServices() { + registerProjectExtensionPoints(Extensions.getArea(project)) + } - super.registerJavaPsiFacade() + fun registerExtensionsFromPlugins(configuration: CompilerConfiguration) { + if (!extensionRegistered) { + registerPluginExtensionPoints(project) + registerExtensionsFromPlugins(project, configuration) + extensionRegistered = true } } + override fun registerJavaPsiFacade() { + with(project) { + registerService( + CoreJavaFileManager::class.java, + ServiceManager.getService(this, JavaFileManager::class.java) as CoreJavaFileManager + ) + + registerKotlinLightClassSupport(project) + + registerService(ExternalAnnotationsManager::class.java, MockExternalAnnotationsManager()) + registerService(InferredAnnotationsManager::class.java, MockInferredAnnotationsManager()) + } + + super.registerJavaPsiFacade() + } + } + private val sourceFiles = mutableListOf() private val rootsIndex: JvmDependenciesDynamicCompoundIndex private val packagePartProviders = mutableListOf() @@ -165,24 +178,11 @@ class KotlinCoreEnvironment private constructor( val project = projectEnvironment.project - registerPluginExtensionPoints(project) - val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY) - for (registrar in configuration.getList(ComponentRegistrar.PLUGIN_COMPONENT_REGISTRARS)) { - try { - registrar.registerProjectComponents(project, configuration) - } catch (e: AbstractMethodError) { - val message = "The provided plugin ${registrar.javaClass.name} is not compatible with this version of compiler" - // Since the scripting plugin is often discovered in the compiler environment, it is often taken from the incompatible - // location, and in many cases this is not a fatal error, therefore strong warning is generated instead of exception - if (registrar.javaClass.simpleName == "ScriptingCompilerConfigurationComponentRegistrar") { - messageCollector?.report(STRONG_WARNING, "Default scripting plugin is disabled: $message") - } else { - throw IllegalStateException(message, e) - } - } - } + (projectEnvironment as? ProjectEnvironment)?.registerExtensionsFromPlugins(configuration) + // otherwise consider that project environment is properly configured before passing to the environment + // TODO: consider some asserts to check important extension points project.registerService(DeclarationProviderFactoryService::class.java, CliDeclarationProviderFactoryService(sourceFiles)) @@ -420,21 +420,9 @@ class KotlinCoreEnvironment private constructor( fun createForProduction( parentDisposable: Disposable, configuration: CompilerConfiguration, configFiles: EnvironmentConfigFiles ): KotlinCoreEnvironment { - val appEnv = getOrCreateApplicationEnvironmentForProduction(configuration) - // Disposing of the environment is unsafe in production then parallel builds are enabled, but turning it off universally - // breaks a lot of tests, therefore it is disabled for production and enabled for tests - if (System.getProperty(KOTLIN_COMPILER_ENVIRONMENT_KEEPALIVE_PROPERTY).toBooleanLenient() != true) { - // JPS may run many instances of the compiler in parallel (there's an option for compiling independent modules in parallel in IntelliJ) - // All projects share the same ApplicationEnvironment, and when the last project is disposed, the ApplicationEnvironment is disposed as well - Disposer.register(parentDisposable, Disposable { - synchronized(APPLICATION_LOCK) { - if (--ourProjectCount <= 0) { - disposeApplicationEnvironment() - } - } - }) - } - val environment = KotlinCoreEnvironment(parentDisposable, appEnv, configuration, configFiles) + val appEnv = getOrCreateApplicationEnvironmentForProduction(parentDisposable, configuration) + val projectEnv = ProjectEnvironment(parentDisposable, appEnv) + val environment = KotlinCoreEnvironment(projectEnv, configuration, configFiles) synchronized(APPLICATION_LOCK) { ourProjectCount++ @@ -442,6 +430,21 @@ class KotlinCoreEnvironment private constructor( return environment } + @JvmStatic + fun createForProduction( + projectEnvironment: JavaCoreProjectEnvironment, configuration: CompilerConfiguration, configFiles: EnvironmentConfigFiles + ): KotlinCoreEnvironment { + val environment = KotlinCoreEnvironment(projectEnvironment, configuration, configFiles) + + if (projectEnvironment.environment == applicationEnvironment) { + // accounting for core environment disposing + synchronized(APPLICATION_LOCK) { + ourProjectCount++ + } + } + return environment + } + @TestOnly @JvmStatic fun createForTests( @@ -449,30 +452,42 @@ class KotlinCoreEnvironment private constructor( ): KotlinCoreEnvironment { val configuration = initialConfiguration.copy() // Tests are supposed to create a single project and dispose it right after use - return KotlinCoreEnvironment( - parentDisposable, - createApplicationEnvironment(parentDisposable, configuration, unitTestMode = true), - configuration, - extensionConfigs - ) + val appEnv = createApplicationEnvironment(parentDisposable, configuration, unitTestMode = true) + val projectEnv = ProjectEnvironment(parentDisposable, appEnv) + return KotlinCoreEnvironment(projectEnv, configuration, extensionConfigs) } // used in the daemon for jar cache cleanup val applicationEnvironment: JavaCoreApplicationEnvironment? get() = ourApplicationEnvironment - private fun getOrCreateApplicationEnvironmentForProduction(configuration: CompilerConfiguration): JavaCoreApplicationEnvironment { + internal fun getOrCreateApplicationEnvironmentForProduction( + parentDisposable: Disposable, configuration: CompilerConfiguration + ): JavaCoreApplicationEnvironment { synchronized(APPLICATION_LOCK) { - if (ourApplicationEnvironment != null) - return ourApplicationEnvironment!! + if (ourApplicationEnvironment == null) { + val disposable = Disposer.newDisposable() + ourApplicationEnvironment = createApplicationEnvironment(disposable, configuration, unitTestMode = false) + ourProjectCount = 0 + Disposer.register(disposable, Disposable { + synchronized(APPLICATION_LOCK) { + ourApplicationEnvironment = null + } + }) + } + // Disposing of the environment is unsafe in production then parallel builds are enabled, but turning it off universally + // breaks a lot of tests, therefore it is disabled for production and enabled for tests + if (System.getProperty(KOTLIN_COMPILER_ENVIRONMENT_KEEPALIVE_PROPERTY).toBooleanLenient() != true) { + // JPS may run many instances of the compiler in parallel (there's an option for compiling independent modules in parallel in IntelliJ) + // All projects share the same ApplicationEnvironment, and when the last project is disposed, the ApplicationEnvironment is disposed as well + Disposer.register(parentDisposable, Disposable { + synchronized(APPLICATION_LOCK) { + if (--ourProjectCount <= 0) { + disposeApplicationEnvironment() + } + } + }) + } - val parentDisposable = Disposer.newDisposable() - ourApplicationEnvironment = createApplicationEnvironment(parentDisposable, configuration, unitTestMode = false) - ourProjectCount = 0 - Disposer.register(parentDisposable, Disposable { - synchronized(APPLICATION_LOCK) { - ourApplicationEnvironment = null - } - }) return ourApplicationEnvironment!! } } @@ -601,6 +616,25 @@ class KotlinCoreEnvironment private constructor( ScriptEvaluationExtension.registerExtensionPoint(project) } + internal fun registerExtensionsFromPlugins(project: MockProject, configuration: CompilerConfiguration) { + val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY) + for (registrar in configuration.getList(ComponentRegistrar.PLUGIN_COMPONENT_REGISTRARS)) { + try { + registrar.registerProjectComponents(project, configuration) + } catch (e: AbstractMethodError) { + val message = "The provided plugin ${registrar.javaClass.name} is not compatible with this version of compiler" + // Since the scripting plugin is often discovered in the compiler environment, it is often taken from the incompatible + // location, and in many cases this is not a fatal error, therefore strong warning is generated instead of exception + if (registrar.javaClass.simpleName == "ScriptingCompilerConfigurationComponentRegistrar") { + messageCollector?.report(STRONG_WARNING, "Default scripting plugin is disabled: $message") + } else { + throw IllegalStateException(message, e) + } + } + } + } + + private fun registerApplicationServicesForCLI(applicationEnvironment: JavaCoreApplicationEnvironment) { // ability to get text from annotations xml files applicationEnvironment.registerFileType(PlainTextFileType.INSTANCE, "xml") diff --git a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/JvmCliScriptEvaluationExtension.kt b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/JvmCliScriptEvaluationExtension.kt index 148e39d3511..51d9ddb2370 100644 --- a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/JvmCliScriptEvaluationExtension.kt +++ b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/JvmCliScriptEvaluationExtension.kt @@ -5,15 +5,19 @@ package org.jetbrains.kotlin.scripting.compiler.plugin +import com.intellij.core.JavaCoreProjectEnvironment import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys import org.jetbrains.kotlin.cli.common.ExitCode import org.jetbrains.kotlin.cli.common.ExitCode.COMPILATION_ERROR import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments +import org.jetbrains.kotlin.cli.common.config.addKotlinSourceRoot import org.jetbrains.kotlin.cli.common.extensions.ScriptEvaluationExtension import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.ERROR +import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment import org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler +import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.config.JVMConfigurationKeys import org.jetbrains.kotlin.script.ScriptDefinitionProvider import org.jetbrains.kotlin.script.StandardScriptDefinition @@ -23,10 +27,13 @@ class JvmCliScriptEvaluationExtension : ScriptEvaluationExtension { override fun isAccepted(arguments: CommonCompilerArguments): Boolean = arguments is K2JVMCompilerArguments && arguments.script - override fun eval(arguments: CommonCompilerArguments, coreEnvironment: KotlinCoreEnvironment): ExitCode { - val configuration = coreEnvironment.configuration + override fun eval( + arguments: CommonCompilerArguments, + configuration: CompilerConfiguration, + projectEnvironment: JavaCoreProjectEnvironment + ): ExitCode { val messageCollector = configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY) - val scriptDefinitionProvider = ScriptDefinitionProvider.getInstance(coreEnvironment.project) + val scriptDefinitionProvider = ScriptDefinitionProvider.getInstance(projectEnvironment.project) if (scriptDefinitionProvider == null) { messageCollector.report(ERROR, "Unable to process the script, scripting plugin is not configured") return COMPILATION_ERROR @@ -40,9 +47,14 @@ class JvmCliScriptEvaluationExtension : ScriptEvaluationExtension { messageCollector.report(ERROR, "Specify path to the script file$extensionHint as the first argument") return COMPILATION_ERROR } + configuration.addKotlinSourceRoot(sourcePath) configuration.put(JVMConfigurationKeys.RETAIN_OUTPUT_IN_MEMORY, true) val scriptArgs = arguments.freeArgs.subList(1, arguments.freeArgs.size) + + val coreEnvironment = + KotlinCoreEnvironment.createForProduction(projectEnvironment, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES) + return KotlinToJVMBytecodeCompiler.compileAndExecuteScript(coreEnvironment, scriptArgs) } } \ No newline at end of file