Prepare scripting infrastructure for REPL:

- refactor script compiler to simplify extending it for repl
- add repl snippet compilation functions to the new scripting compiler
- extract util functions into appropriate files
- extract repl part into separate class
- extract bridge definition and related definitions into separate file
This commit is contained in:
Ilya Chernikov
2019-04-28 18:38:24 +02:00
parent a4c049d26e
commit 89006f16cd
11 changed files with 1010 additions and 534 deletions
@@ -61,6 +61,10 @@ interface IReplStageState<T> {
fun <StateT : IReplStageState<*>> asState(target: Class<out StateT>): StateT =
if (target.isAssignableFrom(this::class.java)) this as StateT
else throw IllegalArgumentException("$this is not an expected instance of IReplStageState")
fun dispose() {
history.reset()
}
}
@@ -161,3 +161,11 @@ fun <R> ResultWithDiagnostics<R>.resultOrNull(): R? = when (this) {
is ResultWithDiagnostics.Success<R> -> value
else -> null
}
/**
* Extracts the result value from the receiver wrapper or run non-returning lambda if receiver represents a Failure
*/
inline fun <R> ResultWithDiagnostics<R>.resultOr(body: (ResultWithDiagnostics.Failure) -> Nothing): R = when (this) {
is ResultWithDiagnostics.Success<R> -> value
else -> body(this as ResultWithDiagnostics.Failure)
}
@@ -0,0 +1,31 @@
/*
* 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 kotlin.script.experimental.api
import java.io.Serializable
const val REPL_SNIPPET_FIRST_NO = 1
const val REPL_Snippet_FIRST_GEN = 1
interface ReplSnippetId : Serializable, Comparable<ReplSnippetId> {
val no: Int
val generation: Int
}
data class ReplSnippetIdImpl(override val no: Int, override val generation: Int, private val codeHash: Int) : ReplSnippetId, Serializable {
constructor(no: Int, generation: Int, code: SourceCode) : this(no, generation, code.text.hashCode())
override fun compareTo(other: ReplSnippetId): Int = (other as? ReplSnippetIdImpl)?.let { otherId ->
no.compareTo(otherId.no).takeIf { it != 0 }
?: generation.compareTo(otherId.generation).takeIf { it != 0 }
?: codeHash.compareTo(otherId.codeHash)
} ?: -1
companion object {
private val serialVersionUID: Long = 1L
}
}
@@ -0,0 +1,145 @@
/*
* 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 kotlin.script.experimental.jvmhost.impl
import com.intellij.openapi.fileTypes.LanguageFileType
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.name.NameUtils
import org.jetbrains.kotlin.psi.KtScript
import org.jetbrains.kotlin.scripting.definitions.KotlinScriptDefinition
import kotlin.reflect.KClass
import kotlin.reflect.KType
import kotlin.reflect.full.starProjectedType
import kotlin.script.dependencies.ScriptContents
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.impl.BridgeDependenciesResolver
import kotlin.script.experimental.util.getOrError
// 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
// NOTE: since KotlinScriptDefinition is not designed to separate static (script definition related) and dynamic (actual script compilation
// configuration) parameters, the implementation is quite hacky, especially for cases as REPL
// TODO: finish refactoring and replace KotlinScriptDefinition with right abstractions
internal class BridgeScriptDefinition(
val scriptCompilationConfiguration: ScriptCompilationConfiguration,
val hostConfiguration: ScriptingHostConfiguration,
dynamicState: BridgeScriptDefinitionDynamicState
) : 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
?.map { (cl.loadClass(it.typeName) as Class<out Annotation>).kotlin }
?: 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,
dynamicState::updateConfiguration,
dynamicState::getScriptSource
)
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
)
}
// TODO: consider synchronization, since it is mutable (or finish the refactoring after all)
internal class BridgeScriptDefinitionDynamicState {
private val _sources = linkedSetOf<SourceCode>()
private val _configurations = hashMapOf<SourceCode, ScriptCompilationConfiguration>()
private var _baseScriptCompilationConfiguration: ScriptCompilationConfiguration? = null
val sources: Set<SourceCode> get() = _sources
val configurations: Map<SourceCode, ScriptCompilationConfiguration> get() = _configurations
val baseScriptCompilationConfiguration: ScriptCompilationConfiguration get() = _baseScriptCompilationConfiguration!!
val mainScript: SourceCode get() = sources.first()
val mainScriptCompilationConfiguration: ScriptCompilationConfiguration
get() = configurations[mainScript] ?: baseScriptCompilationConfiguration
fun configureFor(script: SourceCode, scriptCompilationConfiguration: ScriptCompilationConfiguration) {
_sources.clear()
_sources.add(script)
_configurations.clear()
_baseScriptCompilationConfiguration = scriptCompilationConfiguration
}
fun updateConfiguration(script: SourceCode, updatedConfiguration: ScriptCompilationConfiguration) {
_configurations[script] = updatedConfiguration
updatedConfiguration[ScriptCompilationConfiguration.importScripts]?.let {
_sources.addAll(it)
}
}
fun getScriptSource(scriptContents: ScriptContents): SourceCode? {
val name = scriptContents.file?.name
return sources.find {
// TODO: consider using merged text (likely should be cached)
// on the other hand it may become obsolete when scripting internals will be redesigned properly
(name != null && name == it.scriptFileName(
sources.first(),
mainScriptCompilationConfiguration
)) || it.text == scriptContents.text
}
}
}
@@ -4,575 +4,88 @@
*/
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
import com.intellij.psi.PsiFileFactory
import com.intellij.psi.impl.PsiFileFactoryImpl
import com.intellij.testFramework.LightVirtualFile
import org.jetbrains.kotlin.analyzer.AnalysisResult
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.arguments.Argument
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
import org.jetbrains.kotlin.cli.common.arguments.parseCommandLineArguments
import org.jetbrains.kotlin.cli.common.environment.setIdeaIoUseFallback
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.common.setupCommonArguments
import org.jetbrains.kotlin.cli.jvm.*
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.compiler.NoScopeRecordCliBindingTrace
import org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM
import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot
import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots
import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots
import org.jetbrains.kotlin.codegen.ClassBuilderFactories
import org.jetbrains.kotlin.codegen.CompilationErrorHandler
import org.jetbrains.kotlin.codegen.KotlinCodegenFacade
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
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.config.CompilerConfiguration
import org.jetbrains.kotlin.config.languageVersionSettings
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtScript
import org.jetbrains.kotlin.scripting.compiler.plugin.ScriptingCompilerConfigurationComponentRegistrar
import org.jetbrains.kotlin.scripting.configuration.ScriptingConfigurationKeys
import org.jetbrains.kotlin.scripting.definitions.KotlinScriptDefinition
import org.jetbrains.kotlin.scripting.dependencies.ScriptsCompilationDependencies
import org.jetbrains.kotlin.scripting.dependencies.collectScriptsCompilationDependencies
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import java.util.*
import kotlin.reflect.KClass
import kotlin.reflect.KMutableProperty1
import kotlin.reflect.KType
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.starProjectedType
import kotlin.script.dependencies.ScriptContents
import kotlin.script.experimental.api.*
import kotlin.script.experimental.dependencies.DependenciesResolver
import kotlin.script.experimental.host.FileScriptSource
import kotlin.script.experimental.host.ScriptingHostConfiguration
import kotlin.script.experimental.host.getMergedScriptText
import kotlin.script.experimental.host.getScriptingClass
import kotlin.script.experimental.jvm.JvmDependency
import kotlin.script.experimental.jvm.impl.BridgeDependenciesResolver
import kotlin.script.experimental.jvm.impl.KJvmCompiledScript
import kotlin.script.experimental.jvm.jdkHome
import kotlin.script.experimental.jvm.jvm
import kotlin.script.experimental.jvm.util.KotlinJars
import kotlin.script.experimental.jvm.withUpdatedClasspath
import kotlin.script.experimental.jvmhost.KJvmCompilerProxy
import kotlin.script.experimental.util.getOrError
class KJvmCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) : KJvmCompilerProxy {
override fun compile(
script: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration
): ResultWithDiagnostics<CompiledScript<*>> {
val messageCollector = ScriptDiagnosticsMessageCollector()
val reportingState = ReportingState()
): ResultWithDiagnostics<CompiledScript<*>> =
withMessageCollectorAndDisposable(locationId = script.locationId) { messageCollector, disposable ->
fun failure(vararg diagnostics: ScriptDiagnostic): ResultWithDiagnostics.Failure =
ResultWithDiagnostics.Failure(*messageCollector.diagnostics.toTypedArray(), *diagnostics)
val context = createSharedCompilationContext(scriptCompilationConfiguration, hostConfiguration, messageCollector, disposable)
fun failure(message: String): ResultWithDiagnostics.Failure =
failure(message.asErrorDiagnostics(path = script.locationId))
val mainKtFile =
getScriptKtFile(script, context.baseScriptCompilationConfiguration, context.environment.project, messageCollector)
.resultOr { return it }
val disposable = Disposer.newDisposable()
context.scriptCompilationState.configureFor(script, context.baseScriptCompilationConfiguration)
try {
setIdeaIoUseFallback()
val (sourceFiles, sourceDependencies) = collectRefinedSourcesAndUpdateEnvironment(context, mainKtFile, messageCollector)
// TODO: refactor/cleanup when the internal resolving API will allow easier info passing between resolver and compiler
val analysisResult = analyze(sourceFiles, context.environment)
val kotlinCompilerConfiguration =
createInitialCompilerConfiguration(scriptCompilationConfiguration, messageCollector, reportingState)
if (!analysisResult.shouldGenerateCode) return failure(script, messageCollector, "no code to generate")
if (analysisResult.isError() || messageCollector.hasErrors()) return failure(messageCollector)
val initialScriptCompilationConfiguration =
scriptCompilationConfiguration.withUpdatesFromCompilerConfiguration(kotlinCompilerConfiguration)
val sourcesWithRefinementsState = SourcesWithRefinedConfigurations(script)
kotlinCompilerConfiguration.add(
ScriptingConfigurationKeys.SCRIPT_DEFINITIONS,
makeScriptDefinition(initialScriptCompilationConfiguration, script, sourcesWithRefinementsState)
)
val environment = KotlinCoreEnvironment.createForProduction(
disposable, kotlinCompilerConfiguration, EnvironmentConfigFiles.JVM_CONFIG_FILES
)
val mainKtFile = getMainKtFile(script, initialScriptCompilationConfiguration, environment)
?: return failure("Unable to make PSI file from script")
val ktScript = mainKtFile.declarations.firstIsInstanceOrNull<KtScript>()
?: return failure("Not a script file")
val sourceFiles = arrayListOf(mainKtFile)
val (classpath, newSources, sourceDependencies) =
collectScriptsCompilationDependencies(
kotlinCompilerConfiguration,
environment.project,
sourceFiles
)
// TODO: consider removing, it is probably redundant: the actual index update is performed with environment.updateClasspath
kotlinCompilerConfiguration.addJvmClasspathRoots(classpath)
environment.updateClasspath(classpath.map(::JvmClasspathRoot))
sourceFiles.addAll(newSources)
// collectScriptsCompilationDependencies calls resolver for every file, so at this point all updated configurations are collected
environment.configuration.updateWithRefinedConfigurations(
initialScriptCompilationConfiguration, sourcesWithRefinementsState.refinedConfigurations, messageCollector, reportingState
)
val analysisResult = analyze(sourceFiles, environment)
if (!analysisResult.shouldGenerateCode) return failure("no code to generate")
if (analysisResult.isError() || messageCollector.hasErrors()) return failure()
val generationState = generate(analysisResult, sourceFiles, kotlinCompilerConfiguration)
val generationState = generate(analysisResult, sourceFiles, context.environment.configuration)
val compiledScript =
makeCompiledScript(generationState, script, ktScript, sourceDependencies) { ktFile ->
sourcesWithRefinementsState.refinedConfigurations.entries.find { ktFile.name == it.key.name }?.value
?: initialScriptCompilationConfiguration
makeCompiledScript(generationState, script, sourceFiles.first(), sourceDependencies) { ktFile ->
context.scriptCompilationState.configurations.entries.find { ktFile.name == it.key.name }?.value
?: context.baseScriptCompilationConfiguration
}
return ResultWithDiagnostics.Success(compiledScript, messageCollector.diagnostics)
} catch (ex: Throwable) {
return failure(ex.asDiagnostics(path = script.locationId))
} finally {
disposable.dispose()
ResultWithDiagnostics.Success(compiledScript, messageCollector.diagnostics)
}
}
}
private class SourcesWithRefinedConfigurations(rootScript: SourceCode) {
val knownSources = hashSetOf(rootScript)
val refinedConfigurations = hashMapOf<SourceCode, ScriptCompilationConfiguration>()
}
private fun analyze(sourceFiles: Collection<KtFile>, environment: KotlinCoreEnvironment): AnalysisResult {
val messageCollector = environment.configuration[CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY]!!
private class ReportingState {
var currentArguments = K2JVMCompilerArguments()
}
val analyzerWithCompilerReport = AnalyzerWithCompilerReport(messageCollector, environment.configuration.languageVersionSettings)
private fun makeScriptDefinition(
scriptCompilationConfiguration: ScriptCompilationConfiguration,
mainScript: SourceCode,
sourcsesWithConfigurationsState: SourcesWithRefinedConfigurations
): BridgeScriptDefinition =
BridgeScriptDefinition(
scriptCompilationConfiguration,
hostConfiguration,
{ script, updatedConfiguration ->
sourcsesWithConfigurationsState.refinedConfigurations[script] = updatedConfiguration
updatedConfiguration[ScriptCompilationConfiguration.importScripts]?.let {
sourcsesWithConfigurationsState.knownSources.addAll(it)
}
},
{ scriptContents ->
val name = scriptContents.file?.name
sourcsesWithConfigurationsState.knownSources.find {
// TODO: consider using merged text (likely should be cached)
// on the other hand it may become obsolete when scripting internals will be redesigned properly
(name != null && name == it.scriptFileName(
mainScript,
scriptCompilationConfiguration
)) || it.text == scriptContents.text
}
}
analyzerWithCompilerReport.analyzeAndReport(sourceFiles) {
val project = environment.project
TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(
project,
sourceFiles,
NoScopeRecordCliBindingTrace(),
environment.configuration,
environment::createPackagePartProvider
)
private fun ScriptCompilationConfiguration.withUpdatesFromCompilerConfiguration(kotlinCompilerConfiguration: CompilerConfiguration) =
withUpdatedClasspath(kotlinCompilerConfiguration.jvmClasspathRoots)
private fun createInitialCompilerConfiguration(
scriptCompilationConfiguration: ScriptCompilationConfiguration,
messageCollector: MessageCollector,
reportingState: ReportingState
): CompilerConfiguration {
val baseArguments = K2JVMCompilerArguments()
parseCommandLineArguments(
scriptCompilationConfiguration[ScriptCompilationConfiguration.compilerOptions] ?: emptyList(),
baseArguments
)
reportArgumentsIgnoredGenerally(baseArguments, messageCollector, reportingState)
reportingState.currentArguments = baseArguments
return org.jetbrains.kotlin.config.CompilerConfiguration().apply {
put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector)
setupCommonArguments(baseArguments)
setupJvmSpecificArguments(baseArguments)
// Default value differs from the argument's default (see #KT-29405 and #KT-29319)
put(JVMConfigurationKeys.JVM_TARGET, JvmTarget.JVM_1_8)
val jdkHomeFromConfigurations = scriptCompilationConfiguration.getNoDefault(ScriptCompilationConfiguration.jvm.jdkHome)
?: hostConfiguration[ScriptingHostConfiguration.jvm.jdkHome]
if (jdkHomeFromConfigurations != null) {
messageCollector.report(CompilerMessageSeverity.LOGGING, "Using JDK home directory $jdkHomeFromConfigurations")
put(JVMConfigurationKeys.JDK_HOME, jdkHomeFromConfigurations)
} else {
configureJdkHome(baseArguments)
}
put(JVMConfigurationKeys.RETAIN_OUTPUT_IN_MEMORY, true)
val isModularJava = isModularJava()
scriptCompilationConfiguration[ScriptCompilationConfiguration.dependencies]?.let { dependencies ->
addJvmClasspathRoots(
dependencies.flatMap {
(it as JvmDependency).classpath
}
)
}
add(ComponentRegistrar.PLUGIN_COMPONENT_REGISTRARS, ScriptingCompilerConfigurationComponentRegistrar())
configureExplicitContentRoots(baseArguments)
if (!baseArguments.noStdlib) {
addModularRootIfNotNull(isModularJava, "kotlin.stdlib", KotlinJars.stdlib)
addModularRootIfNotNull(isModularJava, "kotlin.script.runtime", KotlinJars.scriptRuntimeOrNull)
}
// see comments about logic in CompilerConfiguration.configureStandardLibs
if (!baseArguments.noReflect && !baseArguments.noStdlib) {
addModularRootIfNotNull(isModularJava, "kotlin.reflect", KotlinJars.reflectOrNull)
}
put(CommonConfigurationKeys.MODULE_NAME, baseArguments.moduleName ?: "kotlin-script")
configureAdvancedJvmOptions(baseArguments)
}
}
companion object {
private fun SourceCode.scriptFileName(
mainScript: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration
): String =
when {
name != null -> name!!
mainScript == this -> "script.${scriptCompilationConfiguration[ScriptCompilationConfiguration.fileExtension]}"
else -> throw Exception("Unexpected script without name: $this")
}
private fun getMainKtFile(
mainScript: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration,
environment: KotlinCoreEnvironment
): KtFile? {
val psiFileFactory: PsiFileFactoryImpl = PsiFileFactory.getInstance(environment.project) as PsiFileFactoryImpl
val scriptText = getMergedScriptText(mainScript, scriptCompilationConfiguration)
val virtualFile = ScriptLightVirtualFile(
mainScript.scriptFileName(mainScript, scriptCompilationConfiguration),
(mainScript as? FileScriptSource)?.file?.path,
scriptText
)
return psiFileFactory.trySetupPsiForFile(virtualFile, KotlinLanguage.INSTANCE, true, false) as KtFile?
}
private fun CompilerConfiguration.updateWithRefinedConfigurations(
initialScriptCompilationConfiguration: ScriptCompilationConfiguration,
refinedScriptCompilationConfigurations: HashMap<SourceCode, ScriptCompilationConfiguration>,
messageCollector: ScriptDiagnosticsMessageCollector,
reportingState: ReportingState
) {
val updatedCompilerOptions = refinedScriptCompilationConfigurations.flatMap {
it.value[ScriptCompilationConfiguration.compilerOptions] ?: emptyList()
}
if (updatedCompilerOptions.isNotEmpty() &&
updatedCompilerOptions != initialScriptCompilationConfiguration[ScriptCompilationConfiguration.compilerOptions]
) {
val updatedArguments = K2JVMCompilerArguments()
parseCommandLineArguments(updatedCompilerOptions, updatedArguments)
reportArgumentsIgnoredGenerally(updatedArguments, messageCollector, reportingState)
reportArgumentsIgnoredFromRefinement(updatedArguments, messageCollector, reportingState)
setupCommonArguments(updatedArguments)
setupJvmSpecificArguments(updatedArguments)
configureAdvancedJvmOptions(updatedArguments)
}
}
private fun analyze(sourceFiles: Collection<KtFile>, environment: KotlinCoreEnvironment): AnalysisResult {
val messageCollector = environment.configuration[CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY]!!
val analyzerWithCompilerReport = AnalyzerWithCompilerReport(messageCollector, environment.configuration.languageVersionSettings)
analyzerWithCompilerReport.analyzeAndReport(sourceFiles) {
val project = environment.project
TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(
project,
sourceFiles,
NoScopeRecordCliBindingTrace(),
environment.configuration,
environment::createPackagePartProvider
)
}
return analyzerWithCompilerReport.analysisResult
}
private fun generate(
analysisResult: AnalysisResult, sourceFiles: List<KtFile>, kotlinCompilerConfiguration: CompilerConfiguration
): GenerationState {
val generationState = GenerationState.Builder(
sourceFiles.first().project,
ClassBuilderFactories.BINARIES,
analysisResult.moduleDescriptor,
analysisResult.bindingContext,
sourceFiles,
kotlinCompilerConfiguration
).build()
KotlinCodegenFacade.compileCorrectFiles(generationState, CompilationErrorHandler.THROW_EXCEPTION)
return generationState
}
private fun makeCompiledScript(
generationState: GenerationState,
script: SourceCode,
ktScript: KtScript,
sourceDependencies: List<ScriptsCompilationDependencies.SourceDependencies>,
getScriptConfiguration: (KtFile) -> ScriptCompilationConfiguration
): KJvmCompiledScript<Any> {
val scriptDependenciesStack = ArrayDeque<KtScript>()
fun makeOtherScripts(script: KtScript): List<KJvmCompiledScript<*>> {
// TODO: ensure that it is caught earlier (as well) since it would be more economical
if (scriptDependenciesStack.contains(script))
throw IllegalArgumentException("Unable to handle recursive script dependencies")
scriptDependenciesStack.push(script)
val containingKtFile = script.containingKtFile
val otherScripts: List<KJvmCompiledScript<*>> =
sourceDependencies.find { it.scriptFile == containingKtFile }?.sourceDependencies?.mapNotNull { sourceFile ->
sourceFile.declarations.firstIsInstanceOrNull<KtScript>()?.let {
KJvmCompiledScript<Any>(
containingKtFile.virtualFile?.path,
getScriptConfiguration(sourceFile),
it.fqName.asString(),
makeOtherScripts(it),
null
)
}
} ?: emptyList()
scriptDependenciesStack.pop()
return otherScripts
}
val module = makeCompiledModule(generationState)
return KJvmCompiledScript(
script.locationId,
getScriptConfiguration(ktScript.containingKtFile),
ktScript.fqName.asString(),
makeOtherScripts(ktScript),
module
)
}
private fun reportArgumentsIgnoredGenerally(
arguments: K2JVMCompilerArguments,
messageCollector: MessageCollector,
reportingState: ReportingState
) {
reportIgnoredArguments(
arguments,
"The following compiler arguments are ignored on script compilation: ",
messageCollector,
reportingState,
K2JVMCompilerArguments::version,
K2JVMCompilerArguments::destination,
K2JVMCompilerArguments::buildFile,
K2JVMCompilerArguments::commonSources,
K2JVMCompilerArguments::allWarningsAsErrors,
K2JVMCompilerArguments::script,
K2JVMCompilerArguments::scriptTemplates,
K2JVMCompilerArguments::scriptResolverEnvironment,
K2JVMCompilerArguments::disableStandardScript,
K2JVMCompilerArguments::disableDefaultScriptingPlugin,
K2JVMCompilerArguments::pluginClasspaths,
K2JVMCompilerArguments::pluginOptions,
K2JVMCompilerArguments::useJavac,
K2JVMCompilerArguments::compileJava,
K2JVMCompilerArguments::reportPerf,
K2JVMCompilerArguments::dumpPerf
)
}
private fun reportArgumentsIgnoredFromRefinement(
arguments: K2JVMCompilerArguments, messageCollector: MessageCollector, reportingState: ReportingState
) {
reportIgnoredArguments(
arguments,
"The following compiler arguments are ignored when configured from refinement callbacks: ",
messageCollector,
reportingState,
K2JVMCompilerArguments::noJdk,
K2JVMCompilerArguments::jdkHome,
K2JVMCompilerArguments::javaModulePath,
K2JVMCompilerArguments::classpath,
K2JVMCompilerArguments::noStdlib,
K2JVMCompilerArguments::noReflect
)
}
private fun reportIgnoredArguments(
arguments: K2JVMCompilerArguments, message: String,
messageCollector: MessageCollector, reportingState: ReportingState,
vararg toIgnore: KMutableProperty1<K2JVMCompilerArguments, *>
) {
val ignoredArgKeys = toIgnore.mapNotNull { argProperty ->
if (argProperty.get(arguments) != argProperty.get(reportingState.currentArguments)) {
argProperty.findAnnotation<Argument>()?.value
?: throw IllegalStateException("unknown compiler argument property: $argProperty: no Argument annotation found")
} else null
}
if (ignoredArgKeys.isNotEmpty()) {
messageCollector.report(CompilerMessageSeverity.STRONG_WARNING, "$message${ignoredArgKeys.joinToString(", ")}")
}
}
}
return analyzerWithCompilerReport.analysisResult
}
internal class ScriptDiagnosticsMessageCollector : MessageCollector {
private fun generate(
analysisResult: AnalysisResult, sourceFiles: List<KtFile>, kotlinCompilerConfiguration: CompilerConfiguration
): GenerationState {
val generationState = GenerationState.Builder(
sourceFiles.first().project,
ClassBuilderFactories.BINARIES,
analysisResult.moduleDescriptor,
analysisResult.bindingContext,
sourceFiles,
kotlinCompilerConfiguration
).build()
private val _diagnostics = arrayListOf<ScriptDiagnostic>()
val diagnostics: List<ScriptDiagnostic> get() = _diagnostics
override fun clear() {
_diagnostics.clear()
}
override fun hasErrors(): Boolean =
_diagnostics.any { it.severity == ScriptDiagnostic.Severity.ERROR }
override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageLocation?) {
val mappedSeverity = when (severity) {
CompilerMessageSeverity.EXCEPTION,
CompilerMessageSeverity.ERROR -> ScriptDiagnostic.Severity.ERROR
CompilerMessageSeverity.STRONG_WARNING,
CompilerMessageSeverity.WARNING -> ScriptDiagnostic.Severity.WARNING
CompilerMessageSeverity.INFO -> ScriptDiagnostic.Severity.INFO
CompilerMessageSeverity.LOGGING -> ScriptDiagnostic.Severity.DEBUG
else -> null
}
if (mappedSeverity != null) {
val mappedLocation = location?.let {
if (it.line < 0 && it.column < 0) null // special location created by CompilerMessageLocation.create
else SourceCode.Location(SourceCode.Position(it.line, it.column))
}
_diagnostics.add(ScriptDiagnostic(message, mappedSeverity, location?.path, mappedLocation))
}
}
KotlinCodegenFacade.compileCorrectFiles(generationState, CompilationErrorHandler.THROW_EXCEPTION)
return generationState
}
// 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(
val scriptCompilationConfiguration: ScriptCompilationConfiguration,
val hostConfiguration: ScriptingHostConfiguration,
updateConfiguration: (SourceCode, ScriptCompilationConfiguration) -> Unit,
getScriptSource: (ScriptContents) -> SourceCode?
) : 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
?.map { (cl.loadClass(it.typeName) as Class<out Annotation>).kotlin }
?: 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, updateConfiguration, getScriptSource)
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
)
}
internal 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
}
private fun makeCompiledModule(generationState: GenerationState) = KJvmCompiledModuleInMemory(
generationState.factory.asList()
.associateTo(sortedMapOf<String, ByteArray>()) { it.relativePath to it.asByteArray() }
)
@@ -0,0 +1,136 @@
/*
* 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 kotlin.script.experimental.jvmhost.impl
import com.intellij.openapi.Disposable
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.cli.common.environment.setIdeaIoUseFallback
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
import org.jetbrains.kotlin.cli.common.messages.MessageCollectorBasedReporter
import org.jetbrains.kotlin.cli.common.repl.*
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.codegen.ClassBuilderFactories
import org.jetbrains.kotlin.codegen.CompilationErrorHandler
import org.jetbrains.kotlin.codegen.KotlinCodegenFacade
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.scripting.repl.ReplCodeAnalyzer
import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.ScriptingHostConfiguration
import kotlin.script.experimental.jvmhost.repl.JvmReplCompilerState
import kotlin.script.experimental.jvmhost.repl.KJvmReplCompilerProxy
class KJvmReplCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) : KJvmReplCompilerProxy {
override fun createReplCompilationState(scriptCompilationConfiguration: ScriptCompilationConfiguration): JvmReplCompilerState.Compilation {
val context = withMessageCollectorAndDisposable(disposeOnSuccess = false) { messageCollector, disposable ->
createSharedCompilationContext(scriptCompilationConfiguration, hostConfiguration, messageCollector, disposable).asSuccess()
}.resultOr { throw IllegalStateException("Unable to initialize repl compiler:\n ${it.reports.joinToString("\n ")}") }
return ReplCompilationState(context)
}
fun checkSyntax(
script: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration,
project: Project
): ResultWithDiagnostics<Boolean> =
withMessageCollector(script) { messageCollector ->
val ktFile = getScriptKtFile(script, scriptCompilationConfiguration, project, messageCollector)
.resultOr { return it }
val errorHolder = object : MessageCollectorBasedReporter {
override val messageCollector = messageCollector
}
val syntaxErrorReport = AnalyzerWithCompilerReport.reportSyntaxErrors(ktFile, errorHolder)
if (syntaxErrorReport.isHasErrors) failure(messageCollector) else true.asSuccess()
}
fun compileReplSnippet(
compilationState: JvmReplCompilerState.Compilation,
snippet: SourceCode,
snippetId: ReplSnippetId,
history: IReplStageHistory<ScriptDescriptor>
): ResultWithDiagnostics<CompiledScript<*>> =
withMessageCollector(snippet) { messageCollector ->
val context = (compilationState as? ReplCompilationState)?.context
?: return failure(
snippet, messageCollector, "Internal error: unknown parameter passed as compilationState: $compilationState"
)
setIdeaIoUseFallback()
// NOTE: converting between REPL entities from compiler and "new" scripting entities
// TODO: (big) move REPL API from compiler to the new scripting infrastructure and streamline ops
val codeLine = makeReplCodeLine(snippetId, snippet)
val errorHolder = object : MessageCollectorBasedReporter {
override val messageCollector = messageCollector
}
val snippetKtFile =
getScriptKtFile(snippet, context.baseScriptCompilationConfiguration, context.environment.project, messageCollector)
.resultOr { return it }
val (sourceFiles, sourceDependencies) = collectRefinedSourcesAndUpdateEnvironment(context, snippetKtFile, messageCollector)
val analysisResult =
compilationState.analyzerEngine.analyzeReplLineWithImportedScripts(snippetKtFile, sourceFiles.drop(1), codeLine)
AnalyzerWithCompilerReport.reportDiagnostics(analysisResult.diagnostics, errorHolder)
val scriptDescriptor = when (analysisResult) {
is ReplCodeAnalyzer.ReplLineAnalysisResult.WithErrors -> return failure(messageCollector)
is ReplCodeAnalyzer.ReplLineAnalysisResult.Successful -> analysisResult.scriptDescriptor
else -> return failure(snippet, messageCollector, "Unexpected result ${analysisResult::class.java}")
}
val type = (scriptDescriptor as ScriptDescriptor).resultValue?.returnType
val generationState = GenerationState.Builder(
snippetKtFile.project,
ClassBuilderFactories.BINARIES,
compilationState.analyzerEngine.module,
compilationState.analyzerEngine.trace.bindingContext,
sourceFiles,
compilationState.environment.configuration
).build().apply {
replSpecific.resultType = type
replSpecific.scriptResultFieldName = scriptResultFieldName(codeLine.no)
replSpecific.earlierScriptsForReplInterpreter = history.map { it.item }
beforeCompile()
}
KotlinCodegenFacade.generatePackage(
generationState,
snippetKtFile.script!!.containingKtFile.packageFqName,
setOf(snippetKtFile.script!!.containingKtFile),
CompilationErrorHandler.THROW_EXCEPTION
)
val generatedClassname = makeScriptBaseName(codeLine)
history.push(LineId(codeLine), scriptDescriptor)
val compiledScript =
makeCompiledScript(generationState, snippet, sourceFiles.first(), sourceDependencies) { ktFile ->
context.scriptCompilationState.configurations.entries.find { ktFile.name == it.key.name }?.value
?: context.baseScriptCompilationConfiguration
}
ResultWithDiagnostics.Success(compiledScript, messageCollector.diagnostics)
}
}
internal class ReplCompilationState(val context: SharedScriptCompilationContext) : JvmReplCompilerState.Compilation {
override val disposable: Disposable get() = context.disposable
override val baseScriptCompilationConfiguration: ScriptCompilationConfiguration get() = context.baseScriptCompilationConfiguration
override val environment: KotlinCoreEnvironment get() = context.environment
override val analyzerEngine: ReplCodeAnalyzer by lazy {
ReplCodeAnalyzer(context.environment)
}
}
internal fun makeReplCodeLine(id: ReplSnippetId, code: SourceCode): ReplCodeLine =
ReplCodeLine(id.no, id.generation, code.text)
@@ -0,0 +1,223 @@
/*
* 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 kotlin.script.experimental.jvmhost.impl
import com.intellij.openapi.Disposable
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
import org.jetbrains.kotlin.cli.common.arguments.parseCommandLineArguments
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.common.setupCommonArguments
import org.jetbrains.kotlin.cli.jvm.*
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot
import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots
import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots
import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.config.JvmTarget
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.compiler.plugin.ScriptingCompilerConfigurationComponentRegistrar
import org.jetbrains.kotlin.scripting.configuration.ScriptingConfigurationKeys
import org.jetbrains.kotlin.scripting.dependencies.ScriptsCompilationDependencies
import org.jetbrains.kotlin.scripting.dependencies.collectScriptsCompilationDependencies
import kotlin.script.experimental.api.ScriptCompilationConfiguration
import kotlin.script.experimental.api.SourceCode
import kotlin.script.experimental.api.compilerOptions
import kotlin.script.experimental.api.dependencies
import kotlin.script.experimental.host.ScriptingHostConfiguration
import kotlin.script.experimental.jvm.JvmDependency
import kotlin.script.experimental.jvm.jdkHome
import kotlin.script.experimental.jvm.jvm
import kotlin.script.experimental.jvm.util.KotlinJars
import kotlin.script.experimental.jvm.withUpdatedClasspath
internal class SharedScriptCompilationContext(
val disposable: Disposable,
val baseScriptCompilationConfiguration: ScriptCompilationConfiguration,
val environment: KotlinCoreEnvironment,
val ignoredOptionsReportingState: IgnoredOptionsReportingState,
val scriptCompilationState: BridgeScriptDefinitionDynamicState
)
internal fun createSharedCompilationContext(
scriptCompilationConfiguration: ScriptCompilationConfiguration,
hostConfiguration: ScriptingHostConfiguration,
messageCollector: ScriptDiagnosticsMessageCollector,
disposable: Disposable
): SharedScriptCompilationContext {
val ignoredOptionsReportingState = IgnoredOptionsReportingState()
val scriptCompilationState = BridgeScriptDefinitionDynamicState()
val (initialScriptCompilationConfiguration, kotlinCompilerConfiguration) =
createInitialConfigurations(
scriptCompilationConfiguration, hostConfiguration, scriptCompilationState, messageCollector, ignoredOptionsReportingState
)
val environment = KotlinCoreEnvironment.createForProduction(
disposable, kotlinCompilerConfiguration, EnvironmentConfigFiles.JVM_CONFIG_FILES
)
return SharedScriptCompilationContext(
disposable, initialScriptCompilationConfiguration, environment, ignoredOptionsReportingState, scriptCompilationState
)
}
internal fun createInitialConfigurations(
scriptCompilationConfiguration: ScriptCompilationConfiguration,
hostConfiguration: ScriptingHostConfiguration,
scriptCompilationState: BridgeScriptDefinitionDynamicState,
messageCollector: ScriptDiagnosticsMessageCollector,
ignoredOptionsReportingState: IgnoredOptionsReportingState
): Pair<ScriptCompilationConfiguration, CompilerConfiguration> {
val kotlinCompilerConfiguration =
createInitialCompilerConfiguration(
scriptCompilationConfiguration, hostConfiguration, messageCollector, ignoredOptionsReportingState
)
val initialScriptCompilationConfiguration =
scriptCompilationConfiguration.withUpdatesFromCompilerConfiguration(kotlinCompilerConfiguration)
kotlinCompilerConfiguration.add(
ScriptingConfigurationKeys.SCRIPT_DEFINITIONS,
BridgeScriptDefinition(
scriptCompilationConfiguration,
hostConfiguration,
scriptCompilationState
)
)
return Pair(initialScriptCompilationConfiguration, kotlinCompilerConfiguration)
}
private fun ScriptCompilationConfiguration.withUpdatesFromCompilerConfiguration(kotlinCompilerConfiguration: CompilerConfiguration) =
withUpdatedClasspath(kotlinCompilerConfiguration.jvmClasspathRoots)
private fun createInitialCompilerConfiguration(
scriptCompilationConfiguration: ScriptCompilationConfiguration,
hostConfiguration: ScriptingHostConfiguration,
messageCollector: MessageCollector,
reportingState: IgnoredOptionsReportingState
): CompilerConfiguration {
val baseArguments = K2JVMCompilerArguments()
parseCommandLineArguments(
scriptCompilationConfiguration[ScriptCompilationConfiguration.compilerOptions] ?: emptyList(),
baseArguments
)
reportArgumentsIgnoredGenerally(baseArguments, messageCollector, reportingState)
reportingState.currentArguments = baseArguments
return CompilerConfiguration().apply {
put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector)
setupCommonArguments(baseArguments)
setupJvmSpecificArguments(baseArguments)
// Default value differs from the argument's default (see #KT-29405 and #KT-29319)
put(JVMConfigurationKeys.JVM_TARGET, JvmTarget.JVM_1_8)
val jdkHomeFromConfigurations = scriptCompilationConfiguration.getNoDefault(ScriptCompilationConfiguration.jvm.jdkHome)
?: hostConfiguration[ScriptingHostConfiguration.jvm.jdkHome]
if (jdkHomeFromConfigurations != null) {
messageCollector.report(CompilerMessageSeverity.LOGGING, "Using JDK home directory $jdkHomeFromConfigurations")
put(JVMConfigurationKeys.JDK_HOME, jdkHomeFromConfigurations)
} else {
configureJdkHome(baseArguments)
}
put(JVMConfigurationKeys.RETAIN_OUTPUT_IN_MEMORY, true)
val isModularJava = isModularJava()
scriptCompilationConfiguration[ScriptCompilationConfiguration.dependencies]?.let { dependencies ->
addJvmClasspathRoots(
dependencies.flatMap {
(it as JvmDependency).classpath
}
)
}
add(
ComponentRegistrar.PLUGIN_COMPONENT_REGISTRARS,
ScriptingCompilerConfigurationComponentRegistrar()
)
configureExplicitContentRoots(baseArguments)
if (!baseArguments.noStdlib) {
addModularRootIfNotNull(isModularJava, "kotlin.stdlib", KotlinJars.stdlib)
addModularRootIfNotNull(isModularJava, "kotlin.script.runtime", KotlinJars.scriptRuntimeOrNull)
}
// see comments about logic in CompilerConfiguration.configureStandardLibs
if (!baseArguments.noReflect && !baseArguments.noStdlib) {
addModularRootIfNotNull(isModularJava, "kotlin.reflect", KotlinJars.reflectOrNull)
}
put(CommonConfigurationKeys.MODULE_NAME, baseArguments.moduleName ?: "kotlin-script")
configureAdvancedJvmOptions(baseArguments)
}
}
internal fun collectRefinedSourcesAndUpdateEnvironment(
context: SharedScriptCompilationContext,
mainKtFile: KtFile,
messageCollector: ScriptDiagnosticsMessageCollector
): Pair<List<KtFile>, List<ScriptsCompilationDependencies.SourceDependencies>> {
val sourceFiles = arrayListOf(mainKtFile)
val (classpath, newSources, sourceDependencies) =
collectScriptsCompilationDependencies(
context.environment.configuration,
context.environment.project,
sourceFiles
)
// TODO: consider removing, it is probably redundant: the actual index update is performed with environment.updateClasspath
context.environment.configuration.addJvmClasspathRoots(classpath)
context.environment.updateClasspath(classpath.map(::JvmClasspathRoot))
sourceFiles.addAll(newSources)
// collectScriptsCompilationDependencies calls resolver for every file, so at this point all updated configurations are collected
context.environment.configuration.updateWithRefinedConfigurations(
context.baseScriptCompilationConfiguration, context.scriptCompilationState.configurations,
messageCollector, context.ignoredOptionsReportingState
)
return sourceFiles to sourceDependencies
}
private fun CompilerConfiguration.updateWithRefinedConfigurations(
initialScriptCompilationConfiguration: ScriptCompilationConfiguration,
refinedScriptCompilationConfigurations: Map<SourceCode, ScriptCompilationConfiguration>,
messageCollector: ScriptDiagnosticsMessageCollector,
reportingState: IgnoredOptionsReportingState
) {
val updatedCompilerOptions = refinedScriptCompilationConfigurations.flatMap {
it.value[ScriptCompilationConfiguration.compilerOptions] ?: emptyList()
}
if (updatedCompilerOptions.isNotEmpty() &&
updatedCompilerOptions != initialScriptCompilationConfiguration[ScriptCompilationConfiguration.compilerOptions]
) {
val updatedArguments = K2JVMCompilerArguments()
parseCommandLineArguments(updatedCompilerOptions, updatedArguments)
reportArgumentsIgnoredGenerally(updatedArguments, messageCollector, reportingState)
reportArgumentsIgnoredFromRefinement(updatedArguments, messageCollector, reportingState)
setupCommonArguments(updatedArguments)
setupJvmSpecificArguments(updatedArguments)
configureAdvancedJvmOptions(updatedArguments)
}
}
@@ -0,0 +1,135 @@
/*
* 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 kotlin.script.experimental.jvmhost.impl
import org.jetbrains.kotlin.cli.common.arguments.Argument
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import kotlin.reflect.KMutableProperty1
import kotlin.reflect.full.findAnnotation
import kotlin.script.experimental.api.ResultWithDiagnostics
import kotlin.script.experimental.api.ScriptDiagnostic
import kotlin.script.experimental.api.SourceCode
import kotlin.script.experimental.api.asErrorDiagnostics
internal class ScriptDiagnosticsMessageCollector : MessageCollector {
private val _diagnostics = arrayListOf<ScriptDiagnostic>()
val diagnostics: List<ScriptDiagnostic> get() = _diagnostics
override fun clear() {
_diagnostics.clear()
}
override fun hasErrors(): Boolean =
_diagnostics.any { it.severity == ScriptDiagnostic.Severity.ERROR }
override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageLocation?) {
val mappedSeverity = when (severity) {
CompilerMessageSeverity.EXCEPTION,
CompilerMessageSeverity.ERROR -> ScriptDiagnostic.Severity.ERROR
CompilerMessageSeverity.STRONG_WARNING,
CompilerMessageSeverity.WARNING -> ScriptDiagnostic.Severity.WARNING
CompilerMessageSeverity.INFO -> ScriptDiagnostic.Severity.INFO
CompilerMessageSeverity.LOGGING -> ScriptDiagnostic.Severity.DEBUG
else -> null
}
if (mappedSeverity != null) {
val mappedLocation = location?.let {
if (it.line < 0 && it.column < 0) null // special location created by CompilerMessageLocation.create
else SourceCode.Location(
SourceCode.Position(
it.line,
it.column
)
)
}
_diagnostics.add(ScriptDiagnostic(message, mappedSeverity, location?.path, mappedLocation))
}
}
}
internal fun failure(
messageCollector: ScriptDiagnosticsMessageCollector, vararg diagnostics: ScriptDiagnostic
): ResultWithDiagnostics.Failure =
ResultWithDiagnostics.Failure(*messageCollector.diagnostics.toTypedArray(), *diagnostics)
internal fun failure(
script: SourceCode, messageCollector: ScriptDiagnosticsMessageCollector, message: String
): ResultWithDiagnostics.Failure =
failure(messageCollector, message.asErrorDiagnostics(path = script.locationId))
internal class IgnoredOptionsReportingState {
var currentArguments = K2JVMCompilerArguments()
}
internal fun reportArgumentsIgnoredGenerally(
arguments: K2JVMCompilerArguments,
messageCollector: MessageCollector,
reportingState: IgnoredOptionsReportingState
) {
reportIgnoredArguments(
arguments,
"The following compiler arguments are ignored on script compilation: ",
messageCollector,
reportingState,
K2JVMCompilerArguments::version,
K2JVMCompilerArguments::destination,
K2JVMCompilerArguments::buildFile,
K2JVMCompilerArguments::commonSources,
K2JVMCompilerArguments::allWarningsAsErrors,
K2JVMCompilerArguments::script,
K2JVMCompilerArguments::scriptTemplates,
K2JVMCompilerArguments::scriptResolverEnvironment,
K2JVMCompilerArguments::disableStandardScript,
K2JVMCompilerArguments::disableDefaultScriptingPlugin,
K2JVMCompilerArguments::pluginClasspaths,
K2JVMCompilerArguments::pluginOptions,
K2JVMCompilerArguments::useJavac,
K2JVMCompilerArguments::compileJava,
K2JVMCompilerArguments::reportPerf,
K2JVMCompilerArguments::dumpPerf
)
}
internal fun reportArgumentsIgnoredFromRefinement(
arguments: K2JVMCompilerArguments, messageCollector: MessageCollector, reportingState: IgnoredOptionsReportingState
) {
reportIgnoredArguments(
arguments,
"The following compiler arguments are ignored when configured from refinement callbacks: ",
messageCollector,
reportingState,
K2JVMCompilerArguments::noJdk,
K2JVMCompilerArguments::jdkHome,
K2JVMCompilerArguments::javaModulePath,
K2JVMCompilerArguments::classpath,
K2JVMCompilerArguments::noStdlib,
K2JVMCompilerArguments::noReflect
)
}
private fun reportIgnoredArguments(
arguments: K2JVMCompilerArguments, message: String,
messageCollector: MessageCollector, reportingState: IgnoredOptionsReportingState,
vararg toIgnore: KMutableProperty1<K2JVMCompilerArguments, *>
) {
val ignoredArgKeys = toIgnore.mapNotNull { argProperty ->
if (argProperty.get(arguments) != argProperty.get(reportingState.currentArguments)) {
argProperty.findAnnotation<Argument>()?.value
?: throw IllegalStateException("unknown compiler argument property: $argProperty: no Argument annotation found")
} else null
}
if (ignoredArgKeys.isNotEmpty()) {
messageCollector.report(CompilerMessageSeverity.STRONG_WARNING, "$message${ignoredArgKeys.joinToString(", ")}")
}
}
@@ -0,0 +1,156 @@
/*
* 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 kotlin.script.experimental.jvmhost.impl
import com.intellij.openapi.Disposable
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.vfs.CharsetToolkit
import com.intellij.psi.PsiFileFactory
import com.intellij.psi.impl.PsiFileFactoryImpl
import com.intellij.testFramework.LightVirtualFile
import org.jetbrains.kotlin.cli.common.environment.setIdeaIoUseFallback
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtScript
import org.jetbrains.kotlin.scripting.dependencies.ScriptsCompilationDependencies
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import java.util.*
import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.FileScriptSource
import kotlin.script.experimental.host.getMergedScriptText
import kotlin.script.experimental.jvm.impl.KJvmCompiledScript
internal 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
}
internal fun makeCompiledModule(generationState: GenerationState) =
KJvmCompiledModuleInMemory(
generationState.factory.asList()
.associateTo(sortedMapOf<String, ByteArray>()) { it.relativePath to it.asByteArray() }
)
internal fun SourceCode.scriptFileName(
mainScript: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration
): String =
when {
name != null -> name!!
mainScript == this -> "script.${scriptCompilationConfiguration[ScriptCompilationConfiguration.fileExtension]}"
else -> throw Exception("Unexpected script without name: $this")
}
internal inline fun <T> withMessageCollectorAndDisposable(
messageCollector: ScriptDiagnosticsMessageCollector = ScriptDiagnosticsMessageCollector(),
disposable: Disposable = Disposer.newDisposable(),
disposeOnSuccess: Boolean = true,
locationId: String? = null,
body: (ScriptDiagnosticsMessageCollector, Disposable) -> ResultWithDiagnostics<T>
): ResultWithDiagnostics<T> {
var failed = false
return try {
setIdeaIoUseFallback()
body(messageCollector, disposable).also {
failed = it is ResultWithDiagnostics.Failure
}
} catch (ex: Throwable) {
failed = true
failure(messageCollector, ex.asDiagnostics(path = locationId))
} finally {
if (disposeOnSuccess || failed) {
disposable.dispose()
}
}
}
internal inline fun <T> withMessageCollector(
script: SourceCode,
messageCollector: ScriptDiagnosticsMessageCollector = ScriptDiagnosticsMessageCollector(),
body: (ScriptDiagnosticsMessageCollector) -> ResultWithDiagnostics<T>
): ResultWithDiagnostics<T> =
try {
body(messageCollector)
} catch (ex: Throwable) {
failure(messageCollector, ex.asDiagnostics(path = script.locationId))
}
internal fun getScriptKtFile(
script: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration,
project: Project,
messageCollector: ScriptDiagnosticsMessageCollector
): ResultWithDiagnostics<KtFile> {
val psiFileFactory: PsiFileFactoryImpl = PsiFileFactory.getInstance(project) as PsiFileFactoryImpl
val scriptText = getMergedScriptText(script, scriptCompilationConfiguration)
val virtualFile = ScriptLightVirtualFile(
script.scriptFileName(script, scriptCompilationConfiguration),
(script as? FileScriptSource)?.file?.path,
scriptText
)
val ktFile = psiFileFactory.trySetupPsiForFile(virtualFile, KotlinLanguage.INSTANCE, true, false) as KtFile?
return when {
ktFile == null -> failure(script, messageCollector, "Unable to make PSI file from script")
ktFile.declarations.firstIsInstanceOrNull<KtScript>() == null -> failure(script, messageCollector, "Not a script file")
else -> ktFile.asSuccess()
}
}
internal fun makeCompiledScript(
generationState: GenerationState,
script: SourceCode,
ktFile: KtFile,
sourceDependencies: List<ScriptsCompilationDependencies.SourceDependencies>,
getScriptConfiguration: (KtFile) -> ScriptCompilationConfiguration
): KJvmCompiledScript<Any> {
val scriptDependenciesStack = ArrayDeque<KtScript>()
val ktScript = ktFile.declarations.firstIsInstanceOrNull<KtScript>()
?: throw IllegalStateException("Expecting script file: KtScript is not found in ${ktFile.name}")
fun makeOtherScripts(script: KtScript): List<KJvmCompiledScript<*>> {
// TODO: ensure that it is caught earlier (as well) since it would be more economical
if (scriptDependenciesStack.contains(script))
throw IllegalArgumentException("Unable to handle recursive script dependencies")
scriptDependenciesStack.push(script)
val containingKtFile = script.containingKtFile
val otherScripts: List<KJvmCompiledScript<*>> =
sourceDependencies.find { it.scriptFile == containingKtFile }?.sourceDependencies?.mapNotNull { sourceFile ->
sourceFile.declarations.firstIsInstanceOrNull<KtScript>()?.let {
KJvmCompiledScript<Any>(
containingKtFile.virtualFile?.path,
getScriptConfiguration(sourceFile),
it.fqName.asString(),
makeOtherScripts(it),
null
)
}
} ?: emptyList()
scriptDependenciesStack.pop()
return otherScripts
}
val module = makeCompiledModule(generationState)
return KJvmCompiledScript(
script.locationId,
getScriptConfiguration(ktScript.containingKtFile),
ktScript.fqName.asString(),
makeOtherScripts(ktScript),
module
)
}
@@ -0,0 +1,116 @@
/*
* 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 kotlin.script.experimental.jvmhost.repl
import com.intellij.openapi.Disposable
import com.intellij.openapi.util.Disposer
import org.jetbrains.kotlin.cli.common.repl.*
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.scripting.repl.ReplCodeAnalyzer
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.write
import kotlin.script.experimental.api.ScriptCompilationConfiguration
import kotlin.script.experimental.host.ScriptingHostConfiguration
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
import kotlin.script.experimental.jvmhost.impl.KJvmCompilerImpl
import kotlin.script.experimental.jvmhost.impl.withDefaults
class JvmReplCompiler(
val scriptCompilationConfiguration: ScriptCompilationConfiguration,
val hostConfiguration: ScriptingHostConfiguration = defaultJvmScriptingHostConfiguration,
val replCompilerProxy: KJvmReplCompilerProxy = KJvmCompilerImpl(hostConfiguration.withDefaults())
) : ReplCompiler {
override fun createState(lock: ReentrantReadWriteLock): IReplStageState<*> = JvmReplCompilerState(replCompilerProxy, lock)
override fun check(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCheckResult {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun compile(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCompileResult {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
interface KJvmReplCompilerProxy {
fun createReplCompilationState(scriptCompilationConfiguration: ScriptCompilationConfiguration): JvmReplCompilerState.Compilation
}
class JvmReplCompilerStageHistory(private val state: JvmReplCompilerState) :
BasicReplStageHistory<ScriptDescriptor>(state.lock) {
override fun reset(): Iterable<ILineId> {
val removedCompiledLines = super.reset()
val removedAnalyzedLines = state.compilation.analyzerEngine.reset()
checkConsistent(removedCompiledLines, removedAnalyzedLines)
return removedCompiledLines
}
override fun resetTo(id: ILineId): Iterable<ILineId> {
val removedCompiledLines = super.resetTo(id)
val removedAnalyzedLines = state.compilation.analyzerEngine.resetToLine(id)
checkConsistent(removedCompiledLines, removedAnalyzedLines)
return removedCompiledLines
}
private fun checkConsistent(removedCompiledLines: Iterable<ILineId>, removedAnalyzedLines: List<ReplCodeLine>) {
removedCompiledLines.zip(removedAnalyzedLines).forEach { (removedCompiledLine, removedAnalyzedLine) ->
if (removedCompiledLine != LineId(removedAnalyzedLine)) {
throw IllegalStateException("History mismatch when resetting lines: ${removedCompiledLine.no} != $removedAnalyzedLine")
}
}
}
}
class JvmReplCompilerState(
val replCompilerProxy: KJvmReplCompilerProxy,
override val lock: ReentrantReadWriteLock = ReentrantReadWriteLock()
) : IReplStageState<ScriptDescriptor> {
override val history = JvmReplCompilerStageHistory(this)
override val currentGeneration: Int get() = (history as BasicReplStageHistory<*>).currentGeneration.get()
override fun dispose() {
lock.write {
_compilation?.disposable?.dispose()
_compilation = null
super.dispose()
}
}
fun getCompilationState(scriptCompilationConfiguration: ScriptCompilationConfiguration): Compilation = lock.write {
if (_compilation == null) {
initializeCompilation(scriptCompilationConfiguration)
}
_compilation!!
}
internal val compilation: Compilation
get() = _compilation ?: throw IllegalStateException("Compilation state is either not initializad or already destroyed")
private var _compilation: Compilation? = null
val isCompilationInitialized get() = _compilation != null
private fun initializeCompilation(scriptCompilationConfiguration: ScriptCompilationConfiguration) {
if (_compilation != null) throw IllegalStateException("Compilation state is already initialized")
_compilation = replCompilerProxy.createReplCompilationState(scriptCompilationConfiguration)
}
interface Compilation {
val disposable: Disposable
val baseScriptCompilationConfiguration: ScriptCompilationConfiguration
val environment: KotlinCoreEnvironment
val analyzerEngine: ReplCodeAnalyzer
}
}
@@ -90,14 +90,23 @@ class ReplCodeAnalyzer(environment: KotlinCoreEnvironment) {
psiFile.script!!.putUserData(ScriptPriorities.PRIORITY_KEY, codeLine.no)
return doAnalyze(psiFile, codeLine)
return doAnalyze(psiFile, emptyList(), codeLine)
}
private fun doAnalyze(linePsi: KtFile, codeLine: ReplCodeLine): ReplLineAnalysisResult {
fun analyzeReplLineWithImportedScripts(psiFile: KtFile, importedScripts: List<KtFile>, codeLine: ReplCodeLine): ReplLineAnalysisResult {
topDownAnalysisContext.scripts.clear()
trace.clearDiagnostics()
psiFile.script!!.putUserData(ScriptPriorities.PRIORITY_KEY, codeLine.no)
return doAnalyze(psiFile, importedScripts, codeLine)
}
private fun doAnalyze(linePsi: KtFile, importedScripts: List<KtFile>, codeLine: ReplCodeLine): ReplLineAnalysisResult {
scriptDeclarationFactory.setDelegateFactory(FileBasedDeclarationProviderFactory(resolveSession.storageManager, listOf(linePsi)))
replState.submitLine(linePsi, codeLine)
val context = topDownAnalyzer.analyzeDeclarations(topDownAnalysisContext.topDownAnalysisMode, listOf(linePsi))
val context = topDownAnalyzer.analyzeDeclarations(topDownAnalysisContext.topDownAnalysisMode, listOf(linePsi) + importedScripts)
val diagnostics = trace.bindingContext.diagnostics
val hasErrors = diagnostics.any { it.severity == Severity.ERROR }