Implement a compiler config key to add preconfigured extra imports for scripts

This commit is contained in:
Ilya Chernikov
2016-05-30 14:33:56 +02:00
parent 956bcf6d7a
commit 478ae96e50
6 changed files with 96 additions and 25 deletions
@@ -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,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)