0
# Script Caching
1
2
The script caching system provides persistent storage mechanisms for compiled scripts to improve performance through JAR-based caching and avoid recompilation of unchanged scripts.
3
4
## Capabilities
5
6
### CompiledScriptJarsCache
7
8
JAR-based cache implementation that stores compiled scripts as JAR files with dependency information and metadata.
9
10
```kotlin { .api }
11
/**
12
* Cache implementation that stores compiled scripts as JAR files
13
* @param scriptToFile Function mapping script and configuration to cache file location
14
*/
15
open class CompiledScriptJarsCache(
16
val scriptToFile: (SourceCode, ScriptCompilationConfiguration) -> File?
17
) : CompiledJvmScriptsCache {
18
19
/**
20
* Retrieves cached compiled script from JAR file
21
* @param script Source code to look up
22
* @param scriptCompilationConfiguration Compilation configuration for cache key
23
* @returns Cached compiled script or null if not found/invalid
24
*/
25
override fun get(script: SourceCode, scriptCompilationConfiguration: ScriptCompilationConfiguration): CompiledScript?
26
27
/**
28
* Stores compiled script to JAR file cache
29
* @param compiledScript Compiled script to cache
30
* @param script Original source code
31
* @param scriptCompilationConfiguration Compilation configuration used
32
*/
33
override fun store(
34
compiledScript: CompiledScript,
35
script: SourceCode,
36
scriptCompilationConfiguration: ScriptCompilationConfiguration
37
)
38
}
39
```
40
41
**Usage Examples:**
42
43
```kotlin
44
import kotlin.script.experimental.jvmhost.CompiledScriptJarsCache
45
import kotlin.script.experimental.api.*
46
import java.io.File
47
import java.security.MessageDigest
48
49
// Create cache with hash-based file mapping
50
val cache = CompiledScriptJarsCache { script, config ->
51
// Generate cache file path based on script content and configuration
52
val hash = generateScriptHash(script, config)
53
File("cache/scripts", "$hash.jar")
54
}
55
56
fun generateScriptHash(script: SourceCode, config: ScriptCompilationConfiguration): String {
57
val digest = MessageDigest.getInstance("SHA-256")
58
digest.update(script.text.toByteArray())
59
digest.update(config.toString().toByteArray())
60
return digest.digest().joinToString("") { "%02x".format(it) }
61
}
62
63
// Use cache with compilation
64
val compiler = JvmScriptCompiler()
65
val script = "println(\"Hello, cached world!\")".toScriptSource()
66
val config = ScriptCompilationConfiguration {
67
dependencies(JvmDependency(kotlinStdlib))
68
}
69
70
// Try to get from cache first
71
val cachedScript = cache.get(script, config)
72
val compiledScript = if (cachedScript != null) {
73
println("Using cached script")
74
cachedScript
75
} else {
76
println("Compiling and caching script")
77
val compilationResult = compiler(script, config)
78
when (compilationResult) {
79
is ResultWithDiagnostics.Success -> {
80
val compiled = compilationResult.value
81
cache.store(compiled, script, config)
82
compiled
83
}
84
is ResultWithDiagnostics.Failure -> {
85
compilationResult.reports.forEach { println("Error: ${it.message}") }
86
return
87
}
88
}
89
}
90
91
// Advanced cache with versioning
92
class VersionedScriptCache(private val baseDir: File) : CompiledJvmScriptsCache {
93
94
private val scriptToFile: (SourceCode, ScriptCompilationConfiguration) -> File? = { script, config ->
95
val scriptHash = generateScriptHash(script, config)
96
val versionDir = File(baseDir, "v1") // Version for cache format
97
versionDir.mkdirs()
98
File(versionDir, "$scriptHash.jar")
99
}
100
101
private val delegate = CompiledScriptJarsCache(scriptToFile)
102
103
override fun get(script: SourceCode, scriptCompilationConfiguration: ScriptCompilationConfiguration): CompiledScript? {
104
return try {
105
delegate.get(script, scriptCompilationConfiguration)
106
} catch (e: Exception) {
107
// Handle cache corruption or version mismatch
108
println("Cache miss due to error: ${e.message}")
109
null
110
}
111
}
112
113
override fun store(compiledScript: CompiledScript, script: SourceCode, scriptCompilationConfiguration: ScriptCompilationConfiguration) {
114
try {
115
delegate.store(compiledScript, script, scriptCompilationConfiguration)
116
} catch (e: Exception) {
117
println("Failed to cache script: ${e.message}")
118
}
119
}
120
}
121
122
val versionedCache = VersionedScriptCache(File("versioned-cache"))
123
```
124
125
### Cache Integration with Host
126
127
Integrating script caching with the scripting host for automatic cache management.
128
129
**Cache-Enabled Host Example:**
130
131
```kotlin
132
import kotlin.script.experimental.jvmhost.BasicJvmScriptingHost
133
import kotlin.script.experimental.jvm.*
134
135
// Create host configuration with caching
136
val hostConfigWithCache = ScriptingHostConfiguration {
137
jvm {
138
compilationCache(CompiledScriptJarsCache { script, config ->
139
val scriptName = script.name?.substringBeforeLast('.') ?: "unnamed"
140
val hash = generateScriptHash(script, config).take(8)
141
File("cache/compiled", "${scriptName}_$hash.jar")
142
})
143
}
144
}
145
146
// Create host with caching enabled
147
val cachingHost = BasicJvmScriptingHost(
148
baseHostConfiguration = hostConfigWithCache
149
)
150
151
// Scripts will be automatically cached and retrieved
152
@KotlinScript(fileExtension = "kts")
153
class CachedScript
154
155
val result1 = cachingHost.evalWithTemplate<CachedScript>("println(\"First execution\")".toScriptSource())
156
val result2 = cachingHost.evalWithTemplate<CachedScript>("println(\"Second execution - cached!\")".toScriptSource())
157
```
158
159
### JAR Storage Format
160
161
The cache stores compiled scripts as executable JAR files with embedded metadata and dependency information.
162
163
#### JAR Structure
164
165
```text
166
cached-script.jar
167
├── META-INF/
168
│ └── MANIFEST.MF # Manifest with Main-Class and Class-Path
169
├── script_metadata.json # Script compilation metadata
170
└── compiled/ # Compiled class files
171
├── Script.class # Main script class
172
└── Script$*.class # Inner classes
173
```
174
175
#### Manifest Format
176
177
```text
178
Manifest-Version: 1.0
179
Created-By: JetBrains Kotlin
180
Main-Class: Script
181
Class-Path: /path/to/kotlin-stdlib.jar /path/to/dependency.jar
182
```
183
184
### Loading Scripts from Cache
185
186
The cache automatically handles loading scripts from JAR files with dependency resolution and validation.
187
188
```kotlin { .api }
189
/**
190
* Loads compiled script from JAR file
191
* @param checkMissingDependencies Whether to validate all dependencies exist
192
* @returns Loaded compiled script or null if invalid
193
*/
194
fun File.loadScriptFromJar(checkMissingDependencies: Boolean = true): CompiledScript?
195
```
196
197
**Manual JAR Loading Example:**
198
199
```kotlin
200
import kotlin.script.experimental.jvmhost.loadScriptFromJar
201
import java.io.File
202
203
// Load script from cached JAR
204
val jarFile = File("cache/my-script.jar")
205
val loadedScript = jarFile.loadScriptFromJar(checkMissingDependencies = true)
206
207
when (loadedScript) {
208
null -> {
209
println("Failed to load script from JAR - possibly corrupted or missing dependencies")
210
}
211
else -> {
212
println("Successfully loaded script from cache")
213
214
// Get script class
215
val classResult = loadedScript.getClass()
216
when (classResult) {
217
is ResultWithDiagnostics.Success -> {
218
val scriptClass = classResult.value
219
println("Loaded script class: ${scriptClass.simpleName}")
220
221
// Execute if needed
222
val evaluator = BasicJvmScriptEvaluator()
223
val evalResult = evaluator(loadedScript, ScriptEvaluationConfiguration.Default)
224
// Handle evaluation result...
225
}
226
is ResultWithDiagnostics.Failure -> {
227
println("Failed to get script class from cached JAR")
228
}
229
}
230
}
231
}
232
233
// Load with dependency checking disabled (faster but less safe)
234
val quickLoadedScript = jarFile.loadScriptFromJar(checkMissingDependencies = false)
235
```
236
237
## Cache Management
238
239
### Cache Invalidation
240
241
Cached scripts are automatically invalidated when:
242
243
- Source script content changes
244
- Compilation configuration changes
245
- Dependencies are updated or missing
246
- JAR file is corrupted or unreadable
247
248
**Custom Cache Invalidation:**
249
250
```kotlin
251
class SmartScriptCache(private val baseDir: File) {
252
private val cache = CompiledScriptJarsCache { script, config ->
253
File(baseDir, "${script.name}-${config.hashCode()}.jar")
254
}
255
256
fun invalidateScript(scriptName: String) {
257
baseDir.listFiles { _, name ->
258
name.startsWith("$scriptName-") && name.endsWith(".jar")
259
}?.forEach { jarFile ->
260
if (jarFile.delete()) {
261
println("Invalidated cached script: ${jarFile.name}")
262
}
263
}
264
}
265
266
fun clearCache() {
267
baseDir.listFiles { _, name -> name.endsWith(".jar") }
268
?.forEach { it.delete() }
269
println("Cache cleared")
270
}
271
272
fun getCacheStats(): CacheStats {
273
val jarFiles = baseDir.listFiles { _, name -> name.endsWith(".jar") } ?: emptyArray()
274
return CacheStats(
275
totalFiles = jarFiles.size,
276
totalSize = jarFiles.sumOf { it.length() },
277
oldestFile = jarFiles.minByOrNull { it.lastModified() }?.lastModified(),
278
newestFile = jarFiles.maxByOrNull { it.lastModified() }?.lastModified()
279
)
280
}
281
}
282
283
data class CacheStats(
284
val totalFiles: Int,
285
val totalSize: Long,
286
val oldestFile: Long?,
287
val newestFile: Long?
288
)
289
```
290
291
### Performance Considerations
292
293
- **Cache Hit Rate**: Monitor cache effectiveness through hit/miss ratios
294
- **Storage Space**: JAR files include full dependency information, can be large
295
- **Load Time**: Loading from cache is significantly faster than compilation but still involves class loading
296
- **Dependency Validation**: Checking dependencies on each load impacts performance but ensures correctness
297
298
**Performance Monitoring Example:**
299
300
```kotlin
301
class MonitoredScriptCache(private val delegate: CompiledJvmScriptsCache) : CompiledJvmScriptsCache {
302
private var hits = 0
303
private var misses = 0
304
private var stores = 0
305
306
override fun get(script: SourceCode, scriptCompilationConfiguration: ScriptCompilationConfiguration): CompiledScript? {
307
val result = delegate.get(script, scriptCompilationConfiguration)
308
if (result != null) {
309
hits++
310
println("Cache hit (${hits}/${hits + misses} = ${(hits * 100) / (hits + misses)}%)")
311
} else {
312
misses++
313
println("Cache miss")
314
}
315
return result
316
}
317
318
override fun store(compiledScript: CompiledScript, script: SourceCode, scriptCompilationConfiguration: ScriptCompilationConfiguration) {
319
delegate.store(compiledScript, script, scriptCompilationConfiguration)
320
stores++
321
println("Cached script (total stored: $stores)")
322
}
323
324
fun getStats() = "Cache stats: $hits hits, $misses misses, $stores stored"
325
}
326
```