diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtScript.java b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtScript.java index a7a7e2ebd44..41c3ec3d7c0 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtScript.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtScript.java @@ -20,9 +20,9 @@ import com.intellij.lang.ASTNode; import com.intellij.psi.util.PsiTreeUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.name.FqName; -import org.jetbrains.kotlin.name.Name; import org.jetbrains.kotlin.psi.stubs.KotlinScriptStub; import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes; +import org.jetbrains.kotlin.script.GetScriptDefinitionKt; import org.jetbrains.kotlin.script.KotlinScriptDefinition; import org.jetbrains.kotlin.script.KotlinScriptDefinitionProvider; @@ -47,7 +47,7 @@ public class KtScript extends KtNamedDeclarationStub implement } KtFile containingKtFile = getContainingKtFile(); KotlinScriptDefinition kotlinScriptDefinition = - KotlinScriptDefinitionProvider.getInstance(getProject()).findScriptDefinition(containingKtFile.getOriginalFile().getVirtualFile()); + GetScriptDefinitionKt.getScriptDefinition(containingKtFile); assert kotlinScriptDefinition != null : "Should not parse a script without definition"; return containingKtFile.getPackageFqName().child(kotlinScriptDefinition.getScriptName(this)); } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/descriptors/LazyScriptClassMemberScope.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/descriptors/LazyScriptClassMemberScope.kt index a3f2c2a2cb2..74fd56e9314 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/descriptors/LazyScriptClassMemberScope.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/descriptors/LazyScriptClassMemberScope.kt @@ -29,6 +29,7 @@ import org.jetbrains.kotlin.resolve.BindingTrace import org.jetbrains.kotlin.resolve.lazy.ResolveSession import org.jetbrains.kotlin.resolve.lazy.declarations.ClassMemberDeclarationProvider import org.jetbrains.kotlin.script.KotlinScriptDefinitionProvider +import org.jetbrains.kotlin.script.getScriptDefinition import org.jetbrains.kotlin.utils.toReadOnlyList class LazyScriptClassMemberScope( @@ -55,7 +56,7 @@ class LazyScriptClassMemberScope( private fun createScriptParameters(constructor: ConstructorDescriptorImpl): List { val file = scriptDescriptor.scriptInfo.script.getContainingKtFile() - val scriptDefinition = KotlinScriptDefinitionProvider.getInstance(file.project).findScriptDefinition(file.originalFile.virtualFile) ?: + val scriptDefinition = getScriptDefinition(file) ?: throw RuntimeException("file ${file.name} is not a script") return scriptDefinition.getScriptParameters(scriptDescriptor).mapIndexed { index, scriptParameter -> ValueParameterDescriptorImpl( diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/descriptors/LazyScriptDescriptor.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/descriptors/LazyScriptDescriptor.kt index 6f88c2b8d81..174f6ecb480 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/descriptors/LazyScriptDescriptor.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/descriptors/LazyScriptDescriptor.kt @@ -29,6 +29,7 @@ import org.jetbrains.kotlin.resolve.source.toSourceElement import org.jetbrains.kotlin.script.KotlinScriptDefinition import org.jetbrains.kotlin.script.KotlinScriptDefinitionProvider import org.jetbrains.kotlin.script.ScriptPriorities +import org.jetbrains.kotlin.script.getScriptDefinition import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.TypeSubstitutor @@ -58,7 +59,7 @@ class LazyScriptDescriptor( private val scriptDefinition: KotlinScriptDefinition by lazy { val file = scriptInfo.script.getContainingKtFile() - KotlinScriptDefinitionProvider.getInstance(file.project).findScriptDefinition(file.originalFile.virtualFile) ?: + getScriptDefinition(file) ?: throw RuntimeException("file ${file.name} is not a script") } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinConfigurableScriptDefinition.kt b/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinConfigurableScriptDefinition.kt index 6520430770b..2095ba0f9d3 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinConfigurableScriptDefinition.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinConfigurableScriptDefinition.kt @@ -16,9 +16,7 @@ package org.jetbrains.kotlin.script -import com.intellij.openapi.components.AbstractProjectComponent import com.intellij.openapi.project.Project -import com.intellij.openapi.util.JDOMUtil import com.intellij.openapi.vfs.VirtualFile import com.intellij.util.xmlb.XmlSerializer import com.intellij.util.xmlb.annotations.AbstractCollection @@ -32,77 +30,9 @@ import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.parsing.KotlinParserDefinition import org.jetbrains.kotlin.psi.KtScript import org.jetbrains.kotlin.types.KotlinType -import java.io.File -import java.io.InputStream -import java.io.StringWriter -import java.util.* +import org.jetbrains.kotlin.utils.PathUtil -val SCRIPT_CONFIG_FILE_EXTENSION = ".ktscfg.xml" - -fun isScriptDefinitionConfigFile(it: File) = it.isFile && it.name.endsWith(SCRIPT_CONFIG_FILE_EXTENSION, ignoreCase = true) - -fun loadScriptConfigsFromProjectRoot(projectRoot: File): List = - projectRoot.walk().filter ( ::isScriptDefinitionConfigFile ).toList() - .flatMap { loadScriptConfigs(it) } - -fun loadScriptConfigs(configFile: File): List = - JDOMUtil.loadDocument(configFile).rootElement.children.mapNotNull { - XmlSerializer.deserialize(it, KotlinScriptConfig::class.java) - } - -@Suppress("unused") // Used externally -fun loadScriptConfigs(configStream: InputStream): List = - JDOMUtil.loadDocument(configStream).rootElement.children.mapNotNull { - XmlSerializer.deserialize(it, KotlinScriptConfig::class.java) - } - -@Suppress("unused") -fun generateSampleScriptConfig(): String { - - val doc = Document(Element("KotlinScriptDefinitions")) - val element = XmlSerializer.serialize( - KotlinScriptConfig(name = "abc", fileNameMatch = ".*\\.kts", classpath = arrayListOf("aaa", "bbb"), - parameters = arrayListOf(KotlinScriptParameterConfig("p1", "t1")))) - doc.rootElement.addContent(element) - - val sw = StringWriter() - with (XMLOutputter()) { - format = Format.getPrettyFormat() - output(doc, sw) - } - return sw.toString() -} - -@Tag("scriptParam") -class KotlinScriptParameterConfig(@Tag("name") var name: String = "", - @Tag("type") var type: String = "") - -@Tag("script") -class KotlinScriptConfig( - @Tag("name") - var name: String = "KotlinScript", - - @Tag("files") - var fileNameMatch: String = ".*\\.kts", - - @Tag("classpath") - @AbstractCollection(surroundWithTag = false, elementTag = "path", elementValueAttribute = "") - var classpath: MutableList = ArrayList(), - - @Tag("parameters") - @AbstractCollection(surroundWithTag = false, elementValueAttribute = "") - var parameters: MutableList = ArrayList(), - - @Tag("supertypes") - @AbstractCollection(surroundWithTag = false, elementTag = "type", elementValueAttribute = "") - var supertypes: MutableList = ArrayList(), - - @Tag("superclassParameters") - @AbstractCollection(surroundWithTag = false, elementTag = "name", elementValueAttribute = "") - var superclassParamsMapping: MutableList = ArrayList() -) - class KotlinConfigurableScriptDefinition(val config: KotlinScriptConfig, val environmentVars: Map>?) : KotlinScriptDefinition { override val name = config.name override fun getScriptParameters(scriptDescriptor: ScriptDescriptor): List = @@ -119,18 +49,56 @@ class KotlinConfigurableScriptDefinition(val config: KotlinScriptConfig, val env override fun getScriptName(script: KtScript): Name = ScriptNameUtil.fileNameWithExtensionStripped(script, KotlinParserDefinition.STD_SCRIPT_EXT) - // return all combination of replacements of env vars in the classpath entries - // if corresponding list of replacements is empty, all classpath entries containing the reference to the var are removed - // TODO: tests - override fun getScriptDependenciesClasspath(): List = - if (environmentVars == null || environmentVars.isEmpty()) config.classpath - else config.classpath.flatMap { cpentry -> - environmentVars.entries.fold(listOf(cpentry)) { p, v -> - if (v.value.isEmpty() && cpentry.contains("\${${v.key}}")) emptyList() - else v.value.flatMap { valListElement -> - p.map { it.replace("\${${v.key}}", valListElement) } - } - } - } + protected val evaluatedClasspath by lazy { config.classpath.evalDistinctWith(environmentVars) } + + override fun getScriptDependenciesClasspath(): List = evaluatedClasspath } +class KotlinDelegatingScriptDefinitionWithExtraImports internal constructor ( + val delegate: KotlinScriptDefinition, + val extraImports: List +) : KotlinScriptDefinition { + override val name: String get() = delegate.name + override fun getScriptParameters(scriptDescriptor: ScriptDescriptor): List = delegate.getScriptParameters(scriptDescriptor) + override fun getScriptSupertypes(scriptDescriptor: ScriptDescriptor): List = delegate.getScriptSupertypes(scriptDescriptor) + override fun getScriptParametersToPassToSuperclass(scriptDescriptor: ScriptDescriptor): List = + delegate.getScriptParametersToPassToSuperclass(scriptDescriptor) + override fun isScript(file: VirtualFile): Boolean = delegate.isScript(file) + override fun getScriptName(script: KtScript): Name = delegate.getScriptName(script) + + protected val evaluatedExtraClasspath by lazy { + (delegate.getScriptDependenciesClasspath() + extraImports.flatMap { it.classpath }).distinct() + } + + override fun getScriptDependenciesClasspath(): List = evaluatedExtraClasspath +} + + +// return all combination of replacements of vars in the strings +// if corresponding list of replacements is empty, all strings containing the reference to the var are removed +// TODO: fix and tests +// TODO: move to some utils +internal fun List.evalDistinctWith(varsMap: Map>?): List = + if (varsMap == null || varsMap.isEmpty()) this + else this.flatMap { cpentry -> + varsMap.entries.fold(listOf(cpentry)) { p, v -> + if (v.value.isEmpty() && cpentry.contains("\${${v.key}}")) emptyList() + else v.value.flatMap { valListElement -> + p.map { it.replace("\${${v.key}}", valListElement) } + } + } + } + +fun generateKotlinScriptClasspathEnvVarsForCompiler(project: Project): Map> { + val paths = PathUtil.getKotlinPathsForCompiler() + return mapOf("kotlin-runtime" to listOf(paths.runtimePath.canonicalPath), + "project-root" to listOf(project.basePath ?: "."), + "jdk" to PathUtil.getJdkClassesRoots().map { it.canonicalPath }) +} + +fun generateKotlinScriptClasspathEnvVarsForIdea(project: Project): Map> { + val paths = PathUtil.getKotlinPathsForIdeaPlugin() + return mapOf("kotlin-runtime" to listOf(paths.runtimePath.canonicalPath), + "project-root" to listOf(project.basePath ?: "."), + "jdk" to PathUtil.getJdkClassesRoots().map { it.canonicalPath }) +} diff --git a/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinScriptExtraImportsProvider.kt b/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinScriptExtraImportsProvider.kt new file mode 100644 index 00000000000..fe171c6c5c7 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinScriptExtraImportsProvider.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.script + +import com.intellij.openapi.components.ServiceManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile +import java.util.concurrent.locks.ReentrantReadWriteLock +import kotlin.concurrent.read +import kotlin.concurrent.write + +class KotlinScriptExtraImportsProvider(val project: Project) { + private val lock = ReentrantReadWriteLock() + private val cache = hashMapOf>() + private val envVars: Map> by lazy { generateKotlinScriptClasspathEnvVarsForIdea(project) } + + fun isExtraImportsConfig(file: VirtualFile): Boolean = file.name.endsWith(IMPORTSFILEEXTENSION) + + fun getExtraImports(file: VirtualFile): List = lock.read { + cache.getOrPut(file) { + file.parent.findFileByRelativePath(file.name + IMPORTSFILEEXTENSION)?.let { + loadScriptExtraImportConfigs(it.inputStream).map { KotlinScriptExtraImportFromConfig(it, envVars) } + } + ?: listOf() + } + } + + fun invalidateExtraImports(importsFile: VirtualFile) { + importsFile.parent.findFileByRelativePath(importsFile.name.removeSuffix(IMPORTSFILEEXTENSION))?.let { + lock.write { + cache.remove(it) + } + } + } + + companion object { + @JvmStatic + fun getInstance(project: Project): KotlinScriptExtraImportsProvider = + ServiceManager.getService(project, KotlinScriptExtraImportsProvider::class.java) + + val IMPORTSFILEEXTENSION = ".ktsimports.xml" + } +} \ No newline at end of file diff --git a/compiler/frontend/src/org/jetbrains/kotlin/script/getScriptDefinition.kt b/compiler/frontend/src/org/jetbrains/kotlin/script/getScriptDefinition.kt new file mode 100644 index 00000000000..c8ae844aff0 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/script/getScriptDefinition.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.script + +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.PsiFile + +fun getScriptDefinition(file: VirtualFile, project: Project): KotlinScriptDefinition? = + KotlinScriptDefinitionProvider.getInstance(project).findScriptDefinition(file)?.let { kindDef -> + val extraImports = KotlinScriptExtraImportsProvider.getInstance(project).getExtraImports(file) + if (extraImports.isEmpty()) kindDef + else KotlinDelegatingScriptDefinitionWithExtraImports(kindDef, extraImports) + } + +fun getScriptDefinition(psiFile: PsiFile): KotlinScriptDefinition? = + KotlinScriptDefinitionProvider.getInstance(psiFile.project).findScriptDefinition(psiFile.originalFile.virtualFile)?.let { kindDef -> + (psiFile.originalFile.virtualFile ?: psiFile.virtualFile)?.let { file -> + val extraImports = KotlinScriptExtraImportsProvider.getInstance(psiFile.project).getExtraImports(file) + if (extraImports.isEmpty()) kindDef + else KotlinDelegatingScriptDefinitionWithExtraImports(kindDef, extraImports) + } + ?: kindDef + } + diff --git a/compiler/frontend/src/org/jetbrains/kotlin/script/scriptConfig.kt b/compiler/frontend/src/org/jetbrains/kotlin/script/scriptConfig.kt new file mode 100644 index 00000000000..592e2c15abc --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/script/scriptConfig.kt @@ -0,0 +1,98 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.script + +import com.intellij.openapi.util.JDOMUtil +import com.intellij.util.xmlb.XmlSerializer +import com.intellij.util.xmlb.annotations.AbstractCollection +import com.intellij.util.xmlb.annotations.Tag +import org.jdom.Document +import org.jdom.Element +import org.jdom.output.Format +import org.jdom.output.XMLOutputter +import java.io.File +import java.io.InputStream +import java.io.StringWriter +import java.util.* + +val SCRIPT_CONFIG_FILE_EXTENSION = ".ktscfg.xml" + +fun isScriptDefinitionConfigFile(file: java.io.File) = file.isFile && file.name.endsWith(org.jetbrains.kotlin.script.SCRIPT_CONFIG_FILE_EXTENSION) + +fun isScriptDefinitionConfigFile(file: com.intellij.openapi.vfs.VirtualFile) = !file.isDirectory && file.name.endsWith(org.jetbrains.kotlin.script.SCRIPT_CONFIG_FILE_EXTENSION) + +fun loadScriptConfigsFromProjectRoot(projectRoot: java.io.File): List = + projectRoot.walk().filter(::isScriptDefinitionConfigFile).toList() + .flatMap { loadScriptConfigs(it) } + +fun loadScriptConfigs(configFile: File): List = + JDOMUtil.loadDocument(configFile).rootElement.children.mapNotNull { + XmlSerializer.deserialize(it, KotlinScriptConfig::class.java) + } + +@Suppress("unused") // Used externally +fun loadScriptConfigs(configStream: InputStream): List = + JDOMUtil.loadDocument(configStream).rootElement.children.mapNotNull { + XmlSerializer.deserialize(it, KotlinScriptConfig::class.java) + } + +@Suppress("unused") +fun generateSampleScriptConfig(): String { + + val doc = Document(Element("KotlinScriptDefinitions")) + val element = XmlSerializer.serialize( + KotlinScriptConfig(name = "abc", fileNameMatch = ".*\\.kts", classpath = arrayListOf("aaa", "bbb"), + parameters = arrayListOf(KotlinScriptParameterConfig("p1", "t1")))) + doc.rootElement.addContent(element) + + val sw = StringWriter() + with (XMLOutputter()) { + format = Format.getPrettyFormat() + output(doc, sw) + } + return sw.toString() +} + +@Tag("scriptParam") +class KotlinScriptParameterConfig(@Tag("name") var name: String = "", + @Tag("type") var type: String = "") + +@Tag("script") +class KotlinScriptConfig( + @Tag("name") + var name: String = "KotlinScript", + + @Tag("files") + var fileNameMatch: String = ".*\\.kts", + + @Tag("classpath") + @AbstractCollection(surroundWithTag = false, elementTag = "path", elementValueAttribute = "") + var classpath: MutableList = ArrayList(), + + @Tag("parameters") + @AbstractCollection(surroundWithTag = false, elementValueAttribute = "") + var parameters: MutableList = ArrayList(), + + @Tag("supertypes") + @AbstractCollection(surroundWithTag = false, elementTag = "type", elementValueAttribute = "") + var supertypes: MutableList = ArrayList(), + + @Tag("superclassParameters") + @AbstractCollection(surroundWithTag = false, elementTag = "name", elementValueAttribute = "") + var superclassParamsMapping: MutableList = ArrayList() +) + diff --git a/compiler/frontend/src/org/jetbrains/kotlin/script/scriptExtraImports.kt b/compiler/frontend/src/org/jetbrains/kotlin/script/scriptExtraImports.kt new file mode 100644 index 00000000000..68192967836 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/script/scriptExtraImports.kt @@ -0,0 +1,63 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.script + +import com.intellij.openapi.util.JDOMUtil +import com.intellij.util.xmlb.XmlSerializer +import com.intellij.util.xmlb.annotations.AbstractCollection +import com.intellij.util.xmlb.annotations.Tag +import java.io.File +import java.io.InputStream +import java.util.* + +/** + * A particular script dependency, additional to script kind dependencies defined by KotlinScriptDefinition + */ +interface KotlinScriptExtraImport { + val classpath: List + val names: List +} + +// ----- + +@Tag("import") +class KotlinScriptExtraImportConfig { + @Tag("classpath") + @AbstractCollection(surroundWithTag = false, elementTag = "path", elementValueAttribute = "") + var classpath: MutableList = ArrayList() + + @Tag("names") + @AbstractCollection(surroundWithTag = false, elementTag = "name", elementValueAttribute = "") + var names: MutableList = ArrayList() +} + +fun loadScriptExtraImportConfigs(configFile: File): List = + JDOMUtil.loadDocument(configFile).rootElement.children.mapNotNull { + XmlSerializer.deserialize(it, KotlinScriptExtraImportConfig::class.java) + } + +fun loadScriptExtraImportConfigs(configStream: InputStream): List = + JDOMUtil.loadDocument(configStream).rootElement.children.mapNotNull { + XmlSerializer.deserialize(it, KotlinScriptExtraImportConfig::class.java) + } + +class KotlinScriptExtraImportFromConfig(val config : KotlinScriptExtraImportConfig, val envVars: Map>) : KotlinScriptExtraImport { + override val classpath: List by lazy { config.classpath.evalDistinctWith(envVars) } + override val names: List + get() = config.names +} + diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IdeaModuleInfos.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IdeaModuleInfos.kt index 46b376205d7..481c63d1fac 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IdeaModuleInfos.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IdeaModuleInfos.kt @@ -17,16 +17,13 @@ package org.jetbrains.kotlin.idea.caches.resolve import com.intellij.openapi.module.Module -import com.intellij.openapi.module.ModuleManager import com.intellij.openapi.module.impl.scopes.LibraryScopeBase import com.intellij.openapi.project.Project import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.roots.* import com.intellij.openapi.roots.impl.libraries.LibraryEx -import com.intellij.openapi.roots.impl.libraries.LibraryImpl import com.intellij.openapi.roots.libraries.Library import com.intellij.openapi.roots.libraries.LibraryTable -import com.intellij.openapi.roots.libraries.PersistentLibraryKind import com.intellij.openapi.vfs.StandardFileSystems import com.intellij.openapi.vfs.VfsUtilCore import com.intellij.openapi.vfs.VirtualFile @@ -291,7 +288,8 @@ internal object NotUnderContentRootModuleInfo : IdeaModuleInfo { override fun dependencies(): List = listOf(this) } -internal data class CustomizedScriptModuleInfo(val project: Project, val module: Module?, val virtualFile: VirtualFile, val scriptDefinition: KotlinScriptDefinition) : IdeaModuleInfo { +internal data class CustomizedScriptModuleInfo(val project: Project, val module: Module?, val virtualFile: VirtualFile, + val scriptDefinition: KotlinScriptDefinition) : IdeaModuleInfo { override val moduleOrigin: ModuleOrigin get() = ModuleOrigin.OTHER @@ -300,15 +298,18 @@ internal data class CustomizedScriptModuleInfo(val project: Project, val module: override fun contentScope() = GlobalSearchScope.union(dependenciesRoots().map { FileLibraryScope(project, it) }.toTypedArray()) private fun dependenciesRoots(): List { - // TODO: find out whether it should be cashed + // TODO: find out whether it should be cashed (some changes listener should be implemented for the cached roots) val virtualFileManager = VirtualFileManager.getInstance() val jarfs = StandardFileSystems.jar() - return scriptDefinition.getScriptDependenciesClasspath().map { - if (File(it).isFile) - jarfs.findFileByPath(it + URLUtil.JAR_SEPARATOR) ?: throw FileNotFoundException("Classpath entry points to a file that is not a JAR archive: $it") - else - virtualFileManager.findFileByUrl("file://$it") ?: throw FileNotFoundException("Classpath entry points to a non-existent location: $it") - } + return (scriptDefinition.getScriptDependenciesClasspath()) + .map { File(it).canonicalFile } + .distinct() + .map { + if (it.isFile) + jarfs.findFileByPath(it.absolutePath + URLUtil.JAR_SEPARATOR) ?: throw FileNotFoundException("Classpath entry points to a file that is not a JAR archive: ${it.canonicalPath}") + else + virtualFileManager.findFileByUrl("file://${it.absolutePath}") ?: throw FileNotFoundException("Classpath entry points to a non-existent location: ${it.canonicalPath}") + } } override fun dependencies() = diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/getModuleInfo.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/getModuleInfo.kt index fd6ed27dc9b..93fa2b71266 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/getModuleInfo.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/getModuleInfo.kt @@ -30,6 +30,7 @@ import org.jetbrains.kotlin.idea.util.ProjectRootsUtil import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getParentOfType import org.jetbrains.kotlin.script.KotlinScriptDefinitionProvider +import org.jetbrains.kotlin.script.getScriptDefinition import org.jetbrains.kotlin.utils.sure fun PsiElement.getModuleInfo(): IdeaModuleInfo = this.getModuleInfo { reason -> @@ -122,7 +123,7 @@ private fun getModuleInfoByVirtualFile(project: Project, virtualFile: VirtualFil } } - val scriptDefinition = KotlinScriptDefinitionProvider.getInstance(project).findScriptDefinition(virtualFile) + val scriptDefinition = getScriptDefinition(virtualFile, project) if (scriptDefinition != null) return CustomizedScriptModuleInfo(project, module, virtualFile, scriptDefinition) diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index b7b9b742073..a54167122d1 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -248,6 +248,9 @@ + + diff --git a/idea/src/org/jetbrains/kotlin/idea/script/KotlinScriptConfigurationManager.kt b/idea/src/org/jetbrains/kotlin/idea/script/KotlinScriptConfigurationManager.kt index ec5db832742..45d9b217ce4 100644 --- a/idea/src/org/jetbrains/kotlin/idea/script/KotlinScriptConfigurationManager.kt +++ b/idea/src/org/jetbrains/kotlin/idea/script/KotlinScriptConfigurationManager.kt @@ -22,25 +22,33 @@ import com.intellij.openapi.vfs.VirtualFileManager import com.intellij.openapi.vfs.newvfs.BulkFileListener import com.intellij.openapi.vfs.newvfs.events.VFileEvent import org.jetbrains.kotlin.script.* -import org.jetbrains.kotlin.utils.PathUtil import java.io.File @Suppress("unused") // project component -class KotlinScriptConfigurationManager(private val project: Project, private val scriptDefinitionProvider: KotlinScriptDefinitionProvider) : AbstractProjectComponent(project) { +class KotlinScriptConfigurationManager(project: Project, + private val scriptDefinitionProvider: KotlinScriptDefinitionProvider, + private val scriptExtraImportsProvider: KotlinScriptExtraImportsProvider +) : AbstractProjectComponent(project) { - private val kotlinEnvVars: Map> by lazy { - val paths = PathUtil.getKotlinPathsForIdeaPlugin() - mapOf("kotlin-runtime" to listOf(paths.runtimePath.canonicalPath), - "project-root" to listOf(project.basePath ?: "."), - "jdk" to PathUtil.getJdkClassesRoots().map { it.canonicalPath }) - } + private val kotlinEnvVars: Map> by lazy { generateKotlinScriptClasspathEnvVarsForIdea(myProject) } init { reloadScriptDefinitions() - val conn = project.messageBus.connect() + val conn = myProject.messageBus.connect() conn.subscribe(VirtualFileManager.VFS_CHANGES, object : BulkFileListener.Adapter() { override fun after(events: List) { - events.firstOrNull { it is VFileEvent && isScriptDefinitionConfigFile(File(it.path)) }?.let { + var anyScriptDefinitionChanged = false + events.filter { it is VFileEvent }.forEach { + it.file?.let { + if (!anyScriptDefinitionChanged && isScriptDefinitionConfigFile(it)) { + anyScriptDefinitionChanged = true + } + if (scriptExtraImportsProvider.isExtraImportsConfig(it)) { + scriptExtraImportsProvider.invalidateExtraImports(it) + } + } + } + if (anyScriptDefinitionChanged) { reloadScriptDefinitions() } } @@ -48,7 +56,7 @@ class KotlinScriptConfigurationManager(private val project: Project, private val } private fun reloadScriptDefinitions() { - loadScriptConfigsFromProjectRoot(File(project.basePath ?: ".")).let { + loadScriptConfigsFromProjectRoot(File(myProject.basePath ?: ".")).let { if (it.isNotEmpty()) { scriptDefinitionProvider.scriptDefinitions = it.map { KotlinConfigurableScriptDefinition(it, kotlinEnvVars) } + StandardScriptDefinition @@ -57,3 +65,4 @@ class KotlinScriptConfigurationManager(private val project: Project, private val } } +