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
This commit is contained in:
Ilya Chernikov
2019-02-21 17:48:04 +01:00
parent 1178e94f50
commit 4625468de7
4 changed files with 140 additions and 88 deletions
@@ -5,9 +5,10 @@
package org.jetbrains.kotlin.cli.common.extensions 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.ExitCode
import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments 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 import org.jetbrains.kotlin.extensions.ProjectExtensionDescriptor
interface ScriptEvaluationExtension { interface ScriptEvaluationExtension {
@@ -19,5 +20,9 @@ interface ScriptEvaluationExtension {
fun isAccepted(arguments: CommonCompilerArguments): Boolean fun isAccepted(arguments: CommonCompilerArguments): Boolean
// TODO: it would be nice to split KotlinCoreEnvironment to actual environment and compilation/project configuration // 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
} }
@@ -79,36 +79,37 @@ class K2JVMCompiler : CLICompiler<K2JVMCompilerArguments>() {
configuration.configureExplicitContentRoots(arguments) configuration.configureExplicitContentRoots(arguments)
configuration.configureStandardLibs(paths, arguments) configuration.configureStandardLibs(paths, arguments)
configuration.configureAdvancedJvmOptions(arguments)
if (arguments.buildFile == null && arguments.freeArgs.isEmpty() && !arguments.version && !arguments.allowNoSourceFiles) { if (arguments.buildFile == null && !arguments.version && !arguments.allowNoSourceFiles && (arguments.script || arguments.freeArgs.isEmpty())) {
if (arguments.script) { // script or repl
if (arguments.script && arguments.freeArgs.isEmpty()) {
messageCollector.report(ERROR, "Specify script source path to evaluate") messageCollector.report(ERROR, "Specify script source path to evaluate")
return COMPILATION_ERROR 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) { if (arguments.script) {
val sourcePath = arguments.freeArgs.first() val scriptingEvaluator = ScriptEvaluationExtension.getInstances(projectEnvironment.project).find { it.isAccepted(arguments) }
configuration.addKotlinSourceRoot(sourcePath)
val environment = createCoreEnvironment(rootDisposable, configuration, messageCollector)
?: return COMPILATION_ERROR
val scriptingEvaluator = ScriptEvaluationExtension.getInstances(environment.project).find { it.isAccepted(arguments) }
if (scriptingEvaluator == null) { if (scriptingEvaluator == null) {
messageCollector.report(ERROR, "Unable to evaluate script, no scripting plugin found") messageCollector.report(ERROR, "Unable to evaluate script, no scripting plugin found")
return COMPILATION_ERROR return COMPILATION_ERROR
} }
return scriptingEvaluator.eval(arguments, configuration, projectEnvironment)
return scriptingEvaluator.eval(arguments, environment) } else {
ReplFromTerminal.run(rootDisposable, configuration)
return ExitCode.OK
} }
}
messageCollector.report(LOGGING, "Configuring the compilation environment")
try {
val destination = arguments.destination?.let { File(it) } val destination = arguments.destination?.let { File(it) }
val buildFile = arguments.buildFile?.let { File(it) } val buildFile = arguments.buildFile?.let { File(it) }
@@ -121,34 +121,47 @@ import java.util.zip.ZipFile
import javax.xml.stream.XMLInputFactory import javax.xml.stream.XMLInputFactory
class KotlinCoreEnvironment private constructor( class KotlinCoreEnvironment private constructor(
parentDisposable: Disposable, private val projectEnvironment: JavaCoreProjectEnvironment,
applicationEnvironment: JavaCoreApplicationEnvironment,
initialConfiguration: CompilerConfiguration, initialConfiguration: CompilerConfiguration,
configFiles: EnvironmentConfigFiles configFiles: EnvironmentConfigFiles
) { ) {
private val projectEnvironment: JavaCoreProjectEnvironment =
object : KotlinCoreProjectEnvironment(parentDisposable, applicationEnvironment) {
override fun preregisterServices() {
registerProjectExtensionPoints(Extensions.getArea(project))
}
override fun registerJavaPsiFacade() { class ProjectEnvironment(
with(project) { disposable: Disposable, applicationEnvironment: JavaCoreApplicationEnvironment
registerService( ) :
CoreJavaFileManager::class.java, KotlinCoreProjectEnvironment(disposable, applicationEnvironment) {
ServiceManager.getService(this, JavaFileManager::class.java) as CoreJavaFileManager
)
registerKotlinLightClassSupport(project) private var extensionRegistered = false
registerService(ExternalAnnotationsManager::class.java, MockExternalAnnotationsManager()) override fun preregisterServices() {
registerService(InferredAnnotationsManager::class.java, MockInferredAnnotationsManager()) 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<KtFile>() private val sourceFiles = mutableListOf<KtFile>()
private val rootsIndex: JvmDependenciesDynamicCompoundIndex private val rootsIndex: JvmDependenciesDynamicCompoundIndex
private val packagePartProviders = mutableListOf<JvmPackagePartProvider>() private val packagePartProviders = mutableListOf<JvmPackagePartProvider>()
@@ -165,24 +178,11 @@ class KotlinCoreEnvironment private constructor(
val project = projectEnvironment.project val project = projectEnvironment.project
registerPluginExtensionPoints(project)
val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY) val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
for (registrar in configuration.getList(ComponentRegistrar.PLUGIN_COMPONENT_REGISTRARS)) { (projectEnvironment as? ProjectEnvironment)?.registerExtensionsFromPlugins(configuration)
try { // otherwise consider that project environment is properly configured before passing to the environment
registrar.registerProjectComponents(project, configuration) // TODO: consider some asserts to check important extension points
} 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)
}
}
}
project.registerService(DeclarationProviderFactoryService::class.java, CliDeclarationProviderFactoryService(sourceFiles)) project.registerService(DeclarationProviderFactoryService::class.java, CliDeclarationProviderFactoryService(sourceFiles))
@@ -420,21 +420,9 @@ class KotlinCoreEnvironment private constructor(
fun createForProduction( fun createForProduction(
parentDisposable: Disposable, configuration: CompilerConfiguration, configFiles: EnvironmentConfigFiles parentDisposable: Disposable, configuration: CompilerConfiguration, configFiles: EnvironmentConfigFiles
): KotlinCoreEnvironment { ): KotlinCoreEnvironment {
val appEnv = getOrCreateApplicationEnvironmentForProduction(configuration) val appEnv = getOrCreateApplicationEnvironmentForProduction(parentDisposable, configuration)
// Disposing of the environment is unsafe in production then parallel builds are enabled, but turning it off universally val projectEnv = ProjectEnvironment(parentDisposable, appEnv)
// breaks a lot of tests, therefore it is disabled for production and enabled for tests val environment = KotlinCoreEnvironment(projectEnv, configuration, configFiles)
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)
synchronized(APPLICATION_LOCK) { synchronized(APPLICATION_LOCK) {
ourProjectCount++ ourProjectCount++
@@ -442,6 +430,21 @@ class KotlinCoreEnvironment private constructor(
return environment 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 @TestOnly
@JvmStatic @JvmStatic
fun createForTests( fun createForTests(
@@ -449,30 +452,42 @@ class KotlinCoreEnvironment private constructor(
): KotlinCoreEnvironment { ): KotlinCoreEnvironment {
val configuration = initialConfiguration.copy() val configuration = initialConfiguration.copy()
// Tests are supposed to create a single project and dispose it right after use // Tests are supposed to create a single project and dispose it right after use
return KotlinCoreEnvironment( val appEnv = createApplicationEnvironment(parentDisposable, configuration, unitTestMode = true)
parentDisposable, val projectEnv = ProjectEnvironment(parentDisposable, appEnv)
createApplicationEnvironment(parentDisposable, configuration, unitTestMode = true), return KotlinCoreEnvironment(projectEnv, configuration, extensionConfigs)
configuration,
extensionConfigs
)
} }
// used in the daemon for jar cache cleanup // used in the daemon for jar cache cleanup
val applicationEnvironment: JavaCoreApplicationEnvironment? get() = ourApplicationEnvironment val applicationEnvironment: JavaCoreApplicationEnvironment? get() = ourApplicationEnvironment
private fun getOrCreateApplicationEnvironmentForProduction(configuration: CompilerConfiguration): JavaCoreApplicationEnvironment { internal fun getOrCreateApplicationEnvironmentForProduction(
parentDisposable: Disposable, configuration: CompilerConfiguration
): JavaCoreApplicationEnvironment {
synchronized(APPLICATION_LOCK) { synchronized(APPLICATION_LOCK) {
if (ourApplicationEnvironment != null) if (ourApplicationEnvironment == null) {
return ourApplicationEnvironment!! 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!! return ourApplicationEnvironment!!
} }
} }
@@ -601,6 +616,25 @@ class KotlinCoreEnvironment private constructor(
ScriptEvaluationExtension.registerExtensionPoint(project) 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) { private fun registerApplicationServicesForCLI(applicationEnvironment: JavaCoreApplicationEnvironment) {
// ability to get text from annotations xml files // ability to get text from annotations xml files
applicationEnvironment.registerFileType(PlainTextFileType.INSTANCE, "xml") applicationEnvironment.registerFileType(PlainTextFileType.INSTANCE, "xml")
@@ -5,15 +5,19 @@
package org.jetbrains.kotlin.scripting.compiler.plugin 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.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.ExitCode import org.jetbrains.kotlin.cli.common.ExitCode
import org.jetbrains.kotlin.cli.common.ExitCode.COMPILATION_ERROR import org.jetbrains.kotlin.cli.common.ExitCode.COMPILATION_ERROR
import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments 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.extensions.ScriptEvaluationExtension
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.ERROR 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.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler import org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.JVMConfigurationKeys import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.script.ScriptDefinitionProvider import org.jetbrains.kotlin.script.ScriptDefinitionProvider
import org.jetbrains.kotlin.script.StandardScriptDefinition import org.jetbrains.kotlin.script.StandardScriptDefinition
@@ -23,10 +27,13 @@ class JvmCliScriptEvaluationExtension : ScriptEvaluationExtension {
override fun isAccepted(arguments: CommonCompilerArguments): Boolean = override fun isAccepted(arguments: CommonCompilerArguments): Boolean =
arguments is K2JVMCompilerArguments && arguments.script arguments is K2JVMCompilerArguments && arguments.script
override fun eval(arguments: CommonCompilerArguments, coreEnvironment: KotlinCoreEnvironment): ExitCode { override fun eval(
val configuration = coreEnvironment.configuration arguments: CommonCompilerArguments,
configuration: CompilerConfiguration,
projectEnvironment: JavaCoreProjectEnvironment
): ExitCode {
val messageCollector = configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY) val messageCollector = configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
val scriptDefinitionProvider = ScriptDefinitionProvider.getInstance(coreEnvironment.project) val scriptDefinitionProvider = ScriptDefinitionProvider.getInstance(projectEnvironment.project)
if (scriptDefinitionProvider == null) { if (scriptDefinitionProvider == null) {
messageCollector.report(ERROR, "Unable to process the script, scripting plugin is not configured") messageCollector.report(ERROR, "Unable to process the script, scripting plugin is not configured")
return COMPILATION_ERROR return COMPILATION_ERROR
@@ -40,9 +47,14 @@ class JvmCliScriptEvaluationExtension : ScriptEvaluationExtension {
messageCollector.report(ERROR, "Specify path to the script file$extensionHint as the first argument") messageCollector.report(ERROR, "Specify path to the script file$extensionHint as the first argument")
return COMPILATION_ERROR return COMPILATION_ERROR
} }
configuration.addKotlinSourceRoot(sourcePath)
configuration.put(JVMConfigurationKeys.RETAIN_OUTPUT_IN_MEMORY, true) configuration.put(JVMConfigurationKeys.RETAIN_OUTPUT_IN_MEMORY, true)
val scriptArgs = arguments.freeArgs.subList(1, arguments.freeArgs.size) val scriptArgs = arguments.freeArgs.subList(1, arguments.freeArgs.size)
val coreEnvironment =
KotlinCoreEnvironment.createForProduction(projectEnvironment, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
return KotlinToJVMBytecodeCompiler.compileAndExecuteScript(coreEnvironment, scriptArgs) return KotlinToJVMBytecodeCompiler.compileAndExecuteScript(coreEnvironment, scriptArgs)
} }
} }