Implement imported scripts evaluation and construction
This commit is contained in:
@@ -242,7 +242,9 @@ class KotlinCoreEnvironment private constructor(
|
||||
sourceFiles += createKtFiles(project)
|
||||
|
||||
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 }
|
||||
|
||||
@@ -703,29 +705,58 @@ private fun createSourceFilesFromSourceRoots(
|
||||
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,
|
||||
project: Project,
|
||||
initialSources: Iterable<KtFile>
|
||||
): List<KtFile> {
|
||||
): ScriptsCompilationDependencies{
|
||||
val collectedClassPath = ArrayList<File>()
|
||||
val collectedSources = ArrayList<KtFile>()
|
||||
val importsProvider = ScriptDependenciesProvider.getInstance(project)
|
||||
var remainingSources = initialSources.sortedBy { it.virtualFile.path }
|
||||
val collectedSourceDependencies = ArrayList<ScriptsCompilationDependencies.SourceDependencies>()
|
||||
var remainingSources = initialSources
|
||||
val knownSourcePaths = HashSet<String>()
|
||||
val importsProvider = ScriptDependenciesProvider.getInstance(project)
|
||||
while (true) {
|
||||
val dependencies = remainingSources.mapNotNull(importsProvider::getScriptDependencies)
|
||||
configuration.addJvmClasspathRoots(dependencies.flatMap { it.classpath }.distinctBy { it.absolutePath })
|
||||
val addedSourceRoots = dependencies.flatMap { it.scripts.map { KotlinSourceRoot(it.path, false) } }
|
||||
val addedSources = createSourceFilesFromSourceRoots(configuration, project, addedSourceRoots)
|
||||
val newSources = if (addedSources.isEmpty()) emptyList() else {
|
||||
remainingSources.forEach { knownSourcePaths.add(it.virtualFile.path) }
|
||||
addedSources.filterNot { knownSourcePaths.contains(it.virtualFile.path) }
|
||||
val newRemainingSources = ArrayList<KtFile>()
|
||||
for (source in remainingSources) {
|
||||
val dependencies = importsProvider.getScriptDependencies(source)
|
||||
if (dependencies != null) {
|
||||
collectedClassPath.addAll(dependencies.classpath)
|
||||
|
||||
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 {
|
||||
collectedSources += newSources
|
||||
remainingSources = newSources
|
||||
remainingSources = newRemainingSources
|
||||
}
|
||||
}
|
||||
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
|
||||
*/
|
||||
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>(
|
||||
compilationConfiguration: ScriptCompilationConfiguration,
|
||||
generationState: GenerationState,
|
||||
private var scriptClassFQName: String
|
||||
private var scriptClassFQName: String,
|
||||
override val importedScripts: List<CompiledScript<*>>? = null
|
||||
) : CompiledScript<ScriptBase>, Serializable {
|
||||
|
||||
private var _compilationConfiguration: ScriptCompilationConfiguration? = compilationConfiguration
|
||||
|
||||
+83
-13
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
package kotlin.script.experimental.jvmhost.impl
|
||||
|
||||
import com.intellij.openapi.fileTypes.LanguageFileType
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
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.state.GenerationState
|
||||
import org.jetbrains.kotlin.config.*
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
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.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtScript
|
||||
import org.jetbrains.kotlin.script.KotlinScriptDefinition
|
||||
import org.jetbrains.kotlin.script.util.KotlinJars
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||
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.dependencies.DependenciesResolver
|
||||
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?
|
||||
?: return failure("Unable to make PSI file from script".asErrorDiagnostics())
|
||||
|
||||
val sourceFiles = arrayListOf(psiFile).also {
|
||||
it.addAll(collectRequiredSourcesFromDependencies(kotlinCompilerConfiguration, environment.project, it))
|
||||
}
|
||||
val ktScript = psiFile.declarations.firstIsInstanceOrNull<KtScript>()
|
||||
?: 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) {
|
||||
val project = environment.project
|
||||
@@ -160,7 +174,16 @@ class KJvmCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) : KJvm
|
||||
).build()
|
||||
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)
|
||||
} catch (ex: Throwable) {
|
||||
@@ -203,17 +226,37 @@ internal class ScriptDiagnosticsMessageCollector : MessageCollector {
|
||||
}
|
||||
|
||||
// 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(
|
||||
scriptCompilationConfiguration: ScriptCompilationConfiguration,
|
||||
hostConfiguration: ScriptingHostConfiguration,
|
||||
val scriptCompilationConfiguration: ScriptCompilationConfiguration,
|
||||
val hostConfiguration: ScriptingHostConfiguration,
|
||||
updateClasspath: (List<File>) -> Unit
|
||||
) : KotlinScriptDefinition(
|
||||
hostConfiguration.getScriptingClass(
|
||||
scriptCompilationConfiguration.getOrError(ScriptCompilationConfiguration.baseClass),
|
||||
BridgeScriptDefinition::class
|
||||
)
|
||||
) {
|
||||
) : KotlinScriptDefinition(Any::class) {
|
||||
|
||||
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 fileExtension: String
|
||||
get() = scriptCompilationConfiguration[ScriptCompilationConfiguration.fileExtension] ?: super.fileExtension
|
||||
|
||||
override val acceptedAnnotations = run {
|
||||
val cl = this::class.java.classLoader
|
||||
scriptCompilationConfiguration[ScriptCompilationConfiguration.refineConfigurationOnAnnotations]?.annotations
|
||||
@@ -221,6 +264,33 @@ internal class BridgeScriptDefinition(
|
||||
?: 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 =
|
||||
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 {
|
||||
args.addAll(it)
|
||||
}
|
||||
scriptEvaluationConfiguration?.get(ScriptEvaluationConfiguration.constructorArgs)?.let {
|
||||
args.addAll(it)
|
||||
val importedScriptsReports = ArrayList<ScriptDiagnostic>()
|
||||
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()
|
||||
val instance = ctor.newInstance(*args.toArray())
|
||||
if (importedScriptsLoadingFailed) {
|
||||
ResultWithDiagnostics.Failure(importedScriptsReports)
|
||||
} else {
|
||||
|
||||
// TODO: fix result value
|
||||
ResultWithDiagnostics.Success(EvaluationResult(ResultValue.Value("", instance, ""), scriptEvaluationConfiguration))
|
||||
scriptEvaluationConfiguration?.get(ScriptEvaluationConfiguration.constructorArgs)?.let {
|
||||
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) {
|
||||
|
||||
+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
|
||||
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) {
|
||||
getScriptingClass(scriptCompilationConfiguration.getOrError(ScriptCompilationConfiguration.baseClass))
|
||||
|
||||
Reference in New Issue
Block a user