From f986856d03f8d441142b7bee9f6b0236a845f444 Mon Sep 17 00:00:00 2001 From: Ilya Chernikov Date: Tue, 28 May 2019 19:18:13 +0200 Subject: [PATCH] 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 --- .../KotlinJsr223DefaultScriptEngineFactory.kt | 24 ++- .../jvmhost/test/ClassPathTest.kt | 62 ++++++ .../jvm/impl/BridgeDependenciesResolver.kt | 2 +- .../script/experimental/jvm/impl/pathUtil.kt | 15 +- .../experimental/jvm/jvmScriptCompilation.kt | 15 +- .../experimental/jvm/util/jvmClasspathUtil.kt | 199 +++++++++++++----- 6 files changed, 252 insertions(+), 65 deletions(-) create mode 100644 libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ClassPathTest.kt diff --git a/libraries/scripting/jsr223/src/kotlin/script/experimental/jsr223/KotlinJsr223DefaultScriptEngineFactory.kt b/libraries/scripting/jsr223/src/kotlin/script/experimental/jsr223/KotlinJsr223DefaultScriptEngineFactory.kt index e805e4fd131..2e0d3fc1950 100644 --- a/libraries/scripting/jsr223/src/kotlin/script/experimental/jsr223/KotlinJsr223DefaultScriptEngineFactory.kt +++ b/libraries/scripting/jsr223/src/kotlin/script/experimental/jsr223/KotlinJsr223DefaultScriptEngineFactory.kt @@ -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() private val evaluationConfiguration = createJvmEvaluationConfigurationFromTemplate() + private var lastClassLoader: ClassLoader? = null + private var lastClassPath: List? = 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 diff --git a/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ClassPathTest.kt b/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ClassPathTest.kt new file mode 100644 index 00000000000..ad63a39ee83 --- /dev/null +++ b/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ClassPathTest.kt @@ -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, 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, fileNames: Array) { + + fun List.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) +} \ No newline at end of file diff --git a/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/impl/BridgeDependenciesResolver.kt b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/impl/BridgeDependenciesResolver.kt index 16cd320eae4..a939151cc39 100644 --- a/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/impl/BridgeDependenciesResolver.kt +++ b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/impl/BridgeDependenciesResolver.kt @@ -105,7 +105,7 @@ internal fun List?.toClassPathOrEmpty() = this?.flatMap { (it internal fun List?.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() diff --git a/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/impl/pathUtil.kt b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/impl/pathUtil.kt index a76d5b5979f..61d83be0490 100644 --- a/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/impl/pathUtil.kt +++ b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/impl/pathUtil.kt @@ -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? { 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() diff --git a/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/jvmScriptCompilation.kt b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/jvmScriptCompilation.kt index 52196fa7e34..118130645ce 100644 --- a/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/jvmScriptCompilation.kt +++ b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/jvmScriptCompilation.kt @@ -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 + ) ) } diff --git a/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/util/jvmClasspathUtil.kt b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/util/jvmClasspathUtil.kt index b34d7858c40..2612a3fe4c4 100644 --- a/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/util/jvmClasspathUtil.kt +++ b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/util/jvmClasspathUtil.kt @@ -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? = - 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 - result?.filterIsInstance() - } 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? { + val processedJars = hashSetOf() + 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() + 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? { + 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 + result?.asSequence()?.filterIsInstance()?.mapNotNull { it.toValidClasspathFileOrNull() } + } catch (e: Throwable) { + null + } +} + +internal fun ClassLoader.classPathFromManifestResourceUrls(): Sequence = + getResources(JAR_MANIFEST_RESOURCE_NAME).asSequence().distinct().mapNotNull { it.toValidClasspathFileOrNull() } + +private fun File.unpackJarCollection(rootTempDir: File?): Sequence { + val targetDir = createTempDir(nameWithoutExtension, directory = rootTempDir) + return try { + ArrayList().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? = - 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): List? = classpathFromFQN(classLoader, klass.qualifiedName!!) @@ -82,21 +162,22 @@ fun classpathFromFQN(classLoader: ClassLoader, fqn: String): List? { } 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 = HashSet()): Sequence { 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 - 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.takeIfContainsAll(vararg keyNames: String): List? = - takeIf { classpath -> - keyNames.all { key -> classpath.any { it.matchMaybeVersionedFile(key) } } - } + takeIf { classpath -> + keyNames.all { key -> classpath.any { it.matchMaybeVersionedFile(key) } } + } internal fun List.filterIfContainsAll(vararg keyNames: String): List? { val res = hashMapOf() @@ -133,25 +216,30 @@ internal fun List.filterIfContainsAll(vararg keyNames: String): List } internal fun List.takeIfContainsAny(vararg keyNames: String): List? = - 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? { fun List.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 = 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? 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 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() }