diff --git a/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ScriptingHostTest.kt b/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ScriptingHostTest.kt index 52b87407204..2f4b3b6c5de 100644 --- a/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ScriptingHostTest.kt +++ b/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ScriptingHostTest.kt @@ -373,7 +373,7 @@ class ScriptingHostTest : TestCase() { } val res = BasicJvmScriptingHost().eval(script.toScriptSource(), compilationConfiguration, null) assertTrue(res is ResultWithDiagnostics.Failure) - val report = res.reports.find { it.message.startsWith("Source file or directory not found") } + val report = res.reports.find { it.message.startsWith("Imported source file not found") } assertNotNull(report) assertEquals("script.kts", report?.sourcePath) } diff --git a/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/resolve/LazyScriptDescriptor.kt b/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/resolve/LazyScriptDescriptor.kt index d428f61a470..ea99a1b3f75 100644 --- a/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/resolve/LazyScriptDescriptor.kt +++ b/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/resolve/LazyScriptDescriptor.kt @@ -5,20 +5,15 @@ package org.jetbrains.kotlin.scripting.resolve -import com.intellij.openapi.vfs.StandardFileSystems -import com.intellij.openapi.vfs.VirtualFileManager -import com.intellij.openapi.vfs.VirtualFileSystem import com.intellij.psi.PsiElement import com.intellij.psi.PsiManager import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.annotations.Annotations import org.jetbrains.kotlin.descriptors.annotations.FilteredAnnotations import org.jetbrains.kotlin.descriptors.impl.ClassConstructorDescriptorImpl -import org.jetbrains.kotlin.descriptors.impl.PropertyDescriptorImpl import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1 import org.jetbrains.kotlin.diagnostics.Errors -import org.jetbrains.kotlin.diagnostics.Errors.MISSING_IMPORTED_SCRIPT_FILE import org.jetbrains.kotlin.diagnostics.Errors.MISSING_IMPORTED_SCRIPT_PSI import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName @@ -41,7 +36,10 @@ import org.jetbrains.kotlin.resolve.scopes.LexicalScopeImpl import org.jetbrains.kotlin.resolve.scopes.LexicalScopeKind import org.jetbrains.kotlin.resolve.scopes.utils.addImportingScope import org.jetbrains.kotlin.resolve.source.toSourceElement -import org.jetbrains.kotlin.scripting.definitions.* +import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition +import org.jetbrains.kotlin.scripting.definitions.ScriptDependenciesProvider +import org.jetbrains.kotlin.scripting.definitions.ScriptPriorities +import org.jetbrains.kotlin.scripting.definitions.findScriptCompilationConfiguration import org.jetbrains.kotlin.types.TypeSubstitutor import org.jetbrains.kotlin.types.Variance import org.jetbrains.kotlin.types.typeUtil.isNothing @@ -50,7 +48,6 @@ import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull import kotlin.reflect.KClass import kotlin.reflect.KType import kotlin.script.experimental.api.* -import kotlin.script.experimental.host.FileBasedScriptSource import kotlin.script.experimental.host.GetScriptingClass import kotlin.script.experimental.host.ScriptingHostConfiguration import kotlin.script.experimental.host.getScriptingClass @@ -185,10 +182,6 @@ class LazyScriptDescriptor( private inner class ImportedScriptDescriptorsFinder { - val localFS: VirtualFileSystem by lazy(LazyThreadSafetyMode.PUBLICATION) { - val fileManager = VirtualFileManager.getInstance() - fileManager.getFileSystem(StandardFileSystems.FILE_PROTOCOL) - } val psiManager by lazy(LazyThreadSafetyMode.PUBLICATION) { PsiManager.getInstance(scriptInfo.script.project) } operator fun invoke(importedScript: SourceCode): ScriptDescriptor? { @@ -202,20 +195,13 @@ class LazyScriptDescriptor( private fun getKtFile(script: SourceCode): KtFile? { if (script is KtFileScriptSource) return script.ktFile - // TODO: support any kind of ScriptSource. - if (script !is FileBasedScriptSource) return null - fun errorKtFile(errorDiagnostic: DiagnosticFactory1?): KtFile? { - reportErrorString1(errorDiagnostic, script.file.path) + reportErrorString1(errorDiagnostic, script.locationId ?: script.name ?: "unknown script") return null } - val virtualFile = when (script) { - is VirtualFileScriptSource -> script.virtualFile - else -> localFS.findFileByPath(script.file.absolutePath) ?: return errorKtFile(MISSING_IMPORTED_SCRIPT_FILE) - } - - val psiFile = psiManager.findFile(virtualFile) ?: return errorKtFile(MISSING_IMPORTED_SCRIPT_PSI) + val psiFile = (script as? VirtualFileScriptSource)?.let { psiManager.findFile(it.virtualFile) } + ?: return errorKtFile(MISSING_IMPORTED_SCRIPT_PSI) return psiFile as? KtFile } } diff --git a/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/resolve/refineCompilationConfiguration.kt b/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/resolve/refineCompilationConfiguration.kt index 99dfe8528f8..fdbdc39810a 100644 --- a/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/resolve/refineCompilationConfiguration.kt +++ b/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/resolve/refineCompilationConfiguration.kt @@ -9,9 +9,7 @@ import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.editor.Document import com.intellij.openapi.project.Project import com.intellij.openapi.util.text.StringUtil -import com.intellij.openapi.vfs.CharsetToolkit -import com.intellij.openapi.vfs.LocalFileSystem -import com.intellij.openapi.vfs.VirtualFile +import com.intellij.openapi.vfs.* import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.intellij.psi.PsiManager @@ -24,6 +22,7 @@ import org.jetbrains.kotlin.psi.psiUtil.startOffset import org.jetbrains.kotlin.scripting.definitions.KotlinScriptDefinition import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition import org.jetbrains.kotlin.scripting.definitions.runReadAction +import org.jetbrains.kotlin.scripting.scriptFileName import org.jetbrains.kotlin.scripting.withCorrectExtension import java.io.File import java.net.URL @@ -38,6 +37,7 @@ import kotlin.script.experimental.jvm.* import kotlin.script.experimental.jvm.compat.mapToDiagnostics import kotlin.script.experimental.jvm.impl.toClassPathOrEmpty import kotlin.script.experimental.jvm.impl.toDependencies +import kotlin.script.experimental.util.PropertiesCollection internal fun VirtualFile.loadAnnotations( acceptedAnnotations: List>, @@ -146,7 +146,7 @@ abstract class ScriptCompilationConfigurationWrapper(val script: SourceCode) { get() = configuration?.get(ScriptCompilationConfiguration.defaultImports).orEmpty() override val importedScripts: List - get() = configuration?.get(ScriptCompilationConfiguration.importScripts).orEmpty() + get() = (configuration?.get(ScriptCompilationConfiguration.resolvedImportScripts) ?: configuration?.get(ScriptCompilationConfiguration.importScripts)).orEmpty() @Suppress("OverridingDeprecatedMember", "OVERRIDE_DEPRECATION") override val legacyDependencies: ScriptDependencies? @@ -217,12 +217,15 @@ abstract class ScriptCompilationConfigurationWrapper(val script: SourceCode) { typealias ScriptCompilationConfigurationResult = ResultWithDiagnostics +val ScriptCompilationConfigurationKeys.resolvedImportScripts by PropertiesCollection.key>(isTransient = true) + @Suppress("DEPRECATION") fun refineScriptCompilationConfiguration( script: SourceCode, definition: ScriptDefinition, project: Project, - providedConfiguration: ScriptCompilationConfiguration? = null // if null - take from definition + providedConfiguration: ScriptCompilationConfiguration? = null, // if null - take from definition + knownVirtualFileSources: MutableMap? = null ): ScriptCompilationConfigurationResult { // TODO: add location information on refinement errors val ktFileSource = script.toKtFileSource(definition, project) @@ -236,6 +239,8 @@ fun refineScriptCompilationConfiguration( return compilationConfiguration.refineOnAnnotations(script, collectedData) .onSuccess { it.refineBeforeCompiling(script, collectedData) + }.onSuccess { + it.resolveImportsToVirtualFiles(knownVirtualFileSources) }.onSuccess { ScriptCompilationConfigurationWrapper.FromCompilationConfiguration( ktFileSource, @@ -294,6 +299,50 @@ private fun additionalClasspath(definition: ScriptDefinition): List { ?: definition.hostConfiguration[ScriptingHostConfiguration.configurationDependencies].toClassPathOrEmpty()) } +fun ScriptCompilationConfiguration.resolveImportsToVirtualFiles( + knownFileBasedSources: MutableMap? +) +: ResultWithDiagnostics { + // the resolving is needed while CoreVirtualFS does not cache the files, so attempt to find vf and then PSI by path leads + // to different PSI files, which breaks mappings needed by script descriptor + // resolving only to virtual file allows to simplify serialization and maybe a bit more future proof + + val localFS: VirtualFileSystem by lazy(LazyThreadSafetyMode.NONE) { + val fileManager = VirtualFileManager.getInstance() + fileManager.getFileSystem(StandardFileSystems.FILE_PROTOCOL) + } + + val resolvedImports = get(ScriptCompilationConfiguration.importScripts)?.map { sourceCode -> + when (sourceCode) { + is VirtualFileScriptSource -> sourceCode + is FileBasedScriptSource -> { + val path = sourceCode.file.normalize().absolutePath + knownFileBasedSources?.get(path) ?: run { + val virtualFile = localFS.findFileByPath(path) + ?: return@resolveImportsToVirtualFiles makeFailureResult("Imported source file not found: ${sourceCode.file}".asErrorDiagnostics()) + VirtualFileScriptSource(virtualFile).also { + knownFileBasedSources?.set(path, it) + } + } + } + + else -> { + // TODO: support knownFileBasedSources here as well + val scriptFileName = sourceCode.scriptFileName(sourceCode, this) + val virtualFile = ScriptLightVirtualFile( + scriptFileName, + sourceCode.locationId, + sourceCode.text + ) + VirtualFileScriptSource(virtualFile) + } + } + } + + val updatedConfiguration = if (resolvedImports.isNullOrEmpty()) this else this.with { resolvedImportScripts(resolvedImports) } + return updatedConfiguration.asSuccess() +} + internal fun makeScriptContents( file: VirtualFile, legacyDefinition: KotlinScriptDefinition, diff --git a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/definitions/CliScriptDependenciesProvider.kt b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/definitions/CliScriptDependenciesProvider.kt index adfad3e952c..4fc65e85f72 100644 --- a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/definitions/CliScriptDependenciesProvider.kt +++ b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/definitions/CliScriptDependenciesProvider.kt @@ -10,10 +10,7 @@ import com.intellij.openapi.project.Project import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.scripting.definitions.ScriptDependenciesProvider import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition -import org.jetbrains.kotlin.scripting.resolve.KtFileScriptSource -import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult -import org.jetbrains.kotlin.scripting.resolve.ScriptReportSink -import org.jetbrains.kotlin.scripting.resolve.refineScriptCompilationConfiguration +import org.jetbrains.kotlin.scripting.resolve.* import java.util.concurrent.locks.ReentrantReadWriteLock import kotlin.concurrent.read import kotlin.concurrent.write @@ -22,6 +19,7 @@ import kotlin.script.experimental.api.ScriptCompilationConfiguration class CliScriptDependenciesProvider(project: Project) : ScriptDependenciesProvider(project) { private val cacheLock = ReentrantReadWriteLock() private val cache = hashMapOf() + private val knownVirtualFileSources = mutableMapOf() override fun getScriptConfigurationResult(file: KtFile): ScriptCompilationConfigurationResult? = cacheLock.read { calculateRefinedConfiguration(file, null) @@ -43,7 +41,10 @@ class CliScriptDependenciesProvider(project: Project) : ScriptDependenciesProvid else { val scriptDef = file.findScriptDefinition() if (scriptDef != null) { - val result = refineScriptCompilationConfiguration(KtFileScriptSource(file), scriptDef, project, providedConfiguration) + val result = + refineScriptCompilationConfiguration( + KtFileScriptSource(file), scriptDef, project, providedConfiguration, knownVirtualFileSources + ) ServiceManager.getService(project, ScriptReportSink::class.java)?.attachReports(file.virtualFile, result.reports) diff --git a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/dependencies/ScriptsCompilationDependencies.kt b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/dependencies/ScriptsCompilationDependencies.kt index 40edc1de32a..faf9f4d0705 100644 --- a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/dependencies/ScriptsCompilationDependencies.kt +++ b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/dependencies/ScriptsCompilationDependencies.kt @@ -6,12 +6,17 @@ package org.jetbrains.kotlin.scripting.compiler.plugin.dependencies import com.intellij.openapi.project.Project +import com.intellij.psi.PsiManager import org.jetbrains.kotlin.cli.common.config.KotlinSourceRoot import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity import org.jetbrains.kotlin.cli.jvm.compiler.createSourceFilesFromSourceRoots +import org.jetbrains.kotlin.cli.jvm.compiler.report import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.scripting.definitions.ScriptDependenciesProvider +import org.jetbrains.kotlin.scripting.resolve.KtFileScriptSource +import org.jetbrains.kotlin.scripting.resolve.VirtualFileScriptSource import java.io.File import kotlin.script.experimental.api.ResultWithDiagnostics import kotlin.script.experimental.api.ScriptCompilationConfiguration @@ -42,6 +47,7 @@ fun collectScriptsCompilationDependencies( var remainingSources = initialSources val knownSourcePaths = initialSources.mapNotNullTo(HashSet()) { it.virtualFile?.path } val importsProvider = ScriptDependenciesProvider.getInstance(project) + val psiManager by lazy(LazyThreadSafetyMode.NONE) { PsiManager.getInstance(project) } if (importsProvider != null) { while (true) { val newRemainingSources = ArrayList() @@ -54,17 +60,23 @@ fun collectScriptsCompilationDependencies( is ResultWithDiagnostics.Success -> { collectedClassPath.addAll(refinedConfiguration.value.dependenciesClassPath) - val sourceDependenciesRoots = refinedConfiguration.value.importedScripts.mapNotNull { - // TODO: support any kind of ScriptSource. - val path = (it as? FileBasedScriptSource)?.file?.absolutePath ?: return@mapNotNull null - KotlinSourceRoot(path, false) + val sourceDependencies = refinedConfiguration.value.importedScripts.mapNotNull { + if (it is KtFileScriptSource) it.ktFile + else { + val virtualFileSource = (it as? VirtualFileScriptSource) + ?: error("expecting script sources resolved to virtual files here") + (psiManager.findFile(virtualFileSource.virtualFile) as? KtFile).also { + if (it == null) { + configuration.report( + CompilerMessageSeverity.ERROR, + "imported file is not kotlin source: ${virtualFileSource.virtualFile.path}", + // TODO: consider receiving and using precise location from the resolver in the future + CompilerMessageLocation.create(source.virtualFile.path) + ) + } + } + } } - val sourceDependencies = - createSourceFilesFromSourceRoots( - configuration, project, sourceDependenciesRoots, - // TODO: consider receiving and using precise location from the resolver in the future - source.virtualFile?.path?.let { CompilerMessageLocation.create(it) } - ) if (sourceDependencies.isNotEmpty()) { collectedSourceDependencies.add( ScriptsCompilationDependencies.SourceDependencies(