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:
+7
-2
@@ -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
|
||||
}
|
||||
|
||||
@@ -79,36 +79,37 @@ class K2JVMCompiler : CLICompiler<K2JVMCompilerArguments>() {
|
||||
|
||||
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) }
|
||||
|
||||
|
||||
@@ -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<KtFile>()
|
||||
private val rootsIndex: JvmDependenciesDynamicCompoundIndex
|
||||
private val packagePartProviders = mutableListOf<JvmPackagePartProvider>()
|
||||
@@ -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")
|
||||
|
||||
+15
-3
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user