diff --git a/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/KJvmCompiledScript.kt b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/KJvmCompiledScript.kt index 8bb48f450ce..cb37bd4ae61 100644 --- a/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/KJvmCompiledScript.kt +++ b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/KJvmCompiledScript.kt @@ -33,7 +33,7 @@ class KJvmCompiledScript( compilationConfiguration: ScriptCompilationConfiguration, private var scriptClassFQName: String, otherScripts: List> = emptyList(), - private var compiledModule: KJvmCompiledModule? = null + internal var compiledModule: KJvmCompiledModule? = null ) : CompiledScript, Serializable { private var _sourceLocationId: String? = sourceLocationId diff --git a/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/jvmScriptSaving.kt b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/jvmScriptSaving.kt new file mode 100644 index 00000000000..3ed42180365 --- /dev/null +++ b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/jvmScriptSaving.kt @@ -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 { + 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 { + 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() + ?.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()) + diff --git a/libraries/scripting/jvm-host/test/kotlin/script/experimental/jvmhost/test/ScriptingHostTest.kt b/libraries/scripting/jvm-host/test/kotlin/script/experimental/jvmhost/test/ScriptingHostTest.kt index 4a43c6ec817..686e5905e39 100644 --- a/libraries/scripting/jvm-host/test/kotlin/script/experimental/jvmhost/test/ScriptingHostTest.kt +++ b/libraries/scripting/jvm-host/test/kotlin/script/experimental/jvmhost/test/ScriptingHostTest.kt @@ -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() + 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() + 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!"