Implement main method generation in scripts and runnable jar saving
refactor necessary parts on the way
This commit is contained in:
+1
-2
@@ -8,7 +8,6 @@ package kotlin.script.experimental.jvm
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.script.experimental.api.*
|
||||
import kotlin.script.experimental.jvm.impl.getConfigurationWithClassloader
|
||||
import kotlin.script.experimental.jvm.impl.sharedScripts
|
||||
|
||||
open class BasicJvmScriptEvaluator : ScriptEvaluator {
|
||||
|
||||
@@ -23,7 +22,7 @@ open class BasicJvmScriptEvaluator : ScriptEvaluator {
|
||||
// run as SAM
|
||||
// return res
|
||||
|
||||
val sharedScripts = configuration.sharedScripts
|
||||
val sharedScripts = configuration[ScriptEvaluationConfiguration.jvm.scriptsInstancesSharingMap]
|
||||
|
||||
sharedScripts?.get(scriptClass)?.asSuccess()
|
||||
?: compiledScript.otherScripts.mapSuccess {
|
||||
|
||||
+52
-2
@@ -6,7 +6,10 @@
|
||||
package kotlin.script.experimental.jvm.impl
|
||||
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.net.URL
|
||||
import java.net.URLClassLoader
|
||||
import java.util.*
|
||||
|
||||
interface KJvmCompiledModule {
|
||||
fun createClassLoader(baseClassLoader: ClassLoader?): ClassLoader
|
||||
@@ -18,8 +21,55 @@ class KJvmCompiledModuleFromClassPath(val classpath: Collection<File>) : KJvmCom
|
||||
URLClassLoader(classpath.map { it.toURI().toURL() }.toTypedArray(), baseClassLoader)
|
||||
}
|
||||
|
||||
class KJvmCompiledModuleFromLoadedClasses : KJvmCompiledModule {
|
||||
class KJvmCompiledModuleFromClassLoader(val moduleClassLoader: ClassLoader) : KJvmCompiledModule {
|
||||
|
||||
override fun createClassLoader(baseClassLoader: ClassLoader?): ClassLoader =
|
||||
baseClassLoader ?: KJvmCompiledModuleFromLoadedClasses::class.java.classLoader
|
||||
if (baseClassLoader == null) moduleClassLoader
|
||||
else DualClassLoader(moduleClassLoader, baseClassLoader)
|
||||
}
|
||||
|
||||
private class DualClassLoader(fallbackLoader: ClassLoader, parentLoader: ClassLoader?) :
|
||||
ClassLoader(singleClassLoader(fallbackLoader, parentLoader) ?: parentLoader) {
|
||||
|
||||
private class Wrapper(parent: ClassLoader) : ClassLoader(parent) {
|
||||
fun openFindResources(name: String): Enumeration<URL> = super.findResources(name)
|
||||
fun openFindResource(name: String): URL? = super.findResource(name)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private fun singleClassLoader(fallbackLoader: ClassLoader, parentLoader: ClassLoader?): ClassLoader? {
|
||||
tailrec fun ClassLoader.isAncestorOf(other: ClassLoader?): Boolean = when {
|
||||
other == null -> false
|
||||
this === other -> true
|
||||
else -> isAncestorOf(other.parent)
|
||||
}
|
||||
|
||||
return when {
|
||||
parentLoader == null -> fallbackLoader
|
||||
parentLoader.isAncestorOf(fallbackLoader) -> fallbackLoader
|
||||
fallbackLoader.isAncestorOf(parentLoader) -> parentLoader
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val fallbackClassLoader: Wrapper? =
|
||||
if (/* optimization */ parentLoader == null || singleClassLoader(fallbackLoader, parentLoader) != null) null
|
||||
else Wrapper(fallbackLoader)
|
||||
|
||||
override fun findClass(name: String): Class<*> = try {
|
||||
super.findClass(name)
|
||||
} catch (e: ClassNotFoundException) {
|
||||
fallbackClassLoader?.loadClass(name) ?: throw e
|
||||
}
|
||||
|
||||
override fun getResourceAsStream(name: String): InputStream? =
|
||||
super.getResourceAsStream(name) ?: fallbackClassLoader?.getResourceAsStream(name)
|
||||
|
||||
override fun findResources(name: String): Enumeration<URL> =
|
||||
if (fallbackClassLoader == null) super.findResources(name)
|
||||
else Collections.enumeration(super.findResources(name).toList() + fallbackClassLoader.openFindResources(name).asSequence())
|
||||
|
||||
override fun findResource(name: String): URL? =
|
||||
super.findResource(name) ?: fallbackClassLoader?.openFindResource(name)
|
||||
}
|
||||
|
||||
+90
-35
@@ -5,49 +5,77 @@
|
||||
|
||||
package kotlin.script.experimental.jvm.impl
|
||||
|
||||
import java.io.ObjectInputStream
|
||||
import java.io.ObjectOutputStream
|
||||
import java.io.Serializable
|
||||
import java.io.*
|
||||
import java.net.URLClassLoader
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.script.experimental.api.*
|
||||
import kotlin.script.experimental.jvm.*
|
||||
import kotlin.script.experimental.jvm.actualClassLoader
|
||||
|
||||
class KJvmCompiledScript<out ScriptBase : Any>(
|
||||
sourceLocationId: String?,
|
||||
compilationConfiguration: ScriptCompilationConfiguration,
|
||||
private var scriptClassFQName: String,
|
||||
otherScripts: List<CompiledScript<*>> = emptyList(),
|
||||
var compiledModule: KJvmCompiledModule
|
||||
internal class KJvmCompiledScriptData(
|
||||
var sourceLocationId: String?,
|
||||
var compilationConfiguration: ScriptCompilationConfiguration,
|
||||
var scriptClassFQName: String,
|
||||
var otherScripts: List<CompiledScript<*>> = emptyList()
|
||||
) : Serializable {
|
||||
|
||||
private fun writeObject(outputStream: ObjectOutputStream) {
|
||||
outputStream.writeObject(compilationConfiguration)
|
||||
outputStream.writeObject(sourceLocationId)
|
||||
outputStream.writeObject(otherScripts)
|
||||
outputStream.writeObject(scriptClassFQName)
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun readObject(inputStream: ObjectInputStream) {
|
||||
compilationConfiguration = inputStream.readObject() as ScriptCompilationConfiguration
|
||||
sourceLocationId = inputStream.readObject() as String?
|
||||
otherScripts = inputStream.readObject() as List<CompiledScript<*>>
|
||||
scriptClassFQName = inputStream.readObject() as String
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
private val serialVersionUID = 3L
|
||||
}
|
||||
}
|
||||
|
||||
class KJvmCompiledScript<out ScriptBase : Any> internal constructor(
|
||||
internal var data: KJvmCompiledScriptData,
|
||||
var compiledModule: KJvmCompiledModule? // module should be null for imported (other) scripts, so only one reference to the module is kept
|
||||
) : CompiledScript<ScriptBase>, Serializable {
|
||||
|
||||
private var _sourceLocationId: String? = sourceLocationId
|
||||
constructor(
|
||||
sourceLocationId: String?,
|
||||
compilationConfiguration: ScriptCompilationConfiguration,
|
||||
scriptClassFQName: String,
|
||||
otherScripts: List<CompiledScript<*>> = emptyList(),
|
||||
compiledModule: KJvmCompiledModule? // module should be null for imported (other) scripts, so only one reference to the module is kept
|
||||
): this(KJvmCompiledScriptData(sourceLocationId, compilationConfiguration, scriptClassFQName, otherScripts), compiledModule)
|
||||
|
||||
override val sourceLocationId: String?
|
||||
get() = _sourceLocationId
|
||||
|
||||
private var _compilationConfiguration: ScriptCompilationConfiguration? = compilationConfiguration
|
||||
get() = data.sourceLocationId
|
||||
|
||||
override val compilationConfiguration: ScriptCompilationConfiguration
|
||||
get() = _compilationConfiguration!!
|
||||
|
||||
private var _otherScripts: List<CompiledScript<*>> = otherScripts
|
||||
get() = data.compilationConfiguration
|
||||
|
||||
override val otherScripts: List<CompiledScript<*>>
|
||||
get() = _otherScripts
|
||||
get() = data.otherScripts
|
||||
|
||||
val scriptClassFQName: String
|
||||
get() = data.scriptClassFQName
|
||||
|
||||
override suspend fun getClass(scriptEvaluationConfiguration: ScriptEvaluationConfiguration?): ResultWithDiagnostics<KClass<*>> = try {
|
||||
// ensuring proper defaults are used
|
||||
val actualEvaluationConfiguration = scriptEvaluationConfiguration ?: ScriptEvaluationConfiguration()
|
||||
val classLoader = getOrCreateActualClassloader(actualEvaluationConfiguration)
|
||||
|
||||
val clazz = classLoader.loadClass(scriptClassFQName).kotlin
|
||||
val clazz = classLoader.loadClass(data.scriptClassFQName).kotlin
|
||||
clazz.asSuccess()
|
||||
} catch (e: Throwable) {
|
||||
ResultWithDiagnostics.Failure(
|
||||
ScriptDiagnostic(
|
||||
"Unable to instantiate class $scriptClassFQName",
|
||||
"Unable to instantiate class ${data.scriptClassFQName}",
|
||||
sourcePath = sourceLocationId,
|
||||
exception = e
|
||||
)
|
||||
@@ -55,35 +83,32 @@ class KJvmCompiledScript<out ScriptBase : Any>(
|
||||
}
|
||||
|
||||
private fun writeObject(outputStream: ObjectOutputStream) {
|
||||
outputStream.writeObject(compilationConfiguration)
|
||||
outputStream.writeObject(sourceLocationId)
|
||||
outputStream.writeObject(otherScripts)
|
||||
outputStream.writeObject(data)
|
||||
outputStream.writeObject(compiledModule)
|
||||
outputStream.writeObject(scriptClassFQName)
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun readObject(inputStream: ObjectInputStream) {
|
||||
_compilationConfiguration = inputStream.readObject() as ScriptCompilationConfiguration
|
||||
_sourceLocationId = inputStream.readObject() as String?
|
||||
_otherScripts = inputStream.readObject() as List<CompiledScript<*>>
|
||||
compiledModule = inputStream.readObject() as KJvmCompiledModule
|
||||
scriptClassFQName = inputStream.readObject() as String
|
||||
data = inputStream.readObject() as KJvmCompiledScriptData
|
||||
compiledModule = inputStream.readObject() as KJvmCompiledModule?
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
private val serialVersionUID = 2L
|
||||
private val serialVersionUID = 3L
|
||||
}
|
||||
}
|
||||
|
||||
fun KJvmCompiledScript<*>.getOrCreateActualClassloader(evaluationConfiguration: ScriptEvaluationConfiguration): ClassLoader =
|
||||
evaluationConfiguration[ScriptEvaluationConfiguration.jvm.actualClassLoader] ?: run {
|
||||
val module = compiledModule
|
||||
?: throw IllegalStateException("Illegal call sequence, actualClassloader should be set before calling function on the class without module")
|
||||
val baseClassLoader = evaluationConfiguration[ScriptEvaluationConfiguration.jvm.baseClassLoader]
|
||||
val moduleClassLoader = module.createClassLoader(baseClassLoader)
|
||||
val classLoaderWithDeps =
|
||||
if (evaluationConfiguration[ScriptEvaluationConfiguration.jvm.loadDependencies] == false) baseClassLoader
|
||||
else makeClassLoaderFromDependencies(baseClassLoader)
|
||||
compiledModule.createClassLoader(classLoaderWithDeps)
|
||||
if (evaluationConfiguration[ScriptEvaluationConfiguration.jvm.loadDependencies] == false) moduleClassLoader
|
||||
else makeClassLoaderFromDependencies(moduleClassLoader)
|
||||
return classLoaderWithDeps
|
||||
}
|
||||
|
||||
fun getConfigurationWithClassloader(
|
||||
@@ -105,9 +130,7 @@ fun getConfigurationWithClassloader(
|
||||
}
|
||||
}
|
||||
|
||||
val ScriptEvaluationConfiguration.sharedScripts get() = get(ScriptEvaluationConfiguration.jvm.scriptsInstancesSharingMap)
|
||||
|
||||
private fun CompiledScript<*>.makeClassLoaderFromDependencies(baseClassLoader: ClassLoader?): ClassLoader? {
|
||||
private fun CompiledScript<*>.makeClassLoaderFromDependencies(baseClassLoader: ClassLoader): ClassLoader {
|
||||
val processedScripts = mutableSetOf<CompiledScript<*>>()
|
||||
fun seq(res: Sequence<CompiledScript<*>>, script: CompiledScript<*>): Sequence<CompiledScript<*>> {
|
||||
if (processedScripts.contains(script)) return res
|
||||
@@ -127,3 +150,35 @@ private fun CompiledScript<*>.makeClassLoaderFromDependencies(baseClassLoader: C
|
||||
return if (dependencies.none()) baseClassLoader
|
||||
else URLClassLoader(dependencies.toList().toTypedArray(), baseClassLoader)
|
||||
}
|
||||
|
||||
const val KOTLIN_SCRIPT_METADATA_PATH = "META-INF/kotlin/script"
|
||||
const val KOTLIN_SCRIPT_METADATA_EXTENSION_WITH_DOT = ".kotlin_script"
|
||||
fun scriptMetadataPath(scriptClassFQName: String) =
|
||||
"$KOTLIN_SCRIPT_METADATA_PATH/$scriptClassFQName$KOTLIN_SCRIPT_METADATA_EXTENSION_WITH_DOT"
|
||||
|
||||
fun <T : Any> KJvmCompiledScript<T>.copyWithoutModule(): KJvmCompiledScript<T> = KJvmCompiledScript(data, null)
|
||||
|
||||
fun KJvmCompiledScript<*>.toBytes(): ByteArray {
|
||||
val bos = ByteArrayOutputStream()
|
||||
var oos: ObjectOutputStream? = null
|
||||
try {
|
||||
oos = ObjectOutputStream(bos)
|
||||
oos.writeObject(this)
|
||||
oos.flush()
|
||||
return bos.toByteArray()!!
|
||||
} finally {
|
||||
try {
|
||||
oos?.close()
|
||||
} catch (e: IOException) {}
|
||||
}
|
||||
}
|
||||
|
||||
fun createScriptFromClassLoader(scriptClassFQName: String, classLoader: ClassLoader): KJvmCompiledScript<*> {
|
||||
val scriptDataStream = classLoader.getResourceAsStream(scriptMetadataPath(scriptClassFQName))
|
||||
?: throw IllegalArgumentException("Cannot find metadata for script $scriptClassFQName")
|
||||
val script = ObjectInputStream(scriptDataStream).use {
|
||||
it.readObject() as KJvmCompiledScript<*>
|
||||
}
|
||||
script.compiledModule = KJvmCompiledModuleFromClassLoader(classLoader)
|
||||
return script
|
||||
}
|
||||
|
||||
@@ -29,6 +29,11 @@ val JvmScriptEvaluationConfigurationKeys.baseClassLoader by PropertiesCollection
|
||||
*/
|
||||
val JvmScriptEvaluationConfigurationKeys.loadDependencies by PropertiesCollection.key<Boolean>(true)
|
||||
|
||||
/**
|
||||
* Arguments of the main call, if script is executed via its main method
|
||||
*/
|
||||
val JvmScriptEvaluationConfigurationKeys.mainArguments by PropertiesCollection.key<Array<out String>>()
|
||||
|
||||
internal val JvmScriptEvaluationConfigurationKeys.actualClassLoader by PropertiesCollection.key<ClassLoader?>()
|
||||
|
||||
internal val JvmScriptEvaluationConfigurationKeys.scriptsInstancesSharingMap by PropertiesCollection.key<MutableMap<KClass<*>, EvaluationResult>>()
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. 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.jvm
|
||||
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlin.script.experimental.api.ScriptCompilationConfiguration
|
||||
import kotlin.script.experimental.api.ScriptEvaluationConfiguration
|
||||
import kotlin.script.experimental.api.baseClass
|
||||
import kotlin.script.experimental.api.onFailure
|
||||
import kotlin.script.experimental.host.createEvaluationConfigurationFromTemplate
|
||||
import kotlin.script.experimental.jvm.impl.createScriptFromClassLoader
|
||||
|
||||
@Suppress("unused") // script codegen generates a call to it
|
||||
fun runCompiledScript(scriptClass: Class<*>, vararg args: String) {
|
||||
val script = createScriptFromClassLoader(scriptClass.name, scriptClass.classLoader)
|
||||
val evaluator = BasicJvmScriptEvaluator()
|
||||
val baseEvaluationConfiguration =
|
||||
createEvaluationConfigurationFromTemplate(
|
||||
script.compilationConfiguration[ScriptCompilationConfiguration.baseClass]!!,
|
||||
defaultJvmScriptingHostConfiguration,
|
||||
scriptClass.kotlin
|
||||
)
|
||||
val evaluationConfiguration = ScriptEvaluationConfiguration(baseEvaluationConfiguration) {
|
||||
jvm {
|
||||
mainArguments(args)
|
||||
}
|
||||
}
|
||||
val result = runBlocking {
|
||||
evaluator(script, evaluationConfiguration)
|
||||
}.onFailure {
|
||||
it.reports.forEach(System.err::println)
|
||||
}
|
||||
}
|
||||
@@ -119,8 +119,8 @@ internal fun List<File>.filterIfContainsAll(vararg keyNames: String): List<File>
|
||||
val res = hashMapOf<String, File>()
|
||||
for (cpentry in this) {
|
||||
for (prefix in keyNames) {
|
||||
if (cpentry.matchMaybeVersionedFile(prefix) ||
|
||||
(cpentry.isDirectory && cpentry.hasParentNamed(prefix))
|
||||
if (!res.containsKey(prefix) &&
|
||||
(cpentry.matchMaybeVersionedFile(prefix) || (cpentry.isDirectory && cpentry.hasParentNamed(prefix)))
|
||||
) {
|
||||
res[prefix] = cpentry
|
||||
break
|
||||
|
||||
Reference in New Issue
Block a user