Move scripting configuration into compiler plugin
This commit is contained in:
+1
@@ -0,0 +1 @@
|
||||
org.jetbrains.kotlin.scripting.compiler.plugin.ScriptingCommandLineProcessor
|
||||
+1
@@ -0,0 +1 @@
|
||||
org.jetbrains.kotlin.scripting.compiler.plugin.ScriptingCompilerConfigurationComponentRegistrar
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.scripting.compiler.plugin
|
||||
|
||||
import com.intellij.openapi.fileTypes.LanguageFileType
|
||||
import kotlinx.coroutines.experimental.runBlocking
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.name.NameUtils
|
||||
import org.jetbrains.kotlin.psi.KtScript
|
||||
import org.jetbrains.kotlin.script.KotlinScriptDefinition
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.script.experimental.api.ScriptCompileConfigurationParams
|
||||
import kotlin.script.experimental.api.ScriptDefinition
|
||||
import kotlin.script.experimental.api.getOrNull
|
||||
import kotlin.script.experimental.api.resultOrNull
|
||||
import kotlin.script.experimental.dependencies.DependenciesResolver
|
||||
import kotlin.script.experimental.jvm.impl.BridgeDependenciesResolver
|
||||
|
||||
class KotlinScriptDefinitionAdapterFromNewAPI(val scriptDefinition: ScriptDefinition) : KotlinScriptDefinition(scriptDefinition.baseClass) {
|
||||
|
||||
override val name: String get() = scriptDefinition.selector.name
|
||||
|
||||
// TODO: consider creating separate type (subtype? for kotlin scripts)
|
||||
override val fileType: LanguageFileType = KotlinFileType.INSTANCE
|
||||
|
||||
override val annotationsForSamWithReceivers: List<String>
|
||||
get() = emptyList()
|
||||
|
||||
override fun isScript(fileName: String): Boolean =
|
||||
fileName.endsWith("." + scriptDefinition.selector.fileExtension)
|
||||
|
||||
override fun getScriptName(script: KtScript): Name {
|
||||
val fileBasedName = NameUtils.getScriptNameForFile(script.containingKtFile.name)
|
||||
return Name.identifier(scriptDefinition.selector.makeScriptName(fileBasedName.identifier))
|
||||
}
|
||||
|
||||
override val dependencyResolver: DependenciesResolver by lazy {
|
||||
BridgeDependenciesResolver(scriptDefinition.configurator)
|
||||
}
|
||||
|
||||
override val acceptedAnnotations: List<KClass<out Annotation>> by lazy {
|
||||
runBlocking {
|
||||
scriptDefinition.configurator.baseConfiguration(null)
|
||||
}.resultOrNull()?.getOrNull(ScriptCompileConfigurationParams.updateConfigurationOnAnnotations)?.toList()
|
||||
?: emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.scripting.compiler.plugin
|
||||
|
||||
import java.io.File
|
||||
import org.jetbrains.kotlin.compiler.plugin.CliOption
|
||||
import org.jetbrains.kotlin.compiler.plugin.CliOptionProcessingException
|
||||
import org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.config.CompilerConfigurationKey
|
||||
|
||||
object ScriptingConfigurationKeys {
|
||||
val SCRIPT_DEFINITIONS: CompilerConfigurationKey<List<String>> =
|
||||
CompilerConfigurationKey.create("Script definition classes")
|
||||
|
||||
val SCRIPT_DEFINITIONS_CLASSPATH: CompilerConfigurationKey<List<File>> =
|
||||
CompilerConfigurationKey.create("Additional classpath for the script definitions")
|
||||
|
||||
val DISABLE_SCRIPT_DEFINITIONS_FROM_CLSSPATH_OPTION: CompilerConfigurationKey<Boolean> =
|
||||
CompilerConfigurationKey.create("Do not extract script definitions from the compilation classpath")
|
||||
|
||||
val LEGACY_SCRIPT_RESOLVER_ENVIRONMENT_OPTION: CompilerConfigurationKey<MutableMap<String, Any?>> =
|
||||
CompilerConfigurationKey.create("Script resolver environment")
|
||||
}
|
||||
|
||||
class ScriptingCommandLineProcessor : CommandLineProcessor {
|
||||
companion object {
|
||||
val SCRIPT_DEFINITIONS_OPTION = CliOption(
|
||||
"script-definitions", "<fully qualified class name[,]>", "Script definition classes",
|
||||
required = false, allowMultipleOccurrences = true
|
||||
)
|
||||
val SCRIPT_DEFINITIONS_CLASSPATH_OPTION = CliOption(
|
||||
"script-definitions-classpath", "<classpath entry[:]>", "Additional classpath for the script definitions",
|
||||
required = false, allowMultipleOccurrences = true
|
||||
)
|
||||
val DISABLE_SCRIPT_DEFINITIONS_FROM_CLSSPATH_OPTION = CliOption(
|
||||
"disable-script-definitions-from-classpath", "true/false", "Do not extract script definitions from the compilation classpath",
|
||||
required = false, allowMultipleOccurrences = false
|
||||
)
|
||||
val LEGACY_SCRIPT_TEMPLATES_OPTION = CliOption(
|
||||
"script-templates", "<fully qualified class name[,]>", "Script definition template classes",
|
||||
required = false, allowMultipleOccurrences = true
|
||||
)
|
||||
val LEGACY_SCRIPT_RESOLVER_ENVIRONMENT_OPTION = CliOption(
|
||||
"script-resolver-environment", "<key=value[,]>",
|
||||
"Script resolver environment in key-value pairs (the value could be quoted and escaped)",
|
||||
required = false, allowMultipleOccurrences = true
|
||||
)
|
||||
|
||||
val PLUGIN_ID = "kotlin.scripting"
|
||||
}
|
||||
|
||||
override val pluginId = PLUGIN_ID
|
||||
override val pluginOptions =
|
||||
listOf(
|
||||
SCRIPT_DEFINITIONS_OPTION,
|
||||
SCRIPT_DEFINITIONS_CLASSPATH_OPTION,
|
||||
DISABLE_SCRIPT_DEFINITIONS_FROM_CLSSPATH_OPTION,
|
||||
LEGACY_SCRIPT_TEMPLATES_OPTION,
|
||||
LEGACY_SCRIPT_RESOLVER_ENVIRONMENT_OPTION
|
||||
)
|
||||
|
||||
override fun processOption(option: CliOption, value: String, configuration: CompilerConfiguration) = when (option) {
|
||||
SCRIPT_DEFINITIONS_OPTION, LEGACY_SCRIPT_TEMPLATES_OPTION -> {
|
||||
val currentDefs = configuration.getList(ScriptingConfigurationKeys.SCRIPT_DEFINITIONS).toMutableList()
|
||||
currentDefs.addAll(value.split(','))
|
||||
configuration.put(ScriptingConfigurationKeys.SCRIPT_DEFINITIONS, currentDefs)
|
||||
}
|
||||
SCRIPT_DEFINITIONS_CLASSPATH_OPTION -> {
|
||||
val currentCP = configuration.getList(ScriptingConfigurationKeys.SCRIPT_DEFINITIONS_CLASSPATH).toMutableList()
|
||||
currentCP.addAll(value.split(File.pathSeparatorChar).map(::File))
|
||||
configuration.put(ScriptingConfigurationKeys.SCRIPT_DEFINITIONS_CLASSPATH, currentCP)
|
||||
}
|
||||
DISABLE_SCRIPT_DEFINITIONS_FROM_CLSSPATH_OPTION -> {
|
||||
configuration.put(
|
||||
ScriptingConfigurationKeys.DISABLE_SCRIPT_DEFINITIONS_FROM_CLSSPATH_OPTION,
|
||||
value.takeUnless { it.isBlank() }?.toBoolean() ?: true
|
||||
)
|
||||
}
|
||||
LEGACY_SCRIPT_RESOLVER_ENVIRONMENT_OPTION -> {
|
||||
val currentEnv = configuration.getMap(ScriptingConfigurationKeys.LEGACY_SCRIPT_RESOLVER_ENVIRONMENT_OPTION).toMutableMap()
|
||||
// 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()
|
||||
for (envParam in value.split(',')) {
|
||||
val match = envParseRe.matchEntire(envParam)
|
||||
if (match == null || match.groupValues.size < 4 || match.groupValues[1].isBlank()) {
|
||||
throw CliOptionProcessingException("Unable to parse script-resolver-environment argument $envParam")
|
||||
}
|
||||
currentEnv[match.groupValues[1]] =
|
||||
match.groupValues.drop(2).firstOrNull { it.isNotEmpty() }?.let { unescapeRe.replace(it, "\$1") }
|
||||
}
|
||||
configuration.put(ScriptingConfigurationKeys.LEGACY_SCRIPT_RESOLVER_ENVIRONMENT_OPTION, currentEnv)
|
||||
}
|
||||
else -> throw CliOptionProcessingException("Unknown option: ${option.name}")
|
||||
}
|
||||
}
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.scripting.compiler.plugin
|
||||
|
||||
import com.intellij.mock.MockProject
|
||||
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
|
||||
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
||||
import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots
|
||||
import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.config.JVMConfigurationKeys
|
||||
import org.jetbrains.kotlin.extensions.CompilerConfigurationExtension
|
||||
import org.jetbrains.kotlin.script.KotlinScriptDefinitionFromAnnotatedTemplate
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.net.URLClassLoader
|
||||
import java.util.jar.JarFile
|
||||
import kotlin.script.experimental.definitions.ScriptDefinitionFromAnnotatedBaseClass
|
||||
|
||||
class ScriptingCompilerConfigurationExtension(val project: MockProject) : CompilerConfigurationExtension {
|
||||
|
||||
override fun updateConfiguration(configuration: CompilerConfiguration) {
|
||||
|
||||
val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY) ?: MessageCollector.NONE
|
||||
val explicitScriptDefinitions = configuration.getList(ScriptingConfigurationKeys.SCRIPT_DEFINITIONS)
|
||||
|
||||
val scriptDefinitions =
|
||||
if (configuration.getBoolean(ScriptingConfigurationKeys.DISABLE_SCRIPT_DEFINITIONS_FROM_CLSSPATH_OPTION))
|
||||
explicitScriptDefinitions
|
||||
else
|
||||
explicitScriptDefinitions + discoverScriptTemplatesInClasspath(configuration, messageCollector)
|
||||
|
||||
if (scriptDefinitions.isNotEmpty()) {
|
||||
val projectRoot = project.run { basePath ?: baseDir?.canonicalPath }?.let(::File)
|
||||
if (projectRoot != null) {
|
||||
configuration.put(
|
||||
ScriptingConfigurationKeys.LEGACY_SCRIPT_RESOLVER_ENVIRONMENT_OPTION,
|
||||
"projectRoot",
|
||||
projectRoot
|
||||
)
|
||||
}
|
||||
configureScriptDefinitions(
|
||||
scriptDefinitions,
|
||||
configuration,
|
||||
messageCollector,
|
||||
configuration.getMap(ScriptingConfigurationKeys.LEGACY_SCRIPT_RESOLVER_ENVIRONMENT_OPTION)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ScriptingCompilerConfigurationComponentRegistrar : ComponentRegistrar {
|
||||
override fun registerProjectComponents(project: MockProject, configuration: CompilerConfiguration) {
|
||||
CompilerConfigurationExtension.registerExtension(project, ScriptingCompilerConfigurationExtension(project))
|
||||
}
|
||||
}
|
||||
|
||||
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 -> {
|
||||
// this is the compiler behaviour, so the same logic implemented here
|
||||
if (dep.extension == "jar") {
|
||||
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
|
||||
}
|
||||
|
||||
fun configureScriptDefinitions(
|
||||
scriptTemplates: List<String>,
|
||||
configuration: CompilerConfiguration,
|
||||
messageCollector: MessageCollector,
|
||||
scriptResolverEnv: Map<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(
|
||||
CompilerMessageSeverity.INFO,
|
||||
"Added script definition $template to configuration: name = ${def.name}, " +
|
||||
"resolver = ${def.dependencyResolver.javaClass.name}"
|
||||
)
|
||||
} catch (ex: ClassNotFoundException) {
|
||||
messageCollector.report(CompilerMessageSeverity.ERROR, "Cannot find script definition template class $template")
|
||||
hasErrors = true
|
||||
} catch (ex: Exception) {
|
||||
messageCollector.report(
|
||||
CompilerMessageSeverity.ERROR,
|
||||
"Error processing script definition template $template: ${ex.message}"
|
||||
)
|
||||
hasErrors = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (hasErrors) {
|
||||
messageCollector.report(CompilerMessageSeverity.LOGGING, "(Classpath used for templates loading: $classpath)")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user