diff --git a/libraries/examples/kotlin-jsr223-daemon-local-eval-example/pom.xml b/libraries/examples/kotlin-jsr223-daemon-local-eval-example/pom.xml index ee281276b24..342314835aa 100644 --- a/libraries/examples/kotlin-jsr223-daemon-local-eval-example/pom.xml +++ b/libraries/examples/kotlin-jsr223-daemon-local-eval-example/pom.xml @@ -50,18 +50,6 @@ - - org.apache.maven.plugins - maven-dependency-plugin - 2.3 - - - - properties - - - - kotlin-maven-plugin org.jetbrains.kotlin @@ -84,12 +72,6 @@ maven-failsafe-plugin 2.6 - - - ${org.jetbrains.kotlin:kotlin-compiler:jar} - ${org.jetbrains.kotlin:kotlin-runtime:jar} - - diff --git a/libraries/examples/kotlin-jsr223-daemon-remote-eval-example/pom.xml b/libraries/examples/kotlin-jsr223-daemon-remote-eval-example/pom.xml index 44062fea97e..77a9416aaf4 100644 --- a/libraries/examples/kotlin-jsr223-daemon-remote-eval-example/pom.xml +++ b/libraries/examples/kotlin-jsr223-daemon-remote-eval-example/pom.xml @@ -27,6 +27,11 @@ kotlin-runtime ${project.version} + + org.jetbrains.kotlin + kotlin-script-runtime + ${project.version} + org.jetbrains.kotlin kotlin-compiler @@ -50,18 +55,6 @@ - - org.apache.maven.plugins - maven-dependency-plugin - 2.3 - - - - properties - - - - kotlin-maven-plugin org.jetbrains.kotlin @@ -84,12 +77,6 @@ maven-failsafe-plugin 2.6 - - - ${org.jetbrains.kotlin:kotlin-compiler:jar} - ${org.jetbrains.kotlin:kotlin-runtime:jar} - - diff --git a/libraries/examples/kotlin-jsr223-local-example/pom.xml b/libraries/examples/kotlin-jsr223-local-example/pom.xml index 153a4b81920..b4ec989e57e 100644 --- a/libraries/examples/kotlin-jsr223-local-example/pom.xml +++ b/libraries/examples/kotlin-jsr223-local-example/pom.xml @@ -50,18 +50,6 @@ - - org.apache.maven.plugins - maven-dependency-plugin - 2.3 - - - - properties - - - - kotlin-maven-plugin org.jetbrains.kotlin @@ -84,12 +72,6 @@ maven-failsafe-plugin 2.6 - - - ${org.jetbrains.kotlin:kotlin-compiler:jar} - ${org.jetbrains.kotlin:kotlin-runtime:jar} - - diff --git a/libraries/tools/kotlin-script-util/pom.xml b/libraries/tools/kotlin-script-util/pom.xml index 210df64d050..8247f885bee 100644 --- a/libraries/tools/kotlin-script-util/pom.xml +++ b/libraries/tools/kotlin-script-util/pom.xml @@ -39,6 +39,11 @@ kotlin-runtime ${project.version} + + org.jetbrains.kotlin + kotlin-script-runtime + ${project.version} + org.jetbrains.kotlin kotlin-test-junit diff --git a/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223ScriptEngineFactoryExamples.kt b/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223ScriptEngineFactoryExamples.kt index 109f0dd3c89..48cb9836bf5 100644 --- a/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223ScriptEngineFactoryExamples.kt +++ b/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/jsr223/KotlinJsr223ScriptEngineFactoryExamples.kt @@ -20,6 +20,11 @@ package org.jetbrains.kotlin.script.jsr223 import com.intellij.openapi.util.Disposer import org.jetbrains.kotlin.cli.common.repl.KotlinJsr223JvmScriptEngineFactoryBase +import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler +import org.jetbrains.kotlin.script.util.classpathFromClass +import org.jetbrains.kotlin.script.util.classpathFromClassloader +import org.jetbrains.kotlin.script.util.classpathFromClasspathProperty +import org.jetbrains.kotlin.script.util.manifestClassPath import org.jetbrains.kotlin.utils.PathUtil.* import java.io.File import java.io.FileNotFoundException @@ -33,7 +38,7 @@ class KotlinJsr223JvmLocalScriptEngineFactory : KotlinJsr223JvmScriptEngineFacto KotlinJsr223JvmLocalScriptEngine( Disposer.newDisposable(), this, - listOf(kotlinScriptRuntimeJar), + scriptCompilationClasspathFromContext(), "kotlin.script.templates.standard.ScriptTemplateWithBindings", { ctx -> arrayOf(ctx.getBindings(ScriptContext.ENGINE_SCOPE)) }, arrayOf(Map::class.java) @@ -47,7 +52,7 @@ class KotlinJsr223JvmDaemonLocalEvalScriptEngineFactory : KotlinJsr223JvmScriptE Disposer.newDisposable(), this, kotlinCompilerJar, - listOf(kotlinScriptRuntimeJar), + scriptCompilationClasspathFromContext(), "kotlin.script.templates.standard.ScriptTemplateWithBindings", { ctx -> arrayOf(ctx.getBindings(ScriptContext.ENGINE_SCOPE)) }, arrayOf(Map::class.java) @@ -61,7 +66,7 @@ class KotlinJsr223JvmDaemonRemoteEvalScriptEngineFactory : KotlinJsr223JvmScript Disposer.newDisposable(), this, kotlinCompilerJar, - listOf(kotlinScriptRuntimeJar), + scriptCompilationClasspathFromContext(), "kotlin.script.templates.standard.ScriptTemplateWithBindings", ::makeSerializableArgumentsForTemplateWithBindings, arrayOf(Map::class.java) @@ -79,16 +84,50 @@ private fun makeSerializableArgumentsForTemplateWithBindings(ctx: ScriptContext) private fun File.existsOrNull(): File? = existsAndCheckOrNull { true } private inline fun File.existsAndCheckOrNull(check: (File.() -> Boolean)): File? = if (exists() && check()) this else null -private val kotlinCompilerJar = System.getProperty("kotlin.compiler.jar")?.let(::File)?.existsOrNull() - ?: getPathUtilJar().existsAndCheckOrNull { name == KOTLIN_COMPILER_JAR } - ?: throw FileNotFoundException("Cannot find kotlin compiler jar, set kotlin.compiler.jar property to proper location") +private val kotlinCompilerJar: File by lazy { + // highest prio - explicit property + System.getProperty("kotlin.compiler.jar")?.let(::File)?.existsOrNull() + // search classpath from context classloader and `java.calss.path` property + ?: (classpathFromClass(Thread.currentThread().contextClassLoader, K2JVMCompiler::class) + ?: contextClasspath(KOTLIN_COMPILER_JAR, Thread.currentThread().contextClassLoader) + ?: classpathFromClasspathProperty() + )?.firstOrNull { it.matchMaybeVersionedFile(KOTLIN_COMPILER_JAR) } + ?: throw FileNotFoundException("Cannot find kotlin compiler jar, set kotlin.compiler.jar property to proper location") +} + +private fun Iterable.anyOrNull(predicate: (T) -> Boolean) = if (any(predicate)) this else null + +private fun File.matchMaybeVersionedFile(baseName: String) = + name == baseName || + name == baseName.removeSuffix(".jar") || // for classes dirs + name.startsWith(baseName.removeSuffix(".jar") + "-") + +private fun contextClasspath(keyName: String, classLoader: ClassLoader): List? = + ( classpathFromClassloader(classLoader)?.anyOrNull { it.matchMaybeVersionedFile(keyName) } + ?: manifestClassPath(classLoader)?.anyOrNull { it.matchMaybeVersionedFile(keyName) } + )?.toList() + + +private fun scriptCompilationClasspathFromContext(classLoader: ClassLoader = Thread.currentThread().contextClassLoader): List = + ( System.getProperty("kotlin.script.classpath")?.split(File.pathSeparator)?.map(::File) + ?: contextClasspath(KOTLIN_JAVA_RUNTIME_JAR, classLoader) + ?: listOf(kotlinRuntimeJar, kotlinScriptRuntimeJar) + ) + .map { it?.canonicalFile } + .distinct() + .mapNotNull { it?.existsOrNull() } + + +private val kotlinRuntimeJar: File? by lazy { + System.getProperty("kotlin.java.runtime.jar")?.let(::File)?.existsOrNull() + ?: kotlinCompilerJar.let { File(it.parentFile, KOTLIN_JAVA_RUNTIME_JAR) }.existsOrNull() + ?: getResourcePathForClass(JvmStatic::class.java).existsOrNull() +} + +private val kotlinScriptRuntimeJar: File? by lazy { + System.getProperty("kotlin.script.runtime.jar")?.let(::File)?.existsOrNull() + ?: kotlinCompilerJar.let { File(it.parentFile, KOTLIN_JAVA_SCRIPT_RUNTIME_JAR) }.existsOrNull() + ?: getResourcePathForClass(ScriptTemplateWithArgs::class.java).existsOrNull() +} -private val kotlinRuntimeJar = System.getProperty("kotlin.java.runtime.jar")?.let(::File)?.existsOrNull() - ?: kotlinCompilerJar.let { File(it.parentFile, KOTLIN_JAVA_RUNTIME_JAR) }.existsOrNull() - ?: getResourcePathForClass(JvmStatic::class.java).existsOrNull() - ?: throw FileNotFoundException("Cannot find kotlin runtime jar, set kotlin.java.runtime.jar property to proper location") -private val kotlinScriptRuntimeJar = System.getProperty("kotlin.script.runtime.jar")?.let(::File)?.existsOrNull() - ?: kotlinCompilerJar.let { File(it.parentFile, KOTLIN_JAVA_SCRIPT_RUNTIME_JAR) }.existsOrNull() - ?: getResourcePathForClass(ScriptTemplateWithArgs::class.java).existsOrNull() - ?: throw FileNotFoundException("Cannot find kotlin script runtime jar, set kotlin.script.runtime.jar property to proper location") diff --git a/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/context.kt b/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/context.kt new file mode 100644 index 00000000000..e608879be4b --- /dev/null +++ b/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/context.kt @@ -0,0 +1,70 @@ +package org.jetbrains.kotlin.script.util + +import org.jetbrains.kotlin.script.util.resolvers.DirectResolver +import org.jetbrains.kotlin.script.util.resolvers.MavenResolver +import java.io.File +import java.net.URI +import java.net.URL +import java.net.URLClassLoader +import java.util.jar.Manifest +import kotlin.reflect.KClass + + +private fun URL.toFile() = + try { + File(toURI().schemeSpecificPart) + } + catch (e: java.net.URISyntaxException) { + if (protocol != "file") null + else File(file) + } + +fun classpathFromClassloader(classLoader: ClassLoader): List? = + generateSequence(classLoader) { it.parent }.toList().flatMap { (it as? URLClassLoader)?.urLs?.mapNotNull(URL::toFile) ?: emptyList() } + +fun classpathFromClasspathProperty(): List? = + System.getProperty("java.class.path") + ?.split(String.format("\\%s", File.pathSeparatorChar).toRegex()) + ?.dropLastWhile(String::isEmpty) + ?.map(::File) + +fun classpathFromClass(classLoader: ClassLoader, klass: KClass): List? { + val clp = "${klass.qualifiedName?.replace('.', '/')}.class" + val url = classLoader.getResource(clp) + return url?.toURI()?.path?.removeSuffix(clp)?.let { + listOf(File(it)) + } +} + +// Maven runners sometimes place classpath into the manifest, so we can use it for a fallback search +fun manifestClassPath(classLoader: ClassLoader): List? = + classLoader.getResources("META-INF/MANIFEST.MF") + .asSequence() + .mapNotNull { ifFailed(null) { it.openStream().use { Manifest().apply { read(it) } } } } + .flatMap { it.mainAttributes?.getValue("Class-Path")?.splitToSequence(" ") ?: emptySequence() } + .mapNotNull { ifFailed(null) { File(URI.create(it)) } } + .toList() + .let { if (it.isNotEmpty()) it else null } + +private inline fun ifFailed(default: R, block: () -> R) = try { + block() +} catch (t: Throwable) { + default +} + +private val defaultScriptContextClasspath: List by lazy { + classpathFromClass(Thread.currentThread().contextClassLoader, KotlinAnnotatedScriptDependenciesResolver::class) + ?: classpathFromClasspathProperty() + ?: classpathFromClassloader(Thread.currentThread().contextClassLoader) + ?: emptyList() +} + +// NOTE: context-based resolvers may behave unexpectedly with daemon-based REPLs. +// since the context is calculated at the point of resolver creation +// TODO: consider removing as confusing +class ContextBasedResolver : + KotlinAnnotatedScriptDependenciesResolver(defaultScriptContextClasspath, arrayListOf()) + +class ContextAndAnnotationsBasedResolver : + KotlinAnnotatedScriptDependenciesResolver(defaultScriptContextClasspath, arrayListOf(DirectResolver(), MavenResolver())) + diff --git a/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/resolve.kt b/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/resolve.kt index ff268677bbf..0ae8a8c2e4d 100644 --- a/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/resolve.kt +++ b/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/resolve.kt @@ -69,41 +69,5 @@ open class KotlinAnnotatedScriptDependenciesResolver(val baseClassPath: List? = - generateSequence(classLoader) { it.parent }.toList().flatMap { (it as? URLClassLoader)?.urLs?.mapNotNull { it.toFile() } ?: emptyList() } - -private fun classpathFromClasspathProperty(): List? = - System.getProperty("java.class.path")?.let { - it.split(String.format("\\%s", File.pathSeparatorChar).toRegex()).dropLastWhile(String::isEmpty) - .map(::File) - } - -private fun classpathFromClass(classLoader: ClassLoader, klass: KClass): List? { - val clp = "${klass.qualifiedName?.replace('.', '/')}.class" - val url = classLoader.getResource(clp) - return url?.toURI()?.path?.removeSuffix(clp)?.let { - listOf(File(it)) - } -} - -val defaultScriptBaseClasspath: List by lazy { - classpathFromClass(Thread.currentThread().contextClassLoader, KotlinAnnotatedScriptDependenciesResolver::class) - ?: classpathFromClasspathProperty() - ?: classpathFromClassloader(Thread.currentThread().contextClassLoader) - ?: emptyList() -} - -class ContextBasedResolver() : - KotlinAnnotatedScriptDependenciesResolver(defaultScriptBaseClasspath, arrayListOf()) - -class ContextAndAnnotationsBasedResolver : - KotlinAnnotatedScriptDependenciesResolver(defaultScriptBaseClasspath, arrayListOf(DirectResolver(), MavenResolver())) +class AnnotationsBasedResolver : + KotlinAnnotatedScriptDependenciesResolver(emptyList(), arrayListOf(DirectResolver(), MavenResolver())) diff --git a/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/templates/templates.kt b/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/templates/templates.kt index 277a818744c..81d545f6d63 100644 --- a/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/templates/templates.kt +++ b/libraries/tools/kotlin-script-util/src/main/kotlin/org/jetbrains/kotlin/script/util/templates/templates.kt @@ -19,17 +19,16 @@ package org.jetbrains.kotlin.script.util.templates import org.jetbrains.kotlin.script.ScriptTemplateDefinition -import org.jetbrains.kotlin.script.util.ContextAndAnnotationsBasedResolver -import org.jetbrains.kotlin.script.util.ContextBasedResolver +import org.jetbrains.kotlin.script.util.AnnotationsBasedResolver -@ScriptTemplateDefinition(resolver = ContextBasedResolver::class, scriptFilePattern = ".*\\.kts") +@ScriptTemplateDefinition(scriptFilePattern = ".*\\.kts") abstract class StandardScriptTemplate(val args: Array) -@ScriptTemplateDefinition(resolver = ContextAndAnnotationsBasedResolver::class, scriptFilePattern = ".*\\.kts") +@ScriptTemplateDefinition(resolver = AnnotationsBasedResolver::class, scriptFilePattern = ".*\\.kts") abstract class StandardScriptTemplateWithAnnotatedResolving(val args: Array) -@ScriptTemplateDefinition(resolver = ContextBasedResolver::class, scriptFilePattern = ".*\\.kts") +@ScriptTemplateDefinition(scriptFilePattern = ".*\\.kts") abstract class ScriptTemplateWithBindings(val bindings: Map) -@ScriptTemplateDefinition(resolver = ContextAndAnnotationsBasedResolver::class, scriptFilePattern = ".*\\.kts") +@ScriptTemplateDefinition(resolver = AnnotationsBasedResolver::class, scriptFilePattern = ".*\\.kts") abstract class ScriptTemplateWithBindingsAndAnnotatedResolving(val bindings: Map) diff --git a/libraries/tools/kotlin-script-util/src/test/kotlin/org/jetbrains/kotlin/script/util/ScriptUtilIT.kt b/libraries/tools/kotlin-script-util/src/test/kotlin/org/jetbrains/kotlin/script/util/ScriptUtilIT.kt index f601bc44438..26d931f2497 100644 --- a/libraries/tools/kotlin-script-util/src/test/kotlin/org/jetbrains/kotlin/script/util/ScriptUtilIT.kt +++ b/libraries/tools/kotlin-script-util/src/test/kotlin/org/jetbrains/kotlin/script/util/ScriptUtilIT.kt @@ -100,7 +100,7 @@ done val scriptClass = compileScript("args-junit-hello-world.kts", StandardScriptTemplateWithAnnotatedResolving::class) if (scriptClass == null) { - val resolver = ContextAndAnnotationsBasedResolver() + val resolver = AnnotationsBasedResolver() System.err.println(resolver.baseClassPath) } Assert.assertNotNull(scriptClass) @@ -148,8 +148,9 @@ done } else { // attempt to workaround some maven quirks - addJvmClasspathRoots( - Thread.currentThread().contextClassLoader.manifestClassPath().toList()) + manifestClassPath(Thread.currentThread().contextClassLoader)?.let { + addJvmClasspathRoots(it) + } } } put(CommonConfigurationKeys.MODULE_NAME, "kotlin-script-util-test") @@ -178,13 +179,6 @@ done } } - private fun ClassLoader.manifestClassPath() = - getResources("META-INF/MANIFEST.MF") - .asSequence() - .mapNotNull { ifFailed(null) { it.openStream().use { Manifest().apply { read(it) } } } } - .flatMap { it.mainAttributes?.getValue("Class-Path")?.splitToSequence(" ") ?: emptySequence() } - .mapNotNull { ifFailed(null) { File(URI.create(it)) } } - private inline fun ifFailed(default: R, block: () -> R) = try { block() } catch (t: Throwable) {