Implement main method generation in scripts and runnable jar saving

refactor necessary parts on the way
This commit is contained in:
Ilya Chernikov
2019-04-09 08:28:53 +02:00
parent c896b4873b
commit 67ad3773de
14 changed files with 355 additions and 81 deletions
@@ -21,6 +21,7 @@ import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassOrAny
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperInterfaces
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.kotlin.resolve.jvm.AsmTypes.OBJECT_TYPE
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin.Companion.NO_ORIGIN
@@ -222,19 +223,43 @@ class ScriptCodegen private constructor(
}
private fun genMembers() {
var hasMain = false
for (declaration in scriptDeclaration.declarations) {
if (declaration is KtProperty || declaration is KtNamedFunction || declaration is KtTypeAlias) {
genSimpleMember(declaration)
}
else if (declaration is KtClassOrObject) {
genClassOrObject(declaration)
}
else if (declaration is KtDestructuringDeclaration) {
for (entry in declaration.entries) {
when (declaration) {
is KtNamedFunction -> {
genSimpleMember(declaration)
// temporary way to avoid name clashes
// TODO: remove as soon as main generation become an explicit configuration option
if (declaration.name == "main") {
hasMain = true
}
}
is KtProperty, is KtNamedFunction, is KtTypeAlias -> genSimpleMember(declaration)
is KtClassOrObject -> genClassOrObject(declaration)
is KtDestructuringDeclaration -> for (entry in declaration.entries) {
genSimpleMember(entry)
}
}
}
if (!hasMain) {
genMain()
}
}
private fun genMain() {
val mainMethodArgsType = AsmUtil.getArrayType(AsmTypes.JAVA_STRING_TYPE)
val mainMethodDescriptor = Type.getMethodDescriptor(Type.VOID_TYPE, mainMethodArgsType)
val runMethodDescriptor = Type.getMethodDescriptor(Type.VOID_TYPE, AsmTypes.JAVA_CLASS_TYPE, mainMethodArgsType)
InstructionAdapter(
v.newMethod(NO_ORIGIN, ACC_STATIC or ACC_FINAL or ACC_PUBLIC, "main", mainMethodDescriptor, null, null)
).apply {
visitCode()
visitLdcInsn(classAsmType)
load(0, mainMethodArgsType)
visitMethodInsn(INVOKESTATIC, "kotlin/script/experimental/jvm/RunnerKt", "runCompiledScript", runMethodDescriptor, false)
areturn(Type.VOID_TYPE)
visitEnd()
}
}
companion object {
@@ -96,6 +96,7 @@ interface KotlinPaths {
ScriptingImpl(PathUtil.KOTLIN_SCRIPTING_IMPL_NAME),
ScriptingLib(PathUtil.KOTLIN_SCRIPTING_COMMON_NAME),
ScriptingJvmLib(PathUtil.KOTLIN_SCRIPTING_JVM_NAME),
CoroutinesCore(PathUtil.KOTLINX_COROUTINES_CORE_NAME),
}
enum class ClassPaths(val contents: List<Jar> = emptyList()) {
@@ -69,6 +69,8 @@ object PathUtil {
const val KOTLIN_SCRIPTING_COMPILER_PLUGIN_JAR = "$KOTLIN_SCRIPTING_COMPILER_PLUGIN_NAME.jar"
const val KOTLIN_SCRIPTING_IMPL_NAME = "kotlin-scripting-impl"
const val KOTLIN_SCRIPTING_IMPL_JAR = "$KOTLIN_SCRIPTING_IMPL_NAME.jar"
const val KOTLINX_COROUTINES_CORE_NAME = "kotlinx-coroutines-core"
const val KOTLINX_COROUTINES_CORE_JAR = "$KOTLINX_COROUTINES_CORE_NAME.jar"
const val KOTLIN_TEST_NAME = "kotlin-test"
const val KOTLIN_TEST_JAR = "$KOTLIN_TEST_NAME.jar"
@@ -7,6 +7,8 @@
package kotlin.script.experimental.api
import java.io.File
/**
* The single script diagnostic report
* @param message diagnostic message
@@ -25,6 +27,27 @@ data class ScriptDiagnostic(
* The diagnostic severity
*/
enum class Severity { FATAL, ERROR, WARNING, INFO, DEBUG }
override fun toString(): String = buildString {
append(severity.name)
append(' ')
append(message)
if (sourcePath != null || location != null) {
append(" (")
sourcePath?.let { append(it.substringAfterLast(File.separatorChar)) }
location?.let {
append(':')
append(it.start.line)
append(':')
append(it.start.col)
}
append(')')
}
if (exception != null) {
append(": ")
append(exception)
}
}
}
/**
@@ -48,6 +48,6 @@ inline fun <reified T : Any> createJvmEvaluationConfigurationFromTemplate(
): ScriptEvaluationConfiguration = createEvaluationConfigurationFromTemplate(
KotlinType(T::class),
hostConfiguration,
ScriptCompilationConfiguration::class,
ScriptEvaluationConfiguration::class,
body
)
@@ -352,7 +352,6 @@ class KJvmCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) : KJvm
sourceDependencies: List<ScriptsCompilationDependencies.SourceDependencies>,
getScriptConfiguration: (KtFile) -> ScriptCompilationConfiguration
): KJvmCompiledScript<Any> {
val module = makeCompiledModule(generationState)
val scriptDependenciesStack = ArrayDeque<KtScript>()
fun makeOtherScripts(script: KtScript): List<KJvmCompiledScript<*>> {
@@ -371,7 +370,7 @@ class KJvmCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) : KJvm
getScriptConfiguration(sourceFile),
it.fqName.asString(),
makeOtherScripts(it),
module
null
)
}
} ?: emptyList()
@@ -380,6 +379,7 @@ class KJvmCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) : KJvm
return otherScripts
}
val module = makeCompiledModule(generationState)
return KJvmCompiledScript(
script.locationId,
getScriptConfiguration(ktScript.containingKtFile),
@@ -5,15 +5,16 @@
package kotlin.script.experimental.jvmhost
import java.io.File
import java.io.FileOutputStream
import org.jetbrains.kotlin.utils.KotlinPaths
import java.io.*
import java.util.jar.JarEntry
import java.util.jar.JarOutputStream
import java.util.jar.Manifest
import kotlin.script.experimental.api.*
import kotlin.script.experimental.jvm.JvmDependency
import kotlin.script.experimental.jvm.impl.*
import kotlin.script.experimental.jvm.util.scriptCompilationClasspathFromContext
import kotlin.script.experimental.jvmhost.impl.KJvmCompiledModuleInMemory
import kotlin.script.experimental.jvm.impl.KJvmCompiledScript
// TODO: generate execution code (main)
@@ -45,6 +46,41 @@ open class BasicJvmScriptClassFilesGenerator(val outputDir: File) : ScriptEvalua
}
}
fun KJvmCompiledScript<*>.saveToJar(outputJar: File) {
val module = (compiledModule as? KJvmCompiledModuleInMemory)
?: throw IllegalArgumentException("Unsupported module type $compiledModule")
val dependenciesFromScript = compilationConfiguration[ScriptCompilationConfiguration.dependencies]
?.filterIsInstance<JvmDependency>()
?.flatMap { it.classpath }
.orEmpty()
val dependenciesForMain = scriptCompilationClasspathFromContext(
KotlinPaths.Jar.ScriptingLib.baseName, KotlinPaths.Jar.ScriptingJvmLib.baseName, KotlinPaths.Jar.CoroutinesCore.baseName,
classLoader = this::class.java.classLoader,
wholeClasspath = false
)
val dependencies = (dependenciesFromScript + dependenciesForMain).distinct()
FileOutputStream(outputJar).use { fileStream ->
val manifest = Manifest()
manifest.mainAttributes.apply {
putValue("Manifest-Version", "1.0")
putValue("Created-By", "JetBrains Kotlin")
if (dependencies.isNotEmpty()) {
// TODO: implement options for various cases - paths as is (now), absolute paths (local execution only), names only (most likely as a hint only), fat jar
putValue("Class-Path", dependencies.joinToString(" "))
}
putValue("Main-Class", scriptClassFQName)
}
// TODO: fat jar/dependencies
val jarStream = JarOutputStream(fileStream, manifest)
jarStream.putNextEntry(JarEntry(scriptMetadataPath(scriptClassFQName)))
jarStream.write(copyWithoutModule().toBytes())
for ((path, bytes) in module.compilerOutputFiles) {
jarStream.putNextEntry(JarEntry(path))
jarStream.write(bytes)
}
jarStream.finish()
}
}
open class BasicJvmScriptJarGenerator(val outputJar: File) : ScriptEvaluator {
@@ -55,29 +91,7 @@ open class BasicJvmScriptJarGenerator(val outputJar: File) : ScriptEvaluator {
try {
if (compiledScript !is KJvmCompiledScript<*>)
return failure("Cannot generate jar: unsupported compiled script type $compiledScript")
val module = (compiledScript.compiledModule as? KJvmCompiledModuleInMemory)
?: return failure("Cannot generate jar: unsupported module type ${compiledScript.compiledModule}")
val dependencies = compiledScript.compilationConfiguration[ScriptCompilationConfiguration.dependencies]
?.filterIsInstance<JvmDependency>()
?.flatMap { it.classpath }
.orEmpty()
FileOutputStream(outputJar).use { fileStream ->
val manifest = Manifest()
manifest.mainAttributes.apply {
putValue("Manifest-Version", "1.0")
putValue("Created-By", "JetBrains Kotlin")
if (dependencies.isNotEmpty()) {
putValue("Class-Path", dependencies.joinToString(" ") { it.name })
}
}
// TODO: fat jar/dependencies
val jarStream = JarOutputStream(fileStream, manifest)
for ((path, bytes) in module.compilerOutputFiles) {
jarStream.putNextEntry(JarEntry(path))
jarStream.write(bytes)
}
jarStream.finish()
}
compiledScript.saveToJar(outputJar)
return ResultWithDiagnostics.Success(EvaluationResult(ResultValue.Unit, scriptEvaluationConfiguration))
} catch (e: Throwable) {
return ResultWithDiagnostics.Failure(
@@ -13,6 +13,8 @@ import java.io.*
import java.net.URLClassLoader
import java.nio.file.Files
import java.security.MessageDigest
import java.util.concurrent.TimeUnit
import java.util.jar.JarFile
import kotlin.reflect.KClass
import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.BasicScriptingHost
@@ -75,6 +77,60 @@ class ScriptingHostTest : TestCase() {
Assert.assertEquals(greeting, output)
}
@Test
fun testSaveToRunnableJar() {
val greeting = "Hello from script jar!"
val outJar = Files.createTempFile("saveToRunnableJar", ".jar").toFile()
val compilationConfiguration = createJvmCompilationConfigurationFromTemplate<SimpleScriptTemplate>()
val compiler = JvmScriptCompiler(defaultJvmScriptingHostConfiguration)
val scriptName = "SavedRunnableScript"
val compiledScript = runBlocking {
compiler("println(\"$greeting\")".toScriptSource(name = "$scriptName.kts"), compilationConfiguration).throwOnFailure()
.resultOrNull()!!
}
val saver = BasicJvmScriptJarGenerator(outJar)
runBlocking {
saver(compiledScript, ScriptEvaluationConfiguration.Default).throwOnFailure()
}
val classpathFromJar = run {
val manifest = JarFile(outJar).manifest
manifest.mainAttributes.getValue("Class-Path").split(" ") // TODO: quoted paths
.map { File(it).toURI().toURL() }
} + outJar.toURI().toURL()
fun checkInvokeMain(baseClassLoader: ClassLoader?) {
val classloader = URLClassLoader(classpathFromJar.toTypedArray(), baseClassLoader)
val scriptClass = classloader.loadClass(scriptName)
val mainMethod = scriptClass.methods.find { it.name == "main" }
Assert.assertNotNull(mainMethod)
val output = captureOutAndErr {
mainMethod!!.invoke(null, emptyArray<String>())
}.toList().filterNot(String::isEmpty).joinToString("\n")
Assert.assertEquals(greeting, output)
}
checkInvokeMain(null) // isolated
checkInvokeMain(Thread.currentThread().contextClassLoader)
val javaExecutable = File(File(System.getProperty("java.home"), "bin"), "java")
val args = listOf(javaExecutable.absolutePath, "-jar", outJar.path)
val processBuilder = ProcessBuilder(args)
processBuilder.redirectErrorStream(true)
val outputFromProcess = run {
val process = processBuilder.start()
process.waitFor(10, TimeUnit.SECONDS)
val out = process.inputStream.reader().readText()
if (process.isAlive) {
process.destroyForcibly()
"Error: timeout, killing script process\n$out"
} else {
out
}
}.trim()
Assert.assertEquals(greeting, outputFromProcess)
}
@Test
fun testSimpleRequire() {
val greeting = "Hello from required!"
@@ -348,7 +404,7 @@ class ScriptingHostTest : TestCase() {
}
}
fun ResultWithDiagnostics<*>.throwOnFailure(): ResultWithDiagnostics<*> = apply {
fun <T> ResultWithDiagnostics<T>.throwOnFailure(): ResultWithDiagnostics<T> = apply {
if (this is ResultWithDiagnostics.Failure) {
val firstExceptionFromReports = reports.find { it.exception != null }?.exception
throw Exception(
@@ -470,15 +526,23 @@ private class FileBasedScriptCache(val baseDir: File) : ScriptingCacheWithCounte
get() = _retrievedScripts
}
private fun captureOut(body: () -> Unit): String {
private fun captureOut(body: () -> Unit): String = captureOutAndErr(body).first
private fun captureOutAndErr(body: () -> Unit): Pair<String, String> {
val outStream = ByteArrayOutputStream()
val errStream = ByteArrayOutputStream()
val prevOut = System.out
val prevErr = System.err
System.setOut(PrintStream(outStream))
System.setErr(PrintStream(errStream))
try {
body()
} finally {
System.out.flush()
System.err.flush()
System.setOut(prevOut)
System.setErr(prevErr)
}
return outStream.toString().trim()
return outStream.toString().trim() to errStream.toString().trim()
}
@@ -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 {
@@ -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)
}
@@ -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