Kotlin Scripting JVM host for executing and compiling Kotlin scripts in JVM environments with JSR-223 integration and comprehensive caching mechanisms
—
The script persistence system provides utilities for saving compiled scripts as executable JAR files or individual class files for deployment, distribution, and standalone execution.
Generates executable JAR files from compiled scripts with embedded dependencies and proper manifest configuration.
/**
* Generates executable JAR files from compiled scripts
* @param outputJar Target JAR file location
*/
open class BasicJvmScriptJarGenerator(val outputJar: File) : ScriptEvaluator {
/**
* Generates JAR file from compiled script
* @param compiledScript Compiled script to package
* @param scriptEvaluationConfiguration Evaluation configuration (not used for generation)
* @returns Success with NotEvaluated result or failure with diagnostics
*/
override suspend operator fun invoke(
compiledScript: CompiledScript,
scriptEvaluationConfiguration: ScriptEvaluationConfiguration
): ResultWithDiagnostics<EvaluationResult>
}Generates individual class files from compiled scripts for integration into existing projects or custom deployment scenarios.
/**
* Generates individual class files from compiled scripts
* @param outputDir Target directory for class files
*/
open class BasicJvmScriptClassFilesGenerator(val outputDir: File) : ScriptEvaluator {
/**
* Generates class files from compiled script
* @param compiledScript Compiled script to extract classes from
* @param scriptEvaluationConfiguration Evaluation configuration (not used for generation)
* @returns Success with NotEvaluated result or failure with diagnostics
*/
override suspend operator fun invoke(
compiledScript: CompiledScript,
scriptEvaluationConfiguration: ScriptEvaluationConfiguration
): ResultWithDiagnostics<EvaluationResult>
}Usage Examples:
import kotlin.script.experimental.jvmhost.*
import kotlin.script.experimental.api.*
import java.io.File
// Compile a script first
val compiler = JvmScriptCompiler()
val script = """
data class Person(val name: String, val age: Int)
val people = listOf(
Person("Alice", 30),
Person("Bob", 25)
)
println("People: \$people")
people.filter { it.age > 25 }.map { it.name }
""".trimIndent()
val compilationResult = compiler(
script.toScriptSource("people-script.kts"),
ScriptCompilationConfiguration {
dependencies(JvmDependency(kotlinStdlib))
}
)
when (compilationResult) {
is ResultWithDiagnostics.Success -> {
val compiledScript = compilationResult.value
// Generate executable JAR
val jarGenerator = BasicJvmScriptJarGenerator(File("output/people-script.jar"))
val jarResult = jarGenerator(compiledScript, ScriptEvaluationConfiguration.Default)
when (jarResult) {
is ResultWithDiagnostics.Success -> {
println("JAR generated successfully: output/people-script.jar")
// The JAR can now be executed with: java -jar people-script.jar
}
is ResultWithDiagnostics.Failure -> {
jarResult.reports.forEach { println("JAR generation error: ${it.message}") }
}
}
// Generate individual class files
val classGenerator = BasicJvmScriptClassFilesGenerator(File("output/classes"))
val classResult = classGenerator(compiledScript, ScriptEvaluationConfiguration.Default)
when (classResult) {
is ResultWithDiagnostics.Success -> {
println("Class files generated in: output/classes/")
// List generated class files
File("output/classes").walkTopDown()
.filter { it.isFile && it.extension == "class" }
.forEach { println("Generated: ${it.relativeTo(File("output/classes"))}") }
}
is ResultWithDiagnostics.Failure -> {
classResult.reports.forEach { println("Class generation error: ${it.message}") }
}
}
}
is ResultWithDiagnostics.Failure -> {
println("Compilation failed")
}
}Extension functions for working with compiled scripts and JAR files directly.
Saves a compiled script directly to a JAR file with proper manifest and dependency information.
/**
* Saves compiled script to JAR file with manifest and dependencies
* @param outputJar Target JAR file
*/
fun KJvmCompiledScript.saveToJar(outputJar: File)Loads a previously saved script from a JAR file for execution or inspection.
/**
* Loads compiled script from JAR file
* @param checkMissingDependencies Whether to validate all dependencies exist
* @returns Loaded compiled script or null if invalid/missing dependencies
*/
fun File.loadScriptFromJar(checkMissingDependencies: Boolean = true): CompiledScript?Direct JAR Operations Example:
import kotlin.script.experimental.jvmhost.saveToJar
import kotlin.script.experimental.jvmhost.loadScriptFromJar
import kotlin.script.experimental.jvm.impl.KJvmCompiledScript
// Save compiled script directly to JAR
val compiledScript = compilationResult.value as KJvmCompiledScript
val jarFile = File("direct-save.jar")
compiledScript.saveToJar(jarFile)
println("Script saved to: ${jarFile.absolutePath}")
// Load script back from JAR
val loadedScript = jarFile.loadScriptFromJar()
when (loadedScript) {
null -> println("Failed to load script from JAR")
else -> {
println("Successfully loaded script from JAR")
// Execute loaded script
val evaluator = BasicJvmScriptEvaluator()
val evalResult = evaluator(loadedScript, ScriptEvaluationConfiguration.Default)
// Handle evaluation...
}
}
// Load without dependency checking for faster loading
val quickLoadedScript = jarFile.loadScriptFromJar(checkMissingDependencies = false)Generated JAR files follow a specific structure to ensure proper execution and dependency management.
script.jar
├── META-INF/
│ └── MANIFEST.MF # Executable manifest with Main-Class and Class-Path
├── Script_12345.class # Main script class (name includes hash)
├── Script_12345$InnerClass.class# Inner classes if any
├── Script_12345$Data.class # Data classes defined in script
└── script_metadata_Script_12345 # Serialized script metadataThe generated manifest includes:
Example Manifest:
Manifest-Version: 1.0
Created-By: JetBrains Kotlin
Main-Class: Script_a1b2c3d4
Class-Path: /path/to/kotlin-stdlib-2.2.0.jar /path/to/kotlin-reflect-2.2.0.jarDependencies are handled in several ways:
Advanced JAR Generation with Custom Dependencies:
// Create script with custom dependencies
val scriptWithDeps = """
@file:DependsOn("org.apache.commons:commons-lang3:3.12.0")
import org.apache.commons.lang3.StringUtils
val text = " hello world "
val result = StringUtils.capitalize(StringUtils.trim(text))
println("Result: \$result")
result
""".trimIndent()
val configWithMavenDeps = ScriptCompilationConfiguration {
dependencies(
JvmDependency("org.apache.commons:commons-lang3:3.12.0"),
JvmDependency(kotlinStdlib)
)
// Custom dependency resolver
refineConfiguration {
onAnnotations(DependsOn::class) { context ->
val dependsOnList = context.collectedData?.get(DependsOn::class)
if (dependsOnList != null) {
ScriptCompilationConfiguration(context.compilationConfiguration) {
dependencies.append(JvmDependency(dependsOnList.flatMap { it.artifacts }))
}.asSuccess()
} else {
context.compilationConfiguration.asSuccess()
}
}
}
}
val depsCompilationResult = compiler(scriptWithDeps.toScriptSource(), configWithMavenDeps)
when (depsCompilationResult) {
is ResultWithDiagnostics.Success -> {
val jarGenerator = BasicJvmScriptJarGenerator(File("script-with-deps.jar"))
jarGenerator(depsCompilationResult.value, ScriptEvaluationConfiguration.Default)
// The generated JAR will include proper Class-Path entries for Maven dependencies
}
}Individual class file generation provides fine-grained control over script deployment and integration.
Generated class files maintain the package structure and naming conventions of the original script:
output/classes/
├── Script_12345.class # Main script class
├── Script_12345$Person.class # Data class defined in script
├── Script_12345$Helper.class # Helper class or function
└── Script_12345$WhenMappings.class # Compiler-generated utility classesCustom Class Generation Example:
class CustomScriptClassGenerator(
private val outputDir: File,
private val packageName: String? = null
) : ScriptEvaluator {
override suspend operator fun invoke(
compiledScript: CompiledScript,
scriptEvaluationConfiguration: ScriptEvaluationConfiguration
): ResultWithDiagnostics<EvaluationResult> {
return try {
if (compiledScript !is KJvmCompiledScript) {
return ResultWithDiagnostics.Failure(
listOf("Cannot generate classes: unsupported compiled script type".asErrorDiagnostics())
)
}
val module = (compiledScript.getCompiledModule() as? KJvmCompiledModuleInMemory)
?: return ResultWithDiagnostics.Failure(
listOf("Cannot generate classes: unsupported module type".asErrorDiagnostics())
)
// Create package directory structure if specified
val targetDir = if (packageName != null) {
File(outputDir, packageName.replace('.', '/'))
} else {
outputDir
}
if (!targetDir.exists()) {
targetDir.mkdirs()
}
// Write all class files
for ((path, bytes) in module.compilerOutputFiles) {
val classFile = File(targetDir, File(path).name)
classFile.writeBytes(bytes)
println("Generated class file: ${classFile.relativeTo(outputDir)}")
}
ResultWithDiagnostics.Success(
EvaluationResult(ResultValue.NotEvaluated, scriptEvaluationConfiguration)
)
} catch (e: Throwable) {
ResultWithDiagnostics.Failure(
listOf(e.asErrorDiagnostics("Cannot generate script classes: ${e.message}"))
)
}
}
}
// Use custom generator
val customGenerator = CustomScriptClassGenerator(
outputDir = File("custom-output"),
packageName = "com.example.scripts"
)
val customResult = customGenerator(compiledScript, ScriptEvaluationConfiguration.Default)Generated artifacts can be integrated into existing build systems and deployment pipelines.
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<mainClass>kotlin.script.experimental.jvmhost.GenerateScriptJar</mainClass>
<args>
<arg>src/main/scripts/my-script.kts</arg>
<arg>target/generated-scripts/my-script.jar</arg>
</args>
</configuration>
</plugin>tasks.register<JavaExec>("generateScriptJars") {
classpath = configurations.runtimeClasspath.get()
mainClass.set("kotlin.script.experimental.jvmhost.GenerateScriptJar")
args = listOf(
"src/main/scripts/",
"build/generated-jars/"
)
}Generated JAR files can be executed directly:
# Execute generated script JAR
java -jar generated-script.jar
# Execute with additional JVM options
java -Xmx2g -jar data-processing-script.jar
# Execute with custom classpath additions
java -cp "lib/*:generated-script.jar" Script_12345Install with Tessl CLI
npx tessl i tessl/maven-org-jetbrains-kotlin--kotlin-scripting-jvm-host