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:
Ilya Chernikov
2019-05-28 19:18:13 +02:00
parent e95569a273
commit f986856d03
6 changed files with 252 additions and 65 deletions
@@ -6,10 +6,14 @@
package kotlin.script.experimental.jsr223
import org.jetbrains.kotlin.cli.common.repl.KotlinJsr223JvmScriptEngineFactoryBase
import java.io.File
import javax.script.ScriptEngine
import kotlin.script.experimental.api.ScriptCompilationConfiguration
import kotlin.script.experimental.jvm.JvmScriptCompilationConfigurationBuilder
import kotlin.script.experimental.jvm.dependenciesFromCurrentContext
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.createJvmEvaluationConfigurationFromTemplate
import kotlin.script.experimental.jvmhost.jsr223.KotlinJsr223ScriptEngineImpl
@@ -18,13 +22,31 @@ class KotlinJsr223DefaultScriptEngineFactory : KotlinJsr223JvmScriptEngineFactor
private val compilationConfiguration = createJvmCompilationConfigurationFromTemplate<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 =
KotlinJsr223ScriptEngineImpl(
this,
ScriptCompilationConfiguration(compilationConfiguration) {
jvm {
dependenciesFromCurrentContext(wholeClasspath = true)
dependenciesFromCurrentContext()
}
},
evaluationConfiguration
@@ -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)
}
@@ -105,7 +105,7 @@ internal fun List<ScriptDependency>?.toClassPathOrEmpty() = this?.flatMap { (it
internal fun List<SourceCode>?.toFilesOrEmpty() = this?.map {
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})")
} ?: emptyList()
@@ -6,6 +6,7 @@
package kotlin.script.experimental.jvm.impl
import java.io.File
import java.net.JarURLConnection
import java.net.URL
// 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
val protocol = resourceURL.protocol
if (protocol == FILE_PROTOCOL) {
val path = resourceURL.toFile()!!.path
val path = resourceURL.toFileOrNull()!!.path
val testPath = path.replace('\\', '/')
val testResourcePath = resourcePath.replace('\\', '/')
if (testPath.endsWith(testResourcePath, ignoreCase = true)) {
@@ -57,7 +58,7 @@ private fun splitJarUrl(url: String): Pair<String, String>? {
if (jarPath.startsWith(FILE_PROTOCOL)) {
try {
jarPath = URL(jarPath).toFile()!!.path.replace('\\', '/')
jarPath = URL(jarPath).toFileOrNull()!!.path.replace('\\', '/')
} catch (e: Exception) {
jarPath = jarPath.substring(FILE_PROTOCOL.length)
if (jarPath.startsWith(SCHEME_SEPARATOR)) {
@@ -91,11 +92,15 @@ fun tryGetResourcePathForClassByName(name: String, classLoader: ClassLoader): Fi
null
}
internal fun URL.toFile() =
internal fun URL.toFileOrNull() =
try {
File(toURI().schemeSpecificPart)
File(toURI().schemeSpecificPart).canonicalFile
} catch (e: java.net.URISyntaxException) {
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)
}
fun JvmScriptCompilationConfigurationBuilder.dependenciesFromCurrentContext(vararg libraries: String, wholeClasspath: Boolean = false) {
dependenciesFromClassloader(*libraries, wholeClasspath = wholeClasspath)
fun JvmScriptCompilationConfigurationBuilder.dependenciesFromCurrentContext(
vararg libraries: String,
wholeClasspath: Boolean = false,
unpackJarCollections: Boolean = false
) {
dependenciesFromClassloader(*libraries, wholeClasspath = wholeClasspath, unpackJarCollections = unpackJarCollections)
}
fun JvmScriptCompilationConfigurationBuilder.dependenciesFromClassloader(
vararg libraries: String,
classLoader: ClassLoader = Thread.currentThread().contextClassLoader,
wholeClasspath: Boolean = false
wholeClasspath: Boolean = false,
unpackJarCollections: Boolean = false
) {
updateClasspath(
scriptCompilationClasspathFromContext(*libraries, classLoader = classLoader, wholeClasspath = wholeClasspath)
scriptCompilationClasspathFromContext(
*libraries, classLoader = classLoader, wholeClasspath = wholeClasspath, unpackJarCollections = unpackJarCollections
)
)
}
@@ -6,11 +6,13 @@
package kotlin.script.experimental.jvm.util
import java.io.File
import java.io.FileInputStream
import java.io.FileNotFoundException
import java.net.URL
import java.net.URLClassLoader
import java.util.jar.JarInputStream
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.tryGetResourcePathForClassByName
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_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_COMPILER_CLASSPATH_PROPERTY = "kotlin.compiler.classpath"
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"
private val validClasspathFilesExtensions = setOf("jar", "zip", "java")
private val validJarCollectionFilesExtensions = setOf("jar", "war", "zip")
fun classpathFromClassloader(classLoader: ClassLoader): List<File>? =
allRelatedClassLoaders(classLoader).toList().flatMap {
val urls = (it as? URLClassLoader)?.urLs?.asList()
?: try {
// e.g. for IDEA platform UrlClassLoader
val getUrls = it::class.java.getMethod("getUrls")
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 } }
fun classpathFromClassloader(currentClassLoader: ClassLoader, unpackJarCollections: Boolean = false): List<File>? {
val processedJars = hashSetOf<File>()
val unpackJarCollectionsDir by lazy {
createTempDir("unpackedJarCollections").canonicalFile.also {
Runtime.getRuntime().addShutdownHook(Thread {
it.deleteRecursively()
})
}
?: 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>? =
System.getProperty("java.class.path")
?.split(String.format("\\%s", File.pathSeparatorChar).toRegex())
?.dropLastWhile(String::isEmpty)
?.map(::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>? =
classpathFromFQN(classLoader, klass.qualifiedName!!)
@@ -82,21 +162,22 @@ fun classpathFromFQN(classLoader: ClassLoader, fqn: String): List<File>? {
}
fun File.matchMaybeVersionedFile(baseName: String) =
name == baseName ||
name == baseName.removeSuffix(".jar") || // for classes dirs
Regex(Regex.escape(baseName.removeSuffix(".jar")) + "(-\\d.*)?\\.jar").matches(name)
name == baseName ||
name == baseName.removeSuffix(".jar") || // for classes dirs
Regex(Regex.escape(baseName.removeSuffix(".jar")) + "(-\\d.*)?\\.jar").matches(name)
fun File.hasParentNamed(baseName: String): Boolean =
nameWithoutExtension == baseName || parentFile?.hasParentNamed(baseName) ?: false
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> {
if (!visited.add(clsLoader)) return emptySequence()
val singleParent = clsLoader.parent
if (singleParent != null)
return sequenceOf(clsLoader) + sequenceOf(singleParent).flatMap { allRelatedClassLoaders(it, visited) }
return sequenceOf(singleParent).flatMap { allRelatedClassLoaders(it, visited) } + clsLoader
return try {
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")
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) {
sequenceOf(clsLoader)
}
@@ -112,9 +195,9 @@ private fun allRelatedClassLoaders(clsLoader: ClassLoader, visited: MutableSet<C
internal fun List<File>.takeIfContainsAll(vararg keyNames: String): List<File>? =
takeIf { classpath ->
keyNames.all { key -> classpath.any { it.matchMaybeVersionedFile(key) } }
}
takeIf { classpath ->
keyNames.all { key -> classpath.any { it.matchMaybeVersionedFile(key) } }
}
internal fun List<File>.filterIfContainsAll(vararg keyNames: String): List<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>? =
takeIf { classpath ->
keyNames.any { key -> classpath.any { it.matchMaybeVersionedFile(key) } }
}
takeIf { classpath ->
keyNames.any { key -> classpath.any { it.matchMaybeVersionedFile(key) } }
}
fun scriptCompilationClasspathFromContextOrNull(
vararg keyNames: String,
classLoader: ClassLoader = Thread.currentThread().contextClassLoader,
wholeClasspath: Boolean = false
wholeClasspath: Boolean = false,
unpackJarCollections: Boolean = false
): List<File>? {
fun List<File>.takeAndFilter() = when {
isEmpty() -> null
wholeClasspath -> takeIfContainsAll(*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()
}
fun scriptCompilationClasspathFromContextOrStdlib(
vararg keyNames: String,
classLoader: ClassLoader = Thread.currentThread().contextClassLoader,
@@ -162,25 +250,27 @@ fun scriptCompilationClasspathFromContextOrStdlib(
classLoader = classLoader,
wholeClasspath = wholeClasspath
)
?: KotlinJars.kotlinScriptStandardJars
?: KotlinJars.kotlinScriptStandardJars
fun scriptCompilationClasspathFromContext(
vararg keyNames: String,
classLoader: ClassLoader = Thread.currentThread().contextClassLoader,
wholeClasspath: Boolean = false
wholeClasspath: Boolean = false,
unpackJarCollections: Boolean = false
): List<File> =
scriptCompilationClasspathFromContextOrNull(
*keyNames,
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 {
private val explicitCompilerClasspath: List<File>? by lazy {
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 {
@@ -215,7 +305,7 @@ object KotlinJars {
val classpath = explicitCompilerClasspath
// 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() }
?: classpathFromClasspathProperty()
)?.filter { f -> kotlinBaseJars.any { f.matchMaybeVersionedFile(it) } }?.takeIf { it.isNotEmpty() }
@@ -254,16 +344,16 @@ object KotlinJars {
val stdlibOrNull: File? by lazy {
System.getProperty(KOTLIN_STDLIB_JAR_PROPERTY)?.let(::File)?.takeIf(File::exists)
?: getLib(
KOTLIN_RUNTIME_JAR_PROPERTY,
KOTLIN_JAVA_STDLIB_JAR,
JvmStatic::class
)
?: getLib(
KOTLIN_RUNTIME_JAR_PROPERTY,
KOTLIN_JAVA_STDLIB_JAR,
JvmStatic::class
)
}
val stdlib: File by lazy {
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 {
@@ -284,11 +374,12 @@ object KotlinJars {
val scriptRuntime: File by lazy {
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(
stdlibOrNull,
scriptRuntimeOrNull
).filterNotNull()
val kotlinScriptStandardJars
get() = listOf(
stdlibOrNull,
scriptRuntimeOrNull
).filterNotNull()
}