Implement proper collection of script compilation classpath, use it in the JSR223 example factories
Fixes #KT-14713
This commit is contained in:
@@ -50,18 +50,6 @@
|
||||
</resources>
|
||||
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<version>2.3</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>properties</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>kotlin-maven-plugin</artifactId>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
@@ -84,12 +72,6 @@
|
||||
<plugin>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
<version>2.6</version>
|
||||
<configuration>
|
||||
<systemPropertyVariables>
|
||||
<kotlin.compiler.jar>${org.jetbrains.kotlin:kotlin-compiler:jar}</kotlin.compiler.jar>
|
||||
<kotlin.java.runtime.jar>${org.jetbrains.kotlin:kotlin-runtime:jar}</kotlin.java.runtime.jar>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
|
||||
@@ -27,6 +27,11 @@
|
||||
<artifactId>kotlin-runtime</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-script-runtime</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-compiler</artifactId>
|
||||
@@ -50,18 +55,6 @@
|
||||
</resources>
|
||||
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<version>2.3</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>properties</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>kotlin-maven-plugin</artifactId>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
@@ -84,12 +77,6 @@
|
||||
<plugin>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
<version>2.6</version>
|
||||
<configuration>
|
||||
<systemPropertyVariables>
|
||||
<kotlin.compiler.jar>${org.jetbrains.kotlin:kotlin-compiler:jar}</kotlin.compiler.jar>
|
||||
<kotlin.java.runtime.jar>${org.jetbrains.kotlin:kotlin-runtime:jar}</kotlin.java.runtime.jar>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
|
||||
@@ -50,18 +50,6 @@
|
||||
</resources>
|
||||
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<version>2.3</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>properties</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>kotlin-maven-plugin</artifactId>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
@@ -84,12 +72,6 @@
|
||||
<plugin>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
<version>2.6</version>
|
||||
<configuration>
|
||||
<systemPropertyVariables>
|
||||
<kotlin.compiler.jar>${org.jetbrains.kotlin:kotlin-compiler:jar}</kotlin.compiler.jar>
|
||||
<kotlin.java.runtime.jar>${org.jetbrains.kotlin:kotlin-runtime:jar}</kotlin.java.runtime.jar>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
|
||||
@@ -39,6 +39,11 @@
|
||||
<artifactId>kotlin-runtime</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-script-runtime</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-test-junit</artifactId>
|
||||
|
||||
+53
-14
@@ -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 <T> Iterable<T>.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<File>? =
|
||||
( classpathFromClassloader(classLoader)?.anyOrNull { it.matchMaybeVersionedFile(keyName) }
|
||||
?: manifestClassPath(classLoader)?.anyOrNull { it.matchMaybeVersionedFile(keyName) }
|
||||
)?.toList()
|
||||
|
||||
|
||||
private fun scriptCompilationClasspathFromContext(classLoader: ClassLoader = Thread.currentThread().contextClassLoader): List<File> =
|
||||
( 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")
|
||||
|
||||
+70
@@ -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<File>? =
|
||||
generateSequence(classLoader) { it.parent }.toList().flatMap { (it as? URLClassLoader)?.urLs?.mapNotNull(URL::toFile) ?: emptyList() }
|
||||
|
||||
fun classpathFromClasspathProperty(): List<File>? =
|
||||
System.getProperty("java.class.path")
|
||||
?.split(String.format("\\%s", File.pathSeparatorChar).toRegex())
|
||||
?.dropLastWhile(String::isEmpty)
|
||||
?.map(::File)
|
||||
|
||||
fun classpathFromClass(classLoader: ClassLoader, klass: KClass<out Any>): List<File>? {
|
||||
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<File>? =
|
||||
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 <R> ifFailed(default: R, block: () -> R) = try {
|
||||
block()
|
||||
} catch (t: Throwable) {
|
||||
default
|
||||
}
|
||||
|
||||
private val defaultScriptContextClasspath: List<File> 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()))
|
||||
|
||||
+2
-38
@@ -69,41 +69,5 @@ open class KotlinAnnotatedScriptDependenciesResolver(val baseClassPath: List<Fil
|
||||
}
|
||||
}
|
||||
|
||||
private fun URL.toFile() =
|
||||
try {
|
||||
File(toURI().schemeSpecificPart)
|
||||
}
|
||||
catch (e: java.net.URISyntaxException) {
|
||||
if (protocol != "file") null
|
||||
else File(file)
|
||||
}
|
||||
|
||||
private fun classpathFromClassloader(classLoader: ClassLoader): List<File>? =
|
||||
generateSequence(classLoader) { it.parent }.toList().flatMap { (it as? URLClassLoader)?.urLs?.mapNotNull { it.toFile() } ?: emptyList() }
|
||||
|
||||
private fun classpathFromClasspathProperty(): List<File>? =
|
||||
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<out Any>): List<File>? {
|
||||
val clp = "${klass.qualifiedName?.replace('.', '/')}.class"
|
||||
val url = classLoader.getResource(clp)
|
||||
return url?.toURI()?.path?.removeSuffix(clp)?.let {
|
||||
listOf(File(it))
|
||||
}
|
||||
}
|
||||
|
||||
val defaultScriptBaseClasspath: List<File> 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()))
|
||||
|
||||
+5
-6
@@ -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<String>)
|
||||
|
||||
@ScriptTemplateDefinition(resolver = ContextAndAnnotationsBasedResolver::class, scriptFilePattern = ".*\\.kts")
|
||||
@ScriptTemplateDefinition(resolver = AnnotationsBasedResolver::class, scriptFilePattern = ".*\\.kts")
|
||||
abstract class StandardScriptTemplateWithAnnotatedResolving(val args: Array<String>)
|
||||
|
||||
@ScriptTemplateDefinition(resolver = ContextBasedResolver::class, scriptFilePattern = ".*\\.kts")
|
||||
@ScriptTemplateDefinition(scriptFilePattern = ".*\\.kts")
|
||||
abstract class ScriptTemplateWithBindings(val bindings: Map<String, Any?>)
|
||||
|
||||
@ScriptTemplateDefinition(resolver = ContextAndAnnotationsBasedResolver::class, scriptFilePattern = ".*\\.kts")
|
||||
@ScriptTemplateDefinition(resolver = AnnotationsBasedResolver::class, scriptFilePattern = ".*\\.kts")
|
||||
abstract class ScriptTemplateWithBindingsAndAnnotatedResolving(val bindings: Map<String, Any?>)
|
||||
|
||||
+4
-10
@@ -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 <R> ifFailed(default: R, block: () -> R) = try {
|
||||
block()
|
||||
} catch (t: Throwable) {
|
||||
|
||||
Reference in New Issue
Block a user