Implement imported scripts evaluation and construction
This commit is contained in:
@@ -242,7 +242,9 @@ class KotlinCoreEnvironment private constructor(
|
|||||||
sourceFiles += createKtFiles(project)
|
sourceFiles += createKtFiles(project)
|
||||||
|
|
||||||
if (scriptDefinitionProvider != null) {
|
if (scriptDefinitionProvider != null) {
|
||||||
sourceFiles += collectRequiredSourcesFromDependencies(configuration, project, sourceFiles)
|
val (classpath, newSources, _) = collectScriptsCompilationDependencies(configuration, project, sourceFiles)
|
||||||
|
configuration.addJvmClasspathRoots(classpath)
|
||||||
|
sourceFiles += newSources
|
||||||
}
|
}
|
||||||
sourceFiles.sortBy { it.virtualFile.path }
|
sourceFiles.sortBy { it.virtualFile.path }
|
||||||
|
|
||||||
@@ -703,29 +705,58 @@ private fun createSourceFilesFromSourceRoots(
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
fun collectRequiredSourcesFromDependencies(
|
data class ScriptsCompilationDependencies(
|
||||||
|
val classpath: List<File>,
|
||||||
|
val sources: List<KtFile>,
|
||||||
|
val sourceDependencies: List<SourceDependencies>
|
||||||
|
) {
|
||||||
|
data class SourceDependencies(
|
||||||
|
val script: KtFile,
|
||||||
|
val sourceDependencies: List<KtFile>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// recursively collect dependencies from initial and imported scripts
|
||||||
|
fun collectScriptsCompilationDependencies(
|
||||||
configuration: CompilerConfiguration,
|
configuration: CompilerConfiguration,
|
||||||
project: Project,
|
project: Project,
|
||||||
initialSources: Iterable<KtFile>
|
initialSources: Iterable<KtFile>
|
||||||
): List<KtFile> {
|
): ScriptsCompilationDependencies{
|
||||||
|
val collectedClassPath = ArrayList<File>()
|
||||||
val collectedSources = ArrayList<KtFile>()
|
val collectedSources = ArrayList<KtFile>()
|
||||||
val importsProvider = ScriptDependenciesProvider.getInstance(project)
|
val collectedSourceDependencies = ArrayList<ScriptsCompilationDependencies.SourceDependencies>()
|
||||||
var remainingSources = initialSources.sortedBy { it.virtualFile.path }
|
var remainingSources = initialSources
|
||||||
val knownSourcePaths = HashSet<String>()
|
val knownSourcePaths = HashSet<String>()
|
||||||
|
val importsProvider = ScriptDependenciesProvider.getInstance(project)
|
||||||
while (true) {
|
while (true) {
|
||||||
val dependencies = remainingSources.mapNotNull(importsProvider::getScriptDependencies)
|
val newRemainingSources = ArrayList<KtFile>()
|
||||||
configuration.addJvmClasspathRoots(dependencies.flatMap { it.classpath }.distinctBy { it.absolutePath })
|
for (source in remainingSources) {
|
||||||
val addedSourceRoots = dependencies.flatMap { it.scripts.map { KotlinSourceRoot(it.path, false) } }
|
val dependencies = importsProvider.getScriptDependencies(source)
|
||||||
val addedSources = createSourceFilesFromSourceRoots(configuration, project, addedSourceRoots)
|
if (dependencies != null) {
|
||||||
val newSources = if (addedSources.isEmpty()) emptyList() else {
|
collectedClassPath.addAll(dependencies.classpath)
|
||||||
remainingSources.forEach { knownSourcePaths.add(it.virtualFile.path) }
|
|
||||||
addedSources.filterNot { knownSourcePaths.contains(it.virtualFile.path) }
|
val sourceDependenciesRoots = dependencies.scripts.map { KotlinSourceRoot(it.path, false) }
|
||||||
|
val sourceDependencies = createSourceFilesFromSourceRoots(configuration, project, sourceDependenciesRoots)
|
||||||
|
if (sourceDependencies.isNotEmpty()) {
|
||||||
|
collectedSourceDependencies.add(ScriptsCompilationDependencies.SourceDependencies(source, sourceDependencies))
|
||||||
|
|
||||||
|
val newSources = sourceDependencies.filterNot { knownSourcePaths.contains(it.virtualFile.path) }
|
||||||
|
for (newSource in newSources) {
|
||||||
|
collectedSources.add(newSource)
|
||||||
|
newRemainingSources.add(newSource)
|
||||||
|
knownSourcePaths.add(newSource.virtualFile.path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (newSources.isEmpty()) break
|
if (newRemainingSources.isEmpty()) break
|
||||||
else {
|
else {
|
||||||
collectedSources += newSources
|
remainingSources = newRemainingSources
|
||||||
remainingSources = newSources
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return collectedSources
|
return ScriptsCompilationDependencies(
|
||||||
|
collectedClassPath.distinctBy { it.absolutePath },
|
||||||
|
collectedSources,
|
||||||
|
collectedSourceDependencies
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -233,4 +233,7 @@ interface CompiledScript<out ScriptBase : Any> {
|
|||||||
* @return result wrapper, if successful - with loaded KClass
|
* @return result wrapper, if successful - with loaded KClass
|
||||||
*/
|
*/
|
||||||
suspend fun getClass(scriptEvaluationConfiguration: ScriptEvaluationConfiguration?): ResultWithDiagnostics<KClass<*>>
|
suspend fun getClass(scriptEvaluationConfiguration: ScriptEvaluationConfiguration?): ResultWithDiagnostics<KClass<*>>
|
||||||
|
|
||||||
|
val importedScripts: List<CompiledScript<*>>?
|
||||||
|
get() = null
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -19,7 +19,8 @@ import kotlin.script.experimental.jvmhost.baseClassLoader
|
|||||||
class KJvmCompiledScript<out ScriptBase : Any>(
|
class KJvmCompiledScript<out ScriptBase : Any>(
|
||||||
compilationConfiguration: ScriptCompilationConfiguration,
|
compilationConfiguration: ScriptCompilationConfiguration,
|
||||||
generationState: GenerationState,
|
generationState: GenerationState,
|
||||||
private var scriptClassFQName: String
|
private var scriptClassFQName: String,
|
||||||
|
override val importedScripts: List<CompiledScript<*>>? = null
|
||||||
) : CompiledScript<ScriptBase>, Serializable {
|
) : CompiledScript<ScriptBase>, Serializable {
|
||||||
|
|
||||||
private var _compilationConfiguration: ScriptCompilationConfiguration? = compilationConfiguration
|
private var _compilationConfiguration: ScriptCompilationConfiguration? = compilationConfiguration
|
||||||
|
|||||||
+83
-13
@@ -4,6 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
package kotlin.script.experimental.jvmhost.impl
|
package kotlin.script.experimental.jvmhost.impl
|
||||||
|
|
||||||
|
import com.intellij.openapi.fileTypes.LanguageFileType
|
||||||
import com.intellij.openapi.util.Disposer
|
import com.intellij.openapi.util.Disposer
|
||||||
import com.intellij.openapi.util.text.StringUtil
|
import com.intellij.openapi.util.text.StringUtil
|
||||||
import com.intellij.openapi.vfs.CharsetToolkit
|
import com.intellij.openapi.vfs.CharsetToolkit
|
||||||
@@ -26,12 +27,20 @@ import org.jetbrains.kotlin.codegen.CompilationErrorHandler
|
|||||||
import org.jetbrains.kotlin.codegen.KotlinCodegenFacade
|
import org.jetbrains.kotlin.codegen.KotlinCodegenFacade
|
||||||
import org.jetbrains.kotlin.codegen.state.GenerationState
|
import org.jetbrains.kotlin.codegen.state.GenerationState
|
||||||
import org.jetbrains.kotlin.config.*
|
import org.jetbrains.kotlin.config.*
|
||||||
|
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||||
import org.jetbrains.kotlin.idea.KotlinLanguage
|
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||||
|
import org.jetbrains.kotlin.name.Name
|
||||||
|
import org.jetbrains.kotlin.name.NameUtils
|
||||||
import org.jetbrains.kotlin.parsing.KotlinParserDefinition
|
import org.jetbrains.kotlin.parsing.KotlinParserDefinition
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
|
import org.jetbrains.kotlin.psi.KtScript
|
||||||
import org.jetbrains.kotlin.script.KotlinScriptDefinition
|
import org.jetbrains.kotlin.script.KotlinScriptDefinition
|
||||||
import org.jetbrains.kotlin.script.util.KotlinJars
|
import org.jetbrains.kotlin.script.util.KotlinJars
|
||||||
|
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
import kotlin.reflect.KType
|
||||||
|
import kotlin.reflect.full.starProjectedType
|
||||||
import kotlin.script.experimental.api.*
|
import kotlin.script.experimental.api.*
|
||||||
import kotlin.script.experimental.dependencies.DependenciesResolver
|
import kotlin.script.experimental.dependencies.DependenciesResolver
|
||||||
import kotlin.script.experimental.host.ScriptingHostConfiguration
|
import kotlin.script.experimental.host.ScriptingHostConfiguration
|
||||||
@@ -131,9 +140,14 @@ class KJvmCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) : KJvm
|
|||||||
val psiFile: KtFile = psiFileFactory.trySetupPsiForFile(virtualFile, KotlinLanguage.INSTANCE, true, false) as KtFile?
|
val psiFile: KtFile = psiFileFactory.trySetupPsiForFile(virtualFile, KotlinLanguage.INSTANCE, true, false) as KtFile?
|
||||||
?: return failure("Unable to make PSI file from script".asErrorDiagnostics())
|
?: return failure("Unable to make PSI file from script".asErrorDiagnostics())
|
||||||
|
|
||||||
val sourceFiles = arrayListOf(psiFile).also {
|
val ktScript = psiFile.declarations.firstIsInstanceOrNull<KtScript>()
|
||||||
it.addAll(collectRequiredSourcesFromDependencies(kotlinCompilerConfiguration, environment.project, it))
|
?: return failure("Not a script file".asErrorDiagnostics())
|
||||||
}
|
|
||||||
|
val sourceFiles = arrayListOf(psiFile)
|
||||||
|
val (classpath, newSources, sourceDependencies) =
|
||||||
|
collectScriptsCompilationDependencies(kotlinCompilerConfiguration, environment.project, sourceFiles)
|
||||||
|
kotlinCompilerConfiguration.addJvmClasspathRoots(classpath)
|
||||||
|
sourceFiles.addAll(newSources)
|
||||||
|
|
||||||
analyzerWithCompilerReport.analyzeAndReport(sourceFiles) {
|
analyzerWithCompilerReport.analyzeAndReport(sourceFiles) {
|
||||||
val project = environment.project
|
val project = environment.project
|
||||||
@@ -160,7 +174,16 @@ class KJvmCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) : KJvm
|
|||||||
).build()
|
).build()
|
||||||
KotlinCodegenFacade.compileCorrectFiles(generationState, CompilationErrorHandler.THROW_EXCEPTION)
|
KotlinCodegenFacade.compileCorrectFiles(generationState, CompilationErrorHandler.THROW_EXCEPTION)
|
||||||
|
|
||||||
val res = KJvmCompiledScript<Any>(updatedConfiguration, generationState, scriptFileName.capitalize())
|
fun makeCompiledScript(script: KtScript): KJvmCompiledScript<*> {
|
||||||
|
|
||||||
|
val importedScripts = sourceDependencies.find { it.script == script }?.sourceDependencies?.mapNotNull { sourceFile ->
|
||||||
|
sourceFile.declarations.firstIsInstanceOrNull<KtScript>()?.let { makeCompiledScript(it) }
|
||||||
|
} ?: emptyList()
|
||||||
|
|
||||||
|
return KJvmCompiledScript<Any>(updatedConfiguration, generationState, script.fqName.asString(), importedScripts)
|
||||||
|
}
|
||||||
|
|
||||||
|
val res = makeCompiledScript(ktScript)
|
||||||
|
|
||||||
return ResultWithDiagnostics.Success(res, messageCollector.diagnostics)
|
return ResultWithDiagnostics.Success(res, messageCollector.diagnostics)
|
||||||
} catch (ex: Throwable) {
|
} catch (ex: Throwable) {
|
||||||
@@ -203,17 +226,37 @@ internal class ScriptDiagnosticsMessageCollector : MessageCollector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// A bridge to the current scripting
|
// A bridge to the current scripting
|
||||||
|
// mostly copies functionality from KotlinScriptDefinitionAdapterFromNewAPI[Base]
|
||||||
|
// reusing it requires structural changes that doesn't seem justified now, since the internals of the scripting should be reworked soon anyway
|
||||||
|
// TODO: either finish refactoring of the scripting internals or reuse KotlinScriptDefinitionAdapterFromNewAPI[BAse] here
|
||||||
internal class BridgeScriptDefinition(
|
internal class BridgeScriptDefinition(
|
||||||
scriptCompilationConfiguration: ScriptCompilationConfiguration,
|
val scriptCompilationConfiguration: ScriptCompilationConfiguration,
|
||||||
hostConfiguration: ScriptingHostConfiguration,
|
val hostConfiguration: ScriptingHostConfiguration,
|
||||||
updateClasspath: (List<File>) -> Unit
|
updateClasspath: (List<File>) -> Unit
|
||||||
) : KotlinScriptDefinition(
|
) : KotlinScriptDefinition(Any::class) {
|
||||||
hostConfiguration.getScriptingClass(
|
|
||||||
scriptCompilationConfiguration.getOrError(ScriptCompilationConfiguration.baseClass),
|
val baseClass: KClass<*> by lazy(LazyThreadSafetyMode.PUBLICATION) {
|
||||||
BridgeScriptDefinition::class
|
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 fileExtension: String
|
||||||
|
get() = scriptCompilationConfiguration[ScriptCompilationConfiguration.fileExtension] ?: super.fileExtension
|
||||||
|
|
||||||
override val acceptedAnnotations = run {
|
override val acceptedAnnotations = run {
|
||||||
val cl = this::class.java.classLoader
|
val cl = this::class.java.classLoader
|
||||||
scriptCompilationConfiguration[ScriptCompilationConfiguration.refineConfigurationOnAnnotations]?.annotations
|
scriptCompilationConfiguration[ScriptCompilationConfiguration.refineConfigurationOnAnnotations]?.annotations
|
||||||
@@ -221,6 +264,33 @@ internal class BridgeScriptDefinition(
|
|||||||
?: emptyList()
|
?: emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 dependencyResolver: DependenciesResolver =
|
override val dependencyResolver: DependenciesResolver =
|
||||||
BridgeDependenciesResolver(scriptCompilationConfiguration, updateClasspath)
|
BridgeDependenciesResolver(scriptCompilationConfiguration, updateClasspath)
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-6
@@ -41,14 +41,34 @@ open class BasicJvmScriptEvaluator : ScriptEvaluator {
|
|||||||
scriptEvaluationConfiguration?.get(ScriptEvaluationConfiguration.implicitReceivers)?.let {
|
scriptEvaluationConfiguration?.get(ScriptEvaluationConfiguration.implicitReceivers)?.let {
|
||||||
args.addAll(it)
|
args.addAll(it)
|
||||||
}
|
}
|
||||||
scriptEvaluationConfiguration?.get(ScriptEvaluationConfiguration.constructorArgs)?.let {
|
val importedScriptsReports = ArrayList<ScriptDiagnostic>()
|
||||||
args.addAll(it)
|
var importedScriptsLoadingFailed = false
|
||||||
|
compiledScript.importedScripts?.forEach {
|
||||||
|
val importedScriptEvalRes = invoke(it, scriptEvaluationConfiguration)
|
||||||
|
importedScriptsReports.addAll(importedScriptEvalRes.reports)
|
||||||
|
when (importedScriptEvalRes) {
|
||||||
|
is ResultWithDiagnostics.Success -> {
|
||||||
|
args.add(importedScriptEvalRes.value)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
importedScriptsLoadingFailed = true
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val ctor = scriptClass.java.constructors.single()
|
if (importedScriptsLoadingFailed) {
|
||||||
val instance = ctor.newInstance(*args.toArray())
|
ResultWithDiagnostics.Failure(importedScriptsReports)
|
||||||
|
} else {
|
||||||
|
|
||||||
// TODO: fix result value
|
scriptEvaluationConfiguration?.get(ScriptEvaluationConfiguration.constructorArgs)?.let {
|
||||||
ResultWithDiagnostics.Success(EvaluationResult(ResultValue.Value("", instance, ""), scriptEvaluationConfiguration))
|
args.addAll(it)
|
||||||
|
}
|
||||||
|
val ctor = scriptClass.java.constructors.single()
|
||||||
|
val instance = ctor.newInstance(*args.toArray())
|
||||||
|
|
||||||
|
// TODO: fix result value
|
||||||
|
ResultWithDiagnostics.Success(EvaluationResult(ResultValue.Value("", instance, ""), scriptEvaluationConfiguration))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
|
|||||||
+2
-2
@@ -25,9 +25,9 @@ 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
|
// 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 class KotlinScriptDefinitionAdapterFromNewAPIBase : KotlinScriptDefinition(Any::class) {
|
||||||
|
|
||||||
protected abstract val scriptCompilationConfiguration: ScriptCompilationConfiguration
|
abstract val scriptCompilationConfiguration: ScriptCompilationConfiguration
|
||||||
|
|
||||||
protected abstract val hostConfiguration: ScriptingHostConfiguration
|
abstract val hostConfiguration: ScriptingHostConfiguration
|
||||||
|
|
||||||
open val baseClass: KClass<*> by lazy(LazyThreadSafetyMode.PUBLICATION) {
|
open val baseClass: KClass<*> by lazy(LazyThreadSafetyMode.PUBLICATION) {
|
||||||
getScriptingClass(scriptCompilationConfiguration.getOrError(ScriptCompilationConfiguration.baseClass))
|
getScriptingClass(scriptCompilationConfiguration.getOrError(ScriptCompilationConfiguration.baseClass))
|
||||||
|
|||||||
Reference in New Issue
Block a user