Implement proper collection of script compilation classpath, use it in the JSR223 example factories

Fixes #KT-14713
This commit is contained in:
Ilya Chernikov
2016-12-04 14:10:26 +01:00
parent f60eb42420
commit 7c8b6ddca4
9 changed files with 144 additions and 122 deletions
@@ -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>
@@ -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")
@@ -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()))
@@ -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()))
@@ -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?>)
@@ -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) {