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) 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
} }
@@ -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
@@ -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
)
} }
@@ -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) {
@@ -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))