0
# Script Persistence
1
2
The script persistence system provides utilities for saving compiled scripts as executable JAR files or individual class files for deployment, distribution, and standalone execution.
3
4
## Capabilities
5
6
### BasicJvmScriptJarGenerator
7
8
Generates executable JAR files from compiled scripts with embedded dependencies and proper manifest configuration.
9
10
```kotlin { .api }
11
/**
12
* Generates executable JAR files from compiled scripts
13
* @param outputJar Target JAR file location
14
*/
15
open class BasicJvmScriptJarGenerator(val outputJar: File) : ScriptEvaluator {
16
17
/**
18
* Generates JAR file from compiled script
19
* @param compiledScript Compiled script to package
20
* @param scriptEvaluationConfiguration Evaluation configuration (not used for generation)
21
* @returns Success with NotEvaluated result or failure with diagnostics
22
*/
23
override suspend operator fun invoke(
24
compiledScript: CompiledScript,
25
scriptEvaluationConfiguration: ScriptEvaluationConfiguration
26
): ResultWithDiagnostics<EvaluationResult>
27
}
28
```
29
30
### BasicJvmScriptClassFilesGenerator
31
32
Generates individual class files from compiled scripts for integration into existing projects or custom deployment scenarios.
33
34
```kotlin { .api }
35
/**
36
* Generates individual class files from compiled scripts
37
* @param outputDir Target directory for class files
38
*/
39
open class BasicJvmScriptClassFilesGenerator(val outputDir: File) : ScriptEvaluator {
40
41
/**
42
* Generates class files from compiled script
43
* @param compiledScript Compiled script to extract classes from
44
* @param scriptEvaluationConfiguration Evaluation configuration (not used for generation)
45
* @returns Success with NotEvaluated result or failure with diagnostics
46
*/
47
override suspend operator fun invoke(
48
compiledScript: CompiledScript,
49
scriptEvaluationConfiguration: ScriptEvaluationConfiguration
50
): ResultWithDiagnostics<EvaluationResult>
51
}
52
```
53
54
**Usage Examples:**
55
56
```kotlin
57
import kotlin.script.experimental.jvmhost.*
58
import kotlin.script.experimental.api.*
59
import java.io.File
60
61
// Compile a script first
62
val compiler = JvmScriptCompiler()
63
val script = """
64
data class Person(val name: String, val age: Int)
65
66
val people = listOf(
67
Person("Alice", 30),
68
Person("Bob", 25)
69
)
70
71
println("People: \$people")
72
people.filter { it.age > 25 }.map { it.name }
73
""".trimIndent()
74
75
val compilationResult = compiler(
76
script.toScriptSource("people-script.kts"),
77
ScriptCompilationConfiguration {
78
dependencies(JvmDependency(kotlinStdlib))
79
}
80
)
81
82
when (compilationResult) {
83
is ResultWithDiagnostics.Success -> {
84
val compiledScript = compilationResult.value
85
86
// Generate executable JAR
87
val jarGenerator = BasicJvmScriptJarGenerator(File("output/people-script.jar"))
88
val jarResult = jarGenerator(compiledScript, ScriptEvaluationConfiguration.Default)
89
90
when (jarResult) {
91
is ResultWithDiagnostics.Success -> {
92
println("JAR generated successfully: output/people-script.jar")
93
94
// The JAR can now be executed with: java -jar people-script.jar
95
}
96
is ResultWithDiagnostics.Failure -> {
97
jarResult.reports.forEach { println("JAR generation error: ${it.message}") }
98
}
99
}
100
101
// Generate individual class files
102
val classGenerator = BasicJvmScriptClassFilesGenerator(File("output/classes"))
103
val classResult = classGenerator(compiledScript, ScriptEvaluationConfiguration.Default)
104
105
when (classResult) {
106
is ResultWithDiagnostics.Success -> {
107
println("Class files generated in: output/classes/")
108
109
// List generated class files
110
File("output/classes").walkTopDown()
111
.filter { it.isFile && it.extension == "class" }
112
.forEach { println("Generated: ${it.relativeTo(File("output/classes"))}") }
113
}
114
is ResultWithDiagnostics.Failure -> {
115
classResult.reports.forEach { println("Class generation error: ${it.message}") }
116
}
117
}
118
}
119
is ResultWithDiagnostics.Failure -> {
120
println("Compilation failed")
121
}
122
}
123
```
124
125
### Script JAR Extension Functions
126
127
Extension functions for working with compiled scripts and JAR files directly.
128
129
#### KJvmCompiledScript.saveToJar
130
131
Saves a compiled script directly to a JAR file with proper manifest and dependency information.
132
133
```kotlin { .api }
134
/**
135
* Saves compiled script to JAR file with manifest and dependencies
136
* @param outputJar Target JAR file
137
*/
138
fun KJvmCompiledScript.saveToJar(outputJar: File)
139
```
140
141
#### File.loadScriptFromJar
142
143
Loads a previously saved script from a JAR file for execution or inspection.
144
145
```kotlin { .api }
146
/**
147
* Loads compiled script from JAR file
148
* @param checkMissingDependencies Whether to validate all dependencies exist
149
* @returns Loaded compiled script or null if invalid/missing dependencies
150
*/
151
fun File.loadScriptFromJar(checkMissingDependencies: Boolean = true): CompiledScript?
152
```
153
154
**Direct JAR Operations Example:**
155
156
```kotlin
157
import kotlin.script.experimental.jvmhost.saveToJar
158
import kotlin.script.experimental.jvmhost.loadScriptFromJar
159
import kotlin.script.experimental.jvm.impl.KJvmCompiledScript
160
161
// Save compiled script directly to JAR
162
val compiledScript = compilationResult.value as KJvmCompiledScript
163
val jarFile = File("direct-save.jar")
164
compiledScript.saveToJar(jarFile)
165
166
println("Script saved to: ${jarFile.absolutePath}")
167
168
// Load script back from JAR
169
val loadedScript = jarFile.loadScriptFromJar()
170
when (loadedScript) {
171
null -> println("Failed to load script from JAR")
172
else -> {
173
println("Successfully loaded script from JAR")
174
175
// Execute loaded script
176
val evaluator = BasicJvmScriptEvaluator()
177
val evalResult = evaluator(loadedScript, ScriptEvaluationConfiguration.Default)
178
// Handle evaluation...
179
}
180
}
181
182
// Load without dependency checking for faster loading
183
val quickLoadedScript = jarFile.loadScriptFromJar(checkMissingDependencies = false)
184
```
185
186
## JAR File Structure
187
188
Generated JAR files follow a specific structure to ensure proper execution and dependency management.
189
190
### Generated JAR Layout
191
192
```text
193
script.jar
194
├── META-INF/
195
│ └── MANIFEST.MF # Executable manifest with Main-Class and Class-Path
196
├── Script_12345.class # Main script class (name includes hash)
197
├── Script_12345$InnerClass.class# Inner classes if any
198
├── Script_12345$Data.class # Data classes defined in script
199
└── script_metadata_Script_12345 # Serialized script metadata
200
```
201
202
### Manifest Configuration
203
204
The generated manifest includes:
205
206
- **Main-Class**: Points to the compiled script class
207
- **Class-Path**: Lists all dependencies with their locations
208
- **Manifest-Version**: Standard JAR manifest version
209
- **Created-By**: Identifies Kotlin as the creator
210
211
**Example Manifest:**
212
213
```text
214
Manifest-Version: 1.0
215
Created-By: JetBrains Kotlin
216
Main-Class: Script_a1b2c3d4
217
Class-Path: /path/to/kotlin-stdlib-2.2.0.jar /path/to/kotlin-reflect-2.2.0.jar
218
```
219
220
### Dependency Management
221
222
Dependencies are handled in several ways:
223
224
1. **External References**: Dependencies remain as external JAR files listed in Class-Path
225
2. **Relative Paths**: Paths are converted to URIs for proper resolution
226
3. **Missing Dependencies**: Optional validation ensures all dependencies are accessible
227
228
**Advanced JAR Generation with Custom Dependencies:**
229
230
```kotlin
231
// Create script with custom dependencies
232
val scriptWithDeps = """
233
@file:DependsOn("org.apache.commons:commons-lang3:3.12.0")
234
235
import org.apache.commons.lang3.StringUtils
236
237
val text = " hello world "
238
val result = StringUtils.capitalize(StringUtils.trim(text))
239
println("Result: \$result")
240
result
241
""".trimIndent()
242
243
val configWithMavenDeps = ScriptCompilationConfiguration {
244
dependencies(
245
JvmDependency("org.apache.commons:commons-lang3:3.12.0"),
246
JvmDependency(kotlinStdlib)
247
)
248
249
// Custom dependency resolver
250
refineConfiguration {
251
onAnnotations(DependsOn::class) { context ->
252
val dependsOnList = context.collectedData?.get(DependsOn::class)
253
if (dependsOnList != null) {
254
ScriptCompilationConfiguration(context.compilationConfiguration) {
255
dependencies.append(JvmDependency(dependsOnList.flatMap { it.artifacts }))
256
}.asSuccess()
257
} else {
258
context.compilationConfiguration.asSuccess()
259
}
260
}
261
}
262
}
263
264
val depsCompilationResult = compiler(scriptWithDeps.toScriptSource(), configWithMavenDeps)
265
when (depsCompilationResult) {
266
is ResultWithDiagnostics.Success -> {
267
val jarGenerator = BasicJvmScriptJarGenerator(File("script-with-deps.jar"))
268
jarGenerator(depsCompilationResult.value, ScriptEvaluationConfiguration.Default)
269
270
// The generated JAR will include proper Class-Path entries for Maven dependencies
271
}
272
}
273
```
274
275
## Class File Generation
276
277
Individual class file generation provides fine-grained control over script deployment and integration.
278
279
### Class File Organization
280
281
Generated class files maintain the package structure and naming conventions of the original script:
282
283
```text
284
output/classes/
285
├── Script_12345.class # Main script class
286
├── Script_12345$Person.class # Data class defined in script
287
├── Script_12345$Helper.class # Helper class or function
288
└── Script_12345$WhenMappings.class # Compiler-generated utility classes
289
```
290
291
**Custom Class Generation Example:**
292
293
```kotlin
294
class CustomScriptClassGenerator(
295
private val outputDir: File,
296
private val packageName: String? = null
297
) : ScriptEvaluator {
298
299
override suspend operator fun invoke(
300
compiledScript: CompiledScript,
301
scriptEvaluationConfiguration: ScriptEvaluationConfiguration
302
): ResultWithDiagnostics<EvaluationResult> {
303
304
return try {
305
if (compiledScript !is KJvmCompiledScript) {
306
return ResultWithDiagnostics.Failure(
307
listOf("Cannot generate classes: unsupported compiled script type".asErrorDiagnostics())
308
)
309
}
310
311
val module = (compiledScript.getCompiledModule() as? KJvmCompiledModuleInMemory)
312
?: return ResultWithDiagnostics.Failure(
313
listOf("Cannot generate classes: unsupported module type".asErrorDiagnostics())
314
)
315
316
// Create package directory structure if specified
317
val targetDir = if (packageName != null) {
318
File(outputDir, packageName.replace('.', '/'))
319
} else {
320
outputDir
321
}
322
323
if (!targetDir.exists()) {
324
targetDir.mkdirs()
325
}
326
327
// Write all class files
328
for ((path, bytes) in module.compilerOutputFiles) {
329
val classFile = File(targetDir, File(path).name)
330
classFile.writeBytes(bytes)
331
println("Generated class file: ${classFile.relativeTo(outputDir)}")
332
}
333
334
ResultWithDiagnostics.Success(
335
EvaluationResult(ResultValue.NotEvaluated, scriptEvaluationConfiguration)
336
)
337
338
} catch (e: Throwable) {
339
ResultWithDiagnostics.Failure(
340
listOf(e.asErrorDiagnostics("Cannot generate script classes: ${e.message}"))
341
)
342
}
343
}
344
}
345
346
// Use custom generator
347
val customGenerator = CustomScriptClassGenerator(
348
outputDir = File("custom-output"),
349
packageName = "com.example.scripts"
350
)
351
352
val customResult = customGenerator(compiledScript, ScriptEvaluationConfiguration.Default)
353
```
354
355
## Integration with Build Systems
356
357
Generated artifacts can be integrated into existing build systems and deployment pipelines.
358
359
### Maven Integration
360
361
```xml
362
<plugin>
363
<groupId>org.codehaus.mojo</groupId>
364
<artifactId>exec-maven-plugin</artifactId>
365
<version>3.1.0</version>
366
<configuration>
367
<mainClass>kotlin.script.experimental.jvmhost.GenerateScriptJar</mainClass>
368
<args>
369
<arg>src/main/scripts/my-script.kts</arg>
370
<arg>target/generated-scripts/my-script.jar</arg>
371
</args>
372
</configuration>
373
</plugin>
374
```
375
376
### Gradle Integration
377
378
```kotlin
379
tasks.register<JavaExec>("generateScriptJars") {
380
classpath = configurations.runtimeClasspath.get()
381
mainClass.set("kotlin.script.experimental.jvmhost.GenerateScriptJar")
382
args = listOf(
383
"src/main/scripts/",
384
"build/generated-jars/"
385
)
386
}
387
```
388
389
### Standalone Execution
390
391
Generated JAR files can be executed directly:
392
393
```bash
394
# Execute generated script JAR
395
java -jar generated-script.jar
396
397
# Execute with additional JVM options
398
java -Xmx2g -jar data-processing-script.jar
399
400
# Execute with custom classpath additions
401
java -cp "lib/*:generated-script.jar" Script_12345
402
```