Refactor script dependencies management

This commit is contained in:
Ilya Chernikov
2016-06-09 19:56:37 +02:00
committed by Pavel V. Talanov
parent c244414f3c
commit 082290f8e3
23 changed files with 470 additions and 510 deletions
@@ -149,15 +149,9 @@ class KotlinCoreEnvironment private constructor(
?: emptyList() ?: emptyList()
}) })
KotlinScriptExtraImportsProvider.getInstance(project)?.run { KotlinScriptExternalImportsProvider.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))
}
configuration.addJvmClasspathRoots( configuration.addJvmClasspathRoots(
getCombinedClasspathFor(sourceFiles.mapNotNull { src -> src.virtualFile }) getCombinedClasspathFor(sourceFiles)
.map { File(it).canonicalFile } .map { File(it).canonicalFile }
.distinct()) .distinct())
} }
@@ -416,7 +410,7 @@ class KotlinCoreEnvironment private constructor(
with (projectEnvironment.project) { with (projectEnvironment.project) {
val kotlinScriptDefinitionProvider = KotlinScriptDefinitionProvider() val kotlinScriptDefinitionProvider = KotlinScriptDefinitionProvider()
registerService(KotlinScriptDefinitionProvider::class.java, 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(KotlinJavaPsiFacade::class.java, KotlinJavaPsiFacade(this))
registerService(KtLightClassForFacade.FacadeStubCache::class.java, KtLightClassForFacade.FacadeStubCache(this)) registerService(KtLightClassForFacade.FacadeStubCache::class.java, KtLightClassForFacade.FacadeStubCache(this))
} }
@@ -19,11 +19,9 @@ package org.jetbrains.kotlin.config;
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCompilationComponents; import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCompilationComponents;
import org.jetbrains.kotlin.modules.Module; import org.jetbrains.kotlin.modules.Module;
import org.jetbrains.kotlin.script.KotlinScriptDefinition; import org.jetbrains.kotlin.script.KotlinScriptDefinition;
import org.jetbrains.kotlin.script.KotlinScriptExtraImport;
import java.io.File; import java.io.File;
import java.util.List; import java.util.List;
import java.util.Map;
public class JVMConfigurationKeys { public class JVMConfigurationKeys {
private JVMConfigurationKeys() { private JVMConfigurationKeys() {
@@ -43,9 +41,6 @@ public class JVMConfigurationKeys {
public static final CompilerConfigurationKey<List<KotlinScriptDefinition>> SCRIPT_DEFINITIONS = public static final CompilerConfigurationKey<List<KotlinScriptDefinition>> SCRIPT_DEFINITIONS =
CompilerConfigurationKey.create("script definitions"); CompilerConfigurationKey.create("script definitions");
public static final CompilerConfigurationKey<Map<String, KotlinScriptExtraImport>> SCRIPTS_EXTRA_IMPORTS =
CompilerConfigurationKey.create("scripts extra imports");
public static final CompilerConfigurationKey<Boolean> DISABLE_CALL_ASSERTIONS = public static final CompilerConfigurationKey<Boolean> DISABLE_CALL_ASSERTIONS =
CompilerConfigurationKey.create("disable not-null call assertions"); CompilerConfigurationKey.create("disable not-null call assertions");
public static final CompilerConfigurationKey<Boolean> DISABLE_PARAM_ASSERTIONS = public static final CompilerConfigurationKey<Boolean> DISABLE_PARAM_ASSERTIONS =
@@ -34,7 +34,7 @@ import org.jetbrains.kotlin.resolve.scopes.ImportingScope
import org.jetbrains.kotlin.resolve.scopes.LexicalScope import org.jetbrains.kotlin.resolve.scopes.LexicalScope
import org.jetbrains.kotlin.resolve.scopes.SubpackagesImportingScope import org.jetbrains.kotlin.resolve.scopes.SubpackagesImportingScope
import org.jetbrains.kotlin.resolve.source.KotlinSourceElement 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.StorageManager
import org.jetbrains.kotlin.storage.getValue import org.jetbrains.kotlin.storage.getValue
import org.jetbrains.kotlin.types.TypeSubstitutor import org.jetbrains.kotlin.types.TypeSubstitutor
@@ -73,7 +73,7 @@ class FileScopeFactory(
val explicitImportResolver = createImportResolver(ExplicitImportsIndexed(imports), bindingTrace) val explicitImportResolver = createImportResolver(ExplicitImportsIndexed(imports), bindingTrace)
val allUnderImportResolver = createImportResolver(AllUnderImportsIndexed(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 allImplicitImports = defaultImports + extraImports
val defaultImportsFiltered = if (aliasImportNames.isEmpty()) { // optimization val defaultImportsFiltered = if (aliasImportNames.isEmpty()) { // optimization
@@ -17,14 +17,6 @@
package org.jetbrains.kotlin.script package org.jetbrains.kotlin.script
import com.intellij.openapi.project.Project 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.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.parsing.KotlinParserDefinition 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() } private val evaluatedClasspath by lazy { config.classpath.evalWithVars(environmentVars).distinct() }
override fun getScriptDependenciesClasspath(): List<String> = evaluatedClasspath override fun <TF> 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 // if corresponding list of replacements is empty, all strings containing the reference to the var are removed
// TODO: fix and tests // TODO: fix and tests
// TODO: move to some utils // TODO: move to some utils
internal fun List<String>.evalWithVars(varsMap: Map<String, List<String>>?): List<String> = internal fun Iterable<String>.evalWithVars(varsMap: Map<String, List<String>>?): Iterable<String> =
if (varsMap == null || varsMap.isEmpty()) this if (varsMap == null || varsMap.isEmpty()) this
else this.flatMap { cpentry -> else this.flatMap { cpentry ->
varsMap.entries.fold(listOf(cpentry)) { p, v -> varsMap.entries.fold(listOf(cpentry)) { p, v ->
@@ -16,10 +16,12 @@
package org.jetbrains.kotlin.script 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.openapi.vfs.VirtualFile
import com.intellij.psi.PsiFile import com.intellij.psi.PsiFile
import com.intellij.util.PathUtil
import org.jetbrains.kotlin.descriptors.ScriptDescriptor import org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
@@ -36,19 +38,39 @@ import java.io.File
import kotlin.reflect.KClass import kotlin.reflect.KClass
interface KotlinScriptDefinition { 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 <TF> isScript(file: TF): Boolean =
getFileName(file).endsWith(KotlinParserDefinition.STD_SCRIPT_EXT)
// TODO: replace these 3 functions with template property
fun getScriptParameters(scriptDescriptor: ScriptDescriptor): List<ScriptParameter> fun getScriptParameters(scriptDescriptor: ScriptDescriptor): List<ScriptParameter>
fun getScriptSupertypes(scriptDescriptor: ScriptDescriptor): List<KotlinType> = emptyList() fun getScriptSupertypes(scriptDescriptor: ScriptDescriptor): List<KotlinType> = emptyList()
fun getScriptParametersToPassToSuperclass(scriptDescriptor: ScriptDescriptor): List<Name> = emptyList() fun getScriptParametersToPassToSuperclass(scriptDescriptor: ScriptDescriptor): List<Name> = emptyList()
fun <TF> isScript(file: TF): Boolean
fun getScriptName(script: KtScript): Name fun getScriptName(script: KtScript): Name =
fun getScriptDependenciesClasspath(): List<String> = emptyList() ScriptNameUtil.fileNameWithExtensionStripped(script, KotlinParserDefinition.STD_SCRIPT_EXT)
fun <TF> getDependenciesFor(file: TF, project: Project): KotlinScriptExternalDependencies? = null
}
interface KotlinScriptExternalDependencies {
val classpath: Iterable<String> get() = emptyList()
val imports: Iterable<String> get() = emptyList()
val sources: Iterable<String> get() = emptyList()
}
class KotlinScriptExternalDependenciesUnion(val dependencies: Iterable<KotlinScriptExternalDependencies>) : KotlinScriptExternalDependencies {
override val classpath: Iterable<String> get() = dependencies.flatMap { it.classpath }
override val imports: Iterable<String> get() = dependencies.flatMap { it.imports }
override val sources: Iterable<String> get() = dependencies.flatMap { it.sources }
} }
data class ScriptParameter(val name: Name, val type: KotlinType) data class ScriptParameter(val name: Name, val type: KotlinType)
fun <TF> getFileExtension(file: TF) = PathUtil.getFileExtension(getFileName(file))
fun <TF> getFileName(file: TF): String = when (file) { fun <TF> getFileName(file: TF): String = when (file) {
is PsiFile -> file.originalFile.name is PsiFile -> file.originalFile.name
is VirtualFile -> file.name is VirtualFile -> file.name
@@ -56,17 +78,16 @@ fun <TF> getFileName(file: TF): String = when (file) {
else -> throw IllegalArgumentException("Unsupported file type $file") else -> throw IllegalArgumentException("Unsupported file type $file")
} }
fun <TF> 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 { object StandardScriptDefinition : KotlinScriptDefinition {
private val ARGS_NAME = Name.identifier("args") 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 <TF> isScript(file: TF): Boolean =
getFileExtension(file) == KotlinParserDefinition.STD_SCRIPT_SUFFIX
// NOTE: for now we treat .kts files as if they have 'args: Array<String>' parameter // NOTE: for now we treat .kts files as if they have 'args: Array<String>' parameter
// this is not supposed to be final design // this is not supposed to be final design
override fun getScriptParameters(scriptDescriptor: ScriptDescriptor): List<ScriptParameter> = override fun getScriptParameters(scriptDescriptor: ScriptDescriptor): List<ScriptParameter> =
@@ -17,6 +17,7 @@
package org.jetbrains.kotlin.script package org.jetbrains.kotlin.script
import com.intellij.openapi.components.ServiceManager import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.fileTypes.LanguageFileType
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiFile import com.intellij.psi.PsiFile
@@ -28,10 +29,8 @@ class KotlinScriptDefinitionProvider {
private val definitions: MutableList<KotlinScriptDefinition> = arrayListOf(StandardScriptDefinition) private val definitions: MutableList<KotlinScriptDefinition> = arrayListOf(StandardScriptDefinition)
private val definitionsLock = java.util.concurrent.locks.ReentrantReadWriteLock() 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<KotlinScriptDefinition>): Unit { fun setScriptDefinitions(newDefinitions: List<KotlinScriptDefinition>): Boolean {
var changed = false var changed = false
definitionsLock.read { definitionsLock.read {
if (newDefinitions != definitions) { if (newDefinitions != definitions) {
@@ -42,11 +41,7 @@ class KotlinScriptDefinitionProvider {
changed = true changed = true
} }
} }
if (changed) { return changed
handlersLock.read {
notificationHandlers.forEach { it() }
}
}
} }
fun<TF> findScriptDefinition(file: TF): KotlinScriptDefinition? = definitionsLock.read { fun<TF> findScriptDefinition(file: TF): KotlinScriptDefinition? = definitionsLock.read {
@@ -55,10 +50,6 @@ class KotlinScriptDefinitionProvider {
fun<TF> isScript(file: TF): Boolean = findScriptDefinition(file) != null fun<TF> isScript(file: TF): Boolean = findScriptDefinition(file) != null
fun subscribeOnDefinitionsChanged(handler: () -> Unit): Unit {
handlersLock.write { notificationHandlers.add(handler) }
}
fun addScriptDefinition(scriptDefinition: KotlinScriptDefinition) { fun addScriptDefinition(scriptDefinition: KotlinScriptDefinition) {
definitionsLock.write { definitionsLock.write {
definitions.add(0, scriptDefinition) definitions.add(0, scriptDefinition)
@@ -71,6 +62,10 @@ class KotlinScriptDefinitionProvider {
} }
} }
fun getAllKnownFileTypes(): Iterable<LanguageFileType> = definitionsLock.read {
definitions.map { it.fileType }.distinct()
}
companion object { companion object {
@JvmStatic @JvmStatic
fun getInstance(project: Project): KotlinScriptDefinitionProvider = fun getInstance(project: Project): KotlinScriptDefinitionProvider =
@@ -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<String, KotlinScriptExternalDependencies>()
private val cacheOfNulls = hashSetOf<String>()
fun <TF> getExternalImports(vararg files: TF): List<KotlinScriptExternalDependencies> = getExternalImports(files.asIterable())
fun <TF> getExternalImports(files: Iterable<TF>): List<KotlinScriptExternalDependencies> = 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 <TF> cacheExternalImports(files: Iterable<TF>): Unit = cacheLock.write {
val uncached = hashSetOf<String>()
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 <TF> invalidateCachesFor(vararg files: TF) { invalidateCachesFor(files.asIterable()) }
fun <TF> invalidateCachesFor(files: Iterable<TF>) {
cacheLock.write {
files.forEach { file ->
val path = getFilePath(file)
cache.remove(path)
cacheOfNulls.remove(path)
}
}
}
fun getKnownCombinedClasspath(): List<String> = cacheLock.read {
cache.values.flatMap { it.classpath }
}.distinct()
fun <TF> getCombinedClasspathFor(files: Iterable<TF>): List<String> =
getExternalImports(files)
.flatMap { it.classpath }
.distinct()
companion object {
@JvmStatic
fun getInstance(project: Project): KotlinScriptExternalImportsProvider? =
ServiceManager.getService(project, KotlinScriptExternalImportsProvider::class.java)
}
}
@@ -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<String, List<KotlinScriptExtraImport>>()
private val cache = hashMapOf<String, List<KotlinScriptExtraImport>>()
private val envVars: Map<String, List<String>> by lazy { generateKotlinScriptClasspathEnvVars(project) }
private val notificationHandlers = ArrayList<(Iterable<String>) -> 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<KotlinScriptExtraImport> = getExtraImports(files.asIterable())
fun getExtraImports(files: Iterable<VirtualFile>): List<KotlinScriptExtraImport> {
val newCashedFiles = ArrayList<String>()
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<KotlinScriptExtraImport>?) {
cacheLock.write {
if (extraImports != null && extraImports.isNotEmpty()) {
preconfigured[file.path] = extraImports
}
else {
preconfigured.remove(file.path)
}
}
}
fun invalidateExtraImportsByImportsFiles(importsFiles: Iterable<VirtualFile>) {
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<String>) {
if (files.any()) {
handlersLock.read {
notificationHandlers.forEach { it(files) }
}
}
}
fun getKnownCombinedClasspath(): List<String> = cacheLock.read {
cache.values.flatMap { it.flatMap { it.classpath } }
}.distinct()
fun getCombinedClasspathFor(files: Iterable<VirtualFile>): List<String> =
getExtraImports(files)
.flatMap { it.classpath }
.distinct()
fun subscribeOnExtraImportsChanged(handler: (Iterable<String>) -> 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"
}
}
@@ -26,11 +26,11 @@ fun getScriptDefinition(file: VirtualFile, project: Project): KotlinScriptDefini
fun getScriptDefinition(psiFile: PsiFile): KotlinScriptDefinition? = fun getScriptDefinition(psiFile: PsiFile): KotlinScriptDefinition? =
KotlinScriptDefinitionProvider.getInstance(psiFile.project).findScriptDefinition(psiFile) KotlinScriptDefinitionProvider.getInstance(psiFile.project).findScriptDefinition(psiFile)
fun getScriptExtraImports(file: VirtualFile, project: Project): List<KotlinScriptExtraImport> = fun getScriptExternalDependencies(file: VirtualFile, project: Project): List<KotlinScriptExternalDependencies> =
KotlinScriptExtraImportsProvider.getInstance(project)?.getExtraImports(file) ?: emptyList() KotlinScriptExternalImportsProvider.getInstance(project)?.getExternalImports(file) ?: emptyList()
fun getScriptExtraImports(psiFile: PsiFile): List<KotlinScriptExtraImport> = fun getScriptExternalDependencies(psiFile: PsiFile): List<KotlinScriptExternalDependencies> =
psiFile.virtualFile?.let { file -> psiFile.virtualFile?.let { file ->
KotlinScriptExtraImportsProvider.getInstance(psiFile.project)?.getExtraImports(file) KotlinScriptExternalImportsProvider.getInstance(psiFile.project)?.getExternalImports(file)
} ?: emptyList() } ?: emptyList()
@@ -52,7 +52,8 @@ object SimpleUntypedAst {
convert(expression.entries[0]) convert(expression.entries[0])
else else
SimpleUntypedAst.Node.str(name, "") 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) else -> Node.empty(name)
@@ -72,3 +73,9 @@ object SimpleUntypedAst {
} }
} }
fun parseAnnotation(ann: KtAnnotationEntry): SimpleUntypedAst.Node.list<Any> {
val wann = SimpleUntypedAst.KtAnnotationWrapper(ann)
val vals = wann.valueArguments
return SimpleUntypedAst.Node.list(wann.name, vals)
}
@@ -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<String> = ArrayList()
@Tag("imports")
@AbstractCollection(surroundWithTag = false, elementTag = "name", elementValueAttribute = "")
override var imports: MutableList<String> = ArrayList()
@Tag("sources")
@AbstractCollection(surroundWithTag = false, elementTag = "path", elementValueAttribute = "")
override var sources: MutableList<String> = ArrayList()
}
fun loadScriptExternalImportConfigs(configFile: File): List<KotlinScriptExternalDependenciesConfig> =
JDOMUtil.loadDocument(configFile).rootElement.children.mapNotNull {
XmlSerializer.deserialize(it, KotlinScriptExternalDependenciesConfig::class.java)
}
fun loadScriptExternalImportConfigs(configStream: InputStream): List<KotlinScriptExternalDependenciesConfig> =
JDOMUtil.loadDocument(configStream).rootElement.children.mapNotNull {
XmlSerializer.deserialize(it, KotlinScriptExternalDependenciesConfig::class.java)
}
fun <TF> 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)) }
}
@@ -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<String>
val names: List<String>
}
// -----
@Tag("import")
class KotlinScriptExtraImportConfig {
@Tag("classpath")
@AbstractCollection(surroundWithTag = false, elementTag = "path", elementValueAttribute = "")
var classpath: MutableList<String> = ArrayList()
@Tag("names")
@AbstractCollection(surroundWithTag = false, elementTag = "name", elementValueAttribute = "")
var names: MutableList<String> = ArrayList()
}
fun loadScriptExtraImportConfigs(configFile: File): List<KotlinScriptExtraImportConfig> =
JDOMUtil.loadDocument(configFile).rootElement.children.mapNotNull {
XmlSerializer.deserialize(it, KotlinScriptExtraImportConfig::class.java)
}
fun loadScriptExtraImportConfigs(configStream: InputStream): List<KotlinScriptExtraImportConfig> =
JDOMUtil.loadDocument(configStream).rootElement.children.mapNotNull {
XmlSerializer.deserialize(it, KotlinScriptExtraImportConfig::class.java)
}
class KotlinScriptExtraImportFromConfig(val config : KotlinScriptExtraImportConfig, val envVars: Map<String, List<String>>) : KotlinScriptExtraImport {
override val classpath: List<String> by lazy { config.classpath.evalWithVars(envVars).distinct() }
override val names: List<String>
get() = config.names
}
class KotlinScriptExtraImportFromDefinition(val scriptDefinition: KotlinScriptDefinition) : KotlinScriptExtraImport {
override val classpath: List<String> get() = scriptDefinition.getScriptDependenciesClasspath()
override val names: List<String> = emptyList()
}
@@ -16,33 +16,33 @@
package org.jetbrains.kotlin.script 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.openapi.vfs.VirtualFile
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiManager
import org.jetbrains.kotlin.descriptors.ScriptDescriptor import org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.parsing.KotlinParserDefinition import org.jetbrains.kotlin.parsing.KotlinParserDefinition
import org.jetbrains.kotlin.psi.KtAnnotation
import org.jetbrains.kotlin.psi.KtAnnotationEntry import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtScript import org.jetbrains.kotlin.psi.KtScript
import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.KotlinType
import java.io.File
import kotlin.reflect.KClass import kotlin.reflect.KClass
@Target(AnnotationTarget.CLASS) @Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME) @Retention(AnnotationRetention.RUNTIME)
annotation class ScriptFilePattern(val pattern: String) annotation class ScriptFilePattern(val pattern: String)
interface ScriptDependencies {
val classpath: List<String>
val implicitImports: List<String>
}
interface GetScriptDependencies { interface GetScriptDependencies {
operator fun invoke(annotations: Iterable<KtAnnotationEntry>, context: Any?): ScriptDependencies? = null operator fun invoke(annotations: Iterable<KtAnnotationEntry>, context: Any?): KotlinScriptExternalDependencies? = null
operator fun invoke(context: Any?): ScriptDependencies? = null operator fun invoke(context: Any?): KotlinScriptExternalDependencies? = null
} }
@Target(AnnotationTarget.CLASS) @Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME) @Retention(AnnotationRetention.RUNTIME)
annotation class ScriptDependencyExtractor(val extractor: KClass<out GetScriptDependencies>) annotation class ScriptDependencyResolver(val extractor: KClass<out GetScriptDependencies>)
data class KotlinScriptDefinitionFromTemplate(val template: KClass<out Any>, val context: Any?) : KotlinScriptDefinition { data class KotlinScriptDefinitionFromTemplate(val template: KClass<out Any>, val context: Any?) : KotlinScriptDefinition {
override val name = template.simpleName!! override val name = template.simpleName!!
@@ -63,13 +63,39 @@ data class KotlinScriptDefinitionFromTemplate(val template: KClass<out Any>, val
override fun getScriptName(script: KtScript): Name = ScriptNameUtil.fileNameWithExtensionStripped(script, KotlinParserDefinition.STD_SCRIPT_EXT) override fun getScriptName(script: KtScript): Name = ScriptNameUtil.fileNameWithExtensionStripped(script, KotlinParserDefinition.STD_SCRIPT_EXT)
private val dependenciesExtractors by lazy { 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<KotlinScriptExternalDependencies> by lazy {
dependenciesExtractors.mapNotNull { it(context) } dependenciesExtractors.mapNotNull { it(context) }
} }
override fun getScriptDependenciesClasspath(): List<String> = dependencies.flatMap { it.classpath } override fun <TF> getDependenciesFor(file: TF, project: Project): KotlinScriptExternalDependencies? {
val fileAnnotations = getAnnotationEntries(file, project)
val fileDeps = dependenciesExtractors.mapNotNull { it(fileAnnotations, context) }
return KotlinScriptExternalDependenciesUnion(dependencies + fileDeps)
}
private fun <TF> getAnnotationEntries(file: TF, project: Project): Iterable<KtAnnotationEntry> = 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<KtAnnotationEntry> {
val psifile: PsiFile = PsiManager.getInstance(project).findFile(file)
?: throw java.lang.IllegalArgumentException("Unable to load PSI from ${file.canonicalPath}")
return getAnnotationEntriesFromPsiFile(psifile)
}
} }
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.scripts package org.jetbrains.kotlin.scripts
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.descriptors.ScriptDescriptor import org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtScript import org.jetbrains.kotlin.psi.KtScript
@@ -26,15 +27,17 @@ import java.net.URL
import java.net.URLClassLoader import java.net.URLClassLoader
import kotlin.reflect.KClass import kotlin.reflect.KClass
abstract class BaseScriptDefinition (val extension: String, val classpath: List<String>? = null) : KotlinScriptDefinition { abstract class BaseScriptDefinition (val extension: String, val cp: List<String>? = null) : KotlinScriptDefinition {
override val name = "Test Kotlin Script" override val name = "Test Kotlin Script"
override fun <TF> isScript(file: TF): Boolean = org.jetbrains.kotlin.script.getFileName(file).endsWith(extension) override fun <TF> isScript(file: TF): Boolean = getFileName(file).endsWith(extension)
override fun getScriptName(script: KtScript): Name = ScriptNameUtil.fileNameWithExtensionStripped(script, extension) override fun getScriptName(script: KtScript): Name = ScriptNameUtil.fileNameWithExtensionStripped(script, extension)
override fun getScriptDependenciesClasspath(): List<String> = override fun <TF> getDependenciesFor(file: TF, project: Project): KotlinScriptExternalDependencies? =
classpath ?: (classpathFromProperty() + classpathFromClassloader(BaseScriptDefinition::class.java.classLoader)).distinct() object : KotlinScriptExternalDependencies {
override val classpath = cp ?: (classpathFromProperty() + classpathFromClassloader(BaseScriptDefinition::class.java.classLoader)).distinct()
}
} }
open class SimpleParamsWithClasspathTestScriptDefinition(extension: String, val parameters: List<ScriptParameter>, classpath: List<String>? = null) open class SimpleParamsWithClasspathTestScriptDefinition(extension: String, val parameters: List<ScriptParameter>, classpath: List<String>? = null, val extraDependencies: KotlinScriptExternalDependencies? = null)
: BaseScriptDefinition(extension, classpath) : BaseScriptDefinition(extension, classpath)
{ {
override fun getScriptParameters(scriptDescriptor: ScriptDescriptor) = parameters override fun getScriptParameters(scriptDescriptor: ScriptDescriptor) = parameters
@@ -74,10 +77,10 @@ class StandardWithClasspathScriptDefinition(extension: String, classpath: List<S
StandardScriptDefinition.getScriptParameters(scriptDescriptor) StandardScriptDefinition.getScriptParameters(scriptDescriptor)
} }
class SimpleScriptExtraImport( class SimpleScriptExtraDependencies(
override val classpath: List<String>, override val classpath: List<String>,
override val names: List<String> = emptyList() override val imports: List<String> = emptyList()
) : KotlinScriptExtraImport ) : KotlinScriptExternalDependencies
fun classpathFromProperty(): List<String> = fun classpathFromProperty(): List<String> =
System.getProperty("java.class.path")?.let { System.getProperty("java.class.path")?.let {
@@ -26,11 +26,9 @@ import org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler
import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoot import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoot
import org.jetbrains.kotlin.codegen.CompilationException import org.jetbrains.kotlin.codegen.CompilationException
import org.jetbrains.kotlin.config.JVMConfigurationKeys import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.addKotlinSourceRoot import org.jetbrains.kotlin.config.addKotlinSourceRoot
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.script.KotlinScriptDefinition import org.jetbrains.kotlin.script.KotlinScriptDefinition
import org.jetbrains.kotlin.script.KotlinScriptExtraImport
import org.jetbrains.kotlin.script.ScriptParameter import org.jetbrains.kotlin.script.ScriptParameter
import org.jetbrains.kotlin.script.StandardScriptDefinition import org.jetbrains.kotlin.script.StandardScriptDefinition
import org.jetbrains.kotlin.test.ConfigurationKind import org.jetbrains.kotlin.test.ConfigurationKind
@@ -45,14 +43,14 @@ import java.lang.reflect.InvocationTargetException
class ScriptTest { class ScriptTest {
@Test @Test
fun testScriptWithParam() { fun testScriptWithParam() {
val aClass = compileScript("fib.kts", SimpleParamsTestScriptDefinition(".kts", numIntParam()), null) val aClass = compileScript("fib.kts", SimpleParamsTestScriptDefinition(".kts", numIntParam()))
Assert.assertNotNull(aClass) Assert.assertNotNull(aClass)
aClass!!.getConstructor(Integer.TYPE).newInstance(4) aClass!!.getConstructor(Integer.TYPE).newInstance(4)
} }
@Test @Test
fun testStandardScriptWithParams() { fun testStandardScriptWithParams() {
val aClass = compileScript("fib_std.kts", StandardScriptDefinition, null) val aClass = compileScript("fib_std.kts", StandardScriptDefinition)
Assert.assertNotNull(aClass) Assert.assertNotNull(aClass)
val anObj = KotlinToJVMBytecodeCompiler.tryConstructClassPub(aClass!!, listOf("4", "comment")) val anObj = KotlinToJVMBytecodeCompiler.tryConstructClassPub(aClass!!, listOf("4", "comment"))
Assert.assertNotNull(anObj) Assert.assertNotNull(anObj)
@@ -60,7 +58,7 @@ class ScriptTest {
@Test @Test
fun testStandardScriptWithoutParams() { fun testStandardScriptWithoutParams() {
val aClass = compileScript("fib_std.kts", StandardScriptDefinition, null) val aClass = compileScript("fib_std.kts", StandardScriptDefinition)
Assert.assertNotNull(aClass) Assert.assertNotNull(aClass)
val anObj = KotlinToJVMBytecodeCompiler.tryConstructClassPub(aClass!!, emptyList()) val anObj = KotlinToJVMBytecodeCompiler.tryConstructClassPub(aClass!!, emptyList())
Assert.assertNotNull(anObj) Assert.assertNotNull(anObj)
@@ -68,7 +66,7 @@ class ScriptTest {
@Test @Test
fun testScriptWithParamConversion() { fun testScriptWithParamConversion() {
val aClass = compileScript("fib.kts", SimpleParamsTestScriptDefinition(".kts", numIntParam()), null) val aClass = compileScript("fib.kts", SimpleParamsTestScriptDefinition(".kts", numIntParam()))
Assert.assertNotNull(aClass) Assert.assertNotNull(aClass)
val anObj = KotlinToJVMBytecodeCompiler.tryConstructClassPub(aClass!!, listOf("4")) val anObj = KotlinToJVMBytecodeCompiler.tryConstructClassPub(aClass!!, listOf("4"))
Assert.assertNotNull(anObj) Assert.assertNotNull(anObj)
@@ -76,67 +74,67 @@ class ScriptTest {
@Test @Test
fun testScriptWithPackage() { fun testScriptWithPackage() {
val aClass = compileScript("fib.pkg.kts", SimpleParamsTestScriptDefinition(".kts", numIntParam()), null) val aClass = compileScript("fib.pkg.kts", SimpleParamsTestScriptDefinition(".kts", numIntParam()))
Assert.assertNotNull(aClass) Assert.assertNotNull(aClass)
aClass!!.getConstructor(Integer.TYPE).newInstance(4) aClass!!.getConstructor(Integer.TYPE).newInstance(4)
} }
@Test @Test
fun testScriptWithScriptDefinition() { fun testScriptWithScriptDefinition() {
val aClass = compileScript("fib.kts", SimpleParamsTestScriptDefinition(".kts", numIntParam()), null) val aClass = compileScript("fib.kts", SimpleParamsTestScriptDefinition(".kts", numIntParam()))
Assert.assertNotNull(aClass) Assert.assertNotNull(aClass)
aClass!!.getConstructor(Integer.TYPE).newInstance(4) aClass!!.getConstructor(Integer.TYPE).newInstance(4)
} }
@Test @Test
fun testScriptWithClassParameter() { 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) Assert.assertNotNull(aClass)
aClass!!.getConstructor(TestParamClass::class.java).newInstance(TestParamClass(4)) aClass!!.getConstructor(TestParamClass::class.java).newInstance(TestParamClass(4))
} }
@Test @Test
fun testScriptWithBaseClass() { 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) Assert.assertNotNull(aClass)
aClass!!.getConstructor(Integer.TYPE).newInstance(4) aClass!!.getConstructor(Integer.TYPE).newInstance(4)
} }
@Test @Test
fun testScriptWithBaseClassWithParam() { 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) Assert.assertNotNull(aClass)
aClass!!.getConstructor(Integer.TYPE, Integer.TYPE).newInstance(4, 1) aClass!!.getConstructor(Integer.TYPE, Integer.TYPE).newInstance(4, 1)
} }
@Test @Test
fun testScriptWithInterface() { 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) Assert.assertNotNull(aClass)
aClass!!.getConstructor(Integer.TYPE).newInstance(4) aClass!!.getConstructor(Integer.TYPE).newInstance(4)
} }
@Test @Test
fun testScriptWithClasspath() { 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) Assert.assertNull(aClass1)
val cp = classpathFromClassloader(ScriptTest::class.java.classLoader).filter { it.contains("kotlin-runtime") || it.contains("junit") } val cp = classpathFromClassloader(ScriptTest::class.java.classLoader).filter { it.contains("kotlin-runtime") || it.contains("junit") }
Assert.assertFalse(cp.isEmpty()) 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) Assert.assertNotNull(aClass2)
} }
@Test @Test
fun testScriptWithExtraImports() { 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) Assert.assertNull(aClass1)
val cp = classpathFromClassloader(ScriptTest::class.java.classLoader).filter { it.contains("kotlin-runtime") || it.contains("junit") } val cp = classpathFromClassloader(ScriptTest::class.java.classLoader).filter { it.contains("kotlin-runtime") || it.contains("junit") }
Assert.assertFalse(cp.isEmpty()) 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) Assert.assertNotNull(aClass2)
} }
@@ -163,22 +161,20 @@ class ScriptTest {
private fun compileScript( private fun compileScript(
scriptPath: String, scriptPath: String,
scriptDefinition: KotlinScriptDefinition, scriptDefinition: KotlinScriptDefinition,
extraImport: KotlinScriptExtraImport?,
runIsolated: Boolean = true, runIsolated: Boolean = true,
suppressOutput: Boolean = false): Class<*>? = suppressOutput: Boolean = false): Class<*>? =
compileScriptImpl("compiler/testData/script/" + scriptPath, scriptDefinition, extraImport, runIsolated, suppressOutput) compileScriptImpl("compiler/testData/script/" + scriptPath, scriptDefinition, runIsolated, suppressOutput)
private fun compileSmokeTestScript( private fun compileSmokeTestScript(
scriptPath: String, scriptPath: String,
scriptDefinition: KotlinScriptDefinition, scriptDefinition: KotlinScriptDefinition,
runIsolated: Boolean = true, runIsolated: Boolean = true,
suppressOutput: Boolean = false): Class<*>? = 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( private fun compileScriptImpl(
scriptPath: String, scriptPath: String,
scriptDefinition: KotlinScriptDefinition, scriptDefinition: KotlinScriptDefinition,
extraImport: KotlinScriptExtraImport?,
runIsolated: Boolean, runIsolated: Boolean,
suppressOutput: Boolean): Class<*>? suppressOutput: Boolean): Class<*>?
{ {
@@ -193,10 +189,6 @@ class ScriptTest {
configuration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector) configuration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector)
configuration.addKotlinSourceRoot(scriptPath) configuration.addKotlinSourceRoot(scriptPath)
configuration.add(JVMConfigurationKeys.SCRIPT_DEFINITIONS, scriptDefinition) 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) val environment = KotlinCoreEnvironment.createForProduction(rootDisposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
@@ -118,7 +118,7 @@ class GetTestKotlinScriptDependencies : GetScriptDependencies {
private val kotlinPaths by lazy { PathUtil.getKotlinPathsForCompiler() } private val kotlinPaths by lazy { PathUtil.getKotlinPathsForCompiler() }
override fun invoke(annotations: Iterable<KtAnnotationEntry>, context: Any?): ScriptDependencies? { override fun invoke(annotations: Iterable<KtAnnotationEntry>, context: Any?): KotlinScriptExternalDependencies? {
if (annotations.none()) return null if (annotations.none()) return null
val anns = annotations.map { parseAnnotation(it) }.filter { it.name == depends::class.simpleName } val anns = annotations.map { parseAnnotation(it) }.filter { it.name == depends::class.simpleName }
val cp = anns.flatMap { val cp = anns.flatMap {
@@ -129,9 +129,8 @@ class GetTestKotlinScriptDependencies : GetScriptDependencies {
} }
} }
} }
return object : ScriptDependencies { return object : KotlinScriptExternalDependencies {
override val classpath = cp override val classpath = cp
override val implicitImports = emptyList<String>()
} }
} }
@@ -141,30 +140,23 @@ class GetTestKotlinScriptDependencies : GetScriptDependencies {
?.filter { it.contains("out/test") } ?.filter { it.contains("out/test") }
?: emptyList() ?: emptyList()
override fun invoke(context: Any?): ScriptDependencies? { override fun invoke(context: Any?): KotlinScriptExternalDependencies? {
return object : ScriptDependencies { return object : KotlinScriptExternalDependencies {
override val classpath = classpathFromClassloader() override val classpath = classpathFromClassloader()
override val implicitImports = emptyList<String>()
} }
} }
} }
fun parseAnnotation(ann: KtAnnotationEntry): SimpleUntypedAst.Node.list<Any> {
val wann = SimpleUntypedAst.KtAnnotationWrapper(ann)
val vals = wann.valueArguments
return SimpleUntypedAst.Node.list(wann.name, vals)
}
@ScriptFilePattern(".*\\.kts") @ScriptFilePattern(".*\\.kts")
@ScriptDependencyExtractor(GetTestKotlinScriptDependencies::class) @ScriptDependencyResolver(GetTestKotlinScriptDependencies::class)
abstract class ScriptWithIntParam(num: Int) abstract class ScriptWithIntParam(num: Int)
@ScriptFilePattern(".*\\.kts") @ScriptFilePattern(".*\\.kts")
@ScriptDependencyExtractor(GetTestKotlinScriptDependencies::class) @ScriptDependencyResolver(GetTestKotlinScriptDependencies::class)
abstract class ScriptWithClassParam(param: TestParamClass) abstract class ScriptWithClassParam(param: TestParamClass)
@ScriptFilePattern(".*\\.kts") @ScriptFilePattern(".*\\.kts")
@ScriptDependencyExtractor(GetTestKotlinScriptDependencies::class) @ScriptDependencyResolver(GetTestKotlinScriptDependencies::class)
abstract class ScriptWithBaseClass(num: Int, passthrough: Int) : TestDSLClassWithParam(passthrough) abstract class ScriptWithBaseClass(num: Int, passthrough: Int) : TestDSLClassWithParam(passthrough)
@Target(AnnotationTarget.FILE) @Target(AnnotationTarget.FILE)
@@ -23,27 +23,22 @@ import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.roots.* import com.intellij.openapi.roots.*
import com.intellij.openapi.roots.impl.libraries.LibraryEx import com.intellij.openapi.roots.impl.libraries.LibraryEx
import com.intellij.openapi.roots.libraries.Library 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.StandardFileSystems
import com.intellij.openapi.vfs.VfsUtilCore import com.intellij.openapi.vfs.VfsUtilCore
import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.psi.search.DelegatingGlobalSearchScope import com.intellij.psi.search.DelegatingGlobalSearchScope
import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.util.CachedValueProvider import com.intellij.psi.util.CachedValueProvider
import com.intellij.psi.util.CachedValuesManager import com.intellij.psi.util.CachedValuesManager
import com.intellij.util.SmartList import com.intellij.util.SmartList
import com.intellij.util.io.URLUtil import com.intellij.util.io.URLUtil
import org.jdom.Element
import org.jetbrains.kotlin.analyzer.ModuleInfo import org.jetbrains.kotlin.analyzer.ModuleInfo
import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.script.KotlinScriptDefinition import org.jetbrains.kotlin.script.KotlinScriptDefinition
import org.jetbrains.kotlin.script.KotlinScriptExtraImport
import org.jetbrains.kotlin.utils.alwaysNull import org.jetbrains.kotlin.utils.alwaysNull
import org.jetbrains.kotlin.utils.emptyOrSingletonList import org.jetbrains.kotlin.utils.emptyOrSingletonList
import java.io.File import java.io.File
import java.io.FileNotFoundException
import java.lang.reflect.Method import java.lang.reflect.Method
import java.util.* 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, internal data class ScriptModuleInfo(val project: Project, val module: Module?, val scriptFile: VirtualFile,
val scriptDefinition: KotlinScriptDefinition, val scriptDefinition: KotlinScriptDefinition) : IdeaModuleInfo {
val scriptExtraImports: List<KotlinScriptExtraImport>) : IdeaModuleInfo {
override val moduleOrigin: ModuleOrigin override val moduleOrigin: ModuleOrigin
get() = ModuleOrigin.OTHER get() = ModuleOrigin.OTHER
@@ -315,16 +309,17 @@ internal data class ScriptModuleInfo(val project: Project, val module: Module?,
private fun dependenciesRoots(): List<VirtualFile> { private fun dependenciesRoots(): List<VirtualFile> {
// TODO: find out whether it should be cashed (some changes listener should be implemented for the cached roots) // TODO: find out whether it should be cashed (some changes listener should be implemented for the cached roots)
val jarfs = StandardFileSystems.jar() val jarfs = StandardFileSystems.jar()
return (scriptDefinition.getScriptDependenciesClasspath() + scriptExtraImports.flatMap { it.classpath }) return scriptDefinition.getDependenciesFor(scriptFile, project)?.classpath
.map { File(it).canonicalFile } ?.map { File(it).canonicalFile }
.distinct() ?.distinct()
.mapNotNull { ?.mapNotNull {
// TODO: ensure that the entries are checked elsewhere, so diagnostics is delivered to a user if files are not correctly specified // TODO: ensure that the entries are checked elsewhere, so diagnostics is delivered to a user if files are not correctly specified
if (it.isFile) if (it.isFile)
jarfs.findFileByPath(it.absolutePath + URLUtil.JAR_SEPARATOR) ?: null // diag: Classpath entry points to a file that is not a JAR archive jarfs.findFileByPath(it.absolutePath + URLUtil.JAR_SEPARATOR) ?: null // diag: Classpath entry points to a file that is not a JAR archive
else else
StandardFileSystems.local().findFileByPath(it.absolutePath) ?: null // diag: Classpath entry points to a non-existent location StandardFileSystems.local().findFileByPath(it.absolutePath) ?: null // diag: Classpath entry points to a non-existent location
} }
?: emptyList()
} }
override fun dependencies() = override fun dependencies() =
@@ -30,7 +30,6 @@ import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import org.jetbrains.kotlin.script.getScriptDefinition import org.jetbrains.kotlin.script.getScriptDefinition
import org.jetbrains.kotlin.script.getScriptExtraImports
import org.jetbrains.kotlin.utils.sure import org.jetbrains.kotlin.utils.sure
fun PsiElement.getModuleInfo(): IdeaModuleInfo = this.getModuleInfo { reason -> fun PsiElement.getModuleInfo(): IdeaModuleInfo = this.getModuleInfo { reason ->
@@ -125,7 +124,7 @@ private fun getModuleInfoByVirtualFile(project: Project, virtualFile: VirtualFil
val scriptDefinition = getScriptDefinition(virtualFile, project) val scriptDefinition = getScriptDefinition(virtualFile, project)
if (scriptDefinition != null) if (scriptDefinition != null)
return ScriptModuleInfo(project, module, virtualFile, scriptDefinition, getScriptExtraImports(virtualFile, project)) return ScriptModuleInfo(project, module, virtualFile, scriptDefinition)
return NotUnderContentRootModuleInfo return NotUnderContentRootModuleInfo
} }
@@ -22,7 +22,7 @@ import org.jetbrains.kotlin.builtins.DefaultBuiltIns
import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.decompiler.KtDecompiledFile import org.jetbrains.kotlin.idea.decompiler.KtDecompiledFile
import org.jetbrains.kotlin.idea.decompiler.textBuilder.DecompiledTextIndexer 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.KotlinFullClassNameIndex
import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope
import org.jetbrains.kotlin.idea.stubindex.KotlinTopLevelFunctionFqnNameIndex import org.jetbrains.kotlin.idea.stubindex.KotlinTopLevelFunctionFqnNameIndex
@@ -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<String, List<String>> 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<VFileEvent>) {
val changedExtraImportConfigs = ArrayList<VirtualFile>()
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<VirtualFile>? = null
private val cacheLock = java.util.concurrent.locks.ReentrantReadWriteLock()
fun getScriptClasspath(file: VirtualFile): List<VirtualFile> =
scriptExtraImportsProvider
?.getExtraImports(file)
?.flatMap { it.classpath }
?.map { it.classpathEntryToVfs() }
?: emptyList()
fun getAllScriptsClasspath(): List<VirtualFile> = 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<R> VirtualFile.vfsWalkFiles(onFile: (VirtualFile) -> List<R>?): List<R> {
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<VirtualFile> =
KotlinScriptConfigurationManager.getInstance(project).getAllScriptsClasspath().toSet()
override fun getAdditionalRootsToIndex(): Set<VirtualFile> = emptySet()
}
@@ -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<String, List<String>> 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<VirtualFile>? = null
private val cacheLock = ReentrantReadWriteLock()
fun getScriptClasspath(file: VirtualFile): List<VirtualFile> =
scriptExternalImportsProvider
?.getExternalImports(file)
?.flatMap { it.classpath }
?.map { it.classpathEntryToVfs() }
?: emptyList()
fun getAllScriptsClasspath(): List<VirtualFile> = 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<VirtualFile> =
KotlinScriptConfigurationManager.getInstance(project).getAllScriptsClasspath().toSet()
override fun getAdditionalRootsToIndex(): Set<VirtualFile> = emptySet()
}
@@ -14,7 +14,7 @@
* limitations under the License. * 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.project.Project
import com.intellij.openapi.roots.ProjectFileIndex 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.psi.search.GlobalSearchScope
import com.intellij.util.containers.ConcurrentFactoryMap import com.intellij.util.containers.ConcurrentFactoryMap
import org.jetbrains.kotlin.idea.caches.resolve.ScriptModuleSearchScope 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.load.java.JavaClassFinderImpl
import org.jetbrains.kotlin.resolve.jvm.KotlinSafeClassFinder import org.jetbrains.kotlin.resolve.jvm.KotlinSafeClassFinder
class KotlinScriptDependenciesClassFinder(project: Project, class KotlinScriptDependenciesClassFinder(project: Project,
private val kotlinScriptConfigurationManager: KotlinScriptConfigurationManager private val kotlinScriptConfigurationManager: KotlinScriptConfigurationManager
) : NonClasspathClassFinder(project), KotlinSafeClassFinder { ) : NonClasspathClassFinder(project), KotlinSafeClassFinder {
private val myCaches = object : ConcurrentFactoryMap<VirtualFile, PackageDirectoryCache>() { private val myCaches = object : ConcurrentFactoryMap<VirtualFile, PackageDirectoryCache>() {
override fun create(file: VirtualFile): PackageDirectoryCache? { override fun create(file: VirtualFile): PackageDirectoryCache? {
val scriptClasspath = kotlinScriptConfigurationManager.getScriptClasspath(file) val scriptClasspath = kotlinScriptConfigurationManager.getScriptClasspath(file)
val v = NonClasspathClassFinder.createCache(scriptClasspath) val v = createCache(scriptClasspath)
return v return v
} }
} }
+7 -7
View File
@@ -40,9 +40,6 @@
<component> <component>
<implementation-class>org.jetbrains.kotlin.asJava.KotlinCodeBlockModificationListener</implementation-class> <implementation-class>org.jetbrains.kotlin.asJava.KotlinCodeBlockModificationListener</implementation-class>
</component> </component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.script.KotlinScriptConfigurationManager</implementation-class>
</component>
</project-components> </project-components>
<application-components> <application-components>
@@ -250,8 +247,11 @@
<projectService serviceInterface="org.jetbrains.kotlin.script.KotlinScriptDefinitionProvider" <projectService serviceInterface="org.jetbrains.kotlin.script.KotlinScriptDefinitionProvider"
serviceImplementation="org.jetbrains.kotlin.script.KotlinScriptDefinitionProvider"/> serviceImplementation="org.jetbrains.kotlin.script.KotlinScriptDefinitionProvider"/>
<projectService serviceInterface="org.jetbrains.kotlin.script.KotlinScriptExtraImportsProvider" <projectService serviceInterface="org.jetbrains.kotlin.script.KotlinScriptExternalImportsProvider"
serviceImplementation="org.jetbrains.kotlin.script.KotlinScriptExtraImportsProvider"/> serviceImplementation="org.jetbrains.kotlin.script.KotlinScriptExternalImportsProvider"/>
<projectService serviceInterface="org.jetbrains.kotlin.idea.core.script.KotlinScriptConfigurationManager"
serviceImplementation="org.jetbrains.kotlin.idea.core.script.KotlinScriptConfigurationManager"/>
<projectService serviceInterface="org.jetbrains.kotlin.idea.compiler.configuration.KotlinCommonCompilerArgumentsHolder" <projectService serviceInterface="org.jetbrains.kotlin.idea.compiler.configuration.KotlinCommonCompilerArgumentsHolder"
serviceImplementation="org.jetbrains.kotlin.idea.compiler.configuration.KotlinCommonCompilerArgumentsHolder"/> serviceImplementation="org.jetbrains.kotlin.idea.compiler.configuration.KotlinCommonCompilerArgumentsHolder"/>
@@ -293,7 +293,7 @@
<projectService serviceInterface="org.jetbrains.kotlin.console.KotlinConsoleKeeper" <projectService serviceInterface="org.jetbrains.kotlin.console.KotlinConsoleKeeper"
serviceImplementation="org.jetbrains.kotlin.console.KotlinConsoleKeeper"/> serviceImplementation="org.jetbrains.kotlin.console.KotlinConsoleKeeper"/>
<indexedRootsProvider implementation="org.jetbrains.kotlin.idea.script.KotlinScriptDependenciesIndexableSetContributor"/> <indexedRootsProvider implementation="org.jetbrains.kotlin.idea.core.script.KotlinScriptDependenciesIndexableSetContributor"/>
<errorHandler implementation="org.jetbrains.kotlin.idea.reporter.KotlinReportSubmitter"/> <errorHandler implementation="org.jetbrains.kotlin.idea.reporter.KotlinReportSubmitter"/>
@@ -575,7 +575,7 @@
implementationClass="org.jetbrains.kotlin.idea.hierarchy.overrides.KotlinOverrideHierarchyProvider" /> implementationClass="org.jetbrains.kotlin.idea.hierarchy.overrides.KotlinOverrideHierarchyProvider" />
<java.elementFinder implementation="org.jetbrains.kotlin.asJava.JavaElementFinder"/> <java.elementFinder implementation="org.jetbrains.kotlin.asJava.JavaElementFinder"/>
<java.elementFinder implementation="org.jetbrains.kotlin.idea.script.KotlinScriptDependenciesClassFinder"/> <java.elementFinder implementation="org.jetbrains.kotlin.idea.core.script.KotlinScriptDependenciesClassFinder"/>
<java.shortNamesCache implementation="org.jetbrains.kotlin.idea.caches.KotlinShortNamesCache"/> <java.shortNamesCache implementation="org.jetbrains.kotlin.idea.caches.KotlinShortNamesCache"/>
<stubElementTypeHolder class="org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes"/> <stubElementTypeHolder class="org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes"/>