diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt index d25f0420619..e6a8454bfc9 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt @@ -149,15 +149,9 @@ class KotlinCoreEnvironment private constructor( ?: emptyList() }) - KotlinScriptExtraImportsProvider.getInstance(project)?.run { - configuration.getMap(JVMConfigurationKeys.SCRIPTS_EXTRA_IMPORTS).entries.forEach { - val localFile = applicationEnvironment.localFileSystem.findFileByPath(it.key) - ?: throw FileNotFoundException("Unable to find target for extra imports: ${it.key}") - preconfigureExtraImports(localFile, kotlin.collections.listOf(it.value)) - } - + KotlinScriptExternalImportsProvider.getInstance(project)?.run { configuration.addJvmClasspathRoots( - getCombinedClasspathFor(sourceFiles.mapNotNull { src -> src.virtualFile }) + getCombinedClasspathFor(sourceFiles) .map { File(it).canonicalFile } .distinct()) } @@ -416,7 +410,7 @@ class KotlinCoreEnvironment private constructor( with (projectEnvironment.project) { val kotlinScriptDefinitionProvider = KotlinScriptDefinitionProvider() registerService(KotlinScriptDefinitionProvider::class.java, kotlinScriptDefinitionProvider) - registerService(KotlinScriptExtraImportsProvider::class.java, KotlinScriptExtraImportsProvider(projectEnvironment.project, kotlinScriptDefinitionProvider)) + registerService(KotlinScriptExternalImportsProvider::class.java, KotlinScriptExternalImportsProvider(projectEnvironment.project, kotlinScriptDefinitionProvider)) registerService(KotlinJavaPsiFacade::class.java, KotlinJavaPsiFacade(this)) registerService(KtLightClassForFacade.FacadeStubCache::class.java, KtLightClassForFacade.FacadeStubCache(this)) } diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/config/JVMConfigurationKeys.java b/compiler/frontend.java/src/org/jetbrains/kotlin/config/JVMConfigurationKeys.java index 1b06b6d6331..69ca952a5eb 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/config/JVMConfigurationKeys.java +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/config/JVMConfigurationKeys.java @@ -19,11 +19,9 @@ package org.jetbrains.kotlin.config; import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCompilationComponents; import org.jetbrains.kotlin.modules.Module; import org.jetbrains.kotlin.script.KotlinScriptDefinition; -import org.jetbrains.kotlin.script.KotlinScriptExtraImport; import java.io.File; import java.util.List; -import java.util.Map; public class JVMConfigurationKeys { private JVMConfigurationKeys() { @@ -43,9 +41,6 @@ public class JVMConfigurationKeys { public static final CompilerConfigurationKey> SCRIPT_DEFINITIONS = CompilerConfigurationKey.create("script definitions"); - public static final CompilerConfigurationKey> SCRIPTS_EXTRA_IMPORTS = - CompilerConfigurationKey.create("scripts extra imports"); - public static final CompilerConfigurationKey DISABLE_CALL_ASSERTIONS = CompilerConfigurationKey.create("disable not-null call assertions"); public static final CompilerConfigurationKey DISABLE_PARAM_ASSERTIONS = diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/FileScopeFactory.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/FileScopeFactory.kt index f338ee3693a..99181daf696 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/FileScopeFactory.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/FileScopeFactory.kt @@ -34,7 +34,7 @@ import org.jetbrains.kotlin.resolve.scopes.ImportingScope import org.jetbrains.kotlin.resolve.scopes.LexicalScope import org.jetbrains.kotlin.resolve.scopes.SubpackagesImportingScope import org.jetbrains.kotlin.resolve.source.KotlinSourceElement -import org.jetbrains.kotlin.script.getScriptExtraImports +import org.jetbrains.kotlin.script.getScriptExternalDependencies import org.jetbrains.kotlin.storage.StorageManager import org.jetbrains.kotlin.storage.getValue import org.jetbrains.kotlin.types.TypeSubstitutor @@ -73,7 +73,7 @@ class FileScopeFactory( val explicitImportResolver = createImportResolver(ExplicitImportsIndexed(imports), bindingTrace) val allUnderImportResolver = createImportResolver(AllUnderImportsIndexed(imports), bindingTrace) - val extraImports = ktImportsFactory.createImportDirectives(getScriptExtraImports(file).flatMap { it.names.map { ImportPath(it) } }) + val extraImports = ktImportsFactory.createImportDirectives(getScriptExternalDependencies(file).flatMap { it.imports.map { ImportPath(it) } }) val allImplicitImports = defaultImports + extraImports val defaultImportsFiltered = if (aliasImportNames.isEmpty()) { // optimization diff --git a/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinConfigurableScriptDefinition.kt b/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinConfigurableScriptDefinition.kt index b80a85e70f6..c58696abc2c 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinConfigurableScriptDefinition.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinConfigurableScriptDefinition.kt @@ -17,14 +17,6 @@ package org.jetbrains.kotlin.script import com.intellij.openapi.project.Project -import com.intellij.openapi.vfs.VirtualFile -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 org.jetbrains.kotlin.descriptors.ScriptDescriptor import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.parsing.KotlinParserDefinition @@ -54,7 +46,24 @@ data class KotlinConfigurableScriptDefinition(val config: KotlinScriptConfig, va private val evaluatedClasspath by lazy { config.classpath.evalWithVars(environmentVars).distinct() } - override fun getScriptDependenciesClasspath(): List = evaluatedClasspath + override fun getDependenciesFor(file: TF, project: Project): KotlinScriptExternalDependencies? = + if (!isScript(file)) null + else { + val extDeps = getScriptDependenciesFromConfig(file) + when { + extDeps != null -> + object : KotlinScriptExternalDependencies { + override val classpath = evaluatedClasspath + extDeps.classpath.evalWithVars(environmentVars).distinct() + override val imports = extDeps.imports + override val sources = extDeps.sources.evalWithVars(environmentVars).distinct() + } + !evaluatedClasspath.isEmpty() -> + object : KotlinScriptExternalDependencies { + override val classpath = evaluatedClasspath + } + else -> null + } + } } @@ -62,7 +71,7 @@ data class KotlinConfigurableScriptDefinition(val config: KotlinScriptConfig, va // 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.evalWithVars(varsMap: Map>?): List = +internal fun Iterable.evalWithVars(varsMap: Map>?): Iterable = if (varsMap == null || varsMap.isEmpty()) this else this.flatMap { cpentry -> varsMap.entries.fold(listOf(cpentry)) { p, v -> diff --git a/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinScriptDefinition.kt b/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinScriptDefinition.kt index 68901891dcb..a309ce70bcb 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinScriptDefinition.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinScriptDefinition.kt @@ -16,10 +16,12 @@ package org.jetbrains.kotlin.script +import com.intellij.openapi.fileTypes.LanguageFileType +import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiFile -import com.intellij.util.PathUtil import org.jetbrains.kotlin.descriptors.ScriptDescriptor +import org.jetbrains.kotlin.idea.KotlinFileType import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name @@ -36,19 +38,39 @@ import java.io.File import kotlin.reflect.KClass interface KotlinScriptDefinition { - val name: String + val name: String get() = "Kotlin Script" + + // TODO: consider creating separate type (subtype? for kotlin scripts) + val fileType: LanguageFileType get() = KotlinFileType.INSTANCE + + fun isScript(file: TF): Boolean = + getFileName(file).endsWith(KotlinParserDefinition.STD_SCRIPT_EXT) + + // TODO: replace these 3 functions with template property fun getScriptParameters(scriptDescriptor: ScriptDescriptor): List fun getScriptSupertypes(scriptDescriptor: ScriptDescriptor): List = emptyList() fun getScriptParametersToPassToSuperclass(scriptDescriptor: ScriptDescriptor): List = emptyList() - fun isScript(file: TF): Boolean - fun getScriptName(script: KtScript): Name - fun getScriptDependenciesClasspath(): List = emptyList() + + fun getScriptName(script: KtScript): Name = + ScriptNameUtil.fileNameWithExtensionStripped(script, KotlinParserDefinition.STD_SCRIPT_EXT) + + fun getDependenciesFor(file: TF, project: Project): KotlinScriptExternalDependencies? = null +} + +interface KotlinScriptExternalDependencies { + val classpath: Iterable get() = emptyList() + val imports: Iterable get() = emptyList() + val sources: Iterable get() = emptyList() +} + +class KotlinScriptExternalDependenciesUnion(val dependencies: Iterable) : KotlinScriptExternalDependencies { + override val classpath: Iterable get() = dependencies.flatMap { it.classpath } + override val imports: Iterable get() = dependencies.flatMap { it.imports } + override val sources: Iterable get() = dependencies.flatMap { it.sources } } data class ScriptParameter(val name: Name, val type: KotlinType) -fun getFileExtension(file: TF) = PathUtil.getFileExtension(getFileName(file)) - fun getFileName(file: TF): String = when (file) { is PsiFile -> file.originalFile.name is VirtualFile -> file.name @@ -56,17 +78,16 @@ fun getFileName(file: TF): String = when (file) { else -> throw IllegalArgumentException("Unsupported file type $file") } +fun getFilePath(file: TF): String = when (file) { + is PsiFile -> file.originalFile.run { virtualFile?.path ?: name } // TODO: replace name with path of PSI elements + is VirtualFile -> file.path + is File -> file.canonicalPath + else -> throw IllegalArgumentException("Unsupported file type $file") +} + object StandardScriptDefinition : KotlinScriptDefinition { private val ARGS_NAME = Name.identifier("args") - override val name = "Kotlin Script" - - override fun getScriptName(script: KtScript): Name = - ScriptNameUtil.fileNameWithExtensionStripped(script, KotlinParserDefinition.STD_SCRIPT_EXT) - - override fun isScript(file: TF): Boolean = - getFileExtension(file) == KotlinParserDefinition.STD_SCRIPT_SUFFIX - // NOTE: for now we treat .kts files as if they have 'args: Array' parameter // this is not supposed to be final design override fun getScriptParameters(scriptDescriptor: ScriptDescriptor): List = diff --git a/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinScriptDefinitionProvider.kt b/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinScriptDefinitionProvider.kt index bed383bebbb..d20c2e4b664 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinScriptDefinitionProvider.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinScriptDefinitionProvider.kt @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.script import com.intellij.openapi.components.ServiceManager +import com.intellij.openapi.fileTypes.LanguageFileType import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiFile @@ -28,10 +29,8 @@ class KotlinScriptDefinitionProvider { private val definitions: MutableList = arrayListOf(StandardScriptDefinition) private val definitionsLock = java.util.concurrent.locks.ReentrantReadWriteLock() - private val notificationHandlers = ArrayList<() -> Unit>() - private val handlersLock = java.util.concurrent.locks.ReentrantReadWriteLock() - fun setScriptDefinitions(newDefinitions: List): Unit { + fun setScriptDefinitions(newDefinitions: List): Boolean { var changed = false definitionsLock.read { if (newDefinitions != definitions) { @@ -42,11 +41,7 @@ class KotlinScriptDefinitionProvider { changed = true } } - if (changed) { - handlersLock.read { - notificationHandlers.forEach { it() } - } - } + return changed } fun findScriptDefinition(file: TF): KotlinScriptDefinition? = definitionsLock.read { @@ -55,10 +50,6 @@ class KotlinScriptDefinitionProvider { fun isScript(file: TF): Boolean = findScriptDefinition(file) != null - fun subscribeOnDefinitionsChanged(handler: () -> Unit): Unit { - handlersLock.write { notificationHandlers.add(handler) } - } - fun addScriptDefinition(scriptDefinition: KotlinScriptDefinition) { definitionsLock.write { definitions.add(0, scriptDefinition) @@ -71,6 +62,10 @@ class KotlinScriptDefinitionProvider { } } + fun getAllKnownFileTypes(): Iterable = definitionsLock.read { + definitions.map { it.fileType }.distinct() + } + companion object { @JvmStatic fun getInstance(project: Project): KotlinScriptDefinitionProvider = diff --git a/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinScriptExternalImportsProvider.kt b/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinScriptExternalImportsProvider.kt new file mode 100644 index 00000000000..7bc8eea3dcb --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinScriptExternalImportsProvider.kt @@ -0,0 +1,107 @@ +/* + * 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 java.util.concurrent.locks.ReentrantReadWriteLock +import kotlin.concurrent.read +import kotlin.concurrent.write + +class KotlinScriptExternalImportsProvider(val project: Project, private val scriptDefinitionProvider: KotlinScriptDefinitionProvider) { + private val cacheLock = ReentrantReadWriteLock() + private val cache = hashMapOf() + private val cacheOfNulls = hashSetOf() + + fun getExternalImports(vararg files: TF): List = getExternalImports(files.asIterable()) + + fun getExternalImports(files: Iterable): List = cacheLock.read { + files.mapNotNull { file -> + val path = getFilePath(file) + cache[path] + ?: if (cacheOfNulls.contains(path)) null + else scriptDefinitionProvider.findScriptDefinition(file) + ?.let { it.getDependenciesFor(file, project) } + .apply { cacheLock.write { + if (this == null) { + cacheOfNulls.add(path) + } + else { + cache.put(path, this) + } + } + } + } + } + + fun cacheExternalImports(files: Iterable): Unit = cacheLock.write { + val uncached = hashSetOf() + files.forEach { file -> + val path = getFilePath(file) + if (!cache.containsKey(path) && !cacheOfNulls.contains(path) && !uncached.contains(path)) { + val scriptDef = scriptDefinitionProvider.findScriptDefinition(file) + if (scriptDef != null) { + val deps = scriptDef.getDependenciesFor(file, project) + if (deps != null) { + cache.put(path, deps) + } + else { + cacheOfNulls.add(path) + } + } + else { + uncached.add(path) + } + } + } + } + + fun invalidateCaches() { + cacheLock.write { + cache.keys.toList().apply { + cache.clear() + } + } + } + + fun invalidateCachesFor(vararg files: TF) { invalidateCachesFor(files.asIterable()) } + + fun invalidateCachesFor(files: Iterable) { + cacheLock.write { + files.forEach { file -> + val path = getFilePath(file) + cache.remove(path) + cacheOfNulls.remove(path) + } + } + } + + fun getKnownCombinedClasspath(): List = cacheLock.read { + cache.values.flatMap { it.classpath } + }.distinct() + + fun getCombinedClasspathFor(files: Iterable): List = + getExternalImports(files) + .flatMap { it.classpath } + .distinct() + + companion object { + @JvmStatic + fun getInstance(project: Project): KotlinScriptExternalImportsProvider? = + ServiceManager.getService(project, KotlinScriptExternalImportsProvider::class.java) + } +} \ No newline at end of file diff --git a/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinScriptExtraImportsProvider.kt b/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinScriptExtraImportsProvider.kt deleted file mode 100644 index 4aa5a520293..00000000000 --- a/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinScriptExtraImportsProvider.kt +++ /dev/null @@ -1,132 +0,0 @@ -/* - * 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.lang.ref.WeakReference -import java.util.* -import java.util.concurrent.locks.ReentrantReadWriteLock -import kotlin.concurrent.read -import kotlin.concurrent.write - -// TODO: replace VirtualFile usage with File for performance/unnecessary dependencies reasons - -class KotlinScriptExtraImportsProvider(val project: Project, private val scriptDefinitionProvider: KotlinScriptDefinitionProvider) { - private val cacheLock = ReentrantReadWriteLock() - private val preconfigured = hashMapOf>() - private val cache = hashMapOf>() - private val envVars: Map> by lazy { generateKotlinScriptClasspathEnvVars(project) } - private val notificationHandlers = ArrayList<(Iterable) -> Unit>() - private val handlersLock = java.util.concurrent.locks.ReentrantReadWriteLock() - - init { - val weakThis = WeakReference(this) - scriptDefinitionProvider.subscribeOnDefinitionsChanged { weakThis.get()?.invalidateAllExtraImports() } - } - - fun isExtraImportsConfig(file: VirtualFile): Boolean = file.name.endsWith(IMPORTS_FILE_EXTENSION) - - fun getExtraImports(vararg files: VirtualFile): List = getExtraImports(files.asIterable()) - - fun getExtraImports(files: Iterable): List { - val newCashedFiles = ArrayList() - val res = cacheLock.read { - files.flatMap { file -> - if (file.isValid && !file.isDirectory) { - preconfigured[file.path] - ?: cache[file.path] - ?: scriptDefinitionProvider.findScriptDefinition(file)?.let { def -> - (listOf(KotlinScriptExtraImportFromDefinition(def)) + - (file.parent.findFileByRelativePath(file.name + IMPORTS_FILE_EXTENSION)?.let { - loadScriptExtraImportConfigs(it.inputStream).map { KotlinScriptExtraImportFromConfig(it, envVars) } - } ?: emptyList())) - .apply { - cacheLock.write { cache.put(file.path, this) } - newCashedFiles.add(file.path) - } - } - ?: emptyList() - } - else emptyList() - } - } - notifyIfAny(newCashedFiles) - return res - } - - fun preconfigureExtraImports(file: VirtualFile, extraImports: List?) { - cacheLock.write { - if (extraImports != null && extraImports.isNotEmpty()) { - preconfigured[file.path] = extraImports - } - else { - preconfigured.remove(file.path) - } - } - } - - fun invalidateExtraImportsByImportsFiles(importsFiles: Iterable) { - importsFiles.mapNotNull { it.parent.findFileByRelativePath(it.name.removeSuffix(IMPORTS_FILE_EXTENSION))?.path?.let { file -> - cacheLock.write { - cache.remove(it.path)?.let { file } - } - } }.let { - notifyIfAny(it) - } - } - - fun invalidateAllExtraImports() { - cacheLock.write { - cache.keys.toList().apply { - cache.clear() - } - }.let { - notifyIfAny(it) - } - } - - private fun notifyIfAny(files: Iterable) { - if (files.any()) { - handlersLock.read { - notificationHandlers.forEach { it(files) } - } - } - } - - fun getKnownCombinedClasspath(): List = cacheLock.read { - cache.values.flatMap { it.flatMap { it.classpath } } - }.distinct() - - fun getCombinedClasspathFor(files: Iterable): List = - getExtraImports(files) - .flatMap { it.classpath } - .distinct() - - fun subscribeOnExtraImportsChanged(handler: (Iterable) -> Unit): Unit { - handlersLock.write { notificationHandlers.add(handler) } - } - - companion object { - @JvmStatic - fun getInstance(project: Project): KotlinScriptExtraImportsProvider? = - ServiceManager.getService(project, KotlinScriptExtraImportsProvider::class.java) - - val IMPORTS_FILE_EXTENSION = ".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 index 35636f01c9f..3b7e04ee6ee 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/script/getScriptDefinition.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/script/getScriptDefinition.kt @@ -26,11 +26,11 @@ fun getScriptDefinition(file: VirtualFile, project: Project): KotlinScriptDefini fun getScriptDefinition(psiFile: PsiFile): KotlinScriptDefinition? = KotlinScriptDefinitionProvider.getInstance(psiFile.project).findScriptDefinition(psiFile) -fun getScriptExtraImports(file: VirtualFile, project: Project): List = - KotlinScriptExtraImportsProvider.getInstance(project)?.getExtraImports(file) ?: emptyList() +fun getScriptExternalDependencies(file: VirtualFile, project: Project): List = + KotlinScriptExternalImportsProvider.getInstance(project)?.getExternalImports(file) ?: emptyList() -fun getScriptExtraImports(psiFile: PsiFile): List = +fun getScriptExternalDependencies(psiFile: PsiFile): List = psiFile.virtualFile?.let { file -> - KotlinScriptExtraImportsProvider.getInstance(psiFile.project)?.getExtraImports(file) + KotlinScriptExternalImportsProvider.getInstance(psiFile.project)?.getExternalImports(file) } ?: emptyList() diff --git a/compiler/frontend/src/org/jetbrains/kotlin/script/scriptAnnotationsPreprocessing.kt b/compiler/frontend/src/org/jetbrains/kotlin/script/scriptAnnotationsPreprocessing.kt index a3715004640..ec2d2977769 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/script/scriptAnnotationsPreprocessing.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/script/scriptAnnotationsPreprocessing.kt @@ -52,7 +52,8 @@ object SimpleUntypedAst { convert(expression.entries[0]) else SimpleUntypedAst.Node.str(name, "") -// convertStringTemplateExpression(expression, parent, expression.entries.size - 1) + // TODO: parse expressions, etc. e.g.: + // convertStringTemplateExpression(expression, parent, expression.entries.size - 1) } else -> Node.empty(name) @@ -72,3 +73,9 @@ object SimpleUntypedAst { } } +fun parseAnnotation(ann: KtAnnotationEntry): SimpleUntypedAst.Node.list { + val wann = SimpleUntypedAst.KtAnnotationWrapper(ann) + val vals = wann.valueArguments + return SimpleUntypedAst.Node.list(wann.name, vals) +} + diff --git a/compiler/frontend/src/org/jetbrains/kotlin/script/scriptExternalDependenciesConfig.kt b/compiler/frontend/src/org/jetbrains/kotlin/script/scriptExternalDependenciesConfig.kt new file mode 100644 index 00000000000..59e5546586c --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/script/scriptExternalDependenciesConfig.kt @@ -0,0 +1,69 @@ +/* + * 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.openapi.vfs.VirtualFile +import com.intellij.psi.PsiFile +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.* + +@Tag("import") +class KotlinScriptExternalDependenciesConfig : KotlinScriptExternalDependencies { + @Tag("classpath") + @AbstractCollection(surroundWithTag = false, elementTag = "path", elementValueAttribute = "") + override var classpath: MutableList = ArrayList() + + @Tag("imports") + @AbstractCollection(surroundWithTag = false, elementTag = "name", elementValueAttribute = "") + override var imports: MutableList = ArrayList() + + @Tag("sources") + @AbstractCollection(surroundWithTag = false, elementTag = "path", elementValueAttribute = "") + override var sources: MutableList = ArrayList() +} + +fun loadScriptExternalImportConfigs(configFile: File): List = + JDOMUtil.loadDocument(configFile).rootElement.children.mapNotNull { + XmlSerializer.deserialize(it, KotlinScriptExternalDependenciesConfig::class.java) + } + +fun loadScriptExternalImportConfigs(configStream: InputStream): List = + JDOMUtil.loadDocument(configStream).rootElement.children.mapNotNull { + XmlSerializer.deserialize(it, KotlinScriptExternalDependenciesConfig::class.java) + } + +fun getScriptDependenciesFromConfig(file: TF): KotlinScriptExternalDependencies? { + val IMPORTS_FILE_EXTENSION = ".ktsimports.xml" + fun streamFromSibling(file: VirtualFile): InputStream? = + file.parent.findFileByRelativePath(file.name + IMPORTS_FILE_EXTENSION)?.let { it.inputStream } + fun streamFromSibling(file: File): InputStream? { + val sibling = File(file.parentFile, file.name + IMPORTS_FILE_EXTENSION) + return if (sibling.exists()) sibling.inputStream() + else null + } + return when (file) { + is VirtualFile -> streamFromSibling(file) + is PsiFile -> streamFromSibling(file.originalFile.virtualFile) + is File -> streamFromSibling(file) + else -> throw IllegalArgumentException("Unsupported file type $file") + }?.let { KotlinScriptExternalDependenciesUnion(loadScriptExternalImportConfigs(it)) } +} diff --git a/compiler/frontend/src/org/jetbrains/kotlin/script/scriptExtraImports.kt b/compiler/frontend/src/org/jetbrains/kotlin/script/scriptExtraImports.kt deleted file mode 100644 index bbf4e984396..00000000000 --- a/compiler/frontend/src/org/jetbrains/kotlin/script/scriptExtraImports.kt +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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.evalWithVars(envVars).distinct() } - override val names: List - get() = config.names -} - -class KotlinScriptExtraImportFromDefinition(val scriptDefinition: KotlinScriptDefinition) : KotlinScriptExtraImport { - override val classpath: List get() = scriptDefinition.getScriptDependenciesClasspath() - override val names: List = emptyList() -} diff --git a/compiler/frontend/src/org/jetbrains/kotlin/script/scriptTemplate.kt b/compiler/frontend/src/org/jetbrains/kotlin/script/scriptTemplate.kt index beb495240fc..e3ea0d6bdd3 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/script/scriptTemplate.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/script/scriptTemplate.kt @@ -16,33 +16,33 @@ package org.jetbrains.kotlin.script +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.StandardFileSystems import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.PsiFile +import com.intellij.psi.PsiManager import org.jetbrains.kotlin.descriptors.ScriptDescriptor import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.parsing.KotlinParserDefinition -import org.jetbrains.kotlin.psi.KtAnnotation import org.jetbrains.kotlin.psi.KtAnnotationEntry +import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtScript import org.jetbrains.kotlin.types.KotlinType +import java.io.File import kotlin.reflect.KClass @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.RUNTIME) annotation class ScriptFilePattern(val pattern: String) -interface ScriptDependencies { - val classpath: List - val implicitImports: List -} - interface GetScriptDependencies { - operator fun invoke(annotations: Iterable, context: Any?): ScriptDependencies? = null - operator fun invoke(context: Any?): ScriptDependencies? = null + operator fun invoke(annotations: Iterable, context: Any?): KotlinScriptExternalDependencies? = null + operator fun invoke(context: Any?): KotlinScriptExternalDependencies? = null } @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.RUNTIME) -annotation class ScriptDependencyExtractor(val extractor: KClass) +annotation class ScriptDependencyResolver(val extractor: KClass) data class KotlinScriptDefinitionFromTemplate(val template: KClass, val context: Any?) : KotlinScriptDefinition { override val name = template.simpleName!! @@ -63,13 +63,39 @@ data class KotlinScriptDefinitionFromTemplate(val template: KClass, val override fun getScriptName(script: KtScript): Name = ScriptNameUtil.fileNameWithExtensionStripped(script, KotlinParserDefinition.STD_SCRIPT_EXT) private val dependenciesExtractors by lazy { - template.annotations.mapNotNull { it as? ScriptDependencyExtractor }.map { it.extractor.constructors.first().call() } + template.annotations.mapNotNull { it as? ScriptDependencyResolver }.map { it.extractor.constructors.first().call() } } - private val dependencies by lazy { + private val dependencies: List by lazy { dependenciesExtractors.mapNotNull { it(context) } } - override fun getScriptDependenciesClasspath(): List = dependencies.flatMap { it.classpath } + override fun getDependenciesFor(file: TF, project: Project): KotlinScriptExternalDependencies? { + val fileAnnotations = getAnnotationEntries(file, project) + val fileDeps = dependenciesExtractors.mapNotNull { it(fileAnnotations, context) } + return KotlinScriptExternalDependenciesUnion(dependencies + fileDeps) + } + + private fun getAnnotationEntries(file: TF, project: Project): Iterable = when (file) { + is PsiFile -> getAnnotationEntriesFromPsiFile(file) + is VirtualFile -> getAnnotationEntriesFromVirtualFile(file, project) + is File -> { + val virtualFile = (StandardFileSystems.local().findFileByPath(file.absolutePath) + ?: throw java.lang.IllegalArgumentException("Unable to find file ${file.canonicalPath}")) + getAnnotationEntriesFromVirtualFile(virtualFile, project) + } + else -> throw IllegalArgumentException("Unsupported file type $file") + } + + private fun getAnnotationEntriesFromPsiFile(file: PsiFile) = + if (file is KtFile) file.annotationEntries + else throw IllegalArgumentException("Unable to extract kotlin annotations from ${file.name} (${file.fileType})") + + private fun getAnnotationEntriesFromVirtualFile(file: VirtualFile, project: Project): Iterable { + val psifile: PsiFile = PsiManager.getInstance(project).findFile(file) + ?: throw java.lang.IllegalArgumentException("Unable to load PSI from ${file.canonicalPath}") + return getAnnotationEntriesFromPsiFile(psifile) + } } + diff --git a/compiler/tests-common/org/jetbrains/kotlin/scripts/TestScriptDefinitions.kt b/compiler/tests-common/org/jetbrains/kotlin/scripts/TestScriptDefinitions.kt index 44c82350a5f..677311dcad1 100644 --- a/compiler/tests-common/org/jetbrains/kotlin/scripts/TestScriptDefinitions.kt +++ b/compiler/tests-common/org/jetbrains/kotlin/scripts/TestScriptDefinitions.kt @@ -16,6 +16,7 @@ package org.jetbrains.kotlin.scripts +import com.intellij.openapi.project.Project import org.jetbrains.kotlin.descriptors.ScriptDescriptor import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.KtScript @@ -26,15 +27,17 @@ import java.net.URL import java.net.URLClassLoader import kotlin.reflect.KClass -abstract class BaseScriptDefinition (val extension: String, val classpath: List? = null) : KotlinScriptDefinition { +abstract class BaseScriptDefinition (val extension: String, val cp: List? = null) : KotlinScriptDefinition { override val name = "Test Kotlin Script" - override fun isScript(file: TF): Boolean = org.jetbrains.kotlin.script.getFileName(file).endsWith(extension) + override fun isScript(file: TF): Boolean = getFileName(file).endsWith(extension) override fun getScriptName(script: KtScript): Name = ScriptNameUtil.fileNameWithExtensionStripped(script, extension) - override fun getScriptDependenciesClasspath(): List = - classpath ?: (classpathFromProperty() + classpathFromClassloader(BaseScriptDefinition::class.java.classLoader)).distinct() + override fun getDependenciesFor(file: TF, project: Project): KotlinScriptExternalDependencies? = + object : KotlinScriptExternalDependencies { + override val classpath = cp ?: (classpathFromProperty() + classpathFromClassloader(BaseScriptDefinition::class.java.classLoader)).distinct() + } } -open class SimpleParamsWithClasspathTestScriptDefinition(extension: String, val parameters: List, classpath: List? = null) +open class SimpleParamsWithClasspathTestScriptDefinition(extension: String, val parameters: List, classpath: List? = null, val extraDependencies: KotlinScriptExternalDependencies? = null) : BaseScriptDefinition(extension, classpath) { override fun getScriptParameters(scriptDescriptor: ScriptDescriptor) = parameters @@ -74,10 +77,10 @@ class StandardWithClasspathScriptDefinition(extension: String, classpath: List, - override val names: List = emptyList() -) : KotlinScriptExtraImport + override val imports: List = emptyList() +) : KotlinScriptExternalDependencies fun classpathFromProperty(): List = System.getProperty("java.class.path")?.let { diff --git a/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTest.kt b/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTest.kt index c488a336585..a46794d445a 100644 --- a/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTest.kt +++ b/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTest.kt @@ -26,11 +26,9 @@ import org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoot import org.jetbrains.kotlin.codegen.CompilationException import org.jetbrains.kotlin.config.JVMConfigurationKeys -import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.config.addKotlinSourceRoot import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.script.KotlinScriptDefinition -import org.jetbrains.kotlin.script.KotlinScriptExtraImport import org.jetbrains.kotlin.script.ScriptParameter import org.jetbrains.kotlin.script.StandardScriptDefinition import org.jetbrains.kotlin.test.ConfigurationKind @@ -45,14 +43,14 @@ import java.lang.reflect.InvocationTargetException class ScriptTest { @Test fun testScriptWithParam() { - val aClass = compileScript("fib.kts", SimpleParamsTestScriptDefinition(".kts", numIntParam()), null) + val aClass = compileScript("fib.kts", SimpleParamsTestScriptDefinition(".kts", numIntParam())) Assert.assertNotNull(aClass) aClass!!.getConstructor(Integer.TYPE).newInstance(4) } @Test fun testStandardScriptWithParams() { - val aClass = compileScript("fib_std.kts", StandardScriptDefinition, null) + val aClass = compileScript("fib_std.kts", StandardScriptDefinition) Assert.assertNotNull(aClass) val anObj = KotlinToJVMBytecodeCompiler.tryConstructClassPub(aClass!!, listOf("4", "comment")) Assert.assertNotNull(anObj) @@ -60,7 +58,7 @@ class ScriptTest { @Test fun testStandardScriptWithoutParams() { - val aClass = compileScript("fib_std.kts", StandardScriptDefinition, null) + val aClass = compileScript("fib_std.kts", StandardScriptDefinition) Assert.assertNotNull(aClass) val anObj = KotlinToJVMBytecodeCompiler.tryConstructClassPub(aClass!!, emptyList()) Assert.assertNotNull(anObj) @@ -68,7 +66,7 @@ class ScriptTest { @Test fun testScriptWithParamConversion() { - val aClass = compileScript("fib.kts", SimpleParamsTestScriptDefinition(".kts", numIntParam()), null) + val aClass = compileScript("fib.kts", SimpleParamsTestScriptDefinition(".kts", numIntParam())) Assert.assertNotNull(aClass) val anObj = KotlinToJVMBytecodeCompiler.tryConstructClassPub(aClass!!, listOf("4")) Assert.assertNotNull(anObj) @@ -76,67 +74,67 @@ class ScriptTest { @Test fun testScriptWithPackage() { - val aClass = compileScript("fib.pkg.kts", SimpleParamsTestScriptDefinition(".kts", numIntParam()), null) + val aClass = compileScript("fib.pkg.kts", SimpleParamsTestScriptDefinition(".kts", numIntParam())) Assert.assertNotNull(aClass) aClass!!.getConstructor(Integer.TYPE).newInstance(4) } @Test fun testScriptWithScriptDefinition() { - val aClass = compileScript("fib.kts", SimpleParamsTestScriptDefinition(".kts", numIntParam()), null) + val aClass = compileScript("fib.kts", SimpleParamsTestScriptDefinition(".kts", numIntParam())) Assert.assertNotNull(aClass) aClass!!.getConstructor(Integer.TYPE).newInstance(4) } @Test fun testScriptWithClassParameter() { - val aClass = compileScript("fib_cp.kts", ReflectedParamClassTestScriptDefinition(".kts", "param", TestParamClass::class), null, runIsolated = false) + val aClass = compileScript("fib_cp.kts", ReflectedParamClassTestScriptDefinition(".kts", "param", TestParamClass::class), runIsolated = false) Assert.assertNotNull(aClass) aClass!!.getConstructor(TestParamClass::class.java).newInstance(TestParamClass(4)) } @Test fun testScriptWithBaseClass() { - val aClass = compileScript("fib_dsl.kts", ReflectedSuperclassTestScriptDefinition(".kts", numIntParam(), TestDSLClass::class), null, runIsolated = false) + val aClass = compileScript("fib_dsl.kts", ReflectedSuperclassTestScriptDefinition(".kts", numIntParam(), TestDSLClass::class), runIsolated = false) Assert.assertNotNull(aClass) aClass!!.getConstructor(Integer.TYPE).newInstance(4) } @Test fun testScriptWithBaseClassWithParam() { - val aClass = compileScript("fib_dsl.kts", ReflectedSuperclassWithParamsTestScriptDefinition(".kts", numIntParam() + numIntParam("passthrough"), TestDSLClassWithParam::class, numIntParam("passthrough")), null, runIsolated = false) + val aClass = compileScript("fib_dsl.kts", ReflectedSuperclassWithParamsTestScriptDefinition(".kts", numIntParam() + numIntParam("passthrough"), TestDSLClassWithParam::class, numIntParam("passthrough")), runIsolated = false) Assert.assertNotNull(aClass) aClass!!.getConstructor(Integer.TYPE, Integer.TYPE).newInstance(4, 1) } @Test fun testScriptWithInterface() { - val aClass = compileScript("fib_dsl.kts", ReflectedSuperclassTestScriptDefinition(".kts", numIntParam(), TestDSLInterface::class), null, runIsolated = false) + val aClass = compileScript("fib_dsl.kts", ReflectedSuperclassTestScriptDefinition(".kts", numIntParam(), TestDSLInterface::class), runIsolated = false) Assert.assertNotNull(aClass) aClass!!.getConstructor(Integer.TYPE).newInstance(4) } @Test fun testScriptWithClasspath() { - val aClass1 = compileScript("fib_ext.kts", SimpleParamsWithClasspathTestScriptDefinition(".kts", numIntParam(), classpath = emptyList()), null, runIsolated = true, suppressOutput = true) + val aClass1 = compileScript("fib_ext.kts", SimpleParamsWithClasspathTestScriptDefinition(".kts", numIntParam(), classpath = emptyList()), runIsolated = true, suppressOutput = true) Assert.assertNull(aClass1) val cp = classpathFromClassloader(ScriptTest::class.java.classLoader).filter { it.contains("kotlin-runtime") || it.contains("junit") } Assert.assertFalse(cp.isEmpty()) - val aClass2 = compileScript("fib_ext.kts", SimpleParamsWithClasspathTestScriptDefinition(".kts", numIntParam(), classpath = cp), null, runIsolated = true) + val aClass2 = compileScript("fib_ext.kts", SimpleParamsWithClasspathTestScriptDefinition(".kts", numIntParam(), classpath = cp), runIsolated = true) Assert.assertNotNull(aClass2) } @Test fun testScriptWithExtraImports() { - val aClass1 = compileScript("fib_ext.kts", SimpleParamsWithClasspathTestScriptDefinition(".kts", numIntParam(), classpath = emptyList()), extraImport = null, runIsolated = true, suppressOutput = true) + val aClass1 = compileScript("fib_ext.kts", SimpleParamsWithClasspathTestScriptDefinition(".kts", numIntParam(), classpath = emptyList()), runIsolated = true, suppressOutput = true) Assert.assertNull(aClass1) val cp = classpathFromClassloader(ScriptTest::class.java.classLoader).filter { it.contains("kotlin-runtime") || it.contains("junit") } Assert.assertFalse(cp.isEmpty()) - val aClass2 = compileScript("fib_ext.kts", SimpleParamsWithClasspathTestScriptDefinition(".kts", numIntParam(), classpath = emptyList()), extraImport = SimpleScriptExtraImport(cp), runIsolated = true) + val aClass2 = compileScript("fib_ext.kts", SimpleParamsWithClasspathTestScriptDefinition(".kts", numIntParam(), classpath = cp, extraDependencies = SimpleScriptExtraDependencies(cp)), runIsolated = true) Assert.assertNotNull(aClass2) } @@ -163,22 +161,20 @@ class ScriptTest { private fun compileScript( scriptPath: String, scriptDefinition: KotlinScriptDefinition, - extraImport: KotlinScriptExtraImport?, runIsolated: Boolean = true, suppressOutput: Boolean = false): Class<*>? = - compileScriptImpl("compiler/testData/script/" + scriptPath, scriptDefinition, extraImport, runIsolated, suppressOutput) + compileScriptImpl("compiler/testData/script/" + scriptPath, scriptDefinition, runIsolated, suppressOutput) private fun compileSmokeTestScript( scriptPath: String, scriptDefinition: KotlinScriptDefinition, runIsolated: Boolean = true, suppressOutput: Boolean = false): Class<*>? = - compileScriptImpl("compiler/testData/integration/smoke/" + scriptPath, scriptDefinition, null, runIsolated, suppressOutput) + compileScriptImpl("compiler/testData/integration/smoke/" + scriptPath, scriptDefinition, runIsolated, suppressOutput) private fun compileScriptImpl( scriptPath: String, scriptDefinition: KotlinScriptDefinition, - extraImport: KotlinScriptExtraImport?, runIsolated: Boolean, suppressOutput: Boolean): Class<*>? { @@ -193,10 +189,6 @@ class ScriptTest { configuration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector) configuration.addKotlinSourceRoot(scriptPath) configuration.add(JVMConfigurationKeys.SCRIPT_DEFINITIONS, scriptDefinition) - extraImport?.let { - configuration.put(JVMConfigurationKeys.SCRIPTS_EXTRA_IMPORTS, scriptPath, it) - } - scriptDefinition.getScriptDependenciesClasspath().forEach { configuration.addJvmClasspathRoot(File(it)) } val environment = KotlinCoreEnvironment.createForProduction(rootDisposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES) diff --git a/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTest2.kt b/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTest2.kt index ce1483c4a77..f482916f1c9 100644 --- a/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTest2.kt +++ b/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTest2.kt @@ -118,7 +118,7 @@ class GetTestKotlinScriptDependencies : GetScriptDependencies { private val kotlinPaths by lazy { PathUtil.getKotlinPathsForCompiler() } - override fun invoke(annotations: Iterable, context: Any?): ScriptDependencies? { + override fun invoke(annotations: Iterable, context: Any?): KotlinScriptExternalDependencies? { if (annotations.none()) return null val anns = annotations.map { parseAnnotation(it) }.filter { it.name == depends::class.simpleName } val cp = anns.flatMap { @@ -129,9 +129,8 @@ class GetTestKotlinScriptDependencies : GetScriptDependencies { } } } - return object : ScriptDependencies { + return object : KotlinScriptExternalDependencies { override val classpath = cp - override val implicitImports = emptyList() } } @@ -141,30 +140,23 @@ class GetTestKotlinScriptDependencies : GetScriptDependencies { ?.filter { it.contains("out/test") } ?: emptyList() - override fun invoke(context: Any?): ScriptDependencies? { - return object : ScriptDependencies { + override fun invoke(context: Any?): KotlinScriptExternalDependencies? { + return object : KotlinScriptExternalDependencies { override val classpath = classpathFromClassloader() - override val implicitImports = emptyList() } } } -fun parseAnnotation(ann: KtAnnotationEntry): SimpleUntypedAst.Node.list { - val wann = SimpleUntypedAst.KtAnnotationWrapper(ann) - val vals = wann.valueArguments - return SimpleUntypedAst.Node.list(wann.name, vals) -} - @ScriptFilePattern(".*\\.kts") -@ScriptDependencyExtractor(GetTestKotlinScriptDependencies::class) +@ScriptDependencyResolver(GetTestKotlinScriptDependencies::class) abstract class ScriptWithIntParam(num: Int) @ScriptFilePattern(".*\\.kts") -@ScriptDependencyExtractor(GetTestKotlinScriptDependencies::class) +@ScriptDependencyResolver(GetTestKotlinScriptDependencies::class) abstract class ScriptWithClassParam(param: TestParamClass) @ScriptFilePattern(".*\\.kts") -@ScriptDependencyExtractor(GetTestKotlinScriptDependencies::class) +@ScriptDependencyResolver(GetTestKotlinScriptDependencies::class) abstract class ScriptWithBaseClass(num: Int, passthrough: Int) : TestDSLClassWithParam(passthrough) @Target(AnnotationTarget.FILE) 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 121240dbacd..ac1dbe6566a 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 @@ -23,27 +23,22 @@ import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.roots.* import com.intellij.openapi.roots.impl.libraries.LibraryEx import com.intellij.openapi.roots.libraries.Library -import com.intellij.openapi.roots.libraries.LibraryTable import com.intellij.openapi.vfs.StandardFileSystems import com.intellij.openapi.vfs.VfsUtilCore import com.intellij.openapi.vfs.VirtualFile -import com.intellij.openapi.vfs.VirtualFileManager import com.intellij.psi.search.DelegatingGlobalSearchScope import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.util.CachedValueProvider import com.intellij.psi.util.CachedValuesManager import com.intellij.util.SmartList import com.intellij.util.io.URLUtil -import org.jdom.Element import org.jetbrains.kotlin.analyzer.ModuleInfo import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.script.KotlinScriptDefinition -import org.jetbrains.kotlin.script.KotlinScriptExtraImport import org.jetbrains.kotlin.utils.alwaysNull import org.jetbrains.kotlin.utils.emptyOrSingletonList import java.io.File -import java.io.FileNotFoundException import java.lang.reflect.Method import java.util.* @@ -297,8 +292,7 @@ class ScriptModuleSearchScope(val scriptFile: VirtualFile, baseScope: GlobalSear } internal data class ScriptModuleInfo(val project: Project, val module: Module?, val scriptFile: VirtualFile, - val scriptDefinition: KotlinScriptDefinition, - val scriptExtraImports: List) : IdeaModuleInfo { + val scriptDefinition: KotlinScriptDefinition) : IdeaModuleInfo { override val moduleOrigin: ModuleOrigin get() = ModuleOrigin.OTHER @@ -315,16 +309,17 @@ internal data class ScriptModuleInfo(val project: Project, val module: Module?, private fun dependenciesRoots(): List { // TODO: find out whether it should be cashed (some changes listener should be implemented for the cached roots) val jarfs = StandardFileSystems.jar() - return (scriptDefinition.getScriptDependenciesClasspath() + scriptExtraImports.flatMap { it.classpath }) - .map { File(it).canonicalFile } - .distinct() - .mapNotNull { - // TODO: ensure that the entries are checked elsewhere, so diagnostics is delivered to a user if files are not correctly specified - if (it.isFile) - jarfs.findFileByPath(it.absolutePath + URLUtil.JAR_SEPARATOR) ?: null // diag: Classpath entry points to a file that is not a JAR archive - else - StandardFileSystems.local().findFileByPath(it.absolutePath) ?: null // diag: Classpath entry points to a non-existent location - } + return scriptDefinition.getDependenciesFor(scriptFile, project)?.classpath + ?.map { File(it).canonicalFile } + ?.distinct() + ?.mapNotNull { + // TODO: ensure that the entries are checked elsewhere, so diagnostics is delivered to a user if files are not correctly specified + if (it.isFile) + jarfs.findFileByPath(it.absolutePath + URLUtil.JAR_SEPARATOR) ?: null // diag: Classpath entry points to a file that is not a JAR archive + else + StandardFileSystems.local().findFileByPath(it.absolutePath) ?: null // diag: Classpath entry points to a non-existent location + } + ?: emptyList() } 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 7708109aa50..e277420e582 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,7 +30,6 @@ import org.jetbrains.kotlin.idea.util.ProjectRootsUtil import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getParentOfType import org.jetbrains.kotlin.script.getScriptDefinition -import org.jetbrains.kotlin.script.getScriptExtraImports import org.jetbrains.kotlin.utils.sure fun PsiElement.getModuleInfo(): IdeaModuleInfo = this.getModuleInfo { reason -> @@ -125,7 +124,7 @@ private fun getModuleInfoByVirtualFile(project: Project, virtualFile: VirtualFil val scriptDefinition = getScriptDefinition(virtualFile, project) if (scriptDefinition != null) - return ScriptModuleInfo(project, module, virtualFile, scriptDefinition, getScriptExtraImports(virtualFile, project)) + return ScriptModuleInfo(project, module, virtualFile, scriptDefinition) return NotUnderContentRootModuleInfo } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/decompiler/navigation/findDecompiledDeclaration.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/decompiler/navigation/findDecompiledDeclaration.kt index 65237b06c36..8b2df1929d3 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/decompiler/navigation/findDecompiledDeclaration.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/decompiler/navigation/findDecompiledDeclaration.kt @@ -22,7 +22,7 @@ import org.jetbrains.kotlin.builtins.DefaultBuiltIns import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.idea.decompiler.KtDecompiledFile import org.jetbrains.kotlin.idea.decompiler.textBuilder.DecompiledTextIndexer -import org.jetbrains.kotlin.idea.script.KotlinScriptConfigurationManager +import org.jetbrains.kotlin.idea.core.script.KotlinScriptConfigurationManager import org.jetbrains.kotlin.idea.stubindex.KotlinFullClassNameIndex import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope import org.jetbrains.kotlin.idea.stubindex.KotlinTopLevelFunctionFqnNameIndex diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/script/KotlinScriptConfigurationManager.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/script/KotlinScriptConfigurationManager.kt deleted file mode 100644 index 2a64690d0b9..00000000000 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/script/KotlinScriptConfigurationManager.kt +++ /dev/null @@ -1,164 +0,0 @@ -/* - * 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.idea.script - -import com.intellij.openapi.components.AbstractProjectComponent -import com.intellij.openapi.project.Project -import com.intellij.openapi.roots.ex.ProjectRootManagerEx -import com.intellij.openapi.util.EmptyRunnable -import com.intellij.openapi.vfs.StandardFileSystems -import com.intellij.openapi.vfs.VirtualFile -import com.intellij.openapi.vfs.VirtualFileManager -import com.intellij.openapi.vfs.newvfs.BulkFileListener -import com.intellij.openapi.vfs.newvfs.events.VFileEvent -import com.intellij.psi.search.GlobalSearchScope -import com.intellij.util.indexing.IndexableSetContributor -import com.intellij.util.io.URLUtil -import org.jetbrains.kotlin.idea.caches.resolve.FileLibraryScope -import org.jetbrains.kotlin.script.* -import org.jetbrains.kotlin.utils.PathUtil -import java.io.File -import java.io.FileNotFoundException -import java.lang.ref.WeakReference -import java.util.* -import kotlin.concurrent.read -import kotlin.concurrent.write - -@Suppress("unused") // project component -class KotlinScriptConfigurationManager(project: Project, - private val scriptDefinitionProvider: KotlinScriptDefinitionProvider, - private val scriptExtraImportsProvider: KotlinScriptExtraImportsProvider?, - private val kotlinScriptDependenciesIndexableSetContributor: KotlinScriptDependenciesIndexableSetContributor? -) : AbstractProjectComponent(project) { - - private val kotlinEnvVars: Map> by lazy { generateKotlinScriptClasspathEnvVarsFromPaths(myProject, PathUtil.getKotlinPathsForIdeaPlugin()) } - - init { - reloadScriptDefinitions() - - // TODO: get rid of this expensive call as soon as makeRootsChange call will work reliably - cacheAllScriptsExtraImports() - - val weakThis = WeakReference(this) - myProject.messageBus.connect().subscribe(VirtualFileManager.VFS_CHANGES, object : BulkFileListener.Adapter() { - override fun after(events: List) { - val changedExtraImportConfigs = ArrayList() - var anyScriptDefinitionChanged = false - events.forEach { - it.file?.let { - if (isScriptDefinitionConfigFile(it)) { - anyScriptDefinitionChanged = true - } - else { - weakThis.get()?.scriptExtraImportsProvider?.run { - if (isExtraImportsConfig(it)) { - changedExtraImportConfigs.add(it) - } - } - } - } - } - if (anyScriptDefinitionChanged) { - weakThis.get()?.reloadScriptDefinitions() - } - if (changedExtraImportConfigs.isNotEmpty()) { - weakThis.get()?.scriptExtraImportsProvider?.invalidateExtraImportsByImportsFiles(changedExtraImportConfigs) - } - } - }) - // omitting case then scriptExtraImportsProvider is not configured, considering it happens only in tests - scriptExtraImportsProvider?.subscribeOnExtraImportsChanged { files -> - weakThis.get()?.apply { - cacheLock.write { - allScriptsClasspathCache = null - } - ProjectRootManagerEx.getInstanceEx(myProject)?.makeRootsChange(EmptyRunnable.getInstance(), false, true) - } - } - } - - private var allScriptsClasspathCache: List? = null - private val cacheLock = java.util.concurrent.locks.ReentrantReadWriteLock() - - fun getScriptClasspath(file: VirtualFile): List = - scriptExtraImportsProvider - ?.getExtraImports(file) - ?.flatMap { it.classpath } - ?.map { it.classpathEntryToVfs() } - ?: emptyList() - - fun getAllScriptsClasspath(): List = cacheLock.read { - if (allScriptsClasspathCache == null) { - allScriptsClasspathCache = - (scriptExtraImportsProvider?.getKnownCombinedClasspath() ?: emptyList()) - .distinct() - .mapNotNull { it.classpathEntryToVfs() } - } - return allScriptsClasspathCache!! - } - - private fun String.classpathEntryToVfs(): VirtualFile = - if (File(this).isDirectory) - StandardFileSystems.local()?.findFileByPath(this) ?: throw FileNotFoundException("Classpath entry points to a non-existent location: ${this}") - else - StandardFileSystems.jar()?.findFileByPath(this + URLUtil.JAR_SEPARATOR) ?: throw FileNotFoundException("Classpath entry points to a file that is not a JAR archive: ${this}") - - fun getAllScriptsClasspathScope(): GlobalSearchScope? { - return getAllScriptsClasspath().let { cp -> - if (cp.isEmpty()) null - else GlobalSearchScope.union(cp.map { FileLibraryScope(myProject, it) }.toTypedArray()) - } - } - - private fun reloadScriptDefinitions() { - loadScriptConfigsFromProjectRoot(File(myProject.basePath ?: ".")).let { - if (it.isNotEmpty()) { - scriptDefinitionProvider.setScriptDefinitions( - it.map { KotlinConfigurableScriptDefinition(it, kotlinEnvVars) } + StandardScriptDefinition) - } - } - } - - private fun cacheAllScriptsExtraImports() { - fun VirtualFile.vfsWalkFiles(onFile: (VirtualFile) -> List?): List { - assert(isDirectory) - return children.flatMap { when { - it.isDirectory -> it.vfsWalkFiles(onFile) - else -> onFile(it) ?: emptyList() - } } - } - myProject.baseDir.vfsWalkFiles { - scriptExtraImportsProvider?.getExtraImports(it) - } - } - - companion object { - @JvmStatic - fun getInstance(project: Project): KotlinScriptConfigurationManager = - project.getComponent(KotlinScriptConfigurationManager::class.java) - } -} - - -class KotlinScriptDependenciesIndexableSetContributor : IndexableSetContributor() { - - override fun getAdditionalProjectRootsToIndex(project: Project): Set = - KotlinScriptConfigurationManager.getInstance(project).getAllScriptsClasspath().toSet() - - override fun getAdditionalRootsToIndex(): Set = emptySet() -} - diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/KotlinScriptConfigurationManager.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/KotlinScriptConfigurationManager.kt new file mode 100644 index 00000000000..4df18de8933 --- /dev/null +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/KotlinScriptConfigurationManager.kt @@ -0,0 +1,118 @@ +/* + * 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.idea.core.script + +import com.intellij.openapi.components.ServiceManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.StandardFileSystems +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.search.FileTypeIndex +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.util.indexing.IndexableSetContributor +import com.intellij.util.io.URLUtil +import org.jetbrains.kotlin.idea.caches.resolve.FileLibraryScope +import org.jetbrains.kotlin.idea.util.application.runReadAction +import org.jetbrains.kotlin.script.* +import org.jetbrains.kotlin.utils.PathUtil +import java.io.File +import java.io.FileNotFoundException +import java.util.concurrent.locks.ReentrantReadWriteLock +import kotlin.concurrent.read + +class KotlinScriptConfigurationManager( + private val project: Project, + private val scriptDefinitionProvider: KotlinScriptDefinitionProvider, + private val scriptExternalImportsProvider: KotlinScriptExternalImportsProvider? +) { + + private val kotlinEnvVars: Map> by lazy { + generateKotlinScriptClasspathEnvVarsFromPaths(project, PathUtil.getKotlinPathsForIdeaPlugin()) + } + + init { + reloadScriptDefinitions() + // TODO: sort out read/write action business and if possible make it lazy (e.g. move to getAllScriptsClasspath) + runReadAction { cacheAllScriptsExtraImports() } + } + + private var allScriptsClasspathCache: List? = null + private val cacheLock = ReentrantReadWriteLock() + + fun getScriptClasspath(file: VirtualFile): List = + scriptExternalImportsProvider + ?.getExternalImports(file) + ?.flatMap { it.classpath } + ?.map { it.classpathEntryToVfs() } + ?: emptyList() + + fun getAllScriptsClasspath(): List = cacheLock.read { + if (allScriptsClasspathCache == null) { + allScriptsClasspathCache = + (scriptExternalImportsProvider?.getKnownCombinedClasspath() ?: emptyList()) + .distinct() + .mapNotNull { it.classpathEntryToVfs() } + } + return allScriptsClasspathCache!! + } + + private fun String.classpathEntryToVfs(): VirtualFile = + if (File(this).isDirectory) + StandardFileSystems.local()?.findFileByPath(this) ?: throw FileNotFoundException("Classpath entry points to a non-existent location: ${this}") + else + StandardFileSystems.jar()?.findFileByPath(this + URLUtil.JAR_SEPARATOR) ?: throw FileNotFoundException("Classpath entry points to a file that is not a JAR archive: ${this}") + + fun getAllScriptsClasspathScope(): GlobalSearchScope? { + return getAllScriptsClasspath().let { cp -> + if (cp.isEmpty()) null + else GlobalSearchScope.union(cp.map { FileLibraryScope(project, it) }.toTypedArray()) + } + } + + private fun reloadScriptDefinitions() { + loadScriptConfigsFromProjectRoot(File(project.basePath ?: "")).let { + if (it.isNotEmpty()) { + scriptDefinitionProvider.setScriptDefinitions( + it.map { KotlinConfigurableScriptDefinition(it, kotlinEnvVars) } + StandardScriptDefinition) + } + } + } + + private fun cacheAllScriptsExtraImports() { + scriptExternalImportsProvider?.apply { + invalidateCaches() + cacheExternalImports( + scriptDefinitionProvider.getAllKnownFileTypes() + .flatMap { FileTypeIndex.getFiles(it, GlobalSearchScope.allScope(project)) }) + } + } + + companion object { + @JvmStatic + fun getInstance(project: Project): KotlinScriptConfigurationManager = + ServiceManager.getService(project, KotlinScriptConfigurationManager::class.java) + } +} + + +class KotlinScriptDependenciesIndexableSetContributor : IndexableSetContributor() { + + override fun getAdditionalProjectRootsToIndex(project: Project): Set = + KotlinScriptConfigurationManager.getInstance(project).getAllScriptsClasspath().toSet() + + override fun getAdditionalRootsToIndex(): Set = emptySet() +} + diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/script/KotlinScriptDependenciesClassFinder.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/KotlinScriptDependenciesClassFinder.kt similarity index 91% rename from idea/idea-analysis/src/org/jetbrains/kotlin/idea/script/KotlinScriptDependenciesClassFinder.kt rename to idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/KotlinScriptDependenciesClassFinder.kt index 55ae925cbf1..76c70c24de9 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/script/KotlinScriptDependenciesClassFinder.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/KotlinScriptDependenciesClassFinder.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.jetbrains.kotlin.idea.script +package org.jetbrains.kotlin.idea.core.script import com.intellij.openapi.project.Project import com.intellij.openapi.roots.ProjectFileIndex @@ -26,18 +26,19 @@ import com.intellij.psi.search.EverythingGlobalScope import com.intellij.psi.search.GlobalSearchScope import com.intellij.util.containers.ConcurrentFactoryMap import org.jetbrains.kotlin.idea.caches.resolve.ScriptModuleSearchScope +import org.jetbrains.kotlin.idea.core.script.KotlinScriptConfigurationManager import org.jetbrains.kotlin.load.java.JavaClassFinderImpl import org.jetbrains.kotlin.resolve.jvm.KotlinSafeClassFinder class KotlinScriptDependenciesClassFinder(project: Project, - private val kotlinScriptConfigurationManager: KotlinScriptConfigurationManager + private val kotlinScriptConfigurationManager: KotlinScriptConfigurationManager ) : NonClasspathClassFinder(project), KotlinSafeClassFinder { private val myCaches = object : ConcurrentFactoryMap() { override fun create(file: VirtualFile): PackageDirectoryCache? { val scriptClasspath = kotlinScriptConfigurationManager.getScriptClasspath(file) - val v = NonClasspathClassFinder.createCache(scriptClasspath) + val v = createCache(scriptClasspath) return v } } diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 06850a44c42..4510538e61d 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -40,9 +40,6 @@ org.jetbrains.kotlin.asJava.KotlinCodeBlockModificationListener - - org.jetbrains.kotlin.idea.script.KotlinScriptConfigurationManager - @@ -250,8 +247,11 @@ - + + + @@ -293,7 +293,7 @@ - + @@ -575,7 +575,7 @@ implementationClass="org.jetbrains.kotlin.idea.hierarchy.overrides.KotlinOverrideHierarchyProvider" /> - +