Implement main method generation in scripts and runnable jar saving
refactor necessary parts on the way
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -48,6 +48,6 @@ inline fun <reified T : Any> createJvmEvaluationConfigurationFromTemplate(
|
||||
): ScriptEvaluationConfiguration = createEvaluationConfigurationFromTemplate(
|
||||
KotlinType(T::class),
|
||||
hostConfiguration,
|
||||
ScriptCompilationConfiguration::class,
|
||||
ScriptEvaluationConfiguration::class,
|
||||
body
|
||||
)
|
||||
|
||||
+2
-2
@@ -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),
|
||||
|
||||
+40
-26
@@ -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(
|
||||
|
||||
+67
-3
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
+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