Reorganize files and folders in the scripting plugin
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
|
||||
description = "Kotlin Scripting Compiler Plugin"
|
||||
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
id("jps-compatible")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(project(":compiler:frontend"))
|
||||
compileOnly(project(":compiler:frontend.java"))
|
||||
compileOnly(project(":compiler:psi"))
|
||||
compileOnly(project(":compiler:plugin-api"))
|
||||
compileOnly(project(":compiler:cli"))
|
||||
compile(project(":kotlin-scripting-common"))
|
||||
compile(project(":kotlin-scripting-jvm"))
|
||||
compile(kotlinStdlib())
|
||||
compileOnly(project(":kotlin-reflect-api"))
|
||||
compileOnly(intellijCoreDep()) { includeJars("intellij-core") }
|
||||
compileOnly(intellijDep()) { includeJars("asm-all", rootProject = rootProject) }
|
||||
|
||||
testCompile(project(":compiler:frontend"))
|
||||
testCompile(project(":compiler:frontend.script"))
|
||||
testCompile(project(":compiler:plugin-api"))
|
||||
testCompile(project(":compiler:util"))
|
||||
testCompile(project(":compiler:cli"))
|
||||
testCompile(project(":compiler:cli-common"))
|
||||
testCompile(project(":compiler:frontend.java"))
|
||||
testCompile(projectTests(":compiler:tests-common"))
|
||||
testCompile(commonDep("junit:junit"))
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
"main" { projectDefault() }
|
||||
"test" { projectDefault() }
|
||||
}
|
||||
|
||||
tasks.withType<org.jetbrains.kotlin.gradle.dsl.KotlinCompile<*>> {
|
||||
kotlinOptions {
|
||||
languageVersion = "1.2"
|
||||
apiVersion = "1.2"
|
||||
freeCompilerArgs += "-Xskip-metadata-version-check"
|
||||
}
|
||||
}
|
||||
|
||||
publish()
|
||||
|
||||
val jar = runtimeJar {}
|
||||
sourcesJar()
|
||||
javadocJar()
|
||||
|
||||
dist()
|
||||
|
||||
ideaPlugin()
|
||||
|
||||
projectTest {
|
||||
workingDir = rootDir
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
org.jetbrains.kotlin.scripting.compiler.plugin.ScriptingCommandLineProcessor
|
||||
+1
@@ -0,0 +1 @@
|
||||
org.jetbrains.kotlin.scripting.compiler.plugin.ScriptingCompilerConfigurationComponentRegistrar
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* 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 org.jetbrains.kotlin.compiler.plugin.AbstractCliOption
|
||||
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
|
||||
import org.jetbrains.kotlin.config.JVMConfigurationKeys
|
||||
|
||||
object ScriptingConfigurationKeys {
|
||||
val DISABLE_SCRIPTING_PLUGIN_OPTION: CompilerConfigurationKey<Boolean> =
|
||||
CompilerConfigurationKey.create("Disable scripting plugin")
|
||||
|
||||
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_CLASSPATH_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 DISABLE_SCRIPTING_PLUGIN_OPTION = CliOption(
|
||||
"disable", "true/false", "Disable scripting plugin",
|
||||
required = false, allowMultipleOccurrences = false
|
||||
)
|
||||
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_STANDARD_SCRIPT_DEFINITION_OPTION = CliOption(
|
||||
"disable-standard-script", "true/false", "Disable standard kotlin script support",
|
||||
required = false, allowMultipleOccurrences = false
|
||||
)
|
||||
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(
|
||||
DISABLE_SCRIPTING_PLUGIN_OPTION,
|
||||
SCRIPT_DEFINITIONS_OPTION,
|
||||
SCRIPT_DEFINITIONS_CLASSPATH_OPTION,
|
||||
DISABLE_STANDARD_SCRIPT_DEFINITION_OPTION,
|
||||
DISABLE_SCRIPT_DEFINITIONS_FROM_CLSSPATH_OPTION,
|
||||
LEGACY_SCRIPT_TEMPLATES_OPTION,
|
||||
LEGACY_SCRIPT_RESOLVER_ENVIRONMENT_OPTION
|
||||
)
|
||||
|
||||
override fun processOption(option: AbstractCliOption, value: String, configuration: CompilerConfiguration) = when (option) {
|
||||
DISABLE_SCRIPTING_PLUGIN_OPTION -> {
|
||||
configuration.put(
|
||||
ScriptingConfigurationKeys.DISABLE_SCRIPTING_PLUGIN_OPTION,
|
||||
value.takeUnless { it.isBlank() }?.toBoolean() ?: true
|
||||
)
|
||||
}
|
||||
|
||||
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_STANDARD_SCRIPT_DEFINITION_OPTION -> {
|
||||
configuration.put(
|
||||
JVMConfigurationKeys.DISABLE_STANDARD_SCRIPT_DEFINITION,
|
||||
value.takeUnless { it.isBlank() }?.toBoolean() ?: true
|
||||
)
|
||||
}
|
||||
DISABLE_SCRIPT_DEFINITIONS_FROM_CLSSPATH_OPTION -> {
|
||||
configuration.put(
|
||||
ScriptingConfigurationKeys.DISABLE_SCRIPT_DEFINITIONS_FROM_CLASSPATH_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, comma and backslash chars are supported)
|
||||
// TODO: implement generic unescaping
|
||||
// TODO: consider switching to simple parser - current approach is too complicated already and doesn't handle quoted commas (unless they are escaped)
|
||||
val envParseRe = """(\w+)=(?:"([^"\\]*(\\.[^"\\]*)*)"|([^\s]*))""".toRegex()
|
||||
val unescapeRe = """\\(["\\,])""".toRegex()
|
||||
val splitRe = """(?:\\.|[^,\\]++)*""".toRegex()
|
||||
val splitMatches = splitRe.findAll(value)
|
||||
for (envParam in splitMatches.map { it.value }.filter { it.isNotBlank() }) {
|
||||
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.optionName}")
|
||||
}
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* 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.core.CoreFileTypeRegistry
|
||||
import com.intellij.mock.MockProject
|
||||
import com.intellij.openapi.fileTypes.FileTypeRegistry
|
||||
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
|
||||
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
||||
import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.config.JVMConfigurationKeys
|
||||
import org.jetbrains.kotlin.extensions.CompilerConfigurationExtension
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.script.ScriptDefinitionProvider
|
||||
import org.jetbrains.kotlin.script.StandardScriptDefinition
|
||||
import org.jetbrains.kotlin.scripting.compiler.plugin.definitions.ScriptDefinitionsFromClasspathDiscoverySource
|
||||
import org.jetbrains.kotlin.scripting.compiler.plugin.definitions.loadScriptTemplatesFromClasspath
|
||||
import org.jetbrains.kotlin.scripting.legacy.CliScriptDefinitionProvider
|
||||
import java.io.File
|
||||
|
||||
class ScriptingCompilerConfigurationExtension(val project: MockProject) : CompilerConfigurationExtension {
|
||||
|
||||
override fun updateConfiguration(configuration: CompilerConfiguration) {
|
||||
|
||||
if (!configuration.getBoolean(ScriptingConfigurationKeys.DISABLE_SCRIPTING_PLUGIN_OPTION)) {
|
||||
|
||||
val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY) ?: MessageCollector.NONE
|
||||
val projectRoot = project.run { basePath ?: baseDir?.canonicalPath }?.let(::File)
|
||||
if (projectRoot != null) {
|
||||
configuration.put(
|
||||
ScriptingConfigurationKeys.LEGACY_SCRIPT_RESOLVER_ENVIRONMENT_OPTION,
|
||||
"projectRoot",
|
||||
projectRoot
|
||||
)
|
||||
}
|
||||
val scriptResolverEnv = configuration.getMap(ScriptingConfigurationKeys.LEGACY_SCRIPT_RESOLVER_ENVIRONMENT_OPTION)
|
||||
|
||||
val explicitScriptDefinitions = configuration.getList(ScriptingConfigurationKeys.SCRIPT_DEFINITIONS)
|
||||
|
||||
if (explicitScriptDefinitions.isNotEmpty()) {
|
||||
configureScriptDefinitions(
|
||||
explicitScriptDefinitions,
|
||||
configuration,
|
||||
this::class.java.classLoader,
|
||||
messageCollector,
|
||||
scriptResolverEnv
|
||||
)
|
||||
}
|
||||
// If not disabled explicitly, we should always support at least the standard script definition
|
||||
if (!configuration.getBoolean(JVMConfigurationKeys.DISABLE_STANDARD_SCRIPT_DEFINITION) &&
|
||||
!configuration.getList(JVMConfigurationKeys.SCRIPT_DEFINITIONS).contains(StandardScriptDefinition)
|
||||
) {
|
||||
configuration.add(JVMConfigurationKeys.SCRIPT_DEFINITIONS, StandardScriptDefinition)
|
||||
}
|
||||
|
||||
configuration.add(
|
||||
JVMConfigurationKeys.SCRIPT_DEFINITIONS_SOURCES,
|
||||
ScriptDefinitionsFromClasspathDiscoverySource(
|
||||
configuration.jvmClasspathRoots,
|
||||
configuration.get(ScriptingConfigurationKeys.LEGACY_SCRIPT_RESOLVER_ENVIRONMENT_OPTION) ?: emptyMap(),
|
||||
messageCollector
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// If not disabled explicitly, we should always support at least the standard script definition
|
||||
if (!configuration.getBoolean(JVMConfigurationKeys.DISABLE_STANDARD_SCRIPT_DEFINITION) &&
|
||||
StandardScriptDefinition !in configuration.getList(JVMConfigurationKeys.SCRIPT_DEFINITIONS)
|
||||
) {
|
||||
configuration.add(JVMConfigurationKeys.SCRIPT_DEFINITIONS, StandardScriptDefinition)
|
||||
}
|
||||
|
||||
val scriptDefinitionProvider = ScriptDefinitionProvider.getInstance(project) as? CliScriptDefinitionProvider
|
||||
if (scriptDefinitionProvider != null) {
|
||||
scriptDefinitionProvider.setScriptDefinitionsSources(configuration.getList(JVMConfigurationKeys.SCRIPT_DEFINITIONS_SOURCES))
|
||||
scriptDefinitionProvider.setScriptDefinitions(configuration.getList(JVMConfigurationKeys.SCRIPT_DEFINITIONS))
|
||||
|
||||
// Register new file extensions
|
||||
val fileTypeRegistry = FileTypeRegistry.getInstance() as CoreFileTypeRegistry
|
||||
|
||||
scriptDefinitionProvider.getKnownFilenameExtensions().filter {
|
||||
fileTypeRegistry.getFileTypeByExtension(it) != KotlinFileType.INSTANCE
|
||||
}.forEach {
|
||||
fileTypeRegistry.registerFileType(KotlinFileType.INSTANCE, it)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fun configureScriptDefinitions(
|
||||
scriptTemplates: List<String>,
|
||||
configuration: CompilerConfiguration,
|
||||
baseClassloader: ClassLoader,
|
||||
messageCollector: MessageCollector,
|
||||
scriptResolverEnv: Map<String, Any?>
|
||||
) {
|
||||
// TODO: consider using escaping to allow kotlin escaped names in class names
|
||||
val templatesFromClasspath = loadScriptTemplatesFromClasspath(
|
||||
scriptTemplates, configuration.jvmClasspathRoots, emptyList(), baseClassloader, scriptResolverEnv, messageCollector
|
||||
)
|
||||
configuration.addAll(JVMConfigurationKeys.SCRIPT_DEFINITIONS, templatesFromClasspath.toList())
|
||||
}
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2010-2019 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 org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.resolve.extensions.SyntheticResolveExtension
|
||||
import org.jetbrains.kotlin.resolve.lazy.LazyClassContext
|
||||
import org.jetbrains.kotlin.resolve.lazy.ResolveSession
|
||||
import org.jetbrains.kotlin.resolve.lazy.declarations.PackageMemberDeclarationProvider
|
||||
import org.jetbrains.kotlin.scripting.compiler.plugin.resolve.LazyScriptDescriptor
|
||||
|
||||
class ScriptingResolveExtension : SyntheticResolveExtension {
|
||||
override fun generateSyntheticClasses(
|
||||
thisDescriptor: PackageFragmentDescriptor,
|
||||
name: Name,
|
||||
ctx: LazyClassContext,
|
||||
declarationProvider: PackageMemberDeclarationProvider,
|
||||
result: MutableSet<ClassDescriptor>
|
||||
) {
|
||||
declarationProvider.getScriptDeclarations(name).mapTo(result) {
|
||||
LazyScriptDescriptor(ctx as ResolveSession, thisDescriptor, name, it)
|
||||
}
|
||||
|
||||
super.generateSyntheticClasses(thisDescriptor, name, ctx, declarationProvider, result)
|
||||
}
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright 2010-2019 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.definitions
|
||||
|
||||
import com.intellij.openapi.fileTypes.LanguageFileType
|
||||
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.reflect.KType
|
||||
import kotlin.reflect.full.starProjectedType
|
||||
import kotlin.script.experimental.api.*
|
||||
import kotlin.script.experimental.dependencies.DependenciesResolver
|
||||
import kotlin.script.experimental.host.ScriptingHostConfiguration
|
||||
import kotlin.script.experimental.host.getScriptingClass
|
||||
import kotlin.script.experimental.jvm.impl.BridgeDependenciesResolver
|
||||
import kotlin.script.experimental.location.ScriptExpectedLocation
|
||||
import kotlin.script.experimental.util.getOrError
|
||||
|
||||
// temporary trick with passing Any as a template and overwriting it below, TODO: fix after introducing new script definitions hierarchy
|
||||
abstract class KotlinScriptDefinitionAdapterFromNewAPIBase : KotlinScriptDefinition(Any::class) {
|
||||
|
||||
abstract val scriptCompilationConfiguration: ScriptCompilationConfiguration
|
||||
|
||||
abstract val hostConfiguration: ScriptingHostConfiguration
|
||||
|
||||
open val baseClass: KClass<*> by lazy(LazyThreadSafetyMode.PUBLICATION) {
|
||||
getScriptingClass(scriptCompilationConfiguration.getOrError(ScriptCompilationConfiguration.baseClass))
|
||||
}
|
||||
|
||||
override val template: KClass<*> get() = baseClass
|
||||
|
||||
override val name: String
|
||||
get() = scriptCompilationConfiguration[ScriptCompilationConfiguration.displayName] ?: "Kotlin Script"
|
||||
|
||||
override val fileType: LanguageFileType = KotlinFileType.INSTANCE
|
||||
|
||||
override fun isScript(fileName: String): Boolean =
|
||||
fileName.endsWith(".$fileExtension")
|
||||
|
||||
override fun getScriptName(script: KtScript): Name {
|
||||
val fileBasedName = NameUtils.getScriptNameForFile(script.containingKtFile.name)
|
||||
return Name.identifier(fileBasedName.identifier.removeSuffix(".$fileExtension"))
|
||||
}
|
||||
|
||||
override val annotationsForSamWithReceivers: List<String>
|
||||
get() = emptyList()
|
||||
|
||||
override val dependencyResolver: DependenciesResolver by lazy(LazyThreadSafetyMode.PUBLICATION) {
|
||||
BridgeDependenciesResolver(scriptCompilationConfiguration)
|
||||
}
|
||||
|
||||
override val acceptedAnnotations: List<KClass<out Annotation>> by lazy(LazyThreadSafetyMode.PUBLICATION) {
|
||||
scriptCompilationConfiguration[ScriptCompilationConfiguration.refineConfigurationOnAnnotations]?.annotations
|
||||
.orEmpty()
|
||||
.map { getScriptingClass(it) as KClass<out Annotation> }
|
||||
}
|
||||
|
||||
override val implicitReceivers: List<KType> by lazy(LazyThreadSafetyMode.PUBLICATION) {
|
||||
scriptCompilationConfiguration[ScriptCompilationConfiguration.implicitReceivers]
|
||||
.orEmpty()
|
||||
.map { getScriptingClass(it).starProjectedType }
|
||||
}
|
||||
|
||||
override val providedProperties: List<Pair<String, KType>> by lazy(LazyThreadSafetyMode.PUBLICATION) {
|
||||
scriptCompilationConfiguration[ScriptCompilationConfiguration.providedProperties]
|
||||
?.map { (k, v) -> k to getScriptingClass(v).starProjectedType }.orEmpty()
|
||||
}
|
||||
|
||||
override val additionalCompilerArguments: List<String>
|
||||
get() = scriptCompilationConfiguration[ScriptCompilationConfiguration.compilerOptions]
|
||||
.orEmpty()
|
||||
|
||||
override val scriptExpectedLocations: List<ScriptExpectedLocation>
|
||||
get() = scriptCompilationConfiguration[ScriptCompilationConfiguration.ide.acceptedLocations]?.map {
|
||||
when(it) {
|
||||
ScriptAcceptedLocation.Sources -> ScriptExpectedLocation.SourcesOnly
|
||||
ScriptAcceptedLocation.Tests -> ScriptExpectedLocation.TestsOnly
|
||||
ScriptAcceptedLocation.Libraries -> ScriptExpectedLocation.Libraries
|
||||
ScriptAcceptedLocation.Project -> ScriptExpectedLocation.Project
|
||||
ScriptAcceptedLocation.Everywhere -> ScriptExpectedLocation.Everywhere
|
||||
}
|
||||
} ?: listOf(ScriptExpectedLocation.SourcesOnly, ScriptExpectedLocation.TestsOnly)
|
||||
|
||||
private val scriptingClassGetter by lazy(LazyThreadSafetyMode.PUBLICATION) {
|
||||
hostConfiguration[ScriptingHostConfiguration.getScriptingClass]
|
||||
?: throw IllegalArgumentException("Expecting 'getScriptingClass' property in the scripting environment")
|
||||
}
|
||||
|
||||
private fun getScriptingClass(type: KotlinType) =
|
||||
scriptingClassGetter(
|
||||
type,
|
||||
KotlinScriptDefinition::class, // Assuming that the KotlinScriptDefinition class is loaded in the proper classloader
|
||||
hostConfiguration
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
class KotlinScriptDefinitionAdapterFromNewAPI(
|
||||
override val scriptCompilationConfiguration: ScriptCompilationConfiguration,
|
||||
override val hostConfiguration: ScriptingHostConfiguration
|
||||
) : KotlinScriptDefinitionAdapterFromNewAPIBase() {
|
||||
|
||||
override val name: String get() = scriptCompilationConfiguration[ScriptCompilationConfiguration.displayName] ?: super.name
|
||||
|
||||
override val fileExtension: String
|
||||
get() = scriptCompilationConfiguration[ScriptCompilationConfiguration.fileExtension] ?: super.fileExtension
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2010-2019 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.definitions
|
||||
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
|
||||
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
||||
import java.io.File
|
||||
import kotlin.script.experimental.annotations.KotlinScript
|
||||
import kotlin.script.experimental.api.*
|
||||
import kotlin.script.experimental.host.ScriptingHostConfiguration
|
||||
import kotlin.script.experimental.host.configurationDependencies
|
||||
import kotlin.script.experimental.host.createCompilationConfigurationFromTemplate
|
||||
import kotlin.script.experimental.jvm.JvmDependency
|
||||
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
|
||||
|
||||
class LazyScriptDefinitionFromDiscoveredClass internal constructor(
|
||||
private val annotationsFromAsm: ArrayList<BinAnnData>,
|
||||
private val className: String,
|
||||
private val classpath: List<File>,
|
||||
private val messageCollector: MessageCollector
|
||||
) : KotlinScriptDefinitionAdapterFromNewAPIBase() {
|
||||
|
||||
constructor(
|
||||
classBytes: ByteArray,
|
||||
className: String,
|
||||
classpath: List<File>,
|
||||
messageCollector: MessageCollector
|
||||
) : this(loadAnnotationsFromClass(classBytes), className, classpath, messageCollector)
|
||||
|
||||
override val hostConfiguration: ScriptingHostConfiguration by lazy(LazyThreadSafetyMode.PUBLICATION) {
|
||||
ScriptingHostConfiguration(defaultJvmScriptingHostConfiguration) {
|
||||
configurationDependencies.append(JvmDependency(classpath))
|
||||
}
|
||||
}
|
||||
|
||||
override val scriptCompilationConfiguration: ScriptCompilationConfiguration by lazy(LazyThreadSafetyMode.PUBLICATION) {
|
||||
messageCollector.report(
|
||||
CompilerMessageSeverity.LOGGING,
|
||||
"Configure scripting: loading script definition class $className using classpath $classpath\n. ${Thread.currentThread().stackTrace}"
|
||||
)
|
||||
try {
|
||||
createCompilationConfigurationFromTemplate(
|
||||
KotlinType(className),
|
||||
hostConfiguration,
|
||||
LazyScriptDefinitionFromDiscoveredClass::class
|
||||
)
|
||||
} catch (ex: ClassNotFoundException) {
|
||||
messageCollector.report(CompilerMessageSeverity.ERROR, "Cannot find script definition class $className")
|
||||
InvalidScriptDefinition
|
||||
} catch (ex: Exception) {
|
||||
messageCollector.report(
|
||||
CompilerMessageSeverity.ERROR,
|
||||
"Error processing script definition class $className: ${ex.message}\nclasspath:\n${classpath.joinToString("\n", " ")}"
|
||||
)
|
||||
InvalidScriptDefinition
|
||||
}
|
||||
}
|
||||
|
||||
override val fileExtension: String by lazy(LazyThreadSafetyMode.PUBLICATION) {
|
||||
annotationsFromAsm.find { it.name == KotlinScript::class.simpleName }?.args
|
||||
?.find { it.name == "fileExtension" }?.value
|
||||
?: scriptCompilationConfiguration.let {
|
||||
it[ScriptCompilationConfiguration.fileExtension] ?: super.fileExtension
|
||||
}
|
||||
}
|
||||
|
||||
override val name: String by lazy(LazyThreadSafetyMode.PUBLICATION) {
|
||||
annotationsFromAsm.find { it.name == KotlinScript::class.simpleName!! }?.args?.find { it.name == "name" }?.value
|
||||
?: super.name
|
||||
}
|
||||
}
|
||||
|
||||
val InvalidScriptDefinition = ScriptCompilationConfiguration()
|
||||
+344
@@ -0,0 +1,344 @@
|
||||
/*
|
||||
* Copyright 2010-2019 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.definitions
|
||||
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
|
||||
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
||||
import org.jetbrains.kotlin.script.KotlinScriptDefinition
|
||||
import org.jetbrains.kotlin.script.KotlinScriptDefinitionFromAnnotatedTemplate
|
||||
import org.jetbrains.kotlin.script.ScriptDefinitionsSource
|
||||
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.coroutines.experimental.buildSequence
|
||||
import kotlin.script.experimental.annotations.KotlinScript
|
||||
import kotlin.script.experimental.api.KotlinType
|
||||
import kotlin.script.experimental.host.createCompilationConfigurationFromTemplate
|
||||
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
|
||||
import kotlin.script.templates.ScriptTemplateDefinition
|
||||
|
||||
internal const val SCRIPT_DEFINITION_MARKERS_PATH = "META-INF/kotlin/script/templates/"
|
||||
|
||||
class ScriptDefinitionsFromClasspathDiscoverySource(
|
||||
private val classpath: List<File>,
|
||||
private val scriptResolverEnv: Map<String, Any?>,
|
||||
private val messageCollector: MessageCollector
|
||||
) : ScriptDefinitionsSource {
|
||||
|
||||
override val definitions: Sequence<KotlinScriptDefinition> = run {
|
||||
discoverScriptTemplatesInClasspath(
|
||||
classpath,
|
||||
this::class.java.classLoader,
|
||||
scriptResolverEnv,
|
||||
messageCollector
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun discoverScriptTemplatesInClasspath(
|
||||
classpath: List<File>,
|
||||
baseClassLoader: ClassLoader,
|
||||
scriptResolverEnv: Map<String, Any?>,
|
||||
messageCollector: MessageCollector
|
||||
): Sequence<KotlinScriptDefinition> = buildSequence {
|
||||
// TODO: try to find a way to reduce classpath (and classloader) to minimal one needed to load script definition and its dependencies
|
||||
val loader = LazyClasspathWithClassLoader(baseClassLoader) { classpath }
|
||||
|
||||
// for jar files the definition class is expected in the same jar as the discovery file
|
||||
// in case of directories, the class output may come separate from the resources, so some candidates should be deffered and processed later
|
||||
val defferedDirDependencies = ArrayList<File>()
|
||||
val defferedDefinitionCandidates = ArrayList<String>()
|
||||
for (dep in classpath) {
|
||||
try {
|
||||
when {
|
||||
dep.isFile && dep.extension == "jar" -> { // checking for extension is the compiler current behaviour, so the same logic is implemented here
|
||||
JarFile(dep).use { jar ->
|
||||
if (jar.getJarEntry(SCRIPT_DEFINITION_MARKERS_PATH) != null) {
|
||||
val definitionNames = jar.entries().asSequence().mapNotNull {
|
||||
if (it.isDirectory || !it.name.startsWith(SCRIPT_DEFINITION_MARKERS_PATH)) null
|
||||
else it.name.removePrefix(SCRIPT_DEFINITION_MARKERS_PATH)
|
||||
}.toList()
|
||||
val (loadedDefinitions, notFoundClasses) =
|
||||
definitionNames.partitionLoadJarDefinitions(jar, loader, scriptResolverEnv, messageCollector)
|
||||
if (notFoundClasses.isNotEmpty()) {
|
||||
messageCollector.report(
|
||||
CompilerMessageSeverity.STRONG_WARNING,
|
||||
"Configure scripting: unable to find script definitions [${notFoundClasses.joinToString(", ")}]"
|
||||
)
|
||||
}
|
||||
loadedDefinitions.forEach {
|
||||
yield(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
dep.isDirectory -> {
|
||||
defferedDirDependencies.add(dep) // there is no way to know that the dependency is fully "used" so we add it to the list anyway
|
||||
val discoveryDir = File(dep, SCRIPT_DEFINITION_MARKERS_PATH)
|
||||
if (discoveryDir.isDirectory) {
|
||||
val (foundDefinitionClasses, notFoundDefinitions) = discoveryDir.listFiles().map { it.name }
|
||||
.partitionLoadDirDefinitions(dep, loader, scriptResolverEnv, messageCollector)
|
||||
foundDefinitionClasses.forEach {
|
||||
yield(it)
|
||||
}
|
||||
defferedDefinitionCandidates.addAll(notFoundDefinitions)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// assuming that invalid classpath entries will be reported elsewhere anyway, so do not spam user with additional warnings here
|
||||
messageCollector.report(CompilerMessageSeverity.LOGGING, "Configure scripting: Unknown classpath entry $dep")
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
messageCollector.report(
|
||||
CompilerMessageSeverity.STRONG_WARNING, "Configure scripting: unable to process classpath entry $dep: $e"
|
||||
)
|
||||
}
|
||||
}
|
||||
var remainingDefinitionCandidates: List<String> = defferedDefinitionCandidates
|
||||
for (dep in defferedDirDependencies) {
|
||||
if (remainingDefinitionCandidates.isEmpty()) break
|
||||
try {
|
||||
val (foundDefinitionClasses, notFoundDefinitions) =
|
||||
remainingDefinitionCandidates.partitionLoadDirDefinitions(dep, loader, scriptResolverEnv, messageCollector)
|
||||
foundDefinitionClasses.forEach {
|
||||
yield(it)
|
||||
}
|
||||
remainingDefinitionCandidates = notFoundDefinitions
|
||||
} catch (e: IOException) {
|
||||
messageCollector.report(
|
||||
CompilerMessageSeverity.STRONG_WARNING, "Configure scripting: unable to process classpath entry $dep: $e"
|
||||
)
|
||||
}
|
||||
}
|
||||
if (remainingDefinitionCandidates.isNotEmpty()) {
|
||||
messageCollector.report(
|
||||
CompilerMessageSeverity.STRONG_WARNING,
|
||||
"The following script definitions are not found in the classpath: [${remainingDefinitionCandidates.joinToString()}]"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun loadScriptTemplatesFromClasspath(
|
||||
scriptTemplates: List<String>,
|
||||
classpath: List<File>,
|
||||
dependenciesClasspath: List<File>,
|
||||
baseClassLoader: ClassLoader,
|
||||
scriptResolverEnv: Map<String, Any?>,
|
||||
messageCollector: MessageCollector
|
||||
): Sequence<KotlinScriptDefinition> =
|
||||
if (scriptTemplates.isEmpty()) emptySequence()
|
||||
else buildSequence {
|
||||
// trying the direct classloading from baseClassloader first, since this is the most performant variant
|
||||
val (initialLoadedDefinitions, initialNotFoundTemplates) = scriptTemplates.partitionMapNotNull {
|
||||
loadScriptDefinition(
|
||||
baseClassLoader,
|
||||
it,
|
||||
scriptResolverEnv,
|
||||
messageCollector
|
||||
)
|
||||
}
|
||||
initialLoadedDefinitions.forEach {
|
||||
yield(it)
|
||||
}
|
||||
// then searching the remaining templates in the supplied classpath
|
||||
|
||||
var remainingTemplates = initialNotFoundTemplates
|
||||
val classpathAndLoader =
|
||||
LazyClasspathWithClassLoader(baseClassLoader) { classpath + dependenciesClasspath }
|
||||
for (dep in classpath) {
|
||||
if (remainingTemplates.isEmpty()) break
|
||||
|
||||
try {
|
||||
val (loadedDefinitions, notFoundTemplates) = when {
|
||||
dep.isFile && dep.extension == "jar" -> { // checking for extension is the compiler current behaviour, so the same logic is implemented here
|
||||
JarFile(dep).use { jar ->
|
||||
remainingTemplates.partitionLoadJarDefinitions(jar, classpathAndLoader, scriptResolverEnv, messageCollector)
|
||||
}
|
||||
}
|
||||
dep.isDirectory -> {
|
||||
remainingTemplates.partitionLoadDirDefinitions(dep, classpathAndLoader, scriptResolverEnv, messageCollector)
|
||||
}
|
||||
else -> {
|
||||
// assuming that invalid classpath entries will be reported elsewhere anyway, so do not spam user with additional warnings here
|
||||
messageCollector.report(CompilerMessageSeverity.LOGGING, "Configure scripting: Unknown classpath entry $dep")
|
||||
DefinitionsLoadPartitionResult(
|
||||
listOf(),
|
||||
remainingTemplates
|
||||
)
|
||||
}
|
||||
}
|
||||
if (loadedDefinitions.isNotEmpty()) {
|
||||
loadedDefinitions.forEach {
|
||||
yield(it)
|
||||
}
|
||||
remainingTemplates = notFoundTemplates
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
messageCollector.report(
|
||||
CompilerMessageSeverity.STRONG_WARNING,
|
||||
"Configure scripting: unable to process classpath entry $dep: $e"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (remainingTemplates.isNotEmpty()) {
|
||||
messageCollector.report(
|
||||
CompilerMessageSeverity.STRONG_WARNING,
|
||||
"Configure scripting: unable to find script definition classes: ${remainingTemplates.joinToString(", ")}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private data class DefinitionsLoadPartitionResult(
|
||||
val loaded: List<KotlinScriptDefinition>,
|
||||
val notFound: List<String>
|
||||
)
|
||||
|
||||
private inline fun List<String>.partitionLoadDefinitions(
|
||||
classpathAndLoader: LazyClasspathWithClassLoader,
|
||||
scriptResolverEnv: Map<String, Any?>,
|
||||
messageCollector: MessageCollector,
|
||||
getBytes: (String) -> ByteArray?
|
||||
): DefinitionsLoadPartitionResult {
|
||||
val loaded = ArrayList<KotlinScriptDefinition>()
|
||||
val notFound = ArrayList<String>()
|
||||
for (definitionName in this) {
|
||||
val classBytes = getBytes(definitionName)
|
||||
val definition = classBytes?.let {
|
||||
loadScriptDefinition(
|
||||
it,
|
||||
definitionName,
|
||||
classpathAndLoader,
|
||||
scriptResolverEnv,
|
||||
messageCollector
|
||||
)
|
||||
}
|
||||
when {
|
||||
definition != null -> loaded.add(definition)
|
||||
classBytes != null -> {}
|
||||
else -> notFound.add(definitionName)
|
||||
}
|
||||
}
|
||||
return DefinitionsLoadPartitionResult(loaded, notFound)
|
||||
}
|
||||
|
||||
private fun List<String>.partitionLoadJarDefinitions(
|
||||
jar: JarFile,
|
||||
classpathAndLoader: LazyClasspathWithClassLoader,
|
||||
scriptResolverEnv: Map<String, Any?>,
|
||||
messageCollector: MessageCollector
|
||||
): DefinitionsLoadPartitionResult = partitionLoadDefinitions(classpathAndLoader, scriptResolverEnv, messageCollector) { definitionName ->
|
||||
jar.getJarEntry("${definitionName.replace('.', '/')}.class")?.let { jar.getInputStream(it).readBytes() }
|
||||
}
|
||||
|
||||
private fun List<String>.partitionLoadDirDefinitions(
|
||||
dir: File,
|
||||
classpathAndLoader: LazyClasspathWithClassLoader,
|
||||
scriptResolverEnv: Map<String, Any?>,
|
||||
messageCollector: MessageCollector
|
||||
): DefinitionsLoadPartitionResult = partitionLoadDefinitions(classpathAndLoader, scriptResolverEnv, messageCollector) { definitionName ->
|
||||
File(dir, "${definitionName.replace('.', '/')}.class").takeIf { it.exists() && it.isFile }?.readBytes()
|
||||
}
|
||||
|
||||
private fun loadScriptDefinition(
|
||||
templateClassBytes: ByteArray,
|
||||
templateClassName: String,
|
||||
classpathAndLoader: LazyClasspathWithClassLoader,
|
||||
scriptResolverEnv: Map<String, Any?>,
|
||||
messageCollector: MessageCollector
|
||||
): KotlinScriptDefinition? {
|
||||
val anns = loadAnnotationsFromClass(templateClassBytes)
|
||||
for (ann in anns) {
|
||||
var def: KotlinScriptDefinition? = null
|
||||
if (ann.name == KotlinScript::class.simpleName) {
|
||||
def = LazyScriptDefinitionFromDiscoveredClass(
|
||||
anns,
|
||||
templateClassName,
|
||||
classpathAndLoader.classpath,
|
||||
messageCollector
|
||||
)
|
||||
} else if (ann.name == ScriptTemplateDefinition::class.simpleName) {
|
||||
val templateClass = classpathAndLoader.classLoader.loadClass(templateClassName).kotlin
|
||||
def = KotlinScriptDefinitionFromAnnotatedTemplate(templateClass, scriptResolverEnv, classpathAndLoader.classpath)
|
||||
}
|
||||
if (def != null) {
|
||||
messageCollector.report(
|
||||
CompilerMessageSeverity.LOGGING,
|
||||
"Configure scripting: Added template $templateClassName from ${classpathAndLoader.classpath}"
|
||||
)
|
||||
return def
|
||||
}
|
||||
}
|
||||
messageCollector.report(
|
||||
CompilerMessageSeverity.STRONG_WARNING,
|
||||
"Configure scripting: $templateClassName is not marked with any known kotlin script annotation"
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
private fun loadScriptDefinition(
|
||||
classLoader: ClassLoader,
|
||||
template: String,
|
||||
scriptResolverEnv: Map<String, Any?>,
|
||||
messageCollector: MessageCollector
|
||||
): KotlinScriptDefinition? {
|
||||
try {
|
||||
val cls = classLoader.loadClass(template)
|
||||
val def =
|
||||
if (cls.annotations.firstIsInstanceOrNull<KotlinScript>() != null) {
|
||||
val environment = defaultJvmScriptingHostConfiguration
|
||||
KotlinScriptDefinitionAdapterFromNewAPI(
|
||||
createCompilationConfigurationFromTemplate(
|
||||
KotlinType(cls.kotlin),
|
||||
environment,
|
||||
KotlinScriptDefinition::class
|
||||
),
|
||||
environment
|
||||
)
|
||||
} else {
|
||||
KotlinScriptDefinitionFromAnnotatedTemplate(cls.kotlin, scriptResolverEnv)
|
||||
}
|
||||
messageCollector.report(
|
||||
CompilerMessageSeverity.INFO,
|
||||
"Added script definition $template to configuration: name = ${def.name}, " +
|
||||
"resolver = ${def.dependencyResolver.javaClass.name}"
|
||||
)
|
||||
return def
|
||||
} catch (ex: ClassNotFoundException) {
|
||||
// not found - not an error, return null
|
||||
} catch (ex: Exception) {
|
||||
// other exceptions - might be an error
|
||||
messageCollector.report(
|
||||
CompilerMessageSeverity.STRONG_WARNING,
|
||||
"Error on loading script definition $template: ${ex.message}"
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private class LazyClasspathWithClassLoader(baseClassLoader: ClassLoader, getClasspath: () -> List<File>) {
|
||||
val classpath by lazy { getClasspath() }
|
||||
val classLoader by lazy {
|
||||
URLClassLoader(classpath.map { it.toURI().toURL() }.toTypedArray(), baseClassLoader)
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <T, R> Iterable<T>.partitionMapNotNull(fn: (T) -> R?): Pair<List<R>, List<T>> {
|
||||
val mapped = ArrayList<R>()
|
||||
val failed = ArrayList<T>()
|
||||
for (v in this) {
|
||||
val r = fn(v)
|
||||
if (r != null) {
|
||||
mapped.add(r)
|
||||
} else {
|
||||
failed.add(v)
|
||||
}
|
||||
}
|
||||
return mapped to failed
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2010-2019 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.definitions
|
||||
|
||||
import org.jetbrains.org.objectweb.asm.*
|
||||
|
||||
internal class BinAnnArgData(
|
||||
val name: String?,
|
||||
val value: String
|
||||
)
|
||||
|
||||
internal class BinAnnData(
|
||||
val name: String,
|
||||
val args: ArrayList<BinAnnArgData> = arrayListOf()
|
||||
)
|
||||
|
||||
private class TemplateAnnotationVisitor(val anns: ArrayList<BinAnnData> = arrayListOf()) : AnnotationVisitor(Opcodes.API_VERSION) {
|
||||
override fun visit(name: String?, value: Any?) {
|
||||
anns.last().args.add(BinAnnArgData(name, value.toString()))
|
||||
}
|
||||
}
|
||||
|
||||
private class TemplateClassVisitor(val annVisitor: TemplateAnnotationVisitor) : ClassVisitor(Opcodes.API_VERSION) {
|
||||
override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? {
|
||||
val shortName = Type.getType(desc).internalName.substringAfterLast("/")
|
||||
if (shortName.startsWith("KotlinScript") || shortName.startsWith("ScriptTemplate")) {
|
||||
annVisitor.anns.add(BinAnnData(shortName))
|
||||
return annVisitor
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
internal fun loadAnnotationsFromClass(fileContents: ByteArray): ArrayList<BinAnnData> {
|
||||
|
||||
val visitor =
|
||||
TemplateClassVisitor(TemplateAnnotationVisitor())
|
||||
|
||||
ClassReader(fileContents).accept(visitor, ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES)
|
||||
|
||||
return visitor.annVisitor.anns
|
||||
}
|
||||
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2010-2019 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.definitions
|
||||
|
||||
import com.intellij.ide.highlighter.JavaClassFileType
|
||||
import com.intellij.openapi.fileTypes.FileTypeRegistry
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.PsiManager
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.script.KotlinScriptDefinition
|
||||
import org.jetbrains.kotlin.script.ScriptDefinitionProvider
|
||||
|
||||
fun PsiFile.scriptDefinition(): KotlinScriptDefinition? {
|
||||
// Do not use psiFile.script, see comments in findScriptDefinition
|
||||
if (this !is KtFile/* || this.script == null*/) return null
|
||||
val file = virtualFile ?: originalFile.virtualFile ?: return null
|
||||
if (file.isNonScript()) return null
|
||||
|
||||
return scriptDefinitionByFileName(project, file.name)
|
||||
}
|
||||
|
||||
fun VirtualFile.findScriptDefinition(project: Project): KotlinScriptDefinition? {
|
||||
if (isNonScript()) return null
|
||||
// Do not use psiFile.script here because this method can be called during indexes access
|
||||
// and accessing stubs may cause deadlock
|
||||
// TODO: measure performance effect and if necessary consider detecting indexing here or using separate logic for non-IDE operations to speed up filtering
|
||||
if ((PsiManager.getInstance(project).findFile(this) as? KtFile)/*?.script*/ == null) return null
|
||||
|
||||
return scriptDefinitionByFileName(project, name)
|
||||
}
|
||||
|
||||
fun scriptDefinitionByFileName(project: Project, fileName: String): KotlinScriptDefinition {
|
||||
val scriptDefinitionProvider = ScriptDefinitionProvider.getInstance(project) ?: return null
|
||||
?: throw IllegalStateException("Unable to get script definition: ScriptDefinitionProvider is not configured.")
|
||||
|
||||
return scriptDefinitionProvider.findScriptDefinition(fileName) ?: scriptDefinitionProvider.getDefaultScriptDefinition()
|
||||
}
|
||||
|
||||
private fun VirtualFile.isNonScript(): Boolean =
|
||||
isDirectory ||
|
||||
extension == KotlinFileType.EXTENSION ||
|
||||
extension == JavaClassFileType.INSTANCE.defaultExtension ||
|
||||
!this.isKotlinFileType()
|
||||
|
||||
private fun VirtualFile.isKotlinFileType(): Boolean {
|
||||
val typeRegistry = FileTypeRegistry.getInstance()
|
||||
return typeRegistry.getFileTypeByFile(this) == KotlinFileType.INSTANCE ||
|
||||
typeRegistry.getFileTypeByFileName(name) == KotlinFileType.INSTANCE
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2010-2019 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.compiler.plugin.ComponentRegistrar
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.extensions.CompilerConfigurationExtension
|
||||
import org.jetbrains.kotlin.resolve.extensions.SyntheticResolveExtension
|
||||
import org.jetbrains.kotlin.script.ScriptDefinitionProvider
|
||||
import org.jetbrains.kotlin.script.ScriptDependenciesProvider
|
||||
import org.jetbrains.kotlin.script.ScriptReportSink
|
||||
import org.jetbrains.kotlin.scripting.legacy.CliScriptDefinitionProvider
|
||||
import org.jetbrains.kotlin.scripting.legacy.CliScriptDependenciesProvider
|
||||
import org.jetbrains.kotlin.scripting.legacy.CliScriptReportSink
|
||||
|
||||
class ScriptingCompilerConfigurationComponentRegistrar : ComponentRegistrar {
|
||||
override fun registerProjectComponents(project: MockProject, configuration: CompilerConfiguration) {
|
||||
CompilerConfigurationExtension.registerExtension(project,
|
||||
ScriptingCompilerConfigurationExtension(
|
||||
project
|
||||
)
|
||||
)
|
||||
|
||||
val scriptDefinitionProvider = CliScriptDefinitionProvider()
|
||||
project.registerService(ScriptDefinitionProvider::class.java, scriptDefinitionProvider)
|
||||
project.registerService(
|
||||
ScriptDependenciesProvider::class.java,
|
||||
CliScriptDependenciesProvider(project)
|
||||
)
|
||||
SyntheticResolveExtension.registerExtension(project,
|
||||
ScriptingResolveExtension()
|
||||
)
|
||||
|
||||
val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
|
||||
if (messageCollector != null) {
|
||||
project.registerService(
|
||||
ScriptReportSink::class.java,
|
||||
CliScriptReportSink(messageCollector)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2010-2019 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.resolve
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.annotations.Annotations
|
||||
import org.jetbrains.kotlin.descriptors.impl.ClassConstructorDescriptorImpl
|
||||
import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.resolve.BindingTrace
|
||||
import org.jetbrains.kotlin.resolve.lazy.ResolveSession
|
||||
import org.jetbrains.kotlin.resolve.lazy.declarations.ClassMemberDeclarationProvider
|
||||
import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyClassMemberScope
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.KotlinTypeFactory
|
||||
import org.jetbrains.kotlin.types.typeUtil.asTypeProjection
|
||||
|
||||
class LazyScriptClassMemberScope(
|
||||
resolveSession: ResolveSession,
|
||||
declarationProvider: ClassMemberDeclarationProvider,
|
||||
private val scriptDescriptor: LazyScriptDescriptor,
|
||||
trace: BindingTrace
|
||||
) : LazyClassMemberScope(resolveSession, declarationProvider, scriptDescriptor, trace) {
|
||||
|
||||
private val scriptPrimaryConstructor: () -> ClassConstructorDescriptorImpl? = resolveSession.storageManager.createNullableLazyValue {
|
||||
val baseClass = scriptDescriptor.baseClassDescriptor()
|
||||
val baseConstructorDescriptor = baseClass?.unsubstitutedPrimaryConstructor
|
||||
if (baseConstructorDescriptor != null) {
|
||||
val implicitReceiversParamTypes =
|
||||
scriptDescriptor.implicitReceivers.mapIndexed { idx, receiver ->
|
||||
val name =
|
||||
if (receiver is ScriptDescriptor) "$IMPORTED_SCRIPT_PARAM_NAME_PREFIX${receiver.name}"
|
||||
else "$IMPLICIT_RECEIVER_PARAM_NAME_PREFIX$idx"
|
||||
name to receiver.defaultType
|
||||
}
|
||||
val providedPropertiesParamTypes =
|
||||
scriptDescriptor.scriptProvidedProperties.map {
|
||||
it.name.identifier to it.type
|
||||
}
|
||||
val annotations = baseConstructorDescriptor.annotations
|
||||
val constructorDescriptor = ClassConstructorDescriptorImpl.create(
|
||||
scriptDescriptor, annotations, baseConstructorDescriptor.isPrimary, scriptDescriptor.source
|
||||
)
|
||||
var paramsIndexBase = baseConstructorDescriptor.valueParameters.lastIndex + 1
|
||||
val syntheticParameters =
|
||||
(implicitReceiversParamTypes + providedPropertiesParamTypes).map { param: Pair<String, KotlinType> ->
|
||||
ValueParameterDescriptorImpl(
|
||||
constructorDescriptor,
|
||||
null,
|
||||
paramsIndexBase++,
|
||||
Annotations.EMPTY,
|
||||
Name.identifier(param.first),
|
||||
param.second,
|
||||
false, false, false, null, SourceElement.NO_SOURCE
|
||||
)
|
||||
}
|
||||
val parameters = baseConstructorDescriptor.valueParameters.map { it.copy(constructorDescriptor, it.name, it.index) } +
|
||||
syntheticParameters
|
||||
constructorDescriptor.initialize(parameters, baseConstructorDescriptor.visibility)
|
||||
constructorDescriptor.returnType = scriptDescriptor.defaultType
|
||||
constructorDescriptor
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override fun resolvePrimaryConstructor(): ClassConstructorDescriptor? {
|
||||
val constructor = scriptPrimaryConstructor()
|
||||
?: ClassConstructorDescriptorImpl.create(
|
||||
scriptDescriptor,
|
||||
Annotations.EMPTY,
|
||||
true,
|
||||
SourceElement.NO_SOURCE
|
||||
).initialize(
|
||||
emptyList(),
|
||||
Visibilities.PUBLIC
|
||||
)
|
||||
setDeferredReturnType(constructor)
|
||||
return constructor
|
||||
}
|
||||
|
||||
override fun getNonDeclaredProperties(name: Name, result: MutableSet<PropertyDescriptor>) {
|
||||
super.getNonDeclaredProperties(name, result)
|
||||
if (scriptDescriptor.resultFieldName() == name.asString()) {
|
||||
scriptDescriptor.resultValue?.let {
|
||||
result.add(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun createPropertiesFromPrimaryConstructorParameters(name: Name, result: MutableSet<PropertyDescriptor>) {
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val IMPLICIT_RECEIVER_PARAM_NAME_PREFIX = "\$\$implicitReceiver"
|
||||
const val IMPORTED_SCRIPT_PARAM_NAME_PREFIX = "\$\$importedScript"
|
||||
}
|
||||
}
|
||||
|
||||
private fun ClassDescriptor.substitute(vararg types: KotlinType): KotlinType? =
|
||||
KotlinTypeFactory.simpleType(this.defaultType, arguments = types.map { it.asTypeProjection() })
|
||||
+254
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
* Copyright 2010-2019 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.resolve
|
||||
|
||||
import com.intellij.openapi.vfs.StandardFileSystems
|
||||
import com.intellij.openapi.vfs.VirtualFileManager
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiManager
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.annotations.Annotations
|
||||
import org.jetbrains.kotlin.descriptors.annotations.FilteredAnnotations
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.diagnostics.Errors.MISSING_IMPORTED_SCRIPT_FILE
|
||||
import org.jetbrains.kotlin.diagnostics.Errors.MISSING_IMPORTED_SCRIPT_PSI
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getChildOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.module
|
||||
import org.jetbrains.kotlin.resolve.lazy.LazyClassContext
|
||||
import org.jetbrains.kotlin.resolve.lazy.ResolveSession
|
||||
import org.jetbrains.kotlin.resolve.lazy.data.KtScriptInfo
|
||||
import org.jetbrains.kotlin.resolve.lazy.declarations.ClassMemberDeclarationProvider
|
||||
import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyClassDescriptor
|
||||
import org.jetbrains.kotlin.resolve.scopes.LexicalScope
|
||||
import org.jetbrains.kotlin.resolve.scopes.LexicalScopeImpl
|
||||
import org.jetbrains.kotlin.resolve.scopes.LexicalScopeKind
|
||||
import org.jetbrains.kotlin.resolve.source.toSourceElement
|
||||
import org.jetbrains.kotlin.script.KotlinScriptDefinition
|
||||
import org.jetbrains.kotlin.script.ScriptDependenciesProvider
|
||||
import org.jetbrains.kotlin.script.ScriptPriorities
|
||||
import org.jetbrains.kotlin.scripting.compiler.plugin.definitions.scriptDefinitionByFileName
|
||||
import org.jetbrains.kotlin.types.TypeSubstitutor
|
||||
import org.jetbrains.kotlin.types.typeUtil.isNothing
|
||||
import org.jetbrains.kotlin.types.typeUtil.isUnit
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||
import java.io.File
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KType
|
||||
|
||||
|
||||
class LazyScriptDescriptor(
|
||||
val resolveSession: ResolveSession,
|
||||
containingDeclaration: DeclarationDescriptor,
|
||||
name: Name,
|
||||
internal val scriptInfo: KtScriptInfo
|
||||
) : ScriptDescriptor, LazyClassDescriptor(
|
||||
resolveSession,
|
||||
containingDeclaration,
|
||||
name,
|
||||
scriptInfo,
|
||||
/* isExternal = */ false
|
||||
) {
|
||||
init {
|
||||
resolveSession.trace.record(BindingContext.SCRIPT, scriptInfo.script, this)
|
||||
}
|
||||
|
||||
override fun getResultValue(): ReplResultPropertyDescriptor? {
|
||||
val expression = scriptInfo.script
|
||||
.getChildOfType<KtBlockExpression>()
|
||||
?.getChildrenOfType<KtScriptInitializer>()?.lastOrNull()
|
||||
?.getChildOfType<KtExpression>()
|
||||
|
||||
val type = expression?.let {
|
||||
resolveSession.trace.bindingContext.getType(it)
|
||||
}
|
||||
|
||||
return if (type != null && !type.isUnit() && !type.isNothing()) {
|
||||
resultFieldName()?.let {
|
||||
ReplResultPropertyDescriptor(
|
||||
Name.identifier(it),
|
||||
type,
|
||||
this.thisAsReceiverParameter,
|
||||
this,
|
||||
expression.toSourceElement()
|
||||
)
|
||||
}
|
||||
} else null
|
||||
}
|
||||
|
||||
fun resultFieldName(): String? {
|
||||
val scriptPriority = scriptInfo.script.getUserData(ScriptPriorities.PRIORITY_KEY)
|
||||
if (scriptPriority != null) {
|
||||
return "res$scriptPriority"
|
||||
}
|
||||
val scriptName = name.asString()
|
||||
return if (scriptName.startsWith("Line_")) {
|
||||
"res${scriptName.split("_")[1]}"
|
||||
} else "\$\$result"
|
||||
}
|
||||
|
||||
private val sourceElement = scriptInfo.script.toSourceElement()
|
||||
|
||||
override fun getSource() = sourceElement
|
||||
|
||||
private val priority: Int = ScriptPriorities.getScriptPriority(scriptInfo.script)
|
||||
|
||||
override fun getPriority() = priority
|
||||
|
||||
val scriptDefinition: () -> KotlinScriptDefinition = resolveSession.storageManager.createLazyValue {
|
||||
scriptDefinitionByFileName(resolveSession.project, scriptInfo.script.containingKtFile.name)
|
||||
}
|
||||
|
||||
override fun substitute(substitutor: TypeSubstitutor) = this
|
||||
|
||||
override fun <R, D> accept(visitor: DeclarationDescriptorVisitor<R, D>, data: D): R =
|
||||
visitor.visitScriptDescriptor(this, data)
|
||||
|
||||
override fun createMemberScope(c: LazyClassContext, declarationProvider: ClassMemberDeclarationProvider): LazyScriptClassMemberScope =
|
||||
LazyScriptClassMemberScope(
|
||||
// Must be a ResolveSession for scripts
|
||||
c as ResolveSession,
|
||||
declarationProvider,
|
||||
this,
|
||||
c.trace
|
||||
)
|
||||
|
||||
override fun getUnsubstitutedPrimaryConstructor() = super.getUnsubstitutedPrimaryConstructor()!!
|
||||
|
||||
internal val baseClassDescriptor: () -> ClassDescriptor? = resolveSession.storageManager.createNullableLazyValue {
|
||||
val template = scriptDefinition().template
|
||||
findTypeDescriptor(
|
||||
template,
|
||||
if (template.qualifiedName?.startsWith("kotlin.script.templates.standard") == true) Errors.MISSING_SCRIPT_STANDARD_TEMPLATE
|
||||
else Errors.MISSING_SCRIPT_BASE_CLASS
|
||||
)
|
||||
}
|
||||
|
||||
override fun computeSupertypes() = listOf(baseClassDescriptor()?.defaultType ?: builtIns.anyType)
|
||||
|
||||
private inner class ImportedScriptDescriptorsFinder {
|
||||
|
||||
val fileManager = VirtualFileManager.getInstance()
|
||||
val localFS = fileManager.getFileSystem(StandardFileSystems.FILE_PROTOCOL)
|
||||
val psiManager = PsiManager.getInstance(scriptInfo.script.project)
|
||||
|
||||
operator fun invoke(importedScriptFile: File): ScriptDescriptor? {
|
||||
|
||||
fun errorDescriptor(errorDiagnostic: DiagnosticFactory1<PsiElement, String>?): ScriptDescriptor? {
|
||||
reportErrorString1(errorDiagnostic, importedScriptFile.path)
|
||||
return null
|
||||
}
|
||||
|
||||
val vfile = localFS.findFileByPath(importedScriptFile.path)
|
||||
?: return errorDescriptor(MISSING_IMPORTED_SCRIPT_FILE)
|
||||
val psiFile = psiManager.findFile(vfile)
|
||||
?: return errorDescriptor(MISSING_IMPORTED_SCRIPT_PSI)
|
||||
// Note: is not an error now - if import references other valid source file, it is simply compiled along with script
|
||||
// TODO: check if this is the behavior we want to have - see #KT-28916
|
||||
val ktScript = (psiFile as? KtFile)?.declarations?.firstIsInstanceOrNull<KtScript>()
|
||||
?: return null
|
||||
return resolveSession.getScriptDescriptor(ktScript) as ScriptDescriptor
|
||||
}
|
||||
}
|
||||
|
||||
private val scriptImplicitReceivers: () -> List<ClassDescriptor> = resolveSession.storageManager.createLazyValue {
|
||||
val res = ArrayList<ClassDescriptor>()
|
||||
|
||||
val importedScriptsFiles = ScriptDependenciesProvider.getInstance(scriptInfo.script.project)
|
||||
?.getScriptDependencies(scriptInfo.script.containingKtFile)?.scripts
|
||||
if (importedScriptsFiles != null) {
|
||||
val findImportedScriptDescriptor = ImportedScriptDescriptorsFinder()
|
||||
importedScriptsFiles.mapNotNullTo(res) {
|
||||
findImportedScriptDescriptor(it)
|
||||
}
|
||||
}
|
||||
|
||||
scriptDefinition().implicitReceivers.mapNotNullTo(res) { receiver ->
|
||||
findTypeDescriptor(receiver, Errors.MISSING_SCRIPT_RECEIVER_CLASS)
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
internal fun findTypeDescriptor(kClass: KClass<*>, errorDiagnostic: DiagnosticFactory1<PsiElement, String>?): ClassDescriptor? =
|
||||
findTypeDescriptor(kClass.classId, kClass.toString(), errorDiagnostic)
|
||||
|
||||
internal fun findTypeDescriptor(type: KType, errorDiagnostic: DiagnosticFactory1<PsiElement, String>?): ClassDescriptor? =
|
||||
findTypeDescriptor(type.classId, type.toString(), errorDiagnostic)
|
||||
|
||||
internal fun findTypeDescriptor(
|
||||
classId: ClassId?, typeName: String,
|
||||
errorDiagnostic: DiagnosticFactory1<PsiElement, String>?
|
||||
): ClassDescriptor? {
|
||||
val typeDescriptor = classId?.let { module.findClassAcrossModuleDependencies(it) }
|
||||
if (typeDescriptor == null) {
|
||||
reportErrorString1(errorDiagnostic, classId?.asSingleFqName()?.toString() ?: typeName)
|
||||
}
|
||||
return typeDescriptor
|
||||
}
|
||||
|
||||
private fun reportErrorString1(errorDiagnostic: DiagnosticFactory1<PsiElement, String>?, arg: String) {
|
||||
if (errorDiagnostic != null) {
|
||||
// TODO: use PositioningStrategies to highlight some specific place in case of error, instead of treating the whole file as invalid
|
||||
resolveSession.trace.report(
|
||||
errorDiagnostic.on(
|
||||
scriptInfo.script,
|
||||
arg
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getImplicitReceivers(): List<ClassDescriptor> = scriptImplicitReceivers()
|
||||
|
||||
private val scriptProvidedProperties: () -> ScriptProvidedPropertiesDescriptor = resolveSession.storageManager.createLazyValue {
|
||||
ScriptProvidedPropertiesDescriptor(this)
|
||||
}
|
||||
|
||||
override fun getScriptProvidedProperties(): List<PropertyDescriptor> = scriptProvidedProperties().properties()
|
||||
|
||||
private val scriptOuterScope: () -> LexicalScope = resolveSession.storageManager.createLazyValue {
|
||||
var outerScope = super.getOuterScope()
|
||||
val outerScopeReceivers = implicitReceivers.let {
|
||||
if (scriptDefinition().providedProperties.isEmpty()) {
|
||||
it
|
||||
} else {
|
||||
it + ScriptProvidedPropertiesDescriptor(this)
|
||||
}
|
||||
}
|
||||
for (receiverClassDescriptor in outerScopeReceivers.asReversed()) {
|
||||
outerScope = LexicalScopeImpl(
|
||||
outerScope,
|
||||
receiverClassDescriptor,
|
||||
true,
|
||||
receiverClassDescriptor.thisAsReceiverParameter,
|
||||
LexicalScopeKind.CLASS_MEMBER_SCOPE
|
||||
)
|
||||
}
|
||||
outerScope
|
||||
}
|
||||
|
||||
override fun getOuterScope(): LexicalScope = scriptOuterScope()
|
||||
|
||||
private val scriptClassAnnotations: () -> Annotations = resolveSession.storageManager.createLazyValue {
|
||||
baseClassDescriptor()?.annotations?.let { ann ->
|
||||
FilteredAnnotations(ann) { fqname ->
|
||||
val shortName = fqname.shortName().identifier
|
||||
// TODO: consider more precise annotation filtering
|
||||
!shortName.startsWith("KotlinScript") && !shortName.startsWith("ScriptTemplate")
|
||||
}
|
||||
} ?: super.annotations
|
||||
}
|
||||
|
||||
override val annotations: Annotations
|
||||
get() = scriptClassAnnotations()
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2010-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.resolve
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.annotations.Annotations
|
||||
import org.jetbrains.kotlin.descriptors.impl.PropertyDescriptorImpl
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
|
||||
class ReplResultPropertyDescriptor(
|
||||
name: Name,
|
||||
kotlinType: KotlinType,
|
||||
receiver: ReceiverParameterDescriptor?,
|
||||
script: ScriptDescriptor,
|
||||
source: SourceElement
|
||||
) : PropertyDescriptorImpl(
|
||||
script,
|
||||
null,
|
||||
Annotations.EMPTY,
|
||||
Modality.FINAL,
|
||||
Visibilities.PUBLIC,
|
||||
false,
|
||||
name,
|
||||
CallableMemberDescriptor.Kind.SYNTHESIZED,
|
||||
source,
|
||||
/* lateInit = */ false, /* isConst = */ false, /* isExpect = */ false, /* isActual = */ false, /* isExternal = */ false,
|
||||
/* isDelegated = */ false
|
||||
) {
|
||||
init {
|
||||
setType(kotlinType, emptyList(), receiver, null)
|
||||
initialize(
|
||||
null, null
|
||||
)
|
||||
}
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2010-2019 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.resolve
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.impl.MutableClassDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.incremental.components.LookupLocation
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
|
||||
import org.jetbrains.kotlin.resolve.scopes.MemberScope
|
||||
import org.jetbrains.kotlin.resolve.scopes.MemberScopeImpl
|
||||
import org.jetbrains.kotlin.storage.LockBasedStorageManager
|
||||
import org.jetbrains.kotlin.utils.Printer
|
||||
|
||||
class ScriptProvidedPropertiesDescriptor(script: LazyScriptDescriptor) :
|
||||
MutableClassDescriptor(
|
||||
script,
|
||||
ClassKind.CLASS, false, false,
|
||||
Name.special("<synthetic script provided properties for ${script.name}>"),
|
||||
SourceElement.NO_SOURCE,
|
||||
LockBasedStorageManager.NO_LOCKS
|
||||
) {
|
||||
|
||||
init {
|
||||
modality = Modality.FINAL
|
||||
visibility = Visibilities.PUBLIC
|
||||
setTypeParameterDescriptors(emptyList())
|
||||
createTypeConstructor()
|
||||
}
|
||||
|
||||
private val memberScope: () -> ScriptProvidedPropertiesMemberScope = script.resolveSession.storageManager.createLazyValue {
|
||||
ScriptProvidedPropertiesMemberScope(
|
||||
script.name.identifier,
|
||||
properties()
|
||||
)
|
||||
}
|
||||
|
||||
override fun getUnsubstitutedMemberScope(): MemberScope = memberScope()
|
||||
|
||||
val properties: () -> List<ScriptProvidedPropertyDescriptor> = script.resolveSession.storageManager.createLazyValue {
|
||||
script.scriptDefinition().providedProperties.mapNotNull { (name, type) ->
|
||||
script.findTypeDescriptor(type, Errors.MISSING_SCRIPT_PROVIDED_PROPERTY_CLASS)?.let {
|
||||
name to it
|
||||
}
|
||||
}.map { (name, classDescriptor) ->
|
||||
ScriptProvidedPropertyDescriptor(
|
||||
Name.identifier(name),
|
||||
classDescriptor,
|
||||
thisAsReceiverParameter,
|
||||
true,
|
||||
script
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class ScriptProvidedPropertiesMemberScope(
|
||||
private val scriptId: String,
|
||||
private val providedProperties: List<PropertyDescriptor>
|
||||
) : MemberScopeImpl() {
|
||||
override fun getContributedDescriptors(
|
||||
kindFilter: DescriptorKindFilter,
|
||||
nameFilter: (Name) -> Boolean
|
||||
): Collection<DeclarationDescriptor> =
|
||||
providedProperties
|
||||
|
||||
override fun getContributedVariables(name: Name, location: LookupLocation): Collection<PropertyDescriptor> =
|
||||
providedProperties.filter { it.name == name }
|
||||
|
||||
override fun printScopeStructure(p: Printer) {
|
||||
p.println("Scope of script provided properties: $scriptId")
|
||||
}
|
||||
}
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2010-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.resolve
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.annotations.Annotations
|
||||
import org.jetbrains.kotlin.descriptors.impl.PropertyDescriptorImpl
|
||||
import org.jetbrains.kotlin.descriptors.impl.PropertyGetterDescriptorImpl
|
||||
import org.jetbrains.kotlin.descriptors.impl.PropertySetterDescriptorImpl
|
||||
import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
|
||||
class ScriptProvidedPropertyDescriptor(
|
||||
name: Name,
|
||||
typeDescriptor: ClassDescriptor,
|
||||
receiver: ReceiverParameterDescriptor?,
|
||||
isVar: Boolean,
|
||||
script: ScriptDescriptor
|
||||
) : PropertyDescriptorImpl(
|
||||
script,
|
||||
null,
|
||||
Annotations.EMPTY,
|
||||
Modality.FINAL,
|
||||
Visibilities.PRIVATE,
|
||||
isVar,
|
||||
name,
|
||||
CallableMemberDescriptor.Kind.SYNTHESIZED,
|
||||
SourceElement.NO_SOURCE,
|
||||
/* lateInit = */ false, /* isConst = */ false, /* isExpect = */ false, /* isActual = */ false, /* isExternal = */ false,
|
||||
/* isDelegated = */ false
|
||||
) {
|
||||
init {
|
||||
setType(typeDescriptor.defaultType, emptyList(), receiver, null)
|
||||
initialize(
|
||||
makePropertyGetterDescriptor(),
|
||||
if (!isVar) null else makePropertySetterDescriptor()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun PropertyDescriptorImpl.makePropertyGetterDescriptor() =
|
||||
PropertyGetterDescriptorImpl(
|
||||
this,
|
||||
Annotations.EMPTY,
|
||||
this.modality,
|
||||
this.visibility,
|
||||
/* isDefault = */
|
||||
false, /* isExternal = */
|
||||
false, /* isInline = */
|
||||
false,
|
||||
this.kind,
|
||||
null,
|
||||
SourceElement.NO_SOURCE
|
||||
).also {
|
||||
it.initialize(returnType)
|
||||
}
|
||||
|
||||
private fun PropertyDescriptorImpl.makePropertySetterDescriptor() =
|
||||
PropertySetterDescriptorImpl(
|
||||
this,
|
||||
Annotations.EMPTY,
|
||||
this.modality,
|
||||
this.visibility,
|
||||
/* isDefault = */
|
||||
false, /* isExternal = */
|
||||
false, /* isInline = */
|
||||
false,
|
||||
this.kind,
|
||||
null,
|
||||
SourceElement.NO_SOURCE
|
||||
).also {
|
||||
it.initialize(
|
||||
ValueParameterDescriptorImpl(
|
||||
this,
|
||||
null,
|
||||
0,
|
||||
Annotations.EMPTY,
|
||||
Name.special("<set-?>"),
|
||||
returnType,
|
||||
/* declaresDefaultValue = */
|
||||
false, /* isCrossinline = */
|
||||
false, /* isNoinline = */
|
||||
false,
|
||||
null,
|
||||
SourceElement.NO_SOURCE
|
||||
)
|
||||
)
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2010-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.resolve
|
||||
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KType
|
||||
|
||||
val KClass<*>.classId: ClassId
|
||||
get() = this.java.enclosingClass?.kotlin?.classId?.createNestedClassId(Name.identifier(simpleName!!))
|
||||
?: ClassId.topLevel(FqName(qualifiedName!!))
|
||||
|
||||
val KType.classId: ClassId?
|
||||
get() = classifier?.let { it as? KClass<*> }?.classId
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2010-2019 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.legacy
|
||||
|
||||
import org.jetbrains.kotlin.script.KotlinScriptDefinition
|
||||
import org.jetbrains.kotlin.script.ScriptDefinitionsSource
|
||||
import org.jetbrains.kotlin.script.StandardScriptDefinition
|
||||
import kotlin.concurrent.write
|
||||
|
||||
class CliScriptDefinitionProvider : LazyScriptDefinitionProvider() {
|
||||
private val definitionsFromSources: MutableList<Sequence<KotlinScriptDefinition>> = arrayListOf()
|
||||
private val definitions: MutableList<KotlinScriptDefinition> = arrayListOf(StandardScriptDefinition)
|
||||
|
||||
override val currentDefinitions: Sequence<KotlinScriptDefinition> =
|
||||
definitionsFromSources.asSequence().flatMap { it } + definitions.asSequence()
|
||||
|
||||
override fun getDefaultScriptDefinition(): KotlinScriptDefinition {
|
||||
return StandardScriptDefinition
|
||||
}
|
||||
|
||||
fun setScriptDefinitions(newDefinitions: List<KotlinScriptDefinition>) {
|
||||
lock.write {
|
||||
definitions.clear()
|
||||
definitions.addAll(newDefinitions)
|
||||
}
|
||||
}
|
||||
|
||||
fun setScriptDefinitionsSources(newSources: List<ScriptDefinitionsSource>) {
|
||||
lock.write {
|
||||
definitionsFromSources.clear()
|
||||
for (it in newSources) {
|
||||
definitionsFromSources.add(it.definitions.constrainOnce())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.scripting.legacy
|
||||
|
||||
import com.intellij.openapi.components.ServiceManager
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import org.jetbrains.kotlin.script.ScriptContentLoader
|
||||
import org.jetbrains.kotlin.script.ScriptDependenciesProvider
|
||||
import org.jetbrains.kotlin.script.ScriptReportSink
|
||||
import org.jetbrains.kotlin.script.adjustByDefinition
|
||||
import org.jetbrains.kotlin.scripting.compiler.plugin.definitions.findScriptDefinition
|
||||
import java.io.File
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||
import kotlin.concurrent.read
|
||||
import kotlin.concurrent.write
|
||||
import kotlin.script.experimental.dependencies.ScriptDependencies
|
||||
|
||||
class CliScriptDependenciesProvider(private val project: Project) : ScriptDependenciesProvider {
|
||||
private val cacheLock = ReentrantReadWriteLock()
|
||||
private val cache = hashMapOf<String, ScriptDependencies?>()
|
||||
private val scriptContentLoader = ScriptContentLoader(project)
|
||||
|
||||
override fun getScriptDependencies(file: VirtualFile): ScriptDependencies? = cacheLock.read {
|
||||
calculateExternalDependencies(file)
|
||||
}
|
||||
|
||||
private fun calculateExternalDependencies(file: VirtualFile): ScriptDependencies? {
|
||||
val path = file.path
|
||||
val cached = cache[path]
|
||||
return if (cached != null) cached
|
||||
else {
|
||||
val scriptDef = file.findScriptDefinition(project)
|
||||
if (scriptDef != null) {
|
||||
val result = scriptContentLoader.loadContentsAndResolveDependencies(scriptDef, file)
|
||||
|
||||
ServiceManager.getService(project, ScriptReportSink::class.java)?.attachReports(file, result.reports)
|
||||
|
||||
val deps = result.dependencies?.adjustByDefinition(scriptDef)
|
||||
|
||||
if (deps != null) {
|
||||
log.info("[kts] new cached deps for $path: ${deps.classpath.joinToString(File.pathSeparator)}")
|
||||
}
|
||||
cacheLock.write {
|
||||
cache.put(path, deps)
|
||||
}
|
||||
deps
|
||||
} else null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val log = Logger.getInstance(ScriptDependenciesProvider::class.java)
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2010-2019 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.legacy
|
||||
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
|
||||
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
||||
import org.jetbrains.kotlin.script.ScriptReportSink
|
||||
import kotlin.script.experimental.dependencies.ScriptReport
|
||||
|
||||
internal class CliScriptReportSink(private val messageCollector: MessageCollector) : ScriptReportSink {
|
||||
override fun attachReports(scriptFile: VirtualFile, reports: List<ScriptReport>) {
|
||||
reports.forEach {
|
||||
messageCollector.report(it.severity.convertSeverity(), it.message, location(scriptFile, it.position))
|
||||
}
|
||||
}
|
||||
|
||||
private fun location(scriptFile: VirtualFile, position: ScriptReport.Position?): CompilerMessageLocation? {
|
||||
if (position == null) return CompilerMessageLocation.create(scriptFile.path)
|
||||
|
||||
return CompilerMessageLocation.create(scriptFile.path, position.startLine, position.startColumn, null)
|
||||
}
|
||||
|
||||
private fun ScriptReport.Severity.convertSeverity(): CompilerMessageSeverity = when (this) {
|
||||
ScriptReport.Severity.FATAL -> CompilerMessageSeverity.ERROR
|
||||
ScriptReport.Severity.ERROR -> CompilerMessageSeverity.ERROR
|
||||
ScriptReport.Severity.WARNING -> CompilerMessageSeverity.WARNING
|
||||
ScriptReport.Severity.INFO -> CompilerMessageSeverity.INFO
|
||||
ScriptReport.Severity.DEBUG -> CompilerMessageSeverity.LOGGING
|
||||
}
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2010-2019 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.legacy
|
||||
|
||||
import com.intellij.ide.highlighter.JavaFileType
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.script.KotlinScriptDefinition
|
||||
import org.jetbrains.kotlin.script.ScriptDefinitionProvider
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||
import kotlin.concurrent.read
|
||||
import kotlin.concurrent.write
|
||||
|
||||
abstract class LazyScriptDefinitionProvider : ScriptDefinitionProvider {
|
||||
|
||||
protected val lock = ReentrantReadWriteLock()
|
||||
|
||||
protected abstract val currentDefinitions: Sequence<KotlinScriptDefinition>
|
||||
|
||||
private var _cachedDefinitions: Sequence<KotlinScriptDefinition>? = null
|
||||
private val cachedDefinitions: Sequence<KotlinScriptDefinition>
|
||||
get() {
|
||||
assert(lock.readLockCount > 0) { "cachedDefinitions should only be used under the read lock" }
|
||||
if (_cachedDefinitions == null) lock.write {
|
||||
_cachedDefinitions = CachingSequence(currentDefinitions.constrainOnce())
|
||||
}
|
||||
return _cachedDefinitions!!
|
||||
}
|
||||
|
||||
protected fun clearCache() {
|
||||
lock.write {
|
||||
_cachedDefinitions = null
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun nonScriptFileName(fileName: String) = nonScriptFilenameSuffixes.any {
|
||||
fileName.endsWith(it, ignoreCase = true)
|
||||
}
|
||||
|
||||
override fun findScriptDefinition(fileName: String): KotlinScriptDefinition? =
|
||||
if (nonScriptFileName(fileName)) null
|
||||
else lock.read {
|
||||
cachedDefinitions.firstOrNull { it.isScript(fileName) }
|
||||
}
|
||||
|
||||
override fun isScript(fileName: String) = findScriptDefinition(fileName) != null
|
||||
|
||||
override fun getKnownFilenameExtensions(): Sequence<String> = lock.read {
|
||||
cachedDefinitions.map { it.fileExtension }
|
||||
}
|
||||
|
||||
companion object {
|
||||
// TODO: find a common place for storing kotlin-related extensions and reuse values from it everywhere
|
||||
protected val nonScriptFilenameSuffixes = arrayOf(".${KotlinFileType.EXTENSION}", ".${JavaFileType.DEFAULT_EXTENSION}")
|
||||
}
|
||||
}
|
||||
|
||||
private class CachingSequence<T>(from: Sequence<T>) : Sequence<T> {
|
||||
|
||||
private val lock = ReentrantReadWriteLock()
|
||||
private val sequenceIterator = from.iterator()
|
||||
private val cache = arrayListOf<T>()
|
||||
|
||||
private inner class CachingIterator : Iterator<T> {
|
||||
|
||||
private var cacheCursor = 0
|
||||
|
||||
override fun hasNext(): Boolean =
|
||||
lock.read { cacheCursor < cache.size }
|
||||
// iterator's hasNext can mutate the iterator's state, therefore write lock is needed
|
||||
|| lock.write { cacheCursor < cache.size || sequenceIterator.hasNext() }
|
||||
|
||||
override fun next(): T {
|
||||
lock.read {
|
||||
if (cacheCursor < cache.size) return cache[cacheCursor++]
|
||||
}
|
||||
// lock.write is not an upgrade but retake, therefore - one more check needed
|
||||
lock.write {
|
||||
return if (cacheCursor < cache.size) cache[cacheCursor++]
|
||||
else sequenceIterator.next().also { cache.add(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun iterator(): Iterator<T> = CachingIterator()
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
|
||||
import kotlin.script.templates.*
|
||||
|
||||
@ScriptTemplateAdditionalCompilerArguments(["-version"])
|
||||
abstract class TestScriptWithOtherAnnotation
|
||||
plugins/scripting/scripting-compiler/testData/lazyDefinitions/definitions/TestScriptWithReceivers.kt
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
|
||||
import kotlin.script.experimental.annotations.*
|
||||
import kotlin.script.experimental.api.*
|
||||
import kotlin.script.experimental.util.*
|
||||
|
||||
object TestScriptWithReceiversDefinition : ScriptCompilationConfiguration(
|
||||
{
|
||||
implicitReceivers(String::class)
|
||||
})
|
||||
|
||||
@KotlinScript(fileExtension = "1.kts", compilationConfiguration = TestScriptWithReceiversDefinition::class)
|
||||
abstract class TestScriptWithReceivers
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
|
||||
import kotlin.script.experimental.annotations.*
|
||||
import kotlin.script.experimental.api.*
|
||||
import kotlin.script.experimental.util.*
|
||||
|
||||
object TestScriptWithSimpleEnvVarsDefinition : ScriptCompilationConfiguration(
|
||||
{
|
||||
providedProperties("stringVar1" to String::class)
|
||||
})
|
||||
|
||||
@KotlinScript(fileExtension = "2.kts", compilationConfiguration = TestScriptWithSimpleEnvVarsDefinition::class)
|
||||
abstract class TestScriptWithSimpleEnvVars
|
||||
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
|
||||
val res = stringVar1.drop(4)
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
|
||||
val res = drop(4)
|
||||
+255
@@ -0,0 +1,255 @@
|
||||
/*
|
||||
* 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 junit.framework.TestCase
|
||||
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
|
||||
import org.jetbrains.kotlin.cli.common.ExitCode
|
||||
import org.jetbrains.kotlin.cli.common.config.addKotlinSourceRoots
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
|
||||
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
||||
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
|
||||
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.cli.jvm.config.addJvmClasspathRoot
|
||||
import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.config.JVMConfigurationKeys
|
||||
import org.jetbrains.kotlin.scripting.compiler.plugin.definitions.SCRIPT_DEFINITION_MARKERS_PATH
|
||||
import org.jetbrains.kotlin.scripting.compiler.plugin.definitions.discoverScriptTemplatesInClasspath
|
||||
import org.jetbrains.kotlin.scripting.compiler.plugin.definitions.loadScriptTemplatesFromClasspath
|
||||
import org.jetbrains.kotlin.test.ConfigurationKind
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||
import org.jetbrains.kotlin.test.TestCaseWithTmpdir
|
||||
import org.jetbrains.kotlin.test.TestJdkKind
|
||||
import org.jetbrains.kotlin.utils.KotlinPaths
|
||||
import org.jetbrains.kotlin.utils.PathUtil
|
||||
import org.junit.Assert
|
||||
import java.io.File
|
||||
|
||||
class ScriptingCompilerPluginTest : TestCaseWithTmpdir() {
|
||||
|
||||
companion object {
|
||||
const val TEST_DATA_DIR = "plugins/scripting/scripting-cli/testData"
|
||||
}
|
||||
|
||||
private val kotlinPaths: KotlinPaths by lazy(LazyThreadSafetyMode.PUBLICATION) {
|
||||
val paths = PathUtil.kotlinPathsForDistDirectory
|
||||
TestCase.assertTrue("Lib directory doesn't exist. Run 'ant dist'", paths.libPath.absoluteFile.isDirectory)
|
||||
paths
|
||||
}
|
||||
|
||||
val compilerClasspath = listOf(kotlinPaths.compilerPath)
|
||||
val runtimeClasspath = listOf( kotlinPaths.stdlibPath, kotlinPaths.scriptRuntimePath, kotlinPaths.reflectPath)
|
||||
val scriptingClasspath = listOf("kotlin-scripting-common.jar").map { File(kotlinPaths.libPath, it) }
|
||||
|
||||
private fun createEnvironment(
|
||||
sources: List<String>, destDir: File, messageCollector: MessageCollector, confBody: CompilerConfiguration.() -> Unit
|
||||
): KotlinCoreEnvironment {
|
||||
val configuration = KotlinTestUtils.newConfiguration(ConfigurationKind.NO_KOTLIN_REFLECT, TestJdkKind.FULL_JDK).apply {
|
||||
put<MessageCollector>(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector)
|
||||
addKotlinSourceRoots(sources)
|
||||
put(JVMConfigurationKeys.OUTPUT_DIRECTORY, destDir)
|
||||
confBody()
|
||||
}
|
||||
|
||||
return KotlinCoreEnvironment.createForTests(testRootDisposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
|
||||
}
|
||||
|
||||
fun testScriptResolverEnvironmentArgsParsing() {
|
||||
|
||||
val longStr = (1..100).joinToString("\\,") { """\" $it aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \\""" }
|
||||
val unescapeRe = """\\(["\\,])""".toRegex()
|
||||
val cmdlineProcessor = ScriptingCommandLineProcessor()
|
||||
val configuration = CompilerConfiguration()
|
||||
|
||||
cmdlineProcessor.processOption(
|
||||
ScriptingCommandLineProcessor.LEGACY_SCRIPT_RESOLVER_ENVIRONMENT_OPTION,
|
||||
"""abc=def,11="ab cd \\ \"",long="$longStr"""",
|
||||
configuration
|
||||
)
|
||||
|
||||
val res = configuration.getMap(ScriptingConfigurationKeys.LEGACY_SCRIPT_RESOLVER_ENVIRONMENT_OPTION)
|
||||
|
||||
Assert.assertEquals(
|
||||
hashMapOf("abc" to "def", "11" to "ab cd \\ \"", "long" to unescapeRe.replace(longStr, "\$1")),
|
||||
res
|
||||
)
|
||||
}
|
||||
|
||||
fun testLazyScriptDefinitionDiscovery() {
|
||||
|
||||
// Three tests in one function: the direct loading, the discovery code separately, and as a part of regular compilation
|
||||
// tests are combined to avoid multiple compilation of script definition modules
|
||||
|
||||
val defsOut = File(tmpdir, "testLazyScriptDefinition/out/defs")
|
||||
val defsSrc = File(TEST_DATA_DIR, "lazyDefinitions/definitions")
|
||||
val scriptsOut = File(tmpdir, "testLazyScriptDefinition/out/scripts")
|
||||
val scriptsSrc = File(TEST_DATA_DIR, "lazyDefinitions/scripts")
|
||||
val scriptsOut2 = File(tmpdir, "testLazyScriptDefinition/out/scripts2")
|
||||
val defClasses = listOf("TestScriptWithReceivers", "TestScriptWithSimpleEnvVars")
|
||||
|
||||
val messageCollector = TestMessageCollector()
|
||||
|
||||
val definitionsCompileResult = KotlinToJVMBytecodeCompiler.compileBunchOfSources(
|
||||
createEnvironment(defClasses.map { File(defsSrc, "$it.kt").canonicalPath }, defsOut, messageCollector) {
|
||||
addJvmClasspathRoots(runtimeClasspath)
|
||||
addJvmClasspathRoots(scriptingClasspath)
|
||||
}
|
||||
)
|
||||
|
||||
assertTrue(definitionsCompileResult) {
|
||||
"Compilation of script definitions failed: $messageCollector"
|
||||
}
|
||||
|
||||
messageCollector.clear()
|
||||
|
||||
loadScriptTemplatesFromClasspath(
|
||||
listOf("TestScriptWithReceivers", "TestScriptWithSimpleEnvVars"),
|
||||
listOf(defsOut), emptyList(), this::class.java.classLoader, emptyMap(), messageCollector
|
||||
).toList()
|
||||
|
||||
for (def in defClasses) {
|
||||
assertTrue(messageCollector.messages.any { it.message.contains("Configure scripting: Added template $def") }) {
|
||||
"Missing messages from loading sequence (should contain \"Added template $def\"):\n$messageCollector"
|
||||
}
|
||||
assertTrue(messageCollector.messages.none { it.message.contains("Configure scripting: loading script definition class $def") }) {
|
||||
"Unexpected messages from loading sequence (should not contain \"loading script definition class $def\"):\n$messageCollector"
|
||||
}
|
||||
}
|
||||
|
||||
messageCollector.clear()
|
||||
|
||||
// chacking lazy discovery
|
||||
|
||||
val templatesDir = File(defsOut, SCRIPT_DEFINITION_MARKERS_PATH).also { it.mkdirs() }
|
||||
for (def in defClasses) {
|
||||
File(templatesDir, def).createNewFile()
|
||||
}
|
||||
|
||||
val lazyDefsSeq =
|
||||
discoverScriptTemplatesInClasspath(
|
||||
listOf(defsOut),
|
||||
this::class.java.classLoader,
|
||||
emptyMap(),
|
||||
messageCollector
|
||||
)
|
||||
|
||||
assertTrue(messageCollector.messages.isEmpty()) {
|
||||
"Unexpected messages from discovery sequence (should be empty):\n$messageCollector"
|
||||
}
|
||||
|
||||
val lazyDefs = lazyDefsSeq.toList()
|
||||
|
||||
for (def in defClasses) {
|
||||
assertTrue(messageCollector.messages.any { it.message.contains("Configure scripting: Added template $def") }) {
|
||||
"Missing messages from discovery sequence (should contain \"Added template $def\"):\n$messageCollector"
|
||||
}
|
||||
assertTrue(messageCollector.messages.none { it.message.contains("Configure scripting: loading script definition class $def") }) {
|
||||
"Unexpected messages from discovery sequence (should not contain \"loading script definition class $def\"):\n$messageCollector"
|
||||
}
|
||||
}
|
||||
|
||||
messageCollector.clear()
|
||||
|
||||
val scriptFiles = scriptsSrc.listFiles { file: File -> file.extension == "kts" }.map { it.canonicalPath}
|
||||
|
||||
val scriptsCompileEnv = createEnvironment(scriptFiles, scriptsOut, messageCollector) {
|
||||
addJvmClasspathRoots(runtimeClasspath)
|
||||
addJvmClasspathRoots(scriptingClasspath)
|
||||
addJvmClasspathRoot(defsOut)
|
||||
addAll(JVMConfigurationKeys.SCRIPT_DEFINITIONS, lazyDefs)
|
||||
}
|
||||
|
||||
val res = KotlinToJVMBytecodeCompiler.compileBunchOfSources(scriptsCompileEnv)
|
||||
|
||||
assertTrue(res) {
|
||||
"Failed to compile scripts:\n$messageCollector"
|
||||
}
|
||||
|
||||
val cp = (runtimeClasspath + scriptingClasspath + defsOut).joinToString(File.pathSeparator)
|
||||
val exitCode = K2JVMCompiler().exec(System.err, "-cp", cp, *(scriptFiles.toTypedArray()), "-d", scriptsOut2.canonicalPath)
|
||||
|
||||
Assert.assertEquals(ExitCode.OK, exitCode)
|
||||
}
|
||||
|
||||
fun testLazyScriptDefinitionOtherAnnotation() {
|
||||
|
||||
val defsOut = File(tmpdir, "testLazyScriptDefinition/out/otherAnn")
|
||||
val defsSrc = File(TEST_DATA_DIR, "lazyDefinitions/definitions")
|
||||
val defClasses = listOf("TestScriptWithOtherAnnotation")
|
||||
|
||||
val messageCollector = TestMessageCollector()
|
||||
|
||||
val definitionsCompileResult = KotlinToJVMBytecodeCompiler.compileBunchOfSources(
|
||||
createEnvironment(defClasses.map { File(defsSrc, "$it.kt").canonicalPath }, defsOut, messageCollector) {
|
||||
addJvmClasspathRoots(runtimeClasspath)
|
||||
addJvmClasspathRoots(scriptingClasspath)
|
||||
}
|
||||
)
|
||||
|
||||
assertTrue(definitionsCompileResult) {
|
||||
"Compilation of script definitions failed: $messageCollector"
|
||||
}
|
||||
|
||||
val templatesDir = File(defsOut, SCRIPT_DEFINITION_MARKERS_PATH).also { it.mkdirs() }
|
||||
for (def in defClasses) {
|
||||
File(templatesDir, def).createNewFile()
|
||||
}
|
||||
|
||||
messageCollector.clear()
|
||||
|
||||
discoverScriptTemplatesInClasspath(
|
||||
listOf(defsOut),
|
||||
this::class.java.classLoader,
|
||||
emptyMap(),
|
||||
messageCollector
|
||||
).toList()
|
||||
|
||||
assertTrue(
|
||||
messageCollector.messages.isNotEmpty()
|
||||
&& messageCollector.messages.all { it.message.contains("s not marked with any known kotlin script annotation") }
|
||||
) {
|
||||
"Unexpected messages from discovery sequence:\n$messageCollector"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class TestMessageCollector : MessageCollector {
|
||||
data class Message(val severity: CompilerMessageSeverity, val message: String, val location: CompilerMessageLocation?)
|
||||
|
||||
val messages = arrayListOf<Message>()
|
||||
|
||||
override fun clear() {
|
||||
messages.clear()
|
||||
}
|
||||
|
||||
override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageLocation?) {
|
||||
messages.add(Message(severity, message, location))
|
||||
}
|
||||
|
||||
override fun hasErrors(): Boolean = messages.any { it.severity == CompilerMessageSeverity.EXCEPTION || it.severity == CompilerMessageSeverity.ERROR }
|
||||
|
||||
override fun toString(): String {
|
||||
return messages.joinToString("\n") { "${it.severity}: ${it.message}${it.location?.let{" at $it"} ?: ""}" }
|
||||
}
|
||||
}
|
||||
|
||||
fun TestMessageCollector.assertHasMessage(msg: String, desiredSeverity: CompilerMessageSeverity? = null) {
|
||||
assert(messages.any { it.message.contains(msg) && (desiredSeverity == null || it.severity == desiredSeverity) }) {
|
||||
"Expecting message \"$msg\" with severity ${desiredSeverity?.toString() ?: "Any"}, actual:\n" +
|
||||
messages.joinToString("\n") { it.severity.toString() + ": " + it.message }
|
||||
}
|
||||
}
|
||||
|
||||
fun assertTrue(exp: Boolean, msg: () -> String) {
|
||||
if (!exp) {
|
||||
Assert.fail(msg())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user