diff --git a/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinConfigurableScriptDefinition.kt b/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinConfigurableScriptDefinition.kt index 00190d8347b..e2d60229c3f 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinConfigurableScriptDefinition.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinConfigurableScriptDefinition.kt @@ -26,6 +26,7 @@ import org.jetbrains.kotlin.utils.KotlinPaths import org.jetbrains.kotlin.utils.KotlinPathsFromHomeDir import org.jetbrains.kotlin.utils.PathUtil import java.io.File +import java.util.concurrent.Future data class KotlinConfigurableScriptDefinition(val config: KotlinScriptConfig, val environmentVars: Map>?) : KotlinScriptDefinition { @@ -46,24 +47,25 @@ data class KotlinConfigurableScriptDefinition(val config: KotlinScriptConfig, va private val evaluatedClasspath by lazy { config.classpath.evalWithVars(environmentVars).map { File(it) }.distinctBy { it.canonicalPath } } - override fun getDependenciesFor(file: TF, project: Project, previousDependencies: KotlinScriptExternalDependencies?): KotlinScriptExternalDependencies? = - if (!isScript(file)) null - else { - val extDeps = getScriptDependenciesFromConfig(file) - when { - extDeps != null -> - object : KotlinScriptExternalDependencies { - override val classpath: Iterable = evaluatedClasspath + extDeps.classpath - override val imports = extDeps.imports - override val sources: Iterable = extDeps.sources - } - !evaluatedClasspath.isEmpty() -> - object : KotlinScriptExternalDependencies { - override val classpath: Iterable = evaluatedClasspath - } - else -> null - } - } + override fun getDependenciesFor(file: TF, project: Project, previousDependencies: KotlinScriptExternalDependencies?): Future? = + makeNullableFakeFuture( + if (!isScript(file)) null + else { + val extDeps = getScriptDependenciesFromConfig(file) + when { + extDeps != null -> + object : KotlinScriptExternalDependencies { + override val classpath: Iterable = evaluatedClasspath + extDeps.classpath + override val imports = extDeps.imports + override val sources: Iterable = extDeps.sources + } + !evaluatedClasspath.isEmpty() -> + object : KotlinScriptExternalDependencies { + override val classpath: Iterable = evaluatedClasspath + } + else -> null + } + }) } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinScriptDefinition.kt b/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinScriptDefinition.kt index c921f582843..2fe4c0b6030 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinScriptDefinition.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinScriptDefinition.kt @@ -18,8 +18,6 @@ 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 org.jetbrains.kotlin.descriptors.ScriptDescriptor import org.jetbrains.kotlin.idea.KotlinFileType import org.jetbrains.kotlin.name.ClassId @@ -35,6 +33,8 @@ import org.jetbrains.kotlin.storage.LockBasedStorageManager import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.Variance import java.io.File +import java.util.concurrent.Future +import java.util.concurrent.TimeUnit import kotlin.reflect.KClass interface KotlinScriptDefinition { @@ -54,44 +54,25 @@ interface KotlinScriptDefinition { fun getScriptName(script: KtScript): Name = ScriptNameUtil.fileNameWithExtensionStripped(script, KotlinParserDefinition.STD_SCRIPT_EXT) - fun getDependenciesFor(file: TF, project: Project, previousDependencies: KotlinScriptExternalDependencies?): KotlinScriptExternalDependencies? = null + fun getDependenciesFor(file: TF, project: Project, previousDependencies: KotlinScriptExternalDependencies?): Future? = null } interface KotlinScriptExternalDependencies { val classpath: Iterable get() = emptyList() val imports: Iterable get() = emptyList() val sources: Iterable get() = emptyList() + val scripts: Iterable get() = emptyList() } class KotlinScriptExternalDependenciesUnion(val dependencies: Iterable) : KotlinScriptExternalDependencies { override val classpath: Iterable get() = dependencies.flatMap { it.classpath } override val imports: Iterable get() = dependencies.flatMap { it.imports } override val sources: Iterable get() = dependencies.flatMap { it.sources } + override val scripts: Iterable get() = dependencies.flatMap { it.scripts } } data class ScriptParameter(val name: Name, val type: KotlinType) -fun getFileName(file: TF): String = when (file) { - is PsiFile -> file.originalFile.name - is VirtualFile -> file.name - is File -> file.name - else -> throw IllegalArgumentException("Unsupported file type $file") -} - -fun 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") -} - -fun getFile(file: TF): File? = when (file) { - is PsiFile -> file.originalFile.run { File(virtualFile?.path) } - is VirtualFile -> File(file.path) - is File -> file - else -> throw IllegalArgumentException("Unsupported file type $file") -} - object StandardScriptDefinition : KotlinScriptDefinition { private val ARGS_NAME = Name.identifier("args") @@ -119,3 +100,14 @@ fun getKotlinTypeByFqName(scriptDescriptor: ScriptDescriptor, fqName: String): K ClassId.topLevel(FqName(fqName)), NotFoundClasses(LockBasedStorageManager.NO_LOCKS, scriptDescriptor.module) ).defaultType + +class FakeFuture(val value: T) : Future { + override fun isCancelled(): Boolean = false + override fun cancel(mayInterruptIfRunning: Boolean): Boolean = false + override fun get(): T = value + override fun get(timeout: Long, unit: TimeUnit): T = value + override fun isDone(): Boolean = true +} + +fun makeNullableFakeFuture(value: T?): Future? = + value?.let { FakeFuture(it) } ?: (null as Future?) diff --git a/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinScriptExternalImportsProvider.kt b/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinScriptExternalImportsProvider.kt index b3999d3e193..8d2697ea199 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinScriptExternalImportsProvider.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/script/KotlinScriptExternalImportsProvider.kt @@ -37,7 +37,7 @@ class KotlinScriptExternalImportsProvider(val project: Project, private val scri cache[path] ?: if (cacheOfNulls.contains(path)) null else scriptDefinitionProvider.findScriptDefinition(file) - ?.let { it.getDependenciesFor(file, project, null) } + ?.let { it.getDependenciesFor(file, project, null)?.get() } .apply { cacheLock.write { if (this == null) { cacheOfNulls.add(path) @@ -58,7 +58,7 @@ class KotlinScriptExternalImportsProvider(val project: Project, private val scri if (!cache.containsKey(path) && !cacheOfNulls.contains(path) && !uncached.contains(path)) { val scriptDef = scriptDefinitionProvider.findScriptDefinition(file) if (scriptDef != null) { - val deps = scriptDef.getDependenciesFor(file, project, null) + val deps = scriptDef.getDependenciesFor(file, project, null)?.get() if (deps != null) { cache.put(path, deps) } @@ -80,7 +80,7 @@ class KotlinScriptExternalImportsProvider(val project: Project, private val scri val scriptDef = scriptDefinitionProvider.findScriptDefinition(file) if (scriptDef != null) { val oldDeps = cache[path] - val deps = scriptDef.getDependenciesFor(file, project, oldDeps) + val deps = scriptDef.getDependenciesFor(file, project, oldDeps)?.get() when { deps != null && (oldDeps == null || !deps.classpath.isSamePathListAs(oldDeps.classpath) || !deps.sources.isSamePathListAs(oldDeps.sources)) -> { diff --git a/compiler/frontend/src/org/jetbrains/kotlin/script/scriptFileUtil.kt b/compiler/frontend/src/org/jetbrains/kotlin/script/scriptFileUtil.kt new file mode 100644 index 00000000000..b88d1f4a73a --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/script/scriptFileUtil.kt @@ -0,0 +1,58 @@ +/* + * 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.vfs.VirtualFile +import com.intellij.psi.PsiFile +import java.io.File +import java.io.InputStream + +fun getFileName(file: TF): String = when (file) { + is PsiFile -> file.originalFile.name + is VirtualFile -> file.name + is File -> file.name + else -> throw IllegalArgumentException("Unsupported file type $file") +} + +fun 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") +} + +fun getFile(file: TF): File? = when (file) { + is PsiFile -> file.originalFile.run { File(virtualFile?.path) } + is VirtualFile -> File(file.path) + is File -> file + else -> throw IllegalArgumentException("Unsupported file type $file") +} + +fun getFileContents(file: TF): CharSequence = when (file) { + is PsiFile -> file.viewProvider.contents + is VirtualFile -> file.inputStream.reader(charset = file.charset).readText() + is File -> file.readText() + else -> throw IllegalArgumentException("Unsupported file type $file") +} + +fun getFileContentsStream(file: TF): InputStream = when (file) { + is PsiFile -> file.viewProvider.contents.toString().byteInputStream() + is VirtualFile -> file.inputStream + is File -> file.inputStream() + else -> throw IllegalArgumentException("Unsupported file type $file") +} + diff --git a/compiler/frontend/src/org/jetbrains/kotlin/script/scriptTemplate.kt b/compiler/frontend/src/org/jetbrains/kotlin/script/scriptTemplate.kt index dd401ccd2cf..cf3f3d05516 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/script/scriptTemplate.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/script/scriptTemplate.kt @@ -30,15 +30,17 @@ import org.jetbrains.kotlin.psi.KtScript import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull import java.io.File +import java.io.InputStream import java.lang.reflect.InvocationHandler import java.lang.reflect.Method import java.lang.reflect.Proxy +import java.util.concurrent.Future import kotlin.reflect.* @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.RUNTIME) -annotation class ScriptTemplateDefinition(val resolver: KClass, - val scriptFilePattern: String) +annotation class ScriptTemplateDefinition(val resolver: KClass = BasicScriptDependenciesResolver::class, + val scriptFilePattern: String = "*.\\.kts") @Deprecated("Use ScriptTemplateDefinition") @Target(AnnotationTarget.CLASS) @@ -50,15 +52,22 @@ annotation class ScriptFilePattern(val pattern: String) @Retention(AnnotationRetention.RUNTIME) annotation class ScriptDependencyResolver(val resolver: KClass) -interface AnnotationBasedScriptDependenciesResolver { - fun resolve(scriptFile: File?, - annotations: Iterable, - environment: Map?, - previousDependencies: KotlinScriptExternalDependencies? = null - ): KotlinScriptExternalDependencies? = null +interface ScriptContents { + val file: File? + val annotations: Iterable + val contents: CharSequence? + val contentsStream: InputStream? } -@Deprecated("Use new resolver Ex") +// TODO: rename to just ScriptDependenciesResolver as soon as current deprecated one will be dropped +interface ScriptDependenciesResolverEx { + fun resolve(script: ScriptContents, + environment: Map?, + previousDependencies: KotlinScriptExternalDependencies? = null + ): Future? = null +} + +@Deprecated("Use new ScriptDependenciesResolverEx") interface ScriptDependenciesResolver { fun resolve(projectRoot: File?, scriptFile: File?, @@ -67,6 +76,8 @@ interface ScriptDependenciesResolver { ): KotlinScriptExternalDependencies? = null } +class BasicScriptDependenciesResolver : ScriptDependenciesResolverEx + @Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS) @Retention(AnnotationRetention.RUNTIME) annotation class AcceptedAnnotations(vararg val supportedAnnotationClasses: KClass) @@ -74,11 +85,11 @@ annotation class AcceptedAnnotations(vararg val supportedAnnotationClasses: KCla data class KotlinScriptDefinitionFromTemplate(val template: KClass, val environment: Map?) : KotlinScriptDefinition { // TODO: remove this and simplify definitionAnnotation as soon as deprecated annotations will be removed - internal class ScriptTemplateDefinitionData(val resolverClass: KClass, - val resolver: AnnotationBasedScriptDependenciesResolver?, + internal class ScriptTemplateDefinitionData(val resolverClass: KClass, + val resolver: ScriptDependenciesResolverEx?, val scriptFilePattern: String?) { - val acceptedAnnotations by lazy { - val resolveMethod = AnnotationBasedScriptDependenciesResolver::resolve + val acceptedAnnotations: List> by lazy { + val resolveMethod = ScriptDependenciesResolverEx::resolve val resolverMethodAnnotations = resolverClass.memberFunctions.find { it.name == resolveMethod.name && @@ -94,19 +105,18 @@ data class KotlinScriptDefinitionFromTemplate(val template: KClass, val } } - internal class ObsoleteResolverProxy(val resolverAnn: ScriptDependencyResolver?) : AnnotationBasedScriptDependenciesResolver { + internal class ObsoleteResolverProxy(val resolverAnn: ScriptDependencyResolver?) : ScriptDependenciesResolverEx { private val resolver by lazy { resolverAnn?.resolver?.primaryConstructor?.call() } - override fun resolve(scriptFile: File?, - annotations: Iterable, + override fun resolve(script: ScriptContents, environment: Map?, previousDependencies: KotlinScriptExternalDependencies? - ): KotlinScriptExternalDependencies? = + ): Future? = makeNullableFakeFuture( resolver?.resolve( environment?.get("projectRoot") as? File?, - scriptFile, + script.file, emptyList(), environment as Any? - ) + )) } private val definitionData by lazy { @@ -136,7 +146,7 @@ data class KotlinScriptDefinitionFromTemplate(val template: KClass, val // TODO: implement other strategy - e.g. try to extract something from match with ScriptFilePattern override fun getScriptName(script: KtScript): Name = ScriptNameUtil.fileNameWithExtensionStripped(script, KotlinParserDefinition.STD_SCRIPT_EXT) - override fun getDependenciesFor(file: TF, project: Project, previousDependencies: KotlinScriptExternalDependencies?): KotlinScriptExternalDependencies? { + override fun getDependenciesFor(file: TF, project: Project, previousDependencies: KotlinScriptExternalDependencies?): Future? { val fileAnnotations = getAnnotationEntries(file, project) .map { KtAnnotationWrapper(it) } .mapNotNull { wrappedAnn -> @@ -159,7 +169,7 @@ data class KotlinScriptDefinitionFromTemplate(val template: KClass, val val annFQN = annClassToWrapper.first.qualifiedName if (definitionData.acceptedAnnotations.any { it.qualifiedName == annFQN }) annClassToWrapper.second else null } - val fileDeps = definitionData.resolver?.resolve(getFile(file), supportedAnnotations, environment, previousDependencies) + val fileDeps = definitionData.resolver?.resolve(BasicScriptContents(file, supportedAnnotations), environment, previousDependencies) return fileDeps } @@ -183,6 +193,12 @@ data class KotlinScriptDefinitionFromTemplate(val template: KClass, val ?: throw java.lang.IllegalArgumentException("Unable to load PSI from ${file.canonicalPath}") return getAnnotationEntriesFromPsiFile(psiFile) } + + class BasicScriptContents(val myFile: TF, override val annotations: Iterable) : ScriptContents { + override val file: File? get() = getFile(myFile) + override val contents: CharSequence? get() = getFileContents(myFile) + override val contentsStream: InputStream? get() = getFileContentsStream(myFile) + } } class InvalidScriptResolverAnnotation(val name: String, val params: Iterable, val error: Exception? = null) : Annotation diff --git a/compiler/tests-common/org/jetbrains/kotlin/scripts/TestScriptDefinitions.kt b/compiler/tests-common/org/jetbrains/kotlin/scripts/TestScriptDefinitions.kt index 7d58a6399f4..270291fd0d3 100644 --- a/compiler/tests-common/org/jetbrains/kotlin/scripts/TestScriptDefinitions.kt +++ b/compiler/tests-common/org/jetbrains/kotlin/scripts/TestScriptDefinitions.kt @@ -25,16 +25,18 @@ import org.jetbrains.kotlin.types.KotlinType import java.io.File import java.net.URL import java.net.URLClassLoader +import java.util.concurrent.Future import kotlin.reflect.KClass abstract class BaseScriptDefinition (val extension: String, val cp: List? = null) : KotlinScriptDefinition { override val name = "Test Kotlin Script" override fun isScript(file: TF): Boolean = getFileName(file).endsWith(extension) override fun getScriptName(script: KtScript): Name = ScriptNameUtil.fileNameWithExtensionStripped(script, extension) - override fun getDependenciesFor(file: TF, project: Project, previousDependencies: KotlinScriptExternalDependencies?): KotlinScriptExternalDependencies? = - object : KotlinScriptExternalDependencies { - override val classpath: Iterable = cp ?: (classpathFromProperty() + classpathFromClassloader(BaseScriptDefinition::class.java.classLoader)).distinct() - } + override fun getDependenciesFor(file: TF, project: Project, previousDependencies: KotlinScriptExternalDependencies?): Future? = + makeNullableFakeFuture( + object : KotlinScriptExternalDependencies { + override val classpath: Iterable = cp ?: (classpathFromProperty() + classpathFromClassloader(BaseScriptDefinition::class.java.classLoader)).distinct() + }) } open class SimpleParamsWithClasspathTestScriptDefinition(extension: String, val parameters: List, classpath: List? = null, val extraDependencies: KotlinScriptExternalDependencies? = null) diff --git a/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTest2.kt b/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTest2.kt index b4438466626..2f08bb0b04c 100644 --- a/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTest2.kt +++ b/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTest2.kt @@ -34,6 +34,7 @@ import org.junit.Assert import org.junit.Test import java.io.File import java.net.URLClassLoader +import java.util.concurrent.Future import kotlin.reflect.KClass // TODO: the contetnts of this file should go into ScriptTest.kt and replace appropriate xml-based functionality, @@ -118,28 +119,27 @@ class ScriptTest2 { } } -class TestKotlinScriptDependenciesResolver : AnnotationBasedScriptDependenciesResolver { +class TestKotlinScriptDependenciesResolver : ScriptDependenciesResolverEx { private val kotlinPaths by lazy { PathUtil.getKotlinPathsForCompiler() } @AcceptedAnnotations(DependsOn::class) - override fun resolve(scriptFile: File?, - annotations: Iterable, + override fun resolve(script: ScriptContents, environment: Map?, previousDependencies: KotlinScriptExternalDependencies? - ): KotlinScriptExternalDependencies? + ): Future? { - val cp = annotations.flatMap { + val cp = script.annotations.flatMap { when (it) { is DependsOn -> listOf(if (it.path == "@{runtime}") kotlinPaths.runtimePath else File(it.path)) is InvalidScriptResolverAnnotation -> throw Exception("Invalid annotation ${it.name}", it.error) else -> throw Exception("Unknown annotation ${it.javaClass}") } } - return object : KotlinScriptExternalDependencies { + return makeNullableFakeFuture(object : KotlinScriptExternalDependencies { override val classpath: Iterable = classpathFromClassloader() + cp override val imports: Iterable = listOf("org.jetbrains.kotlin.scripts.DependsOn") - } + }) } private fun classpathFromClassloader(): List =