Move scripting configuration into compiler plugin
This commit is contained in:
@@ -165,7 +165,7 @@ public class K2JSCompiler extends CLICompiler<K2JSCompilerArguments> {
|
||||
return COMPILATION_ERROR;
|
||||
}
|
||||
|
||||
ExitCode pluginLoadResult = PluginCliParser.loadPluginsSafe(arguments, configuration);
|
||||
ExitCode pluginLoadResult = PluginCliParser.loadPluginsSafe(arguments.getPluginClasspaths(), arguments.getPluginOptions(), configuration);
|
||||
if (pluginLoadResult != ExitCode.OK) return pluginLoadResult;
|
||||
|
||||
configuration.put(JSConfigurationKeys.LIBRARIES, configureLibraries(arguments, paths, messageCollector));
|
||||
|
||||
@@ -33,7 +33,6 @@ import org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler
|
||||
import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot
|
||||
import org.jetbrains.kotlin.cli.jvm.config.JvmModulePathRoot
|
||||
import org.jetbrains.kotlin.cli.jvm.config.addJavaSourceRoot
|
||||
import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots
|
||||
import org.jetbrains.kotlin.cli.jvm.modules.CoreJrtFileSystem
|
||||
import org.jetbrains.kotlin.cli.jvm.plugins.PluginCliParser
|
||||
import org.jetbrains.kotlin.cli.jvm.repl.ReplFromTerminal
|
||||
@@ -45,22 +44,14 @@ import org.jetbrains.kotlin.javac.JavacWrapper
|
||||
import org.jetbrains.kotlin.load.java.JavaClassesTracker
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCompilationComponents
|
||||
import org.jetbrains.kotlin.script.KotlinScriptDefinitionFromAnnotatedTemplate
|
||||
import org.jetbrains.kotlin.script.ScriptDefinitionProvider
|
||||
import org.jetbrains.kotlin.script.StandardScriptDefinition
|
||||
import org.jetbrains.kotlin.script.experimental.KotlinScriptDefinitionAdapterFromNewAPI
|
||||
import org.jetbrains.kotlin.util.PerformanceCounter
|
||||
import org.jetbrains.kotlin.utils.KotlinPaths
|
||||
import org.jetbrains.kotlin.utils.PathUtil
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.lang.management.ManagementFactory
|
||||
import java.net.URLClassLoader
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.jar.JarFile
|
||||
import kotlin.script.experimental.definitions.ScriptDefinitionFromAnnotatedBaseClass
|
||||
|
||||
class K2JVMCompiler : CLICompiler<K2JVMCompilerArguments>() {
|
||||
override fun doExecute(
|
||||
@@ -77,7 +68,7 @@ class K2JVMCompiler : CLICompiler<K2JVMCompilerArguments>() {
|
||||
if (it != OK) return it
|
||||
}
|
||||
|
||||
val pluginLoadResult = PluginCliParser.loadPluginsSafe(arguments, configuration)
|
||||
val pluginLoadResult = loadPlugins(paths, arguments, configuration)
|
||||
if (pluginLoadResult != ExitCode.OK) return pluginLoadResult
|
||||
|
||||
if (!arguments.script && arguments.buildFile == null) {
|
||||
@@ -153,7 +144,7 @@ class K2JVMCompiler : CLICompiler<K2JVMCompilerArguments>() {
|
||||
|
||||
KotlinToJVMBytecodeCompiler.configureSourceRoots(configuration, moduleChunk.modules, buildFile)
|
||||
|
||||
val environment = createCoreEnvironment(rootDisposable, configuration, arguments, messageCollector)
|
||||
val environment = createCoreEnvironment(rootDisposable, configuration, messageCollector)
|
||||
?: return COMPILATION_ERROR
|
||||
|
||||
registerJavacIfNeeded(environment, arguments).let {
|
||||
@@ -167,7 +158,7 @@ class K2JVMCompiler : CLICompiler<K2JVMCompilerArguments>() {
|
||||
|
||||
configuration.put(JVMConfigurationKeys.RETAIN_OUTPUT_IN_MEMORY, true)
|
||||
|
||||
val environment = createCoreEnvironment(rootDisposable, configuration, arguments, messageCollector)
|
||||
val environment = createCoreEnvironment(rootDisposable, configuration, messageCollector)
|
||||
?: return COMPILATION_ERROR
|
||||
|
||||
val scriptDefinitionProvider = ScriptDefinitionProvider.getInstance(environment.project)
|
||||
@@ -191,7 +182,7 @@ class K2JVMCompiler : CLICompiler<K2JVMCompilerArguments>() {
|
||||
}
|
||||
}
|
||||
|
||||
val environment = createCoreEnvironment(rootDisposable, configuration, arguments, messageCollector)
|
||||
val environment = createCoreEnvironment(rootDisposable, configuration, messageCollector)
|
||||
?: return COMPILATION_ERROR
|
||||
|
||||
registerJavacIfNeeded(environment, arguments).let {
|
||||
@@ -229,6 +220,39 @@ class K2JVMCompiler : CLICompiler<K2JVMCompilerArguments>() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadPlugins(paths: KotlinPaths?, arguments: K2JVMCompilerArguments, configuration: CompilerConfiguration): ExitCode {
|
||||
val pluginClasspaths = arguments.pluginClasspaths?.toMutableList() ?: ArrayList()
|
||||
val pluginOptions = arguments.pluginOptions?.toMutableList() ?: ArrayList()
|
||||
val isEmbeddable =
|
||||
try {
|
||||
this::class.java.classLoader.loadClass("com.intellij.mock.MockProject") == null
|
||||
} catch (_: ClassNotFoundException) {
|
||||
true
|
||||
} catch (_: NoClassDefFoundError) {
|
||||
true
|
||||
}
|
||||
|
||||
if (!isEmbeddable && pluginClasspaths.none { File(it).name == PathUtil.KOTLIN_SCRIPTING_COMPILER_PLUGIN_JAR }) {
|
||||
// if scripting plugin is not enabled explicitly (probably from another path) try to enable it implicitly
|
||||
val libPath = paths?.libPath ?: File(".")
|
||||
val pluginJar = File(libPath, PathUtil.KOTLIN_SCRIPTING_COMPILER_PLUGIN_JAR)
|
||||
val scriptingJar = File(libPath, PathUtil.KOTLIN_SCRIPTING_COMMON_JAR)
|
||||
val scriptingJvmJar = File(libPath, PathUtil.KOTLIN_SCRIPTING_JVM_JAR)
|
||||
if (pluginJar.exists() && scriptingJar.exists() && scriptingJvmJar.exists()) {
|
||||
pluginClasspaths.addAll(listOf(pluginJar.canonicalPath, scriptingJar.canonicalPath, scriptingJvmJar.canonicalPath))
|
||||
if (arguments.scriptTemplates?.isNotEmpty() == true) {
|
||||
pluginOptions.add("-P")
|
||||
pluginOptions.add("plugin:kotlin.scripting:script-templates=${arguments.scriptTemplates!!.joinToString(",")}")
|
||||
}
|
||||
if (arguments.scriptResolverEnvironment?.isNotEmpty() == true) {
|
||||
pluginOptions.add("-P")
|
||||
pluginOptions.add("plugin:kotlin.scripting:script-resolver-environment=${arguments.scriptResolverEnvironment!!.joinToString(",")}")
|
||||
}
|
||||
}
|
||||
}
|
||||
return PluginCliParser.loadPluginsSafe(pluginClasspaths, pluginOptions, configuration)
|
||||
}
|
||||
|
||||
private fun registerJavacIfNeeded(
|
||||
environment: KotlinCoreEnvironment,
|
||||
arguments: K2JVMCompilerArguments
|
||||
@@ -257,17 +281,8 @@ class K2JVMCompiler : CLICompiler<K2JVMCompilerArguments>() {
|
||||
private fun createCoreEnvironment(
|
||||
rootDisposable: Disposable,
|
||||
configuration: CompilerConfiguration,
|
||||
arguments: K2JVMCompilerArguments,
|
||||
messageCollector: MessageCollector
|
||||
): KotlinCoreEnvironment? {
|
||||
val scriptResolverEnv = createScriptResolverEnvironment(arguments, messageCollector) ?: return null
|
||||
val templatesFromClasspath = discoverScriptTemplatesInClasspath(configuration, messageCollector)
|
||||
configureScriptDefinitions(
|
||||
arguments.scriptTemplates.orEmpty().asIterable() + templatesFromClasspath,
|
||||
configuration,
|
||||
messageCollector,
|
||||
scriptResolverEnv
|
||||
)
|
||||
if (messageCollector.hasErrors()) return null
|
||||
|
||||
val environment = KotlinCoreEnvironment.createForProduction(rootDisposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
|
||||
@@ -278,55 +293,7 @@ class K2JVMCompiler : CLICompiler<K2JVMCompilerArguments>() {
|
||||
initStartNanos = 0L
|
||||
}
|
||||
|
||||
if (!messageCollector.hasErrors()) {
|
||||
scriptResolverEnv.put("projectRoot", environment.project.run { basePath ?: baseDir?.canonicalPath }?.let(::File))
|
||||
return environment
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun discoverScriptTemplatesInClasspath(configuration: CompilerConfiguration, messageCollector: MessageCollector): Iterable<String> {
|
||||
val templates = arrayListOf<String>()
|
||||
val templatesPath = "META-INF/kotlin/script/templates/"
|
||||
for (dep in configuration.jvmClasspathRoots) {
|
||||
when {
|
||||
dep.isFile ->
|
||||
try {
|
||||
with(JarFile(dep)) {
|
||||
for (template in entries()) {
|
||||
if (!template.isDirectory && template.name.startsWith(templatesPath)) {
|
||||
val templateClassName = template.name.removePrefix(templatesPath)
|
||||
templates.add(templateClassName)
|
||||
messageCollector.report(
|
||||
CompilerMessageSeverity.LOGGING,
|
||||
"Configure scripting: Added template $templateClassName from $dep"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
messageCollector.report(
|
||||
CompilerMessageSeverity.WARNING,
|
||||
"Configure scripting: unable to process classpath entry $dep: $e"
|
||||
)
|
||||
}
|
||||
dep.isDirectory -> {
|
||||
val dir = File(dep, templatesPath)
|
||||
if (dir.isDirectory) {
|
||||
dir.listFiles().forEach {
|
||||
templates.add(it.name)
|
||||
messageCollector.report(
|
||||
CompilerMessageSeverity.LOGGING,
|
||||
"Configure scripting: Added template ${it.name} from $dep"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> messageCollector.report(CompilerMessageSeverity.WARNING, "Configure scripting: Unknown classpath entry $dep")
|
||||
}
|
||||
}
|
||||
return templates
|
||||
return if (messageCollector.hasErrors()) null else environment
|
||||
}
|
||||
|
||||
override fun setupPlatformSpecificArgumentsAndServices(
|
||||
@@ -506,73 +473,6 @@ class K2JVMCompiler : CLICompiler<K2JVMCompilerArguments>() {
|
||||
|
||||
return OK
|
||||
}
|
||||
|
||||
fun configureScriptDefinitions(
|
||||
scriptTemplates: List<String>,
|
||||
configuration: CompilerConfiguration,
|
||||
messageCollector: MessageCollector,
|
||||
scriptResolverEnv: HashMap<String, Any?>
|
||||
) {
|
||||
val classpath = configuration.jvmClasspathRoots
|
||||
// TODO: consider using escaping to allow kotlin escaped names in class names
|
||||
if (scriptTemplates.isNotEmpty()) {
|
||||
val classloader =
|
||||
URLClassLoader(classpath.map { it.toURI().toURL() }.toTypedArray(), Thread.currentThread().contextClassLoader)
|
||||
var hasErrors = false
|
||||
for (template in scriptTemplates) {
|
||||
try {
|
||||
val cls = classloader.loadClass(template)
|
||||
val def =
|
||||
if (cls.annotations.firstIsInstanceOrNull<kotlin.script.experimental.annotations.KotlinScript>() != null) {
|
||||
KotlinScriptDefinitionAdapterFromNewAPI(ScriptDefinitionFromAnnotatedBaseClass(cls.kotlin))
|
||||
} else {
|
||||
KotlinScriptDefinitionFromAnnotatedTemplate(cls.kotlin, scriptResolverEnv)
|
||||
}
|
||||
configuration.add(JVMConfigurationKeys.SCRIPT_DEFINITIONS, def)
|
||||
messageCollector.report(
|
||||
INFO,
|
||||
"Added script definition $template to configuration: name = ${def.name}, " +
|
||||
"resolver = ${def.dependencyResolver.javaClass.name}"
|
||||
)
|
||||
} catch (ex: ClassNotFoundException) {
|
||||
messageCollector.report(ERROR, "Cannot find script definition template class $template")
|
||||
hasErrors = true
|
||||
} catch (ex: Exception) {
|
||||
messageCollector.report(ERROR, "Error processing script definition template $template: ${ex.message}")
|
||||
hasErrors = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (hasErrors) {
|
||||
messageCollector.report(LOGGING, "(Classpath used for templates loading: $classpath)")
|
||||
return
|
||||
}
|
||||
}
|
||||
configuration.add(JVMConfigurationKeys.SCRIPT_DEFINITIONS, StandardScriptDefinition)
|
||||
}
|
||||
|
||||
fun createScriptResolverEnvironment(arguments: K2JVMCompilerArguments, messageCollector: MessageCollector): HashMap<String, Any?>? {
|
||||
val scriptResolverEnv = hashMapOf<String, Any?>()
|
||||
// parses key/value pairs in the form <key>=<value>, where
|
||||
// <key> - is a single word (\w+ pattern)
|
||||
// <value> - optionally quoted string with allowed escaped chars (only double-quote and backslash chars are supported)
|
||||
// TODO: implement generic unescaping
|
||||
val envParseRe = """(\w+)=(?:"([^"\\]*(\\.[^"\\]*)*)"|([^\s]*))""".toRegex()
|
||||
val unescapeRe = """\\(["\\])""".toRegex()
|
||||
if (arguments.scriptResolverEnvironment != null) {
|
||||
for (envParam in arguments.scriptResolverEnvironment!!) {
|
||||
val match = envParseRe.matchEntire(envParam)
|
||||
if (match == null || match.groupValues.size < 4 || match.groupValues[1].isBlank()) {
|
||||
messageCollector.report(ERROR, "Unable to parse script-resolver-environment argument $envParam")
|
||||
return null
|
||||
}
|
||||
scriptResolverEnv.put(
|
||||
match.groupValues[1],
|
||||
match.groupValues.drop(2).firstOrNull { it.isNotEmpty() }?.let { unescapeRe.replace(it, "\$1") })
|
||||
}
|
||||
}
|
||||
return scriptResolverEnv
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -88,6 +88,7 @@ import org.jetbrains.kotlin.codegen.extensions.ClassBuilderInterceptorExtension
|
||||
import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension
|
||||
import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
|
||||
import org.jetbrains.kotlin.config.*
|
||||
import org.jetbrains.kotlin.extensions.CompilerConfigurationExtension
|
||||
import org.jetbrains.kotlin.extensions.DeclarationAttributeAltererExtension
|
||||
import org.jetbrains.kotlin.extensions.PreprocessedVirtualFileFactoryExtension
|
||||
import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor
|
||||
@@ -178,6 +179,7 @@ class KotlinCoreEnvironment private constructor(
|
||||
DeclarationAttributeAltererExtension.registerExtensionPoint(project)
|
||||
PreprocessedVirtualFileFactoryExtension.registerExtensionPoint(project)
|
||||
JsSyntheticTranslateExtension.registerExtensionPoint(project)
|
||||
CompilerConfigurationExtension.registerExtensionPoint(project)
|
||||
|
||||
for (registrar in configuration.getList(ComponentRegistrar.PLUGIN_COMPONENT_REGISTRARS)) {
|
||||
try {
|
||||
@@ -195,12 +197,19 @@ class KotlinCoreEnvironment private constructor(
|
||||
val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
|
||||
registerProjectServices(projectEnvironment, messageCollector)
|
||||
|
||||
CompilerConfigurationExtension.getInstances(project).forEach {
|
||||
it.updateConfiguration(configuration)
|
||||
}
|
||||
|
||||
sourceFiles += CompileEnvironmentUtil.getKtFiles(project, getSourceRootsCheckingForDuplicates(), this.configuration, {
|
||||
message ->
|
||||
report(ERROR, message)
|
||||
})
|
||||
sourceFiles.sortBy { it.virtualFile.path }
|
||||
|
||||
// We should always support at least the standard script definition
|
||||
configuration.add(JVMConfigurationKeys.SCRIPT_DEFINITIONS, StandardScriptDefinition)
|
||||
|
||||
val scriptDefinitionProvider = ScriptDefinitionProvider.getInstance(project) as? CliScriptDefinitionProvider
|
||||
if (scriptDefinitionProvider != null) {
|
||||
scriptDefinitionProvider.setScriptDefinitions(
|
||||
|
||||
@@ -32,11 +32,15 @@ import java.util.*
|
||||
object PluginCliParser {
|
||||
|
||||
@JvmStatic
|
||||
fun loadPluginsSafe(arguments: CommonCompilerArguments, configuration: CompilerConfiguration): ExitCode {
|
||||
fun loadPluginsSafe(pluginClasspaths: Array<String>?, pluginOptions: Array<String>?, configuration: CompilerConfiguration): ExitCode =
|
||||
loadPluginsSafe(pluginClasspaths?.asIterable(), pluginOptions?.asIterable(), configuration)
|
||||
|
||||
@JvmStatic
|
||||
fun loadPluginsSafe(pluginClasspaths: Iterable<String>?, pluginOptions: Iterable<String>?, configuration: CompilerConfiguration): ExitCode {
|
||||
val messageCollector = configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
|
||||
|
||||
try {
|
||||
PluginCliParser.loadPlugins(arguments, configuration)
|
||||
PluginCliParser.loadPlugins(pluginClasspaths, pluginOptions, configuration)
|
||||
}
|
||||
catch (e: PluginCliOptionProcessingException) {
|
||||
val message = e.message + "\n\n" + cliPluginUsageString(e.pluginId, e.options)
|
||||
@@ -55,9 +59,9 @@ object PluginCliParser {
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun loadPlugins(arguments: CommonCompilerArguments, configuration: CompilerConfiguration) {
|
||||
fun loadPlugins(pluginClasspaths: Iterable<String>?, pluginOptions: Iterable<String>?, configuration: CompilerConfiguration) {
|
||||
val classLoader = PluginURLClassLoader(
|
||||
arguments.pluginClasspaths
|
||||
pluginClasspaths
|
||||
?.map { File(it).toURI().toURL() }
|
||||
?.toTypedArray()
|
||||
?: arrayOf<URL>(),
|
||||
@@ -68,15 +72,15 @@ object PluginCliParser {
|
||||
componentRegistrars.addAll(BundledCompilerPlugins.componentRegistrars)
|
||||
configuration.addAll(ComponentRegistrar.PLUGIN_COMPONENT_REGISTRARS, componentRegistrars)
|
||||
|
||||
processPluginOptions(arguments, configuration, classLoader)
|
||||
processPluginOptions(pluginOptions, configuration, classLoader)
|
||||
}
|
||||
|
||||
private fun processPluginOptions(
|
||||
arguments: CommonCompilerArguments,
|
||||
pluginOptions: Iterable<String>?,
|
||||
configuration: CompilerConfiguration,
|
||||
classLoader: ClassLoader
|
||||
) {
|
||||
val optionValuesByPlugin = arguments.pluginOptions?.map(::parsePluginOption)?.groupBy {
|
||||
val optionValuesByPlugin = pluginOptions?.map(::parsePluginOption)?.groupBy {
|
||||
if (it == null) throw CliOptionProcessingException("Wrong plugin option format: $it, should be ${CommonCompilerArguments.PLUGIN_OPTION_FORMAT}")
|
||||
it.pluginId
|
||||
} ?: mapOf()
|
||||
|
||||
@@ -54,7 +54,7 @@ class K2MetadataCompiler : CLICompiler<K2MetadataCompilerArguments>() {
|
||||
): ExitCode {
|
||||
val collector = configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
|
||||
|
||||
val pluginLoadResult = PluginCliParser.loadPluginsSafe(arguments, configuration)
|
||||
val pluginLoadResult = PluginCliParser.loadPluginsSafe(arguments.pluginClasspaths, arguments.pluginOptions, configuration)
|
||||
if (pluginLoadResult != ExitCode.OK) return pluginLoadResult
|
||||
|
||||
for (arg in arguments.freeArgs) {
|
||||
|
||||
Reference in New Issue
Block a user