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