Improve classpath extraction from classloader:
- implement opt-in unpacking/caching of the jar collection archives, such as spring boot fat jars and WARs, to the temp dir, and creating a valid classpath from extracted archive - turning it on for the new default jsr223 engine - fallback to extracting classpath from resource URLs if the classloader is not known url-based one - refactor and optimize extraction - cache the last retrieved classpath in the default JSR-223 script engine factory
This commit is contained in:
+23
-1
@@ -6,10 +6,14 @@
|
|||||||
package kotlin.script.experimental.jsr223
|
package kotlin.script.experimental.jsr223
|
||||||
|
|
||||||
import org.jetbrains.kotlin.cli.common.repl.KotlinJsr223JvmScriptEngineFactoryBase
|
import org.jetbrains.kotlin.cli.common.repl.KotlinJsr223JvmScriptEngineFactoryBase
|
||||||
|
import java.io.File
|
||||||
import javax.script.ScriptEngine
|
import javax.script.ScriptEngine
|
||||||
import kotlin.script.experimental.api.ScriptCompilationConfiguration
|
import kotlin.script.experimental.api.ScriptCompilationConfiguration
|
||||||
|
import kotlin.script.experimental.jvm.JvmScriptCompilationConfigurationBuilder
|
||||||
import kotlin.script.experimental.jvm.dependenciesFromCurrentContext
|
import kotlin.script.experimental.jvm.dependenciesFromCurrentContext
|
||||||
import kotlin.script.experimental.jvm.jvm
|
import kotlin.script.experimental.jvm.jvm
|
||||||
|
import kotlin.script.experimental.jvm.updateClasspath
|
||||||
|
import kotlin.script.experimental.jvm.util.scriptCompilationClasspathFromContext
|
||||||
import kotlin.script.experimental.jvmhost.createJvmCompilationConfigurationFromTemplate
|
import kotlin.script.experimental.jvmhost.createJvmCompilationConfigurationFromTemplate
|
||||||
import kotlin.script.experimental.jvmhost.createJvmEvaluationConfigurationFromTemplate
|
import kotlin.script.experimental.jvmhost.createJvmEvaluationConfigurationFromTemplate
|
||||||
import kotlin.script.experimental.jvmhost.jsr223.KotlinJsr223ScriptEngineImpl
|
import kotlin.script.experimental.jvmhost.jsr223.KotlinJsr223ScriptEngineImpl
|
||||||
@@ -18,13 +22,31 @@ class KotlinJsr223DefaultScriptEngineFactory : KotlinJsr223JvmScriptEngineFactor
|
|||||||
|
|
||||||
private val compilationConfiguration = createJvmCompilationConfigurationFromTemplate<KotlinJsr223DefaultScript>()
|
private val compilationConfiguration = createJvmCompilationConfigurationFromTemplate<KotlinJsr223DefaultScript>()
|
||||||
private val evaluationConfiguration = createJvmEvaluationConfigurationFromTemplate<KotlinJsr223DefaultScript>()
|
private val evaluationConfiguration = createJvmEvaluationConfigurationFromTemplate<KotlinJsr223DefaultScript>()
|
||||||
|
private var lastClassLoader: ClassLoader? = null
|
||||||
|
private var lastClassPath: List<File>? = null
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
protected fun JvmScriptCompilationConfigurationBuilder.dependenciesFromCurrentContext() {
|
||||||
|
val currentClassLoader = Thread.currentThread().contextClassLoader
|
||||||
|
val classPath = if (lastClassLoader == null || lastClassLoader != currentClassLoader) {
|
||||||
|
scriptCompilationClasspathFromContext(
|
||||||
|
classLoader = currentClassLoader,
|
||||||
|
wholeClasspath = true,
|
||||||
|
unpackJarCollections = true
|
||||||
|
).also {
|
||||||
|
lastClassLoader = currentClassLoader
|
||||||
|
lastClassPath = it
|
||||||
|
}
|
||||||
|
} else lastClassPath!!
|
||||||
|
updateClasspath(classPath)
|
||||||
|
}
|
||||||
|
|
||||||
override fun getScriptEngine(): ScriptEngine =
|
override fun getScriptEngine(): ScriptEngine =
|
||||||
KotlinJsr223ScriptEngineImpl(
|
KotlinJsr223ScriptEngineImpl(
|
||||||
this,
|
this,
|
||||||
ScriptCompilationConfiguration(compilationConfiguration) {
|
ScriptCompilationConfiguration(compilationConfiguration) {
|
||||||
jvm {
|
jvm {
|
||||||
dependenciesFromCurrentContext(wholeClasspath = true)
|
dependenciesFromCurrentContext()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
evaluationConfiguration
|
evaluationConfiguration
|
||||||
|
|||||||
+62
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package kotlin.script.experimental.jvmhost.test
|
||||||
|
|
||||||
|
import junit.framework.TestCase
|
||||||
|
import org.junit.Assert
|
||||||
|
import org.junit.Test
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.net.URLClassLoader
|
||||||
|
import java.util.jar.JarEntry
|
||||||
|
import java.util.jar.JarOutputStream
|
||||||
|
import kotlin.script.experimental.jvm.util.classpathFromClassloader
|
||||||
|
|
||||||
|
class ClassPathTest : TestCase() {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testExtractFromFat() {
|
||||||
|
val collection = createTempFile("col", ".jar").apply { createCollectionJar(emulatedCollectionFiles, "BOOT-INF") }
|
||||||
|
val cl = URLClassLoader(arrayOf(collection.toURI().toURL()), null)
|
||||||
|
val cp = classpathFromClassloader(cl, true)
|
||||||
|
Assert.assertTrue(cp != null && cp.isNotEmpty())
|
||||||
|
|
||||||
|
testUnpackedCollection(cp!!, emulatedCollectionFiles)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val emulatedCollectionFiles = arrayOf(
|
||||||
|
"classes/a/b.class",
|
||||||
|
"lib/c-d.jar"
|
||||||
|
)
|
||||||
|
|
||||||
|
fun File.createCollectionJar(fileNames: Array<String>, infDirName: String) {
|
||||||
|
FileOutputStream(this).use { fileStream ->
|
||||||
|
val jarStream = JarOutputStream(fileStream)
|
||||||
|
jarStream.putNextEntry(JarEntry("$infDirName/classes/"))
|
||||||
|
jarStream.putNextEntry(JarEntry("$infDirName/lib/"))
|
||||||
|
for (name in fileNames) {
|
||||||
|
jarStream.putNextEntry(JarEntry("$infDirName/$name"))
|
||||||
|
jarStream.write(name.toByteArray())
|
||||||
|
}
|
||||||
|
jarStream.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testUnpackedCollection(classpath: List<File>, fileNames: Array<String>) {
|
||||||
|
|
||||||
|
fun List<String>.checkFiles(root: File) = forEach {
|
||||||
|
val file = File(root, it)
|
||||||
|
Assert.assertTrue(file.exists())
|
||||||
|
Assert.assertEquals(it, file.readText())
|
||||||
|
}
|
||||||
|
|
||||||
|
val (classes, jars) = fileNames.partition { it.startsWith("classes") }
|
||||||
|
val (cpClasses, cpJars) = classpath.partition { it.isDirectory && it.name == "classes" }
|
||||||
|
Assert.assertTrue(cpClasses.size == 1)
|
||||||
|
classes.checkFiles(cpClasses.first().parentFile)
|
||||||
|
jars.checkFiles(cpJars.first().parentFile.parentFile)
|
||||||
|
}
|
||||||
+1
-1
@@ -105,7 +105,7 @@ internal fun List<ScriptDependency>?.toClassPathOrEmpty() = this?.flatMap { (it
|
|||||||
|
|
||||||
internal fun List<SourceCode>?.toFilesOrEmpty() = this?.map {
|
internal fun List<SourceCode>?.toFilesOrEmpty() = this?.map {
|
||||||
val externalSource = it as? ExternalSourceCode
|
val externalSource = it as? ExternalSourceCode
|
||||||
externalSource?.externalLocation?.toFile()
|
externalSource?.externalLocation?.toFileOrNull()
|
||||||
?: throw RuntimeException("Unsupported source in requireSources parameter - only local files are supported now (${externalSource?.externalLocation})")
|
?: throw RuntimeException("Unsupported source in requireSources parameter - only local files are supported now (${externalSource?.externalLocation})")
|
||||||
} ?: emptyList()
|
} ?: emptyList()
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
package kotlin.script.experimental.jvm.impl
|
package kotlin.script.experimental.jvm.impl
|
||||||
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.net.JarURLConnection
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
|
||||||
// Based on an implementation in com.intellij.openapi.application.PathManager.getResourceRoot
|
// Based on an implementation in com.intellij.openapi.application.PathManager.getResourceRoot
|
||||||
@@ -29,7 +30,7 @@ private fun extractRoot(resourceURL: URL, resourcePath: String): String? {
|
|||||||
var resultPath: String? = null
|
var resultPath: String? = null
|
||||||
val protocol = resourceURL.protocol
|
val protocol = resourceURL.protocol
|
||||||
if (protocol == FILE_PROTOCOL) {
|
if (protocol == FILE_PROTOCOL) {
|
||||||
val path = resourceURL.toFile()!!.path
|
val path = resourceURL.toFileOrNull()!!.path
|
||||||
val testPath = path.replace('\\', '/')
|
val testPath = path.replace('\\', '/')
|
||||||
val testResourcePath = resourcePath.replace('\\', '/')
|
val testResourcePath = resourcePath.replace('\\', '/')
|
||||||
if (testPath.endsWith(testResourcePath, ignoreCase = true)) {
|
if (testPath.endsWith(testResourcePath, ignoreCase = true)) {
|
||||||
@@ -57,7 +58,7 @@ private fun splitJarUrl(url: String): Pair<String, String>? {
|
|||||||
|
|
||||||
if (jarPath.startsWith(FILE_PROTOCOL)) {
|
if (jarPath.startsWith(FILE_PROTOCOL)) {
|
||||||
try {
|
try {
|
||||||
jarPath = URL(jarPath).toFile()!!.path.replace('\\', '/')
|
jarPath = URL(jarPath).toFileOrNull()!!.path.replace('\\', '/')
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
jarPath = jarPath.substring(FILE_PROTOCOL.length)
|
jarPath = jarPath.substring(FILE_PROTOCOL.length)
|
||||||
if (jarPath.startsWith(SCHEME_SEPARATOR)) {
|
if (jarPath.startsWith(SCHEME_SEPARATOR)) {
|
||||||
@@ -91,11 +92,15 @@ fun tryGetResourcePathForClassByName(name: String, classLoader: ClassLoader): Fi
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun URL.toFile() =
|
internal fun URL.toFileOrNull() =
|
||||||
try {
|
try {
|
||||||
File(toURI().schemeSpecificPart)
|
File(toURI().schemeSpecificPart).canonicalFile
|
||||||
} catch (e: java.net.URISyntaxException) {
|
} catch (e: java.net.URISyntaxException) {
|
||||||
if (protocol != "file") null
|
if (protocol != "file") null
|
||||||
else File(file)
|
else File(file).canonicalFile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun URL.toContainingFileOrNull(): File? =
|
||||||
|
if (protocol == "jar") {
|
||||||
|
(openConnection() as? JarURLConnection)?.jarFileURL?.toFileOrNull()
|
||||||
|
} else toFileOrNull()
|
||||||
|
|||||||
@@ -31,17 +31,24 @@ fun JvmScriptCompilationConfigurationBuilder.dependenciesFromClassContext(
|
|||||||
dependenciesFromClassloader(*libraries, classLoader = contextClass.java.classLoader, wholeClasspath = wholeClasspath)
|
dependenciesFromClassloader(*libraries, classLoader = contextClass.java.classLoader, wholeClasspath = wholeClasspath)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun JvmScriptCompilationConfigurationBuilder.dependenciesFromCurrentContext(vararg libraries: String, wholeClasspath: Boolean = false) {
|
fun JvmScriptCompilationConfigurationBuilder.dependenciesFromCurrentContext(
|
||||||
dependenciesFromClassloader(*libraries, wholeClasspath = wholeClasspath)
|
vararg libraries: String,
|
||||||
|
wholeClasspath: Boolean = false,
|
||||||
|
unpackJarCollections: Boolean = false
|
||||||
|
) {
|
||||||
|
dependenciesFromClassloader(*libraries, wholeClasspath = wholeClasspath, unpackJarCollections = unpackJarCollections)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun JvmScriptCompilationConfigurationBuilder.dependenciesFromClassloader(
|
fun JvmScriptCompilationConfigurationBuilder.dependenciesFromClassloader(
|
||||||
vararg libraries: String,
|
vararg libraries: String,
|
||||||
classLoader: ClassLoader = Thread.currentThread().contextClassLoader,
|
classLoader: ClassLoader = Thread.currentThread().contextClassLoader,
|
||||||
wholeClasspath: Boolean = false
|
wholeClasspath: Boolean = false,
|
||||||
|
unpackJarCollections: Boolean = false
|
||||||
) {
|
) {
|
||||||
updateClasspath(
|
updateClasspath(
|
||||||
scriptCompilationClasspathFromContext(*libraries, classLoader = classLoader, wholeClasspath = wholeClasspath)
|
scriptCompilationClasspathFromContext(
|
||||||
|
*libraries, classLoader = classLoader, wholeClasspath = wholeClasspath, unpackJarCollections = unpackJarCollections
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+145
-54
@@ -6,11 +6,13 @@
|
|||||||
package kotlin.script.experimental.jvm.util
|
package kotlin.script.experimental.jvm.util
|
||||||
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.FileInputStream
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.net.URLClassLoader
|
import java.net.URLClassLoader
|
||||||
|
import java.util.jar.JarInputStream
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
import kotlin.script.experimental.jvm.impl.toFile
|
import kotlin.script.experimental.jvm.impl.toContainingFileOrNull
|
||||||
import kotlin.script.experimental.jvm.impl.tryGetResourcePathForClass
|
import kotlin.script.experimental.jvm.impl.tryGetResourcePathForClass
|
||||||
import kotlin.script.experimental.jvm.impl.tryGetResourcePathForClassByName
|
import kotlin.script.experimental.jvm.impl.tryGetResourcePathForClassByName
|
||||||
import kotlin.script.templates.standard.ScriptTemplateWithArgs
|
import kotlin.script.templates.standard.ScriptTemplateWithArgs
|
||||||
@@ -32,6 +34,11 @@ internal const val KOTLIN_SCRIPTING_JVM_JAR = "kotlin-scripting-jvm.jar"
|
|||||||
internal const val KOTLIN_COMPILER_NAME = "kotlin-compiler"
|
internal const val KOTLIN_COMPILER_NAME = "kotlin-compiler"
|
||||||
internal const val KOTLIN_COMPILER_JAR = "$KOTLIN_COMPILER_NAME.jar"
|
internal const val KOTLIN_COMPILER_JAR = "$KOTLIN_COMPILER_NAME.jar"
|
||||||
|
|
||||||
|
private val JAR_COLLECTIONS_CLASSES_PATHS = arrayOf("BOOT-INF/classes", "WEB-INF/classes")
|
||||||
|
private val JAR_COLLECTIONS_LIB_PATHS = arrayOf("BOOT-INF/lib", "WEB-INF/lib")
|
||||||
|
private val JAR_COLLECTIONS_KEY_PATHS = JAR_COLLECTIONS_CLASSES_PATHS + JAR_COLLECTIONS_LIB_PATHS
|
||||||
|
private const val JAR_MANIFEST_RESOURCE_NAME = "META-INF/MANIFEST.MF"
|
||||||
|
|
||||||
internal const val KOTLIN_SCRIPT_CLASSPATH_PROPERTY = "kotlin.script.classpath"
|
internal const val KOTLIN_SCRIPT_CLASSPATH_PROPERTY = "kotlin.script.classpath"
|
||||||
internal const val KOTLIN_COMPILER_CLASSPATH_PROPERTY = "kotlin.compiler.classpath"
|
internal const val KOTLIN_COMPILER_CLASSPATH_PROPERTY = "kotlin.compiler.classpath"
|
||||||
internal const val KOTLIN_COMPILER_JAR_PROPERTY = "kotlin.compiler.jar"
|
internal const val KOTLIN_COMPILER_JAR_PROPERTY = "kotlin.compiler.jar"
|
||||||
@@ -43,32 +50,105 @@ internal const val KOTLIN_RUNTIME_JAR_PROPERTY = "kotlin.java.runtime.jar"
|
|||||||
internal const val KOTLIN_SCRIPT_RUNTIME_JAR_PROPERTY = "kotlin.script.runtime.jar"
|
internal const val KOTLIN_SCRIPT_RUNTIME_JAR_PROPERTY = "kotlin.script.runtime.jar"
|
||||||
|
|
||||||
private val validClasspathFilesExtensions = setOf("jar", "zip", "java")
|
private val validClasspathFilesExtensions = setOf("jar", "zip", "java")
|
||||||
|
private val validJarCollectionFilesExtensions = setOf("jar", "war", "zip")
|
||||||
|
|
||||||
fun classpathFromClassloader(classLoader: ClassLoader): List<File>? =
|
fun classpathFromClassloader(currentClassLoader: ClassLoader, unpackJarCollections: Boolean = false): List<File>? {
|
||||||
allRelatedClassLoaders(classLoader).toList().flatMap {
|
val processedJars = hashSetOf<File>()
|
||||||
val urls = (it as? URLClassLoader)?.urLs?.asList()
|
val unpackJarCollectionsDir by lazy {
|
||||||
?: try {
|
createTempDir("unpackedJarCollections").canonicalFile.also {
|
||||||
// e.g. for IDEA platform UrlClassLoader
|
Runtime.getRuntime().addShutdownHook(Thread {
|
||||||
val getUrls = it::class.java.getMethod("getUrls")
|
it.deleteRecursively()
|
||||||
getUrls.isAccessible = true
|
})
|
||||||
val result = getUrls.invoke(it) as? List<Any?>
|
|
||||||
result?.filterIsInstance<URL>()
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
urls?.mapNotNull {
|
|
||||||
// taking only classpath elements pointing to dirs (presumably with classes) or jars, because this classpath is intended for
|
|
||||||
// usage with the kotlin compiler, which cannot process other types of entries, e.g. jni libs
|
|
||||||
it.toFile()?.takeIf { el -> el.isDirectory || validClasspathFilesExtensions.any { el.extension == it } }
|
|
||||||
}
|
}
|
||||||
?: emptyList()
|
}
|
||||||
}.distinct().takeIf { it.isNotEmpty() }
|
return allRelatedClassLoaders(currentClassLoader).flatMap { classLoader ->
|
||||||
|
var classPath = emptySequence<File>()
|
||||||
|
if (unpackJarCollections && JAR_COLLECTIONS_KEY_PATHS.any { classLoader.getResource(it)?.file?.isNotEmpty() == true }) {
|
||||||
|
// if cache dir is specified, find all jar collections (spring boot fat jars and WARs so far, and unpack it accordingly
|
||||||
|
val jarCollections = JAR_COLLECTIONS_KEY_PATHS.asSequence().flatMap { currentClassLoader.getResources(it).asSequence() }
|
||||||
|
.mapNotNull {
|
||||||
|
it.toContainingFileOrNull()?.takeIf { file ->
|
||||||
|
// additionally mark/check processed collection jars since unpacking is expensive
|
||||||
|
file.extension in validJarCollectionFilesExtensions && processedJars.add(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
classPath += jarCollections.flatMap { it.unpackJarCollection(unpackJarCollectionsDir) }.filter { it.isValidClasspathFile() }
|
||||||
|
}
|
||||||
|
classPath += when (classLoader) {
|
||||||
|
is URLClassLoader -> {
|
||||||
|
classLoader.urLs.asSequence().mapNotNull { url -> url.toValidClasspathFileOrNull() }
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
classLoader.classPathFromGetUrlsMethodOrNull()
|
||||||
|
?: classLoader.classPathFromManifestResourceUrls()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
classPath
|
||||||
|
}.filter { processedJars.add(it) }
|
||||||
|
.toList().takeIf { it.isNotEmpty() }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun URL.toValidClasspathFileOrNull(): File? = toContainingFileOrNull()?.takeIf { it.isValidClasspathFile() }
|
||||||
|
|
||||||
|
internal fun File.isValidClasspathFile(): Boolean =
|
||||||
|
isDirectory || (isFile && extension in validClasspathFilesExtensions)
|
||||||
|
|
||||||
|
private fun ClassLoader.classPathFromGetUrlsMethodOrNull(): Sequence<File>? {
|
||||||
|
return try {
|
||||||
|
// e.g. for IDEA platform UrlClassLoader
|
||||||
|
val getUrls = this::class.java.getMethod("getUrls")
|
||||||
|
getUrls.isAccessible = true
|
||||||
|
val result = getUrls.invoke(this) as? List<Any?>
|
||||||
|
result?.asSequence()?.filterIsInstance<URL>()?.mapNotNull { it.toValidClasspathFileOrNull() }
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun ClassLoader.classPathFromManifestResourceUrls(): Sequence<File> =
|
||||||
|
getResources(JAR_MANIFEST_RESOURCE_NAME).asSequence().distinct().mapNotNull { it.toValidClasspathFileOrNull() }
|
||||||
|
|
||||||
|
private fun File.unpackJarCollection(rootTempDir: File?): Sequence<File> {
|
||||||
|
val targetDir = createTempDir(nameWithoutExtension, directory = rootTempDir)
|
||||||
|
return try {
|
||||||
|
ArrayList<File>().apply {
|
||||||
|
JarInputStream(FileInputStream(this@unpackJarCollection)).use { jarInputStream ->
|
||||||
|
for (classesDir in JAR_COLLECTIONS_CLASSES_PATHS) {
|
||||||
|
add(File(targetDir, classesDir))
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
val entry = jarInputStream.nextJarEntry
|
||||||
|
if (entry != null) {
|
||||||
|
try {
|
||||||
|
if (!entry.isDirectory) {
|
||||||
|
val file = File(targetDir, entry.name)
|
||||||
|
if (JAR_COLLECTIONS_LIB_PATHS.any { entry.name.startsWith("$it/") }) {
|
||||||
|
add(file)
|
||||||
|
}
|
||||||
|
file.parentFile.mkdirs()
|
||||||
|
file.outputStream().use { outputStream ->
|
||||||
|
jarInputStream.copyTo(outputStream)
|
||||||
|
outputStream.flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
jarInputStream.closeEntry()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (entry != null)
|
||||||
|
}
|
||||||
|
}.asSequence()
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
targetDir.deleteRecursively()
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun classpathFromClasspathProperty(): List<File>? =
|
fun classpathFromClasspathProperty(): List<File>? =
|
||||||
System.getProperty("java.class.path")
|
System.getProperty("java.class.path")
|
||||||
?.split(String.format("\\%s", File.pathSeparatorChar).toRegex())
|
?.split(String.format("\\%s", File.pathSeparatorChar).toRegex())
|
||||||
?.dropLastWhile(String::isEmpty)
|
?.dropLastWhile(String::isEmpty)
|
||||||
?.map(::File)
|
?.map(::File)
|
||||||
|
|
||||||
fun classpathFromClass(classLoader: ClassLoader, klass: KClass<out Any>): List<File>? =
|
fun classpathFromClass(classLoader: ClassLoader, klass: KClass<out Any>): List<File>? =
|
||||||
classpathFromFQN(classLoader, klass.qualifiedName!!)
|
classpathFromFQN(classLoader, klass.qualifiedName!!)
|
||||||
@@ -82,21 +162,22 @@ fun classpathFromFQN(classLoader: ClassLoader, fqn: String): List<File>? {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun File.matchMaybeVersionedFile(baseName: String) =
|
fun File.matchMaybeVersionedFile(baseName: String) =
|
||||||
name == baseName ||
|
name == baseName ||
|
||||||
name == baseName.removeSuffix(".jar") || // for classes dirs
|
name == baseName.removeSuffix(".jar") || // for classes dirs
|
||||||
Regex(Regex.escape(baseName.removeSuffix(".jar")) + "(-\\d.*)?\\.jar").matches(name)
|
Regex(Regex.escape(baseName.removeSuffix(".jar")) + "(-\\d.*)?\\.jar").matches(name)
|
||||||
|
|
||||||
fun File.hasParentNamed(baseName: String): Boolean =
|
fun File.hasParentNamed(baseName: String): Boolean =
|
||||||
nameWithoutExtension == baseName || parentFile?.hasParentNamed(baseName) ?: false
|
nameWithoutExtension == baseName || parentFile?.hasParentNamed(baseName) ?: false
|
||||||
|
|
||||||
private const val KOTLIN_COMPILER_EMBEDDABLE_JAR = "$KOTLIN_COMPILER_NAME-embeddable.jar"
|
private const val KOTLIN_COMPILER_EMBEDDABLE_JAR = "$KOTLIN_COMPILER_NAME-embeddable.jar"
|
||||||
|
|
||||||
|
// Iterating over classloaders tree in a regular, parent-first order
|
||||||
private fun allRelatedClassLoaders(clsLoader: ClassLoader, visited: MutableSet<ClassLoader> = HashSet()): Sequence<ClassLoader> {
|
private fun allRelatedClassLoaders(clsLoader: ClassLoader, visited: MutableSet<ClassLoader> = HashSet()): Sequence<ClassLoader> {
|
||||||
if (!visited.add(clsLoader)) return emptySequence()
|
if (!visited.add(clsLoader)) return emptySequence()
|
||||||
|
|
||||||
val singleParent = clsLoader.parent
|
val singleParent = clsLoader.parent
|
||||||
if (singleParent != null)
|
if (singleParent != null)
|
||||||
return sequenceOf(clsLoader) + sequenceOf(singleParent).flatMap { allRelatedClassLoaders(it, visited) }
|
return sequenceOf(singleParent).flatMap { allRelatedClassLoaders(it, visited) } + clsLoader
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
val field = clsLoader.javaClass.getDeclaredField("myParents") // com.intellij.ide.plugins.cl.PluginClassLoader
|
val field = clsLoader.javaClass.getDeclaredField("myParents") // com.intellij.ide.plugins.cl.PluginClassLoader
|
||||||
@@ -104,7 +185,9 @@ private fun allRelatedClassLoaders(clsLoader: ClassLoader, visited: MutableSet<C
|
|||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
val arrayOfClassLoaders = field.get(clsLoader) as Array<ClassLoader>
|
val arrayOfClassLoaders = field.get(clsLoader) as Array<ClassLoader>
|
||||||
sequenceOf(clsLoader) + arrayOfClassLoaders.asSequence().flatMap { allRelatedClassLoaders(it, visited) }
|
// TODO: PluginClassLoader uses filtering (mustBeLoadedByPlatform), consider using the same logic, if possible
|
||||||
|
// (untill proper compiling from classloader instead of classpath is implemented)
|
||||||
|
arrayOfClassLoaders.asSequence().flatMap { allRelatedClassLoaders(it, visited) } + clsLoader
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
sequenceOf(clsLoader)
|
sequenceOf(clsLoader)
|
||||||
}
|
}
|
||||||
@@ -112,9 +195,9 @@ private fun allRelatedClassLoaders(clsLoader: ClassLoader, visited: MutableSet<C
|
|||||||
|
|
||||||
|
|
||||||
internal fun List<File>.takeIfContainsAll(vararg keyNames: String): List<File>? =
|
internal fun List<File>.takeIfContainsAll(vararg keyNames: String): List<File>? =
|
||||||
takeIf { classpath ->
|
takeIf { classpath ->
|
||||||
keyNames.all { key -> classpath.any { it.matchMaybeVersionedFile(key) } }
|
keyNames.all { key -> classpath.any { it.matchMaybeVersionedFile(key) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun List<File>.filterIfContainsAll(vararg keyNames: String): List<File>? {
|
internal fun List<File>.filterIfContainsAll(vararg keyNames: String): List<File>? {
|
||||||
val res = hashMapOf<String, File>()
|
val res = hashMapOf<String, File>()
|
||||||
@@ -133,25 +216,30 @@ internal fun List<File>.filterIfContainsAll(vararg keyNames: String): List<File>
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal fun List<File>.takeIfContainsAny(vararg keyNames: String): List<File>? =
|
internal fun List<File>.takeIfContainsAny(vararg keyNames: String): List<File>? =
|
||||||
takeIf { classpath ->
|
takeIf { classpath ->
|
||||||
keyNames.any { key -> classpath.any { it.matchMaybeVersionedFile(key) } }
|
keyNames.any { key -> classpath.any { it.matchMaybeVersionedFile(key) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun scriptCompilationClasspathFromContextOrNull(
|
fun scriptCompilationClasspathFromContextOrNull(
|
||||||
vararg keyNames: String,
|
vararg keyNames: String,
|
||||||
classLoader: ClassLoader = Thread.currentThread().contextClassLoader,
|
classLoader: ClassLoader = Thread.currentThread().contextClassLoader,
|
||||||
wholeClasspath: Boolean = false
|
wholeClasspath: Boolean = false,
|
||||||
|
unpackJarCollections: Boolean = false
|
||||||
): List<File>? {
|
): List<File>? {
|
||||||
fun List<File>.takeAndFilter() = when {
|
fun List<File>.takeAndFilter() = when {
|
||||||
isEmpty() -> null
|
isEmpty() -> null
|
||||||
wholeClasspath -> takeIfContainsAll(*keyNames)
|
wholeClasspath -> takeIfContainsAll(*keyNames)
|
||||||
else -> filterIfContainsAll(*keyNames)
|
else -> filterIfContainsAll(*keyNames)
|
||||||
}
|
}
|
||||||
return System.getProperty(KOTLIN_SCRIPT_CLASSPATH_PROPERTY)?.split(File.pathSeparator)?.map(::File)
|
|
||||||
?: classpathFromClassloader(classLoader)?.takeAndFilter()
|
val fromProperty = System.getProperty(KOTLIN_SCRIPT_CLASSPATH_PROPERTY)?.split(File.pathSeparator)?.map(::File)
|
||||||
|
if (fromProperty != null) return fromProperty
|
||||||
|
|
||||||
|
return classpathFromClassloader(classLoader, unpackJarCollections)?.takeAndFilter()
|
||||||
?: classpathFromClasspathProperty()?.takeAndFilter()
|
?: classpathFromClasspathProperty()?.takeAndFilter()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun scriptCompilationClasspathFromContextOrStdlib(
|
fun scriptCompilationClasspathFromContextOrStdlib(
|
||||||
vararg keyNames: String,
|
vararg keyNames: String,
|
||||||
classLoader: ClassLoader = Thread.currentThread().contextClassLoader,
|
classLoader: ClassLoader = Thread.currentThread().contextClassLoader,
|
||||||
@@ -162,25 +250,27 @@ fun scriptCompilationClasspathFromContextOrStdlib(
|
|||||||
classLoader = classLoader,
|
classLoader = classLoader,
|
||||||
wholeClasspath = wholeClasspath
|
wholeClasspath = wholeClasspath
|
||||||
)
|
)
|
||||||
?: KotlinJars.kotlinScriptStandardJars
|
?: KotlinJars.kotlinScriptStandardJars
|
||||||
|
|
||||||
fun scriptCompilationClasspathFromContext(
|
fun scriptCompilationClasspathFromContext(
|
||||||
vararg keyNames: String,
|
vararg keyNames: String,
|
||||||
classLoader: ClassLoader = Thread.currentThread().contextClassLoader,
|
classLoader: ClassLoader = Thread.currentThread().contextClassLoader,
|
||||||
wholeClasspath: Boolean = false
|
wholeClasspath: Boolean = false,
|
||||||
|
unpackJarCollections: Boolean = false
|
||||||
): List<File> =
|
): List<File> =
|
||||||
scriptCompilationClasspathFromContextOrNull(
|
scriptCompilationClasspathFromContextOrNull(
|
||||||
*keyNames,
|
*keyNames,
|
||||||
classLoader = classLoader,
|
classLoader = classLoader,
|
||||||
wholeClasspath = wholeClasspath
|
wholeClasspath = wholeClasspath,
|
||||||
|
unpackJarCollections = unpackJarCollections
|
||||||
)
|
)
|
||||||
?: throw Exception("Unable to get script compilation classpath from context, please specify explicit classpath via \"$KOTLIN_SCRIPT_CLASSPATH_PROPERTY\" property")
|
?: throw Exception("Unable to get script compilation classpath from context, please specify explicit classpath via \"$KOTLIN_SCRIPT_CLASSPATH_PROPERTY\" property")
|
||||||
|
|
||||||
object KotlinJars {
|
object KotlinJars {
|
||||||
|
|
||||||
private val explicitCompilerClasspath: List<File>? by lazy {
|
private val explicitCompilerClasspath: List<File>? by lazy {
|
||||||
System.getProperty(KOTLIN_COMPILER_CLASSPATH_PROPERTY)?.split(File.pathSeparator)?.map(::File)
|
System.getProperty(KOTLIN_COMPILER_CLASSPATH_PROPERTY)?.split(File.pathSeparator)?.map(::File)
|
||||||
?: System.getProperty(KOTLIN_COMPILER_JAR_PROPERTY)?.let(::File)?.takeIf(File::exists)?.let { listOf(it) }
|
?: System.getProperty(KOTLIN_COMPILER_JAR_PROPERTY)?.let(::File)?.takeIf(File::exists)?.let { listOf(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
val compilerClasspath: List<File> by lazy {
|
val compilerClasspath: List<File> by lazy {
|
||||||
@@ -215,7 +305,7 @@ object KotlinJars {
|
|||||||
|
|
||||||
val classpath = explicitCompilerClasspath
|
val classpath = explicitCompilerClasspath
|
||||||
// search classpath from context classloader and `java.class.path` property
|
// search classpath from context classloader and `java.class.path` property
|
||||||
?: (classpathFromFQN( Thread.currentThread().contextClassLoader, "org.jetbrains.kotlin.cli.jvm.K2JVMCompiler" )
|
?: (classpathFromFQN(Thread.currentThread().contextClassLoader, "org.jetbrains.kotlin.cli.jvm.K2JVMCompiler")
|
||||||
?: classpathFromClassloader(Thread.currentThread().contextClassLoader)?.takeIf { it.isNotEmpty() }
|
?: classpathFromClassloader(Thread.currentThread().contextClassLoader)?.takeIf { it.isNotEmpty() }
|
||||||
?: classpathFromClasspathProperty()
|
?: classpathFromClasspathProperty()
|
||||||
)?.filter { f -> kotlinBaseJars.any { f.matchMaybeVersionedFile(it) } }?.takeIf { it.isNotEmpty() }
|
)?.filter { f -> kotlinBaseJars.any { f.matchMaybeVersionedFile(it) } }?.takeIf { it.isNotEmpty() }
|
||||||
@@ -254,16 +344,16 @@ object KotlinJars {
|
|||||||
|
|
||||||
val stdlibOrNull: File? by lazy {
|
val stdlibOrNull: File? by lazy {
|
||||||
System.getProperty(KOTLIN_STDLIB_JAR_PROPERTY)?.let(::File)?.takeIf(File::exists)
|
System.getProperty(KOTLIN_STDLIB_JAR_PROPERTY)?.let(::File)?.takeIf(File::exists)
|
||||||
?: getLib(
|
?: getLib(
|
||||||
KOTLIN_RUNTIME_JAR_PROPERTY,
|
KOTLIN_RUNTIME_JAR_PROPERTY,
|
||||||
KOTLIN_JAVA_STDLIB_JAR,
|
KOTLIN_JAVA_STDLIB_JAR,
|
||||||
JvmStatic::class
|
JvmStatic::class
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val stdlib: File by lazy {
|
val stdlib: File by lazy {
|
||||||
stdlibOrNull
|
stdlibOrNull
|
||||||
?: throw Exception("Unable to find kotlin stdlib, please specify it explicitly via \"$KOTLIN_STDLIB_JAR_PROPERTY\" property")
|
?: throw Exception("Unable to find kotlin stdlib, please specify it explicitly via \"$KOTLIN_STDLIB_JAR_PROPERTY\" property")
|
||||||
}
|
}
|
||||||
|
|
||||||
val reflectOrNull: File? by lazy {
|
val reflectOrNull: File? by lazy {
|
||||||
@@ -284,11 +374,12 @@ object KotlinJars {
|
|||||||
|
|
||||||
val scriptRuntime: File by lazy {
|
val scriptRuntime: File by lazy {
|
||||||
scriptRuntimeOrNull
|
scriptRuntimeOrNull
|
||||||
?: throw Exception("Unable to find kotlin script runtime, please specify it explicitly via \"$KOTLIN_SCRIPT_RUNTIME_JAR_PROPERTY\" property")
|
?: throw Exception("Unable to find kotlin script runtime, please specify it explicitly via \"$KOTLIN_SCRIPT_RUNTIME_JAR_PROPERTY\" property")
|
||||||
}
|
}
|
||||||
|
|
||||||
val kotlinScriptStandardJars get() = listOf(
|
val kotlinScriptStandardJars
|
||||||
stdlibOrNull,
|
get() = listOf(
|
||||||
scriptRuntimeOrNull
|
stdlibOrNull,
|
||||||
).filterNotNull()
|
scriptRuntimeOrNull
|
||||||
|
).filterNotNull()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user