Refactor script definitions and resolving/refining infrastructure:

- implement wrappers to wrap old and new API providers and resolvers
- make old API deprecated (with error where possible)
- drop old internal classes related to the old API
- refactor usages accordingly
- fix and add missing features to the scripting API where necessary
This commit is contained in:
Ilya Chernikov
2019-05-12 22:26:37 +02:00
parent e5054f9648
commit e542c9ea84
94 changed files with 1554 additions and 764 deletions
@@ -6,13 +6,13 @@
package org.jetbrains.kotlin.scripting.configuration
import org.jetbrains.kotlin.config.CompilerConfigurationKey
import org.jetbrains.kotlin.scripting.definitions.KotlinScriptDefinition
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinitionsSource
import java.io.File
object ScriptingConfigurationKeys {
val SCRIPT_DEFINITIONS = CompilerConfigurationKey.create<List<KotlinScriptDefinition>>("script definitions")
val SCRIPT_DEFINITIONS = CompilerConfigurationKey.create<List<ScriptDefinition>>("script definitions")
val SCRIPT_DEFINITIONS_SOURCES =
CompilerConfigurationKey.create<List<ScriptDefinitionsSource>>("script definitions sources")
@@ -5,11 +5,14 @@
package org.jetbrains.kotlin.scripting.configuration
import com.intellij.openapi.project.Project
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.scripting.definitions.loadScriptTemplatesFromClasspath
import org.jetbrains.kotlin.scripting.definitions.reporter
import kotlin.script.experimental.host.ScriptingHostConfiguration
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
const val KOTLIN_SCRIPTING_PLUGIN_ID = "kotlin.scripting"
@@ -18,12 +21,16 @@ fun configureScriptDefinitions(
configuration: CompilerConfiguration,
baseClassloader: ClassLoader,
messageCollector: MessageCollector,
scriptResolverEnv: Map<String, Any?>
hostConfiguration: ScriptingHostConfiguration
) {
// TODO: consider using escaping to allow kotlin escaped names in class names
val templatesFromClasspath = loadScriptTemplatesFromClasspath(
scriptTemplates, configuration.jvmClasspathRoots, emptyList(), baseClassloader, scriptResolverEnv, messageCollector.reporter
scriptTemplates, configuration.jvmClasspathRoots, emptyList(), baseClassloader, hostConfiguration, messageCollector.reporter
)
configuration.addAll(ScriptingConfigurationKeys.SCRIPT_DEFINITIONS, templatesFromClasspath.toList())
}
fun makeHostConfiguration(project: Project, configuration: CompilerConfiguration): ScriptingHostConfiguration =
ScriptingHostConfiguration(defaultJvmScriptingHostConfiguration) {
// TODO: add jdk path and other params if needed
}
@@ -17,6 +17,8 @@ import kotlin.reflect.KType
import kotlin.script.experimental.dependencies.DependenciesResolver
import kotlin.script.templates.standard.ScriptTemplateWithArgs
// Legacy definition, will be obsolete soon
// TODO: make deprecated and drop usages where possible
open class KotlinScriptDefinition(open val template: KClass<out Any>) : UserDataHolderBase() {
open val name: String = "Kotlin Script"
@@ -17,6 +17,7 @@ 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.compat.mapToLegacyExpectedLocations
import kotlin.script.experimental.jvm.impl.BridgeDependenciesResolver
import kotlin.script.experimental.location.ScriptExpectedLocation
import kotlin.script.experimental.util.getOrError
@@ -80,15 +81,8 @@ abstract class KotlinScriptDefinitionAdapterFromNewAPIBase : KotlinScriptDefinit
@Suppress("DEPRECATION")
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)
get() = scriptCompilationConfiguration[ScriptCompilationConfiguration.ide.acceptedLocations]?.mapToLegacyExpectedLocations()
?: listOf(ScriptExpectedLocation.SourcesOnly, ScriptExpectedLocation.TestsOnly)
private val scriptingClassGetter by lazy(LazyThreadSafetyMode.PUBLICATION) {
hostConfiguration[ScriptingHostConfiguration.getScriptingClass]
@@ -9,10 +9,17 @@ import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.project.Project
interface ScriptDefinitionProvider {
@Deprecated("Migrating to configuration refinement", level = DeprecationLevel.ERROR)
fun findScriptDefinition(fileName: String): KotlinScriptDefinition?
fun isScript(fileName: String): Boolean
@Deprecated("Migrating to configuration refinement", level = DeprecationLevel.ERROR)
fun getDefaultScriptDefinition(): KotlinScriptDefinition
fun isScript(fileName: String): Boolean
fun findDefinition(fileName: String): ScriptDefinition?
fun getDefaultDefinition(): ScriptDefinition
fun getKnownFilenameExtensions(): Sequence<String>
companion object {
@@ -21,4 +28,3 @@ interface ScriptDefinitionProvider {
}
}
@@ -10,44 +10,56 @@ import java.io.File
import kotlin.script.experimental.annotations.KotlinScript
import kotlin.script.experimental.api.KotlinType
import kotlin.script.experimental.api.ScriptCompilationConfiguration
import kotlin.script.experimental.api.ScriptEvaluationConfiguration
import kotlin.script.experimental.api.fileExtension
import kotlin.script.experimental.host.ScriptingHostConfiguration
import kotlin.script.experimental.host.configurationDependencies
import kotlin.script.experimental.host.createCompilationConfigurationFromTemplate
import kotlin.script.experimental.host.createEvaluationConfigurationFromTemplate
import kotlin.script.experimental.jvm.JvmDependency
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
class LazyScriptDefinitionFromDiscoveredClass internal constructor(
baseHostConfiguration: ScriptingHostConfiguration,
private val annotationsFromAsm: ArrayList<BinAnnData>,
private val className: String,
private val classpath: List<File>,
private val messageReporter: MessageReporter
) : KotlinScriptDefinitionAdapterFromNewAPIBase() {
) : ScriptDefinition.FromConfigurationsBase() {
constructor(
baseHostConfiguration: ScriptingHostConfiguration,
classBytes: ByteArray,
className: String,
classpath: List<File>,
messageReporter: MessageReporter
) : this(loadAnnotationsFromClass(classBytes), className, classpath, messageReporter)
) : this(baseHostConfiguration, loadAnnotationsFromClass(classBytes), className, classpath, messageReporter)
override val hostConfiguration: ScriptingHostConfiguration by lazy(LazyThreadSafetyMode.PUBLICATION) {
ScriptingHostConfiguration(defaultJvmScriptingHostConfiguration) {
ScriptingHostConfiguration(baseHostConfiguration) {
configurationDependencies.append(JvmDependency(classpath))
}
}
override val scriptCompilationConfiguration: ScriptCompilationConfiguration by lazy(LazyThreadSafetyMode.PUBLICATION) {
private val configurations by lazy(LazyThreadSafetyMode.PUBLICATION) {
messageReporter(
CompilerMessageSeverity.LOGGING,
"Configure scripting: loading script definition class $className using classpath $classpath\n. ${Thread.currentThread().stackTrace}"
)
try {
createCompilationConfigurationFromTemplate(
KotlinType(className),
hostConfiguration,
LazyScriptDefinitionFromDiscoveredClass::class
)
val compileCfg =
createCompilationConfigurationFromTemplate(
KotlinType(className),
hostConfiguration,
LazyScriptDefinitionFromDiscoveredClass::class
)
val evalCfg =
createEvaluationConfigurationFromTemplate(
KotlinType(className),
hostConfiguration,
LazyScriptDefinitionFromDiscoveredClass::class
)
compileCfg to evalCfg
} catch (ex: ClassNotFoundException) {
messageReporter(CompilerMessageSeverity.ERROR, "Cannot find script definition class $className")
InvalidScriptDefinition
@@ -60,10 +72,13 @@ class LazyScriptDefinitionFromDiscoveredClass internal constructor(
}
}
override val compilationConfiguration: ScriptCompilationConfiguration get() = configurations.first
override val evaluationConfiguration: ScriptEvaluationConfiguration get() = configurations.second
override val fileExtension: String by lazy(LazyThreadSafetyMode.PUBLICATION) {
annotationsFromAsm.find { it.name == KotlinScript::class.simpleName }?.args
?.find { it.name == "fileExtension" }?.value
?: scriptCompilationConfiguration.let {
?: compilationConfiguration.let {
it[ScriptCompilationConfiguration.fileExtension] ?: super.fileExtension
}
}
@@ -74,4 +89,4 @@ class LazyScriptDefinitionFromDiscoveredClass internal constructor(
}
}
val InvalidScriptDefinition = ScriptCompilationConfiguration()
val InvalidScriptDefinition = ScriptCompilationConfiguration() to ScriptEvaluationConfiguration()
@@ -10,15 +10,22 @@ import org.jetbrains.kotlin.idea.KotlinFileType
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.write
import kotlin.script.experimental.host.ScriptingHostConfiguration
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
abstract class LazyScriptDefinitionProvider : ScriptDefinitionProvider {
protected val lock = ReentrantReadWriteLock()
protected abstract val currentDefinitions: Sequence<KotlinScriptDefinition>
protected abstract val currentDefinitions: Sequence<ScriptDefinition>
private var _cachedDefinitions: Sequence<KotlinScriptDefinition>? = null
private val cachedDefinitions: Sequence<KotlinScriptDefinition>
protected open fun getScriptingHostConfiguration(): ScriptingHostConfiguration = defaultJvmScriptingHostConfiguration
override fun getDefaultDefinition(): ScriptDefinition =
ScriptDefinition.FromLegacy(getScriptingHostConfiguration(), StandardScriptDefinition)
private var _cachedDefinitions: Sequence<ScriptDefinition>? = null
private val cachedDefinitions: Sequence<ScriptDefinition>
get() {
assert(lock.readLockCount > 0) { "cachedDefinitions should only be used under the read lock" }
if (_cachedDefinitions == null) lock.write {
@@ -37,18 +44,22 @@ abstract class LazyScriptDefinitionProvider : ScriptDefinitionProvider {
fileName.endsWith(it, ignoreCase = true)
}
override fun findScriptDefinition(fileName: String): KotlinScriptDefinition? =
override fun findDefinition(fileName: String): ScriptDefinition? =
if (nonScriptFileName(fileName)) null
else lock.read {
cachedDefinitions.firstOrNull { it.isScript(fileName) }
}
override fun isScript(fileName: String) = findScriptDefinition(fileName) != null
override fun findScriptDefinition(fileName: String): KotlinScriptDefinition? = findDefinition(fileName)?.legacyDefinition
override fun isScript(fileName: String) = findDefinition(fileName) != null
override fun getKnownFilenameExtensions(): Sequence<String> = lock.read {
cachedDefinitions.map { it.fileExtension }
}
override fun getDefaultScriptDefinition(): KotlinScriptDefinition = getDefaultDefinition().legacyDefinition
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}")
@@ -0,0 +1,90 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.definitions
import java.io.File
import kotlin.script.dependencies.Environment
import kotlin.script.dependencies.ScriptContents
import kotlin.script.experimental.api.*
import kotlin.script.experimental.dependencies.DependenciesResolver
import kotlin.script.experimental.host.FileBasedScriptSource
import kotlin.script.experimental.host.FileScriptSource
import kotlin.script.experimental.host.ScriptingHostConfiguration
import kotlin.script.experimental.host.ScriptingHostConfigurationKeys
import kotlin.script.experimental.jvm.JvmDependency
import kotlin.script.experimental.jvm.compat.mapLegacyDiagnosticSeverity
import kotlin.script.experimental.jvm.compat.mapLegacyExpectedLocations
import kotlin.script.experimental.jvm.jdkHome
import kotlin.script.experimental.jvm.jvm
import kotlin.script.experimental.util.PropertiesCollection
class ScriptCompilationConfigurationFromDefinition(
val hostConfiguration: ScriptingHostConfiguration,
val scriptDefinition: KotlinScriptDefinition
) : ScriptCompilationConfiguration(
{
hostConfiguration(hostConfiguration)
displayName(scriptDefinition.name)
fileExtension(scriptDefinition.fileExtension)
baseClass(KotlinType(scriptDefinition.template))
implicitReceivers.putIfAny(scriptDefinition.implicitReceivers.map(::KotlinType))
providedProperties.putIfAny(scriptDefinition.providedProperties.map { it.first to KotlinType(it.second) })
annotationsForSamWithReceivers.put(scriptDefinition.annotationsForSamWithReceivers.map(::KotlinType))
platform(scriptDefinition.platform)
compilerOptions.putIfAny(scriptDefinition.additionalCompilerArguments)
ide {
acceptedLocations.put(scriptDefinition.scriptExpectedLocations.mapLegacyExpectedLocations())
}
if (scriptDefinition.dependencyResolver != DependenciesResolver.NoDependencies) {
refineConfiguration {
onAnnotations(scriptDefinition.acceptedAnnotations.map(::KotlinType)) { context ->
val resolveResult: DependenciesResolver.ResolveResult = scriptDefinition.dependencyResolver.resolve(
ScriptContentsFromRefinementContext(context),
context.compilationConfiguration[ScriptCompilationConfiguration.hostConfiguration]?.let {
it[ScriptingHostConfiguration.getEnvironment]?.invoke()
}.orEmpty()
)
val reports = resolveResult.reports.map { ScriptDiagnostic(it.message, mapLegacyDiagnosticSeverity(it.severity)) }
val resolvedDeps = (resolveResult as? DependenciesResolver.ResolveResult.Success)?.dependencies
if (resolvedDeps == null) ResultWithDiagnostics.Failure(reports)
else ScriptCompilationConfiguration(context.compilationConfiguration) {
if (resolvedDeps.classpath.isNotEmpty()) {
dependencies.append(JvmDependency(resolvedDeps.classpath))
}
defaultImports.append(resolvedDeps.imports)
importScripts.append(resolvedDeps.scripts.map { FileScriptSource(it) })
jvm {
jdkHome.putIfNotNull(resolvedDeps.javaHome) // TODO: check if it is correct to supply javaHome as jdkHome
}
if (resolvedDeps.sources.isNotEmpty()) {
ide {
dependenciesSources.append(JvmDependency(resolvedDeps.sources))
}
}
}.asSuccess(reports)
}
}
}
}
)
private class ScriptContentsFromRefinementContext(val context: ScriptConfigurationRefinementContext) : ScriptContents {
override val file: File?
get() = (context.script as? FileBasedScriptSource)?.file
override val annotations: Iterable<Annotation>
get() = context.collectedData?.get(ScriptCollectedData.foundAnnotations) ?: emptyList()
override val text: CharSequence?
get() = context.script.text
}
val ScriptCompilationConfigurationKeys.annotationsForSamWithReceivers by PropertiesCollection.key<List<KotlinType>>()
val ScriptCompilationConfigurationKeys.platform by PropertiesCollection.key<String>()
val ScriptingHostConfigurationKeys.getEnvironment by PropertiesCollection.key<() -> Environment?>()
@@ -0,0 +1,164 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.definitions
import com.intellij.openapi.util.UserDataHolderBase
import org.jetbrains.kotlin.scripting.resolve.KotlinScriptDefinitionFromAnnotatedTemplate
import java.io.File
import kotlin.reflect.KClass
import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.ScriptingHostConfiguration
import kotlin.script.experimental.host.createCompilationConfigurationFromTemplate
import kotlin.script.experimental.host.createEvaluationConfigurationFromTemplate
import kotlin.script.experimental.jvm.baseClassLoader
import kotlin.script.experimental.jvm.jvm
// Transitional class/implementation - migrating to the new API
// TODO: deprecate KotlinScriptDefinition
// TODO: name could be confused with KotlinScriptDefinition, discuss naming
abstract class ScriptDefinition : UserDataHolderBase() {
abstract val legacyDefinition: KotlinScriptDefinition
abstract val hostConfiguration: ScriptingHostConfiguration
abstract val compilationConfiguration: ScriptCompilationConfiguration
abstract val evaluationConfiguration: ScriptEvaluationConfiguration
abstract fun isScript(fileName: String): Boolean
abstract val fileExtension: String
abstract val name: String
// TODO: used in settings, find out the reason and refactor accordingly
abstract val definitionId: String
abstract val contextClassLoader: ClassLoader?
// Target platform for script, ex. "JVM", "JS", "NATIVE"
open val platform: String
get() = "JVM"
open val isDefault = false
abstract val baseClassType: KotlinType
abstract val compilerOptions: Iterable<String>
inline fun <reified T : KotlinScriptDefinition> asLegacyOrNull(): T? =
if (this is FromLegacy) legacyDefinition as? T else null
open class FromLegacy(
override val hostConfiguration: ScriptingHostConfiguration,
override val legacyDefinition: KotlinScriptDefinition
) : ScriptDefinition() {
override val compilationConfiguration by lazy {
ScriptCompilationConfigurationFromDefinition(
hostConfiguration,
legacyDefinition
)
}
override val evaluationConfiguration by lazy {
ScriptEvaluationConfigurationFromDefinition(
hostConfiguration,
legacyDefinition
)
}
override fun isScript(fileName: String): Boolean = legacyDefinition.isScript(fileName)
override val fileExtension: String get() = legacyDefinition.fileExtension
override val name: String get() = legacyDefinition.name
override val definitionId: String get() = legacyDefinition::class.qualifiedName ?: "unknown"
override val platform: String
get() = legacyDefinition.platform
override val contextClassLoader: ClassLoader?
get() = legacyDefinition.template.java.classLoader
override val baseClassType: KotlinType
get() = KotlinType(legacyDefinition.template)
override val compilerOptions: Iterable<String>
get() = legacyDefinition.additionalCompilerArguments ?: emptyList()
override fun equals(other: Any?): Boolean = this === other || legacyDefinition == (other as? FromLegacy)?.legacyDefinition
override fun hashCode(): Int = legacyDefinition.hashCode()
}
open class FromLegacyTemplate(
hostConfiguration: ScriptingHostConfiguration,
template: KClass<*>,
templateClasspath: List<File> = emptyList()
) : FromLegacy(
hostConfiguration,
KotlinScriptDefinitionFromAnnotatedTemplate(
template,
hostConfiguration[ScriptingHostConfiguration.getEnvironment]?.invoke(),
templateClasspath
)
)
abstract class FromConfigurationsBase : ScriptDefinition() {
override val legacyDefinition by lazy {
KotlinScriptDefinitionAdapterFromNewAPI(
compilationConfiguration,
hostConfiguration
)
}
override fun isScript(fileName: String): Boolean = fileName.endsWith(".$fileExtension")
override val fileExtension: String get() = compilationConfiguration[ScriptCompilationConfiguration.fileExtension]!!
override val name: String get() = compilationConfiguration[ScriptCompilationConfiguration.displayName]!!
override val definitionId: String get() = compilationConfiguration[ScriptCompilationConfiguration.baseClass]!!.typeName
override val contextClassLoader: ClassLoader? by lazy {
compilationConfiguration[ScriptCompilationConfiguration.baseClass]?.fromClass?.java?.classLoader
?: hostConfiguration[ScriptingHostConfiguration.jvm.baseClassLoader]
}
override val baseClassType: KotlinType
get() = compilationConfiguration[ScriptCompilationConfiguration.baseClass]!!
override val compilerOptions: Iterable<String>
get() = compilationConfiguration[ScriptCompilationConfiguration.compilerOptions].orEmpty()
override fun equals(other: Any?): Boolean = this === other ||
(other as? FromConfigurations)?.let {
compilationConfiguration == it.compilationConfiguration && evaluationConfiguration == it.evaluationConfiguration
} == true
override fun hashCode(): Int = compilationConfiguration.hashCode() + 37 * evaluationConfiguration.hashCode()
}
open class FromConfigurations(
override val hostConfiguration: ScriptingHostConfiguration,
override val compilationConfiguration: ScriptCompilationConfiguration,
override val evaluationConfiguration: ScriptEvaluationConfiguration
) : FromConfigurationsBase()
open class FromTemplate(
hostConfiguration: ScriptingHostConfiguration,
template: KClass<*>,
contextClass: KClass<*> = ScriptCompilationConfiguration::class
) : FromConfigurations(
hostConfiguration,
createCompilationConfigurationFromTemplate(KotlinType(template), hostConfiguration, contextClass),
createEvaluationConfigurationFromTemplate(KotlinType(template), hostConfiguration, contextClass)
)
companion object {
fun getDefault(hostConfiguration: ScriptingHostConfiguration) =
object : FromLegacy(hostConfiguration, StandardScriptDefinition) {
override val isDefault = true
}
}
}
@@ -7,5 +7,5 @@ package org.jetbrains.kotlin.scripting.definitions
interface ScriptDefinitionsSource {
val definitions: Sequence<KotlinScriptDefinition>
val definitions: Sequence<ScriptDefinition>
}
@@ -9,11 +9,24 @@ import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult
import kotlin.script.experimental.api.valueOrNull
import kotlin.script.experimental.dependencies.ScriptDependencies
interface ScriptDependenciesProvider {
fun getScriptDependencies(file: VirtualFile): ScriptDependencies?
fun getScriptDependencies(file: PsiFile) = getScriptDependencies(file.virtualFile ?: file.originalFile.virtualFile)
@Deprecated("Migrating to configuration refinement", level = DeprecationLevel.ERROR)
fun getScriptDependencies(file: VirtualFile): ScriptDependencies? =
getScriptConfigurationResult(file)?.valueOrNull()?.legacyDependencies
@Deprecated("Migrating to configuration refinement", level = DeprecationLevel.ERROR)
fun getScriptDependencies(file: PsiFile): ScriptDependencies? =
getScriptConfigurationResult(file.virtualFile ?: file.originalFile.virtualFile)?.valueOrNull()?.legacyDependencies
fun getScriptConfigurationResult(file: VirtualFile): ScriptCompilationConfigurationResult? = null
fun getScriptConfigurationResult(file: PsiFile): ScriptCompilationConfigurationResult? =
getScriptConfigurationResult(file.virtualFile ?: file.originalFile.virtualFile)
companion object {
fun getInstance(project: Project): ScriptDependenciesProvider? =
@@ -0,0 +1,19 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.definitions
import kotlin.script.experimental.api.ScriptEvaluationConfiguration
import kotlin.script.experimental.api.hostConfiguration
import kotlin.script.experimental.host.ScriptingHostConfiguration
class ScriptEvaluationConfigurationFromDefinition(
val hostConfiguration: ScriptingHostConfiguration,
val scriptDefinition: KotlinScriptDefinition
) : ScriptEvaluationConfiguration(
{
hostConfiguration(hostConfiguration)
}
)
@@ -11,13 +11,13 @@ import org.jetbrains.kotlin.scripting.resolve.KotlinScriptDefinitionFromAnnotate
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import java.io.File
import java.io.IOException
import java.lang.IllegalArgumentException
import java.net.URLClassLoader
import java.util.jar.JarFile
import kotlin.script.experimental.annotations.KotlinScript
import kotlin.script.experimental.api.KotlinType
import kotlin.script.experimental.host.ScriptingHostConfiguration
import kotlin.script.experimental.host.createCompilationConfigurationFromTemplate
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
import kotlin.script.experimental.host.createEvaluationConfigurationFromTemplate
import kotlin.script.templates.ScriptTemplateDefinition
const val SCRIPT_DEFINITION_MARKERS_PATH = "META-INF/kotlin/script/templates/"
@@ -32,15 +32,15 @@ val MessageCollector.reporter: MessageReporter
class ScriptDefinitionsFromClasspathDiscoverySource(
private val classpath: List<File>,
private val scriptResolverEnv: Map<String, Any?>,
private val hostConfiguration: ScriptingHostConfiguration,
private val messageReporter: MessageReporter
) : ScriptDefinitionsSource {
override val definitions: Sequence<KotlinScriptDefinition> = run {
override val definitions: Sequence<ScriptDefinition> = run {
discoverScriptTemplatesInClasspath(
classpath,
this::class.java.classLoader,
scriptResolverEnv,
hostConfiguration,
messageReporter
)
}
@@ -50,9 +50,9 @@ private const val MANIFEST_RESOURCE_NAME = "/META-INF/MANIFEST.MF"
fun discoverScriptTemplatesInClassLoader(
classLoader: ClassLoader,
scriptResolverEnv: Map<String, Any?>,
hostConfiguration: ScriptingHostConfiguration,
messageReporter: MessageReporter
): Sequence<KotlinScriptDefinition> {
): Sequence<ScriptDefinition> {
val classpath = classLoader.getResources(MANIFEST_RESOURCE_NAME).asSequence().mapNotNull {
try {
File(it.toURI()).takeIf(File::exists)
@@ -61,26 +61,26 @@ fun discoverScriptTemplatesInClassLoader(
}
}
val classpathWithLoader = SimpleClasspathWithClassLoader(classpath.toList(), classLoader)
return scriptTemplatesDiscoverySequence(classpathWithLoader, scriptResolverEnv, messageReporter)
return scriptTemplatesDiscoverySequence(classpathWithLoader, hostConfiguration, messageReporter)
}
fun discoverScriptTemplatesInClasspath(
classpath: List<File>,
baseClassLoader: ClassLoader?,
scriptResolverEnv: Map<String, Any?>,
hostConfiguration: ScriptingHostConfiguration,
messageReporter: MessageReporter
): Sequence<KotlinScriptDefinition> {
): Sequence<ScriptDefinition> {
// TODO: try to find a way to reduce classpath (and classloader) to minimal one needed to load script definition and its dependencies
val classpathWithLoader = LazyClasspathWithClassLoader(baseClassLoader) { classpath }
return scriptTemplatesDiscoverySequence(classpathWithLoader, scriptResolverEnv, messageReporter)
return scriptTemplatesDiscoverySequence(classpathWithLoader, hostConfiguration, messageReporter)
}
private fun scriptTemplatesDiscoverySequence(
classpathWithLoader: ClasspathWithClassLoader,
scriptResolverEnv: Map<String, Any?>,
hostConfiguration: ScriptingHostConfiguration,
messageReporter: MessageReporter
): Sequence<KotlinScriptDefinition> {
): Sequence<ScriptDefinition> {
return sequence {
// 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
@@ -102,7 +102,7 @@ private fun scriptTemplatesDiscoverySequence(
definitionNames.partitionLoadJarDefinitions(
jar,
classpathWithLoader,
scriptResolverEnv,
hostConfiguration,
messageReporter
)
if (notFoundClasses.isNotEmpty()) {
@@ -122,7 +122,7 @@ private fun scriptTemplatesDiscoverySequence(
val discoveryMarkers = File(dep, SCRIPT_DEFINITION_MARKERS_PATH).listFiles()
if (discoveryMarkers?.isEmpty() == false) {
val (foundDefinitionClasses, notFoundDefinitions) = discoveryMarkers.map { it.name }
.partitionLoadDirDefinitions(dep, classpathWithLoader, scriptResolverEnv, messageReporter)
.partitionLoadDirDefinitions(dep, classpathWithLoader, hostConfiguration, messageReporter)
foundDefinitionClasses.forEach {
yield(it)
}
@@ -145,7 +145,7 @@ private fun scriptTemplatesDiscoverySequence(
if (remainingDefinitionCandidates.isEmpty()) break
try {
val (foundDefinitionClasses, notFoundDefinitions) =
remainingDefinitionCandidates.partitionLoadDirDefinitions(dep, classpathWithLoader, scriptResolverEnv, messageReporter)
remainingDefinitionCandidates.partitionLoadDirDefinitions(dep, classpathWithLoader, hostConfiguration, messageReporter)
foundDefinitionClasses.forEach {
yield(it)
}
@@ -170,9 +170,9 @@ fun loadScriptTemplatesFromClasspath(
classpath: List<File>,
dependenciesClasspath: List<File>,
baseClassLoader: ClassLoader,
scriptResolverEnv: Map<String, Any?>,
hostConfiguration: ScriptingHostConfiguration,
messageReporter: MessageReporter
): Sequence<KotlinScriptDefinition> =
): Sequence<ScriptDefinition> =
if (scriptTemplates.isEmpty()) emptySequence()
else sequence {
// trying the direct classloading from baseClassloader first, since this is the most performant variant
@@ -180,7 +180,7 @@ fun loadScriptTemplatesFromClasspath(
loadScriptDefinition(
baseClassLoader,
it,
scriptResolverEnv,
hostConfiguration,
messageReporter
)
}
@@ -199,11 +199,11 @@ fun loadScriptTemplatesFromClasspath(
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, classpathWithLoader, scriptResolverEnv, messageReporter)
remainingTemplates.partitionLoadJarDefinitions(jar, classpathWithLoader, hostConfiguration, messageReporter)
}
}
dep.isDirectory -> {
remainingTemplates.partitionLoadDirDefinitions(dep, classpathWithLoader, scriptResolverEnv, messageReporter)
remainingTemplates.partitionLoadDirDefinitions(dep, classpathWithLoader, hostConfiguration, messageReporter)
}
else -> {
// assuming that invalid classpath entries will be reported elsewhere anyway, so do not spam user with additional warnings here
@@ -237,17 +237,17 @@ fun loadScriptTemplatesFromClasspath(
}
private data class DefinitionsLoadPartitionResult(
val loaded: List<KotlinScriptDefinition>,
val loaded: List<ScriptDefinition>,
val notFound: List<String>
)
private inline fun List<String>.partitionLoadDefinitions(
classpathWithLoader: ClasspathWithClassLoader,
scriptResolverEnv: Map<String, Any?>,
hostConfiguration: ScriptingHostConfiguration,
noinline messageReporter: MessageReporter,
getBytes: (String) -> ByteArray?
): DefinitionsLoadPartitionResult {
val loaded = ArrayList<KotlinScriptDefinition>()
val loaded = ArrayList<ScriptDefinition>()
val notFound = ArrayList<String>()
for (definitionName in this) {
val classBytes = getBytes(definitionName)
@@ -256,7 +256,7 @@ private inline fun List<String>.partitionLoadDefinitions(
it,
definitionName,
classpathWithLoader,
scriptResolverEnv,
hostConfiguration,
messageReporter
)
}
@@ -273,18 +273,18 @@ private inline fun List<String>.partitionLoadDefinitions(
private fun List<String>.partitionLoadJarDefinitions(
jar: JarFile,
classpathWithLoader: ClasspathWithClassLoader,
scriptResolverEnv: Map<String, Any?>,
hostConfiguration: ScriptingHostConfiguration,
messageReporter: MessageReporter
): DefinitionsLoadPartitionResult = partitionLoadDefinitions(classpathWithLoader, scriptResolverEnv, messageReporter) { definitionName ->
): DefinitionsLoadPartitionResult = partitionLoadDefinitions(classpathWithLoader, hostConfiguration, messageReporter) { definitionName ->
jar.getJarEntry("${definitionName.replace('.', '/')}.class")?.let { jar.getInputStream(it).readBytes() }
}
private fun List<String>.partitionLoadDirDefinitions(
dir: File,
classpathWithLoader: ClasspathWithClassLoader,
scriptResolverEnv: Map<String, Any?>,
hostConfiguration: ScriptingHostConfiguration,
messageReporter: MessageReporter
): DefinitionsLoadPartitionResult = partitionLoadDefinitions(classpathWithLoader, scriptResolverEnv, messageReporter) { definitionName ->
): DefinitionsLoadPartitionResult = partitionLoadDefinitions(classpathWithLoader, hostConfiguration, messageReporter) { definitionName ->
File(dir, "${definitionName.replace('.', '/')}.class").takeIf { it.exists() && it.isFile }?.readBytes()
}
@@ -292,14 +292,15 @@ private fun loadScriptDefinition(
templateClassBytes: ByteArray,
templateClassName: String,
classpathWithLoader: ClasspathWithClassLoader,
scriptResolverEnv: Map<String, Any?>,
hostConfiguration: ScriptingHostConfiguration,
messageReporter: MessageReporter
): KotlinScriptDefinition? {
): ScriptDefinition? {
val anns = loadAnnotationsFromClass(templateClassBytes)
for (ann in anns) {
var def: KotlinScriptDefinition? = null
var def: ScriptDefinition? = null
if (ann.name == KotlinScript::class.simpleName) {
def = LazyScriptDefinitionFromDiscoveredClass(
hostConfiguration,
anns,
templateClassName,
classpathWithLoader.classpath,
@@ -307,7 +308,14 @@ private fun loadScriptDefinition(
)
} else if (ann.name == ScriptTemplateDefinition::class.simpleName) {
val templateClass = classpathWithLoader.classLoader.loadClass(templateClassName).kotlin
def = KotlinScriptDefinitionFromAnnotatedTemplate(templateClass, scriptResolverEnv, classpathWithLoader.classpath)
def = ScriptDefinition.FromLegacy(
hostConfiguration,
KotlinScriptDefinitionFromAnnotatedTemplate(
templateClass,
hostConfiguration[ScriptingHostConfiguration.getEnvironment]?.invoke().orEmpty(),
classpathWithLoader.classpath
)
)
}
if (def != null) {
messageReporter(
@@ -327,29 +335,20 @@ private fun loadScriptDefinition(
private fun loadScriptDefinition(
classLoader: ClassLoader,
template: String,
scriptResolverEnv: Map<String, Any?>,
hostConfiguration: ScriptingHostConfiguration,
messageReporter: MessageReporter
): KotlinScriptDefinition? {
): ScriptDefinition? {
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
)
ScriptDefinition.FromTemplate(hostConfiguration, cls.kotlin, ScriptDefinition::class)
} else {
KotlinScriptDefinitionFromAnnotatedTemplate(cls.kotlin, scriptResolverEnv)
ScriptDefinition.FromLegacyTemplate(hostConfiguration, cls.kotlin)
}
messageReporter(
CompilerMessageSeverity.INFO,
"Added script definition $template to configuration: name = ${def.name}, " +
"resolver = ${def.dependencyResolver.javaClass.name}"
"Added script definition $template to configuration: name = ${def.name}"
)
return def
} catch (ex: ClassNotFoundException) {
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.scripting.definitions
import com.intellij.ide.highlighter.JavaClassFileType
import com.intellij.openapi.application.runReadAction
import com.intellij.openapi.fileTypes.FileTypeRegistry
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
@@ -14,30 +15,31 @@ import com.intellij.psi.PsiManager
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.psi.KtFile
fun PsiFile.scriptDefinition(): KotlinScriptDefinition? {
fun PsiFile.findScriptDefinition(): ScriptDefinition? {
// 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)
return findScriptDefinitionByFileName(project, file.name)
}
fun VirtualFile.findScriptDefinition(project: Project): KotlinScriptDefinition? {
fun VirtualFile.findScriptDefinition(project: Project): ScriptDefinition? {
if (!isValid || 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)
if (runReadAction { PsiManager.getInstance(project).findFile(this) as? KtFile }/*?.script*/ == null) return null
return findScriptDefinitionByFileName(project, name)
}
fun scriptDefinitionByFileName(project: Project, fileName: String): KotlinScriptDefinition {
fun findScriptDefinitionByFileName(project: Project, fileName: String): ScriptDefinition {
val scriptDefinitionProvider = ScriptDefinitionProvider.getInstance(project) ?: return null
?: throw IllegalStateException("Unable to get script definition: ScriptDefinitionProvider is not configured.")
return scriptDefinitionProvider.findScriptDefinition(fileName) ?: scriptDefinitionProvider.getDefaultScriptDefinition()
return scriptDefinitionProvider.findDefinition(fileName) ?: scriptDefinitionProvider.getDefaultDefinition()
}
private fun VirtualFile.isNonScript(): Boolean =
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.definitions.ScriptDependenciesProvider
import java.io.File
import kotlin.script.experimental.api.valueOrNull
data class ScriptsCompilationDependencies(
val classpath: List<File>,
@@ -41,11 +42,11 @@ fun collectScriptsCompilationDependencies(
while (true) {
val newRemainingSources = ArrayList<KtFile>()
for (source in remainingSources) {
val dependencies = importsProvider.getScriptDependencies(source)
if (dependencies != null) {
collectedClassPath.addAll(dependencies.classpath)
val refinedConfiguration = importsProvider.getScriptConfigurationResult(source)?.valueOrNull()
if (refinedConfiguration != null) {
collectedClassPath.addAll(refinedConfiguration.dependenciesClassPath)
val sourceDependenciesRoots = dependencies.scripts.map {
val sourceDependenciesRoots = refinedConfiguration.importedScripts.map {
KotlinSourceRoot(it.path, false)
}
val sourceDependencies =
@@ -11,6 +11,7 @@ import org.jetbrains.kotlin.psi.KtImportInfo
import org.jetbrains.kotlin.resolve.ImportPath
import org.jetbrains.kotlin.resolve.extensions.ExtraImportsProviderExtension
import org.jetbrains.kotlin.scripting.definitions.ScriptDependenciesProvider
import kotlin.script.experimental.api.valueOrNull
class ScriptExtraImportsProviderExtension : ExtraImportsProviderExtension {
@@ -28,8 +29,8 @@ class ScriptExtraImportsProviderExtension : ExtraImportsProviderExtension {
override fun getExtraImports(ktFile: KtFile): Collection<KtImportInfo> =
ktFile.takeIf { it.isScript() }?.let { file ->
val scriptDependencies = ScriptDependenciesProvider.getInstance(file.project)?.getScriptDependencies(file.originalFile)
scriptDependencies?.imports?.map {
val refinedConfiguration = ScriptDependenciesProvider.getInstance(file.project)?.getScriptConfigurationResult(file.originalFile)?.valueOrNull()
refinedConfiguration?.defaultImports?.map {
ScriptExtraImportImpl(
ImportPath.fromString(it)
)
@@ -25,8 +25,10 @@ import org.jetbrains.kotlin.parsing.KotlinParserDefinition
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.configuration.ScriptingConfigurationKeys
import org.jetbrains.kotlin.scripting.definitions.KotlinScriptDefinition
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
import org.jetbrains.kotlin.scripting.repl.messages.ConsoleDiagnosticMessageHolder
import kotlin.concurrent.write
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
const val KOTLIN_REPL_JVM_TARGET_PROPERTY = "kotlin.repl.jvm.target"
@@ -39,7 +41,7 @@ open class GenericReplChecker(
internal val environment = run {
compilerConfiguration.apply {
add(ScriptingConfigurationKeys.SCRIPT_DEFINITIONS, scriptDefinition)
add(ScriptingConfigurationKeys.SCRIPT_DEFINITIONS, ScriptDefinition.FromLegacy(defaultJvmScriptingHostConfiguration, scriptDefinition))
put<MessageCollector>(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector)
put(JVMConfigurationKeys.RETAIN_OUTPUT_IN_MEMORY, true)
@@ -23,6 +23,7 @@ import org.jetbrains.kotlin.scripting.definitions.ScriptDependenciesProvider
import java.io.File
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.write
import kotlin.script.experimental.api.valueOrNull
// WARNING: not thread safe, assuming external synchronization
@@ -64,7 +65,9 @@ open class GenericReplCompiler(
Pair(compilerState.lastLineState!!.psiFile, compilerState.lastLineState!!.errorHolder)
}
val newDependencies = ScriptDependenciesProvider.getInstance(checker.environment.project)?.getScriptDependencies(psiFile)
val newDependencies =
ScriptDependenciesProvider.getInstance(checker.environment.project)?.getScriptConfigurationResult(psiFile)?.valueOrNull()
?.legacyDependencies
var classpathAddendum: List<File>? = null
if (compilerState.lastDependencies != newDependencies) {
compilerState.lastDependencies = newDependencies
@@ -33,10 +33,10 @@ 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.scripting.definitions.KotlinScriptDefinition
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
import org.jetbrains.kotlin.scripting.definitions.ScriptDependenciesProvider
import org.jetbrains.kotlin.scripting.definitions.ScriptPriorities
import org.jetbrains.kotlin.scripting.definitions.scriptDefinitionByFileName
import org.jetbrains.kotlin.scripting.definitions.findScriptDefinitionByFileName
import org.jetbrains.kotlin.types.TypeSubstitutor
import org.jetbrains.kotlin.types.typeUtil.isNothing
import org.jetbrains.kotlin.types.typeUtil.isUnit
@@ -44,6 +44,7 @@ import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import java.io.File
import kotlin.reflect.KClass
import kotlin.reflect.KType
import kotlin.script.experimental.api.valueOrNull
class LazyScriptDescriptor(
@@ -104,8 +105,9 @@ class LazyScriptDescriptor(
override fun getPriority() = priority
val scriptDefinition: () -> KotlinScriptDefinition = resolveSession.storageManager.createLazyValue {
scriptDefinitionByFileName(
// rewrite without using legacyDefinition below
val scriptDefinition: () -> ScriptDefinition = resolveSession.storageManager.createLazyValue {
findScriptDefinitionByFileName(
resolveSession.project,
scriptInfo.script.containingKtFile.name
)
@@ -128,7 +130,7 @@ class LazyScriptDescriptor(
override fun getUnsubstitutedPrimaryConstructor() = super.getUnsubstitutedPrimaryConstructor()!!
internal val baseClassDescriptor: () -> ClassDescriptor? = resolveSession.storageManager.createNullableLazyValue {
val template = scriptDefinition().template
val template = scriptDefinition().legacyDefinition.template
findTypeDescriptor(
template,
if (template.qualifiedName?.startsWith("kotlin.script.templates.standard") == true) Errors.MISSING_SCRIPT_STANDARD_TEMPLATE
@@ -167,7 +169,7 @@ class LazyScriptDescriptor(
val res = ArrayList<ClassDescriptor>()
val importedScriptsFiles = ScriptDependenciesProvider.getInstance(scriptInfo.script.project)
?.getScriptDependencies(scriptInfo.script.containingKtFile)?.scripts
?.getScriptConfigurationResult(scriptInfo.script.containingKtFile)?.valueOrNull()?.importedScripts
if (importedScriptsFiles != null) {
val findImportedScriptDescriptor = ImportedScriptDescriptorsFinder()
importedScriptsFiles.mapNotNullTo(res) {
@@ -175,7 +177,7 @@ class LazyScriptDescriptor(
}
}
scriptDefinition().implicitReceivers.mapNotNullTo(res) { receiver ->
scriptDefinition().legacyDefinition.implicitReceivers.mapNotNullTo(res) { receiver ->
findTypeDescriptor(receiver, Errors.MISSING_SCRIPT_RECEIVER_CLASS)
}
@@ -222,7 +224,7 @@ class LazyScriptDescriptor(
private val scriptOuterScope: () -> LexicalScope = resolveSession.storageManager.createLazyValue {
var outerScope = super.getOuterScope()
val outerScopeReceivers = implicitReceivers.let {
if (scriptDefinition().providedProperties.isEmpty()) {
if (scriptDefinition().legacyDefinition.providedProperties.isEmpty()) {
it
} else {
it + ScriptProvidedPropertiesDescriptor(this)
@@ -5,16 +5,10 @@
package org.jetbrains.kotlin.scripting.resolve
import com.intellij.openapi.application.ApplicationManager
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.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.definitions.KotlinScriptDefinition
import java.io.File
import kotlin.reflect.KClass
import kotlin.script.dependencies.ScriptContents
import kotlin.script.experimental.dependencies.DependenciesResolver
import kotlin.script.experimental.dependencies.DependenciesResolver.ResolveResult.Failure
@@ -22,43 +16,20 @@ import kotlin.script.experimental.dependencies.ScriptDependencies
import kotlin.script.experimental.dependencies.ScriptReport
class ScriptContentLoader(private val project: Project) {
fun getScriptContents(scriptDefinition: KotlinScriptDefinition, file: VirtualFile)
= BasicScriptContents(
file,
getAnnotations = { loadAnnotations(scriptDefinition, file) })
private fun loadAnnotations(scriptDefinition: KotlinScriptDefinition, file: VirtualFile): List<Annotation> {
val classLoader = scriptDefinition.template.java.classLoader
// TODO_R: report error on failure to load annotation class
return ApplicationManager.getApplication().runReadAction<List<Annotation>> {
getAnnotationEntries(file, project)
.mapNotNull { psiAnn ->
// TODO: consider advanced matching using semantic similar to actual resolving
scriptDefinition.acceptedAnnotations.find { ann ->
psiAnn.typeName.let { it == ann.simpleName || it == ann.qualifiedName }
}?.let {
@Suppress("UNCHECKED_CAST")
constructAnnotation(
psiAnn,
classLoader.loadClass(it.qualifiedName).kotlin as KClass<out Annotation>,
project
)
}
}
}
}
private fun getAnnotationEntries(file: VirtualFile, project: Project): Iterable<KtAnnotationEntry> {
val psiFile: PsiFile = PsiManager.getInstance(project).findFile(file)
?: throw IllegalArgumentException("Unable to load PSI from ${file.canonicalPath}")
return (psiFile as? KtFile)?.annotationEntries
?: throw IllegalArgumentException("Unable to extract kotlin annotations from ${file.name} (${file.fileType})")
}
fun getScriptContents(scriptDefinition: KotlinScriptDefinition, file: VirtualFile) =
makeScriptContents(
file,
scriptDefinition,
project,
scriptDefinition.template::class.java.classLoader
)
class BasicScriptContents(virtualFile: VirtualFile, getAnnotations: () -> Iterable<Annotation>) : ScriptContents {
override val file: File = File(virtualFile.path)
override val annotations: Iterable<Annotation> by lazy(LazyThreadSafetyMode.PUBLICATION) { getAnnotations() }
override val text: CharSequence? by lazy(LazyThreadSafetyMode.PUBLICATION) { virtualFile.inputStream.reader(charset = virtualFile.charset).readText() }
override val text: CharSequence? by lazy(LazyThreadSafetyMode.PUBLICATION) {
virtualFile.inputStream.reader(charset = virtualFile.charset).readText()
}
}
fun loadContentsAndResolveDependencies(
@@ -80,11 +51,11 @@ class ScriptContentLoader(private val project: Project) {
}
fun getEnvironment(scriptDef: KotlinScriptDefinition) =
(scriptDef as? KotlinScriptDefinitionFromAnnotatedTemplate)?.environment.orEmpty()
(scriptDef as? KotlinScriptDefinitionFromAnnotatedTemplate)?.environment.orEmpty()
}
fun ScriptDependencies.adjustByDefinition(
scriptDef: KotlinScriptDefinition
scriptDef: KotlinScriptDefinition
): ScriptDependencies {
val additionalClasspath = (scriptDef as? KotlinScriptDefinitionFromAnnotatedTemplate)?.templateClasspath ?: return this
if (additionalClasspath.isEmpty()) return this
@@ -42,7 +42,7 @@ class ScriptProvidedPropertiesDescriptor(script: LazyScriptDescriptor) :
override fun getUnsubstitutedMemberScope(): MemberScope = memberScope()
val properties: () -> List<ScriptProvidedPropertyDescriptor> = script.resolveSession.storageManager.createLazyValue {
script.scriptDefinition().providedProperties.mapNotNull { (name, type) ->
script.scriptDefinition().legacyDefinition.providedProperties.mapNotNull { (name, type) ->
script.findTypeDescriptor(type, Errors.MISSING_SCRIPT_PROVIDED_PROPERTY_CLASS)?.let {
name to it
}
@@ -0,0 +1,338 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.resolve
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.vfs.CharsetToolkit
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiManager
import com.intellij.testFramework.LightVirtualFile
import kotlinx.coroutines.runBlocking
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.definitions.KotlinScriptDefinition
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
import java.io.File
import java.net.URL
import kotlin.reflect.KClass
import kotlin.script.experimental.api.*
import kotlin.script.experimental.dependencies.AsyncDependenciesResolver
import kotlin.script.experimental.dependencies.DependenciesResolver
import kotlin.script.experimental.dependencies.ScriptDependencies
import kotlin.script.experimental.host.*
import kotlin.script.experimental.jvm.JvmGetScriptingClass
import kotlin.script.experimental.jvm.compat.mapToDiagnostics
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
import kotlin.script.experimental.jvm.impl.refineWith
import kotlin.script.experimental.jvm.impl.toClassPathOrEmpty
import kotlin.script.experimental.jvm.impl.toDependencies
import kotlin.script.experimental.jvm.jdkHome
import kotlin.script.experimental.jvm.jvm
internal fun VirtualFile.loadAnnotations(
acceptedAnnotations: List<KClass<out Annotation>>,
project: Project,
classLoader: ClassLoader?
): List<Annotation> =
// TODO_R: report error on failure to load annotation class
ApplicationManager.getApplication().runReadAction<List<Annotation>> {
this.getAnnotationEntries(project).construct(classLoader, acceptedAnnotations, project)
}
internal fun VirtualFile.getAnnotationEntries(project: Project): Iterable<KtAnnotationEntry> {
val psiFile: PsiFile = PsiManager.getInstance(project).findFile(this)
?: throw IllegalArgumentException("Unable to load PSI from $canonicalPath")
return (psiFile as? KtFile)?.annotationEntries
?: throw IllegalArgumentException("Unable to extract kotlin annotations from $name ($fileType)")
}
/**
* The implementation of the SourceCode for a script located in a virtual file
*/
open class VirtualFileScriptSource(val virtualFile: VirtualFile, private val preloadedText: String? = null) :
FileBasedScriptSource()
{
override val file: File get() = File(virtualFile.path)
override val externalLocation: URL get() = URL(virtualFile.url)
override val text: String by lazy { preloadedText ?: virtualFile.inputStream.bufferedReader().readText() }
override val name: String? get() = virtualFile.name
override val locationId: String? get() = virtualFile.path
override fun equals(other: Any?): Boolean =
this === other || (other as? VirtualFileScriptSource)?.let { virtualFile == it.virtualFile } == true
override fun hashCode(): Int = virtualFile.hashCode()
}
/**
* The implementation of the SourceCode for a script located in a KtFile
*/
open class KtFileScriptSource(val ktFile: KtFile, preloadedText: String? = null) :
VirtualFileScriptSource(ktFile.virtualFile ?: ktFile.originalFile.virtualFile, preloadedText) {
override val text: String by lazy { preloadedText ?: ktFile.text }
override val name: String? get() = ktFile.name
override fun equals(other: Any?): Boolean =
this === other
|| (other as? KtFileScriptSource)?.let { ktFile == it.ktFile } == true
|| super.equals(other)
override fun hashCode(): Int = ktFile.hashCode()
}
class ScriptLightVirtualFile(name: String, private val _path: String?, text: String) :
LightVirtualFile(
name,
KotlinLanguage.INSTANCE,
StringUtil.convertLineSeparators(text)
) {
init {
charset = CharsetToolkit.UTF8_CHARSET
}
override fun getPath(): String = _path ?: super.getPath()
override fun getCanonicalPath(): String? = path
}
abstract class ScriptCompilationConfigurationWrapper(val script: SourceCode) {
abstract val configuration: ScriptCompilationConfiguration?
@Deprecated("Use configuration collection instead")
abstract val legacyDependencies: ScriptDependencies?
// optimizing most common ops for the IDE
// TODO: consider dropping after complete migration
abstract val dependenciesClassPath: List<File>
abstract val dependenciesSources: List<File>
abstract val javaHome: File?
abstract val defaultImports: List<String>
abstract val importedScripts: List<File>
override fun equals(other: Any?): Boolean = script == (other as? ScriptCompilationConfigurationWrapper)?.script
override fun hashCode(): Int = script.hashCode()
class FromCompilationConfiguration(
script: SourceCode,
override val configuration: ScriptCompilationConfiguration?
) : ScriptCompilationConfigurationWrapper(script) {
// TODO: check whether implemented optimization for frequent calls makes sense here
override val dependenciesClassPath: List<File> by lazy {
configuration?.get(ScriptCompilationConfiguration.dependencies).toClassPathOrEmpty()
}
// TODO: check whether implemented optimization for frequent calls makes sense here
override val dependenciesSources: List<File> by lazy {
configuration?.get(ScriptCompilationConfiguration.ide.dependenciesSources).toClassPathOrEmpty()
}
override val javaHome: File?
get() = configuration?.get(ScriptCompilationConfiguration.hostConfiguration)?.get(ScriptingHostConfiguration.jvm.jdkHome)
override val defaultImports: List<String>
get() = configuration?.get(ScriptCompilationConfiguration.defaultImports).orEmpty()
override val importedScripts: List<File>
get() = configuration?.get(ScriptCompilationConfiguration.importScripts)
?.mapNotNull { (it as? FileBasedScriptSource)?.file }.orEmpty()
override val legacyDependencies: ScriptDependencies?
get() = configuration?.toDependencies(dependenciesClassPath)
override fun equals(other: Any?): Boolean =
super.equals(other) && other is FromCompilationConfiguration && configuration == other.configuration
override fun hashCode(): Int = super.hashCode() + 23 * (configuration?.hashCode() ?: 1)
}
class FromLegacy(
script: SourceCode,
override val legacyDependencies: ScriptDependencies?
) : ScriptCompilationConfigurationWrapper(script) {
override val dependenciesClassPath: List<File>
get() = legacyDependencies?.classpath.orEmpty()
override val dependenciesSources: List<File>
get() = legacyDependencies?.sources.orEmpty()
override val javaHome: File?
get() = legacyDependencies?.javaHome
override val defaultImports: List<String>
get() = legacyDependencies?.imports.orEmpty()
override val importedScripts: List<File>
get() = legacyDependencies?.scripts.orEmpty()
override val configuration: ScriptCompilationConfiguration?
get() = legacyDependencies?.let {
TODO("drop or implement")
}
override fun equals(other: Any?): Boolean =
super.equals(other) && other is FromLegacy && legacyDependencies == other.legacyDependencies
override fun hashCode(): Int = super.hashCode() + 31 * (legacyDependencies?.hashCode() ?: 1)
}
}
typealias ScriptCompilationConfigurationResult = ResultWithDiagnostics<ScriptCompilationConfigurationWrapper>
fun refineScriptCompilationConfiguration(
script: SourceCode,
definition: ScriptDefinition,
project: Project
): ScriptCompilationConfigurationResult {
val ktFileSource = script.toKtFileSource(definition, project)
val legacyDefinition = definition.asLegacyOrNull<KotlinScriptDefinition>()
if (legacyDefinition == null) {
val compilationConfiguration = definition.compilationConfiguration
val collectedData =
getScriptCollectedData(ktFileSource.ktFile, compilationConfiguration, project, definition.contextClassLoader)
return compilationConfiguration.refineWith(
compilationConfiguration[ScriptCompilationConfiguration.refineConfigurationOnAnnotations]?.handler, collectedData, script
).onSuccess {
it.refineWith(
compilationConfiguration[ScriptCompilationConfiguration.refineConfigurationBeforeCompiling]?.handler, collectedData, script
)
}.onSuccess {
ScriptCompilationConfigurationWrapper.FromCompilationConfiguration(ktFileSource, it).asSuccess()
}
} else {
val file = script.getVirtualFile(definition)
val scriptContents =
makeScriptContents(file, legacyDefinition, project, definition.contextClassLoader)
val environment = (legacyDefinition as? KotlinScriptDefinitionFromAnnotatedTemplate)?.environment.orEmpty()
val result: DependenciesResolver.ResolveResult = try {
val resolver = legacyDefinition.dependencyResolver
if (resolver is AsyncDependenciesResolver) {
// since the only known async resolver is gradle, the following logic is taken from AsyncScriptDependenciesLoader
// runBlocking is using there to avoid loading dependencies asynchronously
// because it leads to starting more than one gradle daemon in case of resolving dependencies in build.gradle.kts
// It is more efficient to use one hot daemon consistently than multiple daemon in parallel
runBlocking {
resolver.resolveAsync(scriptContents, environment)
}
} else {
resolver.resolve(scriptContents, environment)
}
} catch (e: Throwable) {
return makeFailureResult(e.asDiagnostics(severity = ScriptDiagnostic.Severity.FATAL))
}
return if (result is DependenciesResolver.ResolveResult.Failure)
makeFailureResult(
result.reports.mapToDiagnostics()
)
else
ScriptCompilationConfigurationWrapper.FromLegacy(
ktFileSource,
result.dependencies?.adjustByDefinition(definition.legacyDefinition)
).asSuccess(result.reports.mapToDiagnostics())
}
}
internal fun makeScriptContents(
file: VirtualFile,
legacyDefinition: KotlinScriptDefinition,
project: Project,
classLoader: ClassLoader?
): ScriptContentLoader.BasicScriptContents =
ScriptContentLoader.BasicScriptContents(
file,
getAnnotations = {
file.loadAnnotations(legacyDefinition.acceptedAnnotations, project, classLoader)
})
fun SourceCode.getVirtualFile(definition: ScriptDefinition): VirtualFile {
if (this is VirtualFileScriptSource) return virtualFile
if (this is KtFileScriptSource) {
val vFile = virtualFile
if (vFile != null) return vFile
}
if (this is FileScriptSource) {
val vFile = LocalFileSystem.getInstance().findFileByIoFile(file)
if (vFile != null) return vFile
}
val scriptName = name ?: "script.${definition.fileExtension}"
val scriptPath = when (this) {
is FileScriptSource -> file.path
is ExternalSourceCode -> externalLocation.toString()
else -> null
}
val scriptText = definition.asLegacyOrNull<KotlinScriptDefinition>()?.let { text }
?: getMergedScriptText(this, definition.compilationConfiguration)
return ScriptLightVirtualFile(scriptName, scriptPath, scriptText)
}
fun SourceCode.getKtFile(definition: ScriptDefinition, project: Project): KtFile =
if (this is KtFileScriptSource) ktFile
else {
val file = getVirtualFile(definition)
ApplicationManager.getApplication().runReadAction<KtFile> {
val psiFile: PsiFile = PsiManager.getInstance(project).findFile(file)
?: throw IllegalArgumentException("Unable to load PSI from ${file.path}")
(psiFile as? KtFile)
?: throw IllegalArgumentException("Not a kotlin file ${file.path} (${file.fileType})")
}
}
fun SourceCode.toKtFileSource(definition: ScriptDefinition, project: Project): KtFileScriptSource =
if (this is KtFileScriptSource) this
else {
KtFileScriptSource(this.getKtFile(definition, project))
}
fun getScriptCollectedData(
scriptFile: KtFile,
compilationConfiguration: ScriptCompilationConfiguration,
project: Project,
contextClassLoader: ClassLoader?
): ScriptCollectedData {
val hostConfiguration =
compilationConfiguration[ScriptCompilationConfiguration.hostConfiguration] ?: defaultJvmScriptingHostConfiguration
val getScriptingClass = hostConfiguration[ScriptingHostConfiguration.getScriptingClass]
val jvmGetScriptingClass = (getScriptingClass as? JvmGetScriptingClass)
?: throw IllegalArgumentException("Expecting JvmGetScriptingClass in the hostConfiguration[getScriptingClass], got $getScriptingClass")
val acceptedAnnotations =
compilationConfiguration[ScriptCompilationConfiguration.refineConfigurationOnAnnotations]?.annotations?.mapNotNull {
jvmGetScriptingClass(it, contextClassLoader, hostConfiguration) as? KClass<Annotation> // TODO errors
}.orEmpty()
val annotations = scriptFile.annotationEntries.construct(contextClassLoader, acceptedAnnotations, project)
return ScriptCollectedData(
mapOf(
ScriptCollectedData.foundAnnotations to annotations
)
)
}
private fun Iterable<KtAnnotationEntry>.construct(
classLoader: ClassLoader?, acceptedAnnotations: List<KClass<out Annotation>>, project: Project
): List<Annotation> =
mapNotNull { psiAnn ->
// TODO: consider advanced matching using semantic similar to actual resolving
acceptedAnnotations.find { ann ->
psiAnn.typeName.let { it == ann.simpleName || it == ann.qualifiedName }
}?.let {
@Suppress("UNCHECKED_CAST")
(constructAnnotation(
psiAnn,
(classLoader ?: ClassLoader.getSystemClassLoader()).loadClass(it.qualifiedName).kotlin as KClass<out Annotation>,
project
))
}
}