Implement imported scripts evaluation and construction

This commit is contained in:
Ilya Chernikov
2018-12-07 21:37:37 +01:00
parent 19a969a521
commit fce68102bd
6 changed files with 163 additions and 38 deletions
@@ -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
}
@@ -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
@@ -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
)
}
@@ -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) {
@@ -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))