Implement a compiler config key to add preconfigured extra imports for scripts
This commit is contained in:
@@ -91,6 +91,7 @@ import org.jetbrains.kotlin.script.*
|
||||
import org.jetbrains.kotlin.utils.PathUtil
|
||||
import org.jetbrains.kotlin.utils.ifEmpty
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.util.*
|
||||
|
||||
class KotlinCoreEnvironment private constructor(
|
||||
@@ -149,6 +150,12 @@ class KotlinCoreEnvironment private constructor(
|
||||
})
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
configuration.addJvmClasspathRoots(
|
||||
getCombinedClasspathFor(sourceFiles.mapNotNull { src -> src.virtualFile })
|
||||
.map { File(it).canonicalFile }
|
||||
|
||||
@@ -19,9 +19,11 @@ 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() {
|
||||
@@ -41,6 +43,9 @@ 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 =
|
||||
|
||||
@@ -62,7 +62,18 @@ public class CompilerConfiguration {
|
||||
return data == null ? Collections.<T>emptyList() : data;
|
||||
}
|
||||
|
||||
public <T> void put(@NotNull CompilerConfigurationKey<T> key, @NotNull T value) {
|
||||
@NotNull
|
||||
public <K,V> Map<K,V> getMap(@NotNull CompilerConfigurationKey<Map<K,V>> key) {
|
||||
Map<K,V> data = (Map<K,V>) map.get(key.ideaKey);
|
||||
if (data == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
else {
|
||||
return Collections.unmodifiableMap(data);
|
||||
}
|
||||
}
|
||||
|
||||
public <T> void put(@NotNull CompilerConfigurationKey<T> key, @Nullable T value) {
|
||||
checkReadOnly();
|
||||
map.put(key.ideaKey, value);
|
||||
}
|
||||
@@ -77,6 +88,16 @@ public class CompilerConfiguration {
|
||||
list.add(value);
|
||||
}
|
||||
|
||||
public <K, V> void put(@NotNull CompilerConfigurationKey<Map<K,V>> key, @NotNull K vkey, @NotNull V value) {
|
||||
checkReadOnly();
|
||||
Key<Map<K,V>> ideaKey = key.ideaKey;
|
||||
if (map.get(ideaKey) == null) {
|
||||
map.put(ideaKey, new HashMap<K,V>());
|
||||
}
|
||||
Map<K,V> data = (Map<K,V>) map.get(ideaKey);
|
||||
data.put(vkey, value);
|
||||
}
|
||||
|
||||
public <T> void addAll(@NotNull CompilerConfigurationKey<List<T>> key, @NotNull Collection<T> values) {
|
||||
checkReadOnly();
|
||||
checkForNullElements(values);
|
||||
|
||||
+25
-10
@@ -25,11 +25,14 @@ 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 cache = hashMapOf<VirtualFile, List<KotlinScriptExtraImport>>()
|
||||
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<VirtualFile>) -> Unit>()
|
||||
private val notificationHandlers = ArrayList<(Iterable<String>) -> Unit>()
|
||||
private val handlersLock = java.util.concurrent.locks.ReentrantReadWriteLock()
|
||||
|
||||
init {
|
||||
@@ -42,19 +45,20 @@ class KotlinScriptExtraImportsProvider(val project: Project, private val scriptD
|
||||
fun getExtraImports(vararg files: VirtualFile): List<KotlinScriptExtraImport> = getExtraImports(files.asIterable())
|
||||
|
||||
fun getExtraImports(files: Iterable<VirtualFile>): List<KotlinScriptExtraImport> {
|
||||
val newCashedFiles = ArrayList<VirtualFile>()
|
||||
val newCashedFiles = ArrayList<String>()
|
||||
val res = cacheLock.read {
|
||||
files.flatMap { file ->
|
||||
if (file.isValid && !file.isDirectory) {
|
||||
cache[file]
|
||||
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, this) }
|
||||
newCashedFiles.add(file)
|
||||
cacheLock.write { cache.put(file.path, this) }
|
||||
newCashedFiles.add(file.path)
|
||||
}
|
||||
}
|
||||
?: emptyList()
|
||||
@@ -66,10 +70,21 @@ class KotlinScriptExtraImportsProvider(val project: Project, private val scriptD
|
||||
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))?.let { file ->
|
||||
importsFiles.mapNotNull { it.parent.findFileByRelativePath(it.name.removeSuffix(IMPORTS_FILE_EXTENSION))?.path?.let { file ->
|
||||
cacheLock.write {
|
||||
cache.remove(it)?.let { file }
|
||||
cache.remove(it.path)?.let { file }
|
||||
}
|
||||
} }.let {
|
||||
notifyIfAny(it)
|
||||
@@ -86,7 +101,7 @@ class KotlinScriptExtraImportsProvider(val project: Project, private val scriptD
|
||||
}
|
||||
}
|
||||
|
||||
private fun notifyIfAny(files: Iterable<VirtualFile>) {
|
||||
private fun notifyIfAny(files: Iterable<String>) {
|
||||
if (files.any()) {
|
||||
handlersLock.read {
|
||||
notificationHandlers.forEach { it(files) }
|
||||
@@ -103,7 +118,7 @@ class KotlinScriptExtraImportsProvider(val project: Project, private val scriptD
|
||||
.flatMap { it.classpath }
|
||||
.distinct()
|
||||
|
||||
fun subscribeOnExtraImportsChanged(handler: (Iterable<VirtualFile>) -> Unit): Unit {
|
||||
fun subscribeOnExtraImportsChanged(handler: (Iterable<String>) -> Unit): Unit {
|
||||
handlersLock.write { notificationHandlers.add(handler) }
|
||||
}
|
||||
|
||||
|
||||
@@ -75,6 +75,11 @@ class StandardWithClasspathScriptDefinition(extension: String, classpath: List<S
|
||||
StandardScriptDefinition.getScriptParameters(scriptDescriptor)
|
||||
}
|
||||
|
||||
class SimpleScriptExtraImport(
|
||||
override val classpath: List<String>,
|
||||
override val names: List<String> = emptyList()
|
||||
) : KotlinScriptExtraImport
|
||||
|
||||
fun classpathFromProperty(): List<String> =
|
||||
System.getProperty("java.class.path")?.let {
|
||||
it.split(String.format("\\%s", File.pathSeparatorChar).toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
|
||||
@@ -30,6 +30,7 @@ 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
|
||||
@@ -44,14 +45,14 @@ import java.lang.reflect.InvocationTargetException
|
||||
class ScriptTest {
|
||||
@Test
|
||||
fun testScriptWithParam() {
|
||||
val aClass = compileScript("fib.kts", SimpleParamsTestScriptDefinition(".kts", numIntParam()))
|
||||
val aClass = compileScript("fib.kts", SimpleParamsTestScriptDefinition(".kts", numIntParam()), null)
|
||||
Assert.assertNotNull(aClass)
|
||||
aClass!!.getConstructor(Integer.TYPE).newInstance(4)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStandardScriptWithParams() {
|
||||
val aClass = compileScript("fib_std.kts", StandardScriptDefinition)
|
||||
val aClass = compileScript("fib_std.kts", StandardScriptDefinition, null)
|
||||
Assert.assertNotNull(aClass)
|
||||
val anObj = KotlinToJVMBytecodeCompiler.tryConstructClassPub(aClass!!, listOf("4", "comment"))
|
||||
Assert.assertNotNull(anObj)
|
||||
@@ -59,7 +60,7 @@ class ScriptTest {
|
||||
|
||||
@Test
|
||||
fun testStandardScriptWithoutParams() {
|
||||
val aClass = compileScript("fib_std.kts", StandardScriptDefinition)
|
||||
val aClass = compileScript("fib_std.kts", StandardScriptDefinition, null)
|
||||
Assert.assertNotNull(aClass)
|
||||
val anObj = KotlinToJVMBytecodeCompiler.tryConstructClassPub(aClass!!, emptyList())
|
||||
Assert.assertNotNull(anObj)
|
||||
@@ -67,7 +68,7 @@ class ScriptTest {
|
||||
|
||||
@Test
|
||||
fun testScriptWithParamConversion() {
|
||||
val aClass = compileScript("fib.kts", SimpleParamsTestScriptDefinition(".kts", numIntParam()))
|
||||
val aClass = compileScript("fib.kts", SimpleParamsTestScriptDefinition(".kts", numIntParam()), null)
|
||||
Assert.assertNotNull(aClass)
|
||||
val anObj = KotlinToJVMBytecodeCompiler.tryConstructClassPub(aClass!!, listOf("4"))
|
||||
Assert.assertNotNull(anObj)
|
||||
@@ -75,55 +76,67 @@ class ScriptTest {
|
||||
|
||||
@Test
|
||||
fun testScriptWithPackage() {
|
||||
val aClass = compileScript("fib.pkg.kts", SimpleParamsTestScriptDefinition(".kts", numIntParam()))
|
||||
val aClass = compileScript("fib.pkg.kts", SimpleParamsTestScriptDefinition(".kts", numIntParam()), null)
|
||||
Assert.assertNotNull(aClass)
|
||||
aClass!!.getConstructor(Integer.TYPE).newInstance(4)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testScriptWithScriptDefinition() {
|
||||
val aClass = compileScript("fib.fib.kt", SimpleParamsTestScriptDefinition(".fib.kt", numIntParam()))
|
||||
val aClass = compileScript("fib.fib.kt", SimpleParamsTestScriptDefinition(".fib.kt", numIntParam()), null)
|
||||
Assert.assertNotNull(aClass)
|
||||
aClass!!.getConstructor(Integer.TYPE).newInstance(4)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testScriptWithClassParameter() {
|
||||
val aClass = compileScript("fib_cp.kts", ReflectedParamClassTestScriptDefinition(".kts", "param", TestParamClass::class), runIsolated = false)
|
||||
val aClass = compileScript("fib_cp.kts", ReflectedParamClassTestScriptDefinition(".kts", "param", TestParamClass::class), null, 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), runIsolated = false)
|
||||
val aClass = compileScript("fib_dsl.kts", ReflectedSuperclassTestScriptDefinition(".kts", numIntParam(), TestDSLClass::class), null, 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")), runIsolated = false)
|
||||
val aClass = compileScript("fib_dsl.kts", ReflectedSuperclassWithParamsTestScriptDefinition(".kts", numIntParam() + numIntParam("passthrough"), TestDSLClassWithParam::class, numIntParam("passthrough")), null, 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), runIsolated = false)
|
||||
val aClass = compileScript("fib_dsl.kts", ReflectedSuperclassTestScriptDefinition(".kts", numIntParam(), TestDSLInterface::class), null, 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()), runIsolated = true, suppressOutput = true)
|
||||
val aClass1 = compileScript("fib_ext.kts", SimpleParamsWithClasspathTestScriptDefinition(".kts", numIntParam(), classpath = emptyList()), null, 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), runIsolated = true)
|
||||
val aClass2 = compileScript("fib_ext.kts", SimpleParamsWithClasspathTestScriptDefinition(".kts", numIntParam(), classpath = cp), null, 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)
|
||||
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)
|
||||
Assert.assertNotNull(aClass2)
|
||||
}
|
||||
|
||||
@@ -150,20 +163,22 @@ class ScriptTest {
|
||||
private fun compileScript(
|
||||
scriptPath: String,
|
||||
scriptDefinition: KotlinScriptDefinition,
|
||||
extraImport: KotlinScriptExtraImport?,
|
||||
runIsolated: Boolean = true,
|
||||
suppressOutput: Boolean = false): Class<*>? =
|
||||
compileScriptImpl("compiler/testData/script/" + scriptPath, scriptDefinition, runIsolated, suppressOutput)
|
||||
compileScriptImpl("compiler/testData/script/" + scriptPath, scriptDefinition, extraImport, runIsolated, suppressOutput)
|
||||
|
||||
private fun compileSmokeTestScript(
|
||||
scriptPath: String,
|
||||
scriptDefinition: KotlinScriptDefinition,
|
||||
runIsolated: Boolean = true,
|
||||
suppressOutput: Boolean = false): Class<*>? =
|
||||
compileScriptImpl("compiler/testData/integration/smoke/" + scriptPath, scriptDefinition, runIsolated, suppressOutput)
|
||||
compileScriptImpl("compiler/testData/integration/smoke/" + scriptPath, scriptDefinition, null, runIsolated, suppressOutput)
|
||||
|
||||
private fun compileScriptImpl(
|
||||
scriptPath: String,
|
||||
scriptDefinition: KotlinScriptDefinition,
|
||||
extraImport: KotlinScriptExtraImport?,
|
||||
runIsolated: Boolean,
|
||||
suppressOutput: Boolean): Class<*>?
|
||||
{
|
||||
@@ -178,6 +193,9 @@ 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)
|
||||
|
||||
Reference in New Issue
Block a user