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()
})
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))
}
@@ -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<List<KotlinScriptDefinition>> 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 =
CompilerConfigurationKey.create("disable not-null call 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.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
@@ -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<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
// TODO: fix and tests
// 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
else this.flatMap { cpentry ->
varsMap.entries.fold(listOf(cpentry)) { p, v ->
@@ -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 <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 getScriptSupertypes(scriptDescriptor: ScriptDescriptor): List<KotlinType> = emptyList()
fun getScriptParametersToPassToSuperclass(scriptDescriptor: ScriptDescriptor): List<Name> = emptyList()
fun <TF> isScript(file: TF): Boolean
fun getScriptName(script: KtScript): Name
fun getScriptDependenciesClasspath(): List<String> = emptyList()
fun getScriptName(script: KtScript): Name =
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)
fun <TF> getFileExtension(file: TF) = PathUtil.getFileExtension(getFileName(file))
fun <TF> getFileName(file: TF): String = when (file) {
is PsiFile -> file.originalFile.name
is VirtualFile -> file.name
@@ -56,17 +78,16 @@ fun <TF> getFileName(file: TF): String = when (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 {
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
// this is not supposed to be final design
override fun getScriptParameters(scriptDescriptor: ScriptDescriptor): List<ScriptParameter> =
@@ -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<KotlinScriptDefinition> = 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<KotlinScriptDefinition>): Unit {
fun setScriptDefinitions(newDefinitions: List<KotlinScriptDefinition>): 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<TF> findScriptDefinition(file: TF): KotlinScriptDefinition? = definitionsLock.read {
@@ -55,10 +50,6 @@ class KotlinScriptDefinitionProvider {
fun<TF> 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<LanguageFileType> = definitionsLock.read {
definitions.map { it.fileType }.distinct()
}
companion object {
@JvmStatic
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? =
KotlinScriptDefinitionProvider.getInstance(psiFile.project).findScriptDefinition(psiFile)
fun getScriptExtraImports(file: VirtualFile, project: Project): List<KotlinScriptExtraImport> =
KotlinScriptExtraImportsProvider.getInstance(project)?.getExtraImports(file) ?: emptyList()
fun getScriptExternalDependencies(file: VirtualFile, project: Project): List<KotlinScriptExternalDependencies> =
KotlinScriptExternalImportsProvider.getInstance(project)?.getExternalImports(file) ?: emptyList()
fun getScriptExtraImports(psiFile: PsiFile): List<KotlinScriptExtraImport> =
fun getScriptExternalDependencies(psiFile: PsiFile): List<KotlinScriptExternalDependencies> =
psiFile.virtualFile?.let { file ->
KotlinScriptExtraImportsProvider.getInstance(psiFile.project)?.getExtraImports(file)
KotlinScriptExternalImportsProvider.getInstance(psiFile.project)?.getExternalImports(file)
} ?: emptyList()
@@ -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<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
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<String>
val implicitImports: List<String>
}
interface GetScriptDependencies {
operator fun invoke(annotations: Iterable<KtAnnotationEntry>, context: Any?): ScriptDependencies? = null
operator fun invoke(context: Any?): ScriptDependencies? = null
operator fun invoke(annotations: Iterable<KtAnnotationEntry>, context: Any?): KotlinScriptExternalDependencies? = null
operator fun invoke(context: Any?): KotlinScriptExternalDependencies? = null
}
@Target(AnnotationTarget.CLASS)
@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 {
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)
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) }
}
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
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<String>? = null) : KotlinScriptDefinition {
abstract class BaseScriptDefinition (val extension: String, val cp: List<String>? = null) : KotlinScriptDefinition {
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 getScriptDependenciesClasspath(): List<String> =
classpath ?: (classpathFromProperty() + classpathFromClassloader(BaseScriptDefinition::class.java.classLoader)).distinct()
override fun <TF> 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<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)
{
override fun getScriptParameters(scriptDescriptor: ScriptDescriptor) = parameters
@@ -74,10 +77,10 @@ class StandardWithClasspathScriptDefinition(extension: String, classpath: List<S
StandardScriptDefinition.getScriptParameters(scriptDescriptor)
}
class SimpleScriptExtraImport(
class SimpleScriptExtraDependencies(
override val classpath: List<String>,
override val names: List<String> = emptyList()
) : KotlinScriptExtraImport
override val imports: List<String> = emptyList()
) : KotlinScriptExternalDependencies
fun classpathFromProperty(): List<String> =
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.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)
@@ -118,7 +118,7 @@ class GetTestKotlinScriptDependencies : GetScriptDependencies {
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
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<String>()
}
}
@@ -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<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")
@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)
@@ -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<KotlinScriptExtraImport>) : 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<VirtualFile> {
// 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() =
@@ -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
}
@@ -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
@@ -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.
*/
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<VirtualFile, PackageDirectoryCache>() {
override fun create(file: VirtualFile): PackageDirectoryCache? {
val scriptClasspath = kotlinScriptConfigurationManager.getScriptClasspath(file)
val v = NonClasspathClassFinder.createCache(scriptClasspath)
val v = createCache(scriptClasspath)
return v
}
}
+7 -7
View File
@@ -40,9 +40,6 @@
<component>
<implementation-class>org.jetbrains.kotlin.asJava.KotlinCodeBlockModificationListener</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.script.KotlinScriptConfigurationManager</implementation-class>
</component>
</project-components>
<application-components>
@@ -250,8 +247,11 @@
<projectService serviceInterface="org.jetbrains.kotlin.script.KotlinScriptDefinitionProvider"
serviceImplementation="org.jetbrains.kotlin.script.KotlinScriptDefinitionProvider"/>
<projectService serviceInterface="org.jetbrains.kotlin.script.KotlinScriptExtraImportsProvider"
serviceImplementation="org.jetbrains.kotlin.script.KotlinScriptExtraImportsProvider"/>
<projectService serviceInterface="org.jetbrains.kotlin.script.KotlinScriptExternalImportsProvider"
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"
serviceImplementation="org.jetbrains.kotlin.idea.compiler.configuration.KotlinCommonCompilerArgumentsHolder"/>
@@ -293,7 +293,7 @@
<projectService serviceInterface="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"/>
@@ -575,7 +575,7 @@
implementationClass="org.jetbrains.kotlin.idea.hierarchy.overrides.KotlinOverrideHierarchyProvider" />
<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"/>
<stubElementTypeHolder class="org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes"/>