Implement simple "saving evaluators" to classes and to a jar

This commit is contained in:
Ilya Chernikov
2019-03-29 18:02:50 +01:00
parent a65f50bb00
commit c51d4a4405
3 changed files with 121 additions and 1 deletions
@@ -33,7 +33,7 @@ class KJvmCompiledScript<out ScriptBase : Any>(
compilationConfiguration: ScriptCompilationConfiguration,
private var scriptClassFQName: String,
otherScripts: List<CompiledScript<*>> = emptyList(),
private var compiledModule: KJvmCompiledModule? = null
internal var compiledModule: KJvmCompiledModule? = null
) : CompiledScript<ScriptBase>, Serializable {
private var _sourceLocationId: String? = sourceLocationId
@@ -0,0 +1,89 @@
/*
* 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.jvmhost
import java.io.File
import java.io.FileOutputStream
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.jvmhost.impl.KJvmCompiledScript
// TODO: generate execution code (main)
open class BasicJvmScriptClassFilesGenerator(val outputDir: File) : ScriptEvaluator {
override suspend operator fun invoke(
compiledScript: CompiledScript<*>,
scriptEvaluationConfiguration: ScriptEvaluationConfiguration?
): ResultWithDiagnostics<EvaluationResult> {
try {
if (compiledScript !is KJvmCompiledScript<*>)
return failure("Unsupported compiled script type ${compiledScript::class.qualifiedName}")
val module = compiledScript.compiledModule ?: return failure("Cannot generate classes: module is null")
for ((path, bytes) in module.compilerOutputFiles) {
File(outputDir, path).apply {
if (!parentFile.isDirectory) {
parentFile.mkdirs()
}
writeBytes(bytes)
}
}
return ResultWithDiagnostics.Success(EvaluationResult(ResultValue.Unit, scriptEvaluationConfiguration))
} catch (e: Throwable) {
return ResultWithDiagnostics.Failure(
e.asDiagnostics("Error saving compiled script classes", path = compiledScript.sourceLocationId)
)
}
}
}
open class BasicJvmScriptJarGenerator(val outputJar: File) : ScriptEvaluator {
override suspend operator fun invoke(
compiledScript: CompiledScript<*>,
scriptEvaluationConfiguration: ScriptEvaluationConfiguration?
): ResultWithDiagnostics<EvaluationResult> {
try {
if (compiledScript !is KJvmCompiledScript<*>)
return failure("Unsupported compiled script type ${compiledScript::class.qualifiedName}")
val module = compiledScript.compiledModule ?: return failure("Cannot generate classes: module is null")
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()
}
return ResultWithDiagnostics.Success(EvaluationResult(ResultValue.Unit, scriptEvaluationConfiguration))
} catch (e: Throwable) {
return ResultWithDiagnostics.Failure(
e.asDiagnostics("Error saving compiled script jar", path = compiledScript.sourceLocationId)
)
}
}
}
private fun failure(msg: String) =
ResultWithDiagnostics.Failure(msg.asErrorDiagnostics())
@@ -10,6 +10,7 @@ import kotlinx.coroutines.runBlocking
import org.junit.Assert
import org.junit.Test
import java.io.*
import java.net.URLClassLoader
import java.nio.file.Files
import java.security.MessageDigest
import kotlin.reflect.KClass
@@ -38,6 +39,36 @@ class ScriptingHostTest : TestCase() {
Assert.assertEquals(greeting, output)
}
@Test
fun testSaveToClasses() {
val greeting = "Hello from script classes!"
val outDir = Files.createTempDirectory("saveToClassesOut").toFile()
val compilationConfiguration = createJvmCompilationConfigurationFromTemplate<SimpleScriptTemplate>()
val host = BasicJvmScriptingHost(evaluator = BasicJvmScriptClassFilesGenerator(outDir))
host.eval("println(\"$greeting\")".toScriptSource(name = "SavedScript.kts"), compilationConfiguration, null).throwOnFailure()
val classloader = URLClassLoader(arrayOf(outDir.toURI().toURL()), ScriptingHostTest::class.java.classLoader)
val scriptClass = classloader.loadClass("SavedScript")
val output = captureOut {
scriptClass.newInstance()
}
Assert.assertEquals(greeting, output)
}
@Test
fun testSaveToJar() {
val greeting = "Hello from script jar!"
val outJar = Files.createTempFile("saveToJar", ".jar").toFile()
val compilationConfiguration = createJvmCompilationConfigurationFromTemplate<SimpleScriptTemplate>()
val host = BasicJvmScriptingHost(evaluator = BasicJvmScriptJarGenerator(outJar))
host.eval("println(\"$greeting\")".toScriptSource(name = "SavedScript.kts"), compilationConfiguration, null).throwOnFailure()
val classloader = URLClassLoader(arrayOf(outJar.toURI().toURL()), ScriptingHostTest::class.java.classLoader)
val scriptClass = classloader.loadClass("SavedScript")
val output = captureOut {
scriptClass.newInstance()
}
Assert.assertEquals(greeting, output)
}
@Test
fun testSimpleRequire() {
val greeting = "Hello from required!"