diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt index 5f6f84c7eda..73d707b142d 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt @@ -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 } diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/config/JVMConfigurationKeys.java b/compiler/frontend.java/src/org/jetbrains/kotlin/config/JVMConfigurationKeys.java index 952b7a681ae..6cd5d129f8d 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/config/JVMConfigurationKeys.java +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/config/JVMConfigurationKeys.java @@ -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> SCRIPT_DEFINITIONS = CompilerConfigurationKey.create("script definitions"); + public static final CompilerConfigurationKey> SCRIPTS_EXTRA_IMPORTS = + CompilerConfigurationKey.create("scripts extra imports"); + public static final CompilerConfigurationKey DISABLE_CALL_ASSERTIONS = CompilerConfigurationKey.create("disable not-null call assertions"); public static final CompilerConfigurationKey DISABLE_PARAM_ASSERTIONS = diff --git a/compiler/frontend/src/org/jetbrains/kotlin/config/CompilerConfiguration.java b/compiler/frontend/src/org/jetbrains/kotlin/config/CompilerConfiguration.java index f841f093e3b..eba98fe872a 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/config/CompilerConfiguration.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/config/CompilerConfiguration.java @@ -62,7 +62,18 @@ public class CompilerConfiguration { return data == null ? Collections.emptyList() : data; } - public void put(@NotNull CompilerConfigurationKey key, @NotNull T value) { + @NotNull + public Map getMap(@NotNull CompilerConfigurationKey> key) { + Map data = (Map) map.get(key.ideaKey); + if (data == null) { + return Collections.emptyMap(); + } + else { + return Collections.unmodifiableMap(data); + } + } + + public void put(@NotNull CompilerConfigurationKey key, @Nullable T value) { checkReadOnly(); map.put(key.ideaKey, value); } @@ -77,6 +88,16 @@ public class CompilerConfiguration { list.add(value); } + public void put(@NotNull CompilerConfigurationKey> key, @NotNull K vkey, @NotNull V value) { + checkReadOnly(); + Key> ideaKey = key.ideaKey; + if (map.get(ideaKey) == null) { + map.put(ideaKey, new HashMap()); + } + Map data = (Map) map.get(ideaKey); + data.put(vkey, value); + } + public void addAll(@NotNull CompilerConfigurationKey> key, @NotNull Collection values) { checkReadOnly(); checkForNullElements(values); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinScriptExtraImportsProvider.kt b/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinScriptExtraImportsProvider.kt index 4da1e531545..4aa5a520293 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinScriptExtraImportsProvider.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinScriptExtraImportsProvider.kt @@ -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>() + private val preconfigured = hashMapOf>() + private val cache = hashMapOf>() private val envVars: Map> by lazy { generateKotlinScriptClasspathEnvVars(project) } - private val notificationHandlers = ArrayList<(Iterable) -> Unit>() + private val notificationHandlers = ArrayList<(Iterable) -> 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 = getExtraImports(files.asIterable()) fun getExtraImports(files: Iterable): List { - val newCashedFiles = ArrayList() + val newCashedFiles = ArrayList() 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?) { + cacheLock.write { + if (extraImports != null && extraImports.isNotEmpty()) { + preconfigured[file.path] = extraImports + } + else { + preconfigured.remove(file.path) + } + } + } + fun invalidateExtraImportsByImportsFiles(importsFiles: Iterable) { - 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) { + private fun notifyIfAny(files: Iterable) { 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) -> Unit): Unit { + fun subscribeOnExtraImportsChanged(handler: (Iterable) -> Unit): Unit { handlersLock.write { notificationHandlers.add(handler) } } diff --git a/compiler/tests-common/org/jetbrains/kotlin/scripts/TestScriptDefinitions.kt b/compiler/tests-common/org/jetbrains/kotlin/scripts/TestScriptDefinitions.kt index d7a5d8da816..a1b6f14e9a3 100644 --- a/compiler/tests-common/org/jetbrains/kotlin/scripts/TestScriptDefinitions.kt +++ b/compiler/tests-common/org/jetbrains/kotlin/scripts/TestScriptDefinitions.kt @@ -75,6 +75,11 @@ class StandardWithClasspathScriptDefinition(extension: String, classpath: List, + override val names: List = emptyList() +) : KotlinScriptExtraImport + fun classpathFromProperty(): List = System.getProperty("java.class.path")?.let { it.split(String.format("\\%s", File.pathSeparatorChar).toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() diff --git a/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTest.kt b/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTest.kt index 97865e240e7..6ed4f13847f 100644 --- a/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTest.kt +++ b/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTest.kt @@ -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)