CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-jetbrains-kotlin--kotlin-scripting-jvm-host

Kotlin Scripting JVM host for executing and compiling Kotlin scripts in JVM environments with JSR-223 integration and comprehensive caching mechanisms

Pending
Overview
Eval results
Files

script-caching.mddocs/

Script Caching

The script caching system provides persistent storage mechanisms for compiled scripts to improve performance through JAR-based caching and avoid recompilation of unchanged scripts.

Capabilities

CompiledScriptJarsCache

JAR-based cache implementation that stores compiled scripts as JAR files with dependency information and metadata.

/**
 * Cache implementation that stores compiled scripts as JAR files
 * @param scriptToFile Function mapping script and configuration to cache file location
 */
open class CompiledScriptJarsCache(
    val scriptToFile: (SourceCode, ScriptCompilationConfiguration) -> File?
) : CompiledJvmScriptsCache {
    
    /**
     * Retrieves cached compiled script from JAR file
     * @param script Source code to look up
     * @param scriptCompilationConfiguration Compilation configuration for cache key
     * @returns Cached compiled script or null if not found/invalid
     */
    override fun get(script: SourceCode, scriptCompilationConfiguration: ScriptCompilationConfiguration): CompiledScript?
    
    /**
     * Stores compiled script to JAR file cache
     * @param compiledScript Compiled script to cache
     * @param script Original source code
     * @param scriptCompilationConfiguration Compilation configuration used
     */
    override fun store(
        compiledScript: CompiledScript,
        script: SourceCode,
        scriptCompilationConfiguration: ScriptCompilationConfiguration
    )
}

Usage Examples:

import kotlin.script.experimental.jvmhost.CompiledScriptJarsCache
import kotlin.script.experimental.api.*
import java.io.File
import java.security.MessageDigest

// Create cache with hash-based file mapping
val cache = CompiledScriptJarsCache { script, config ->
    // Generate cache file path based on script content and configuration
    val hash = generateScriptHash(script, config)
    File("cache/scripts", "$hash.jar")
}

fun generateScriptHash(script: SourceCode, config: ScriptCompilationConfiguration): String {
    val digest = MessageDigest.getInstance("SHA-256")
    digest.update(script.text.toByteArray())
    digest.update(config.toString().toByteArray())
    return digest.digest().joinToString("") { "%02x".format(it) }
}

// Use cache with compilation
val compiler = JvmScriptCompiler()
val script = "println(\"Hello, cached world!\")".toScriptSource()
val config = ScriptCompilationConfiguration {
    dependencies(JvmDependency(kotlinStdlib))
}

// Try to get from cache first
val cachedScript = cache.get(script, config)
val compiledScript = if (cachedScript != null) {
    println("Using cached script")
    cachedScript
} else {
    println("Compiling and caching script")
    val compilationResult = compiler(script, config)
    when (compilationResult) {
        is ResultWithDiagnostics.Success -> {
            val compiled = compilationResult.value
            cache.store(compiled, script, config)
            compiled
        }
        is ResultWithDiagnostics.Failure -> {
            compilationResult.reports.forEach { println("Error: ${it.message}") }
            return
        }
    }
}

// Advanced cache with versioning
class VersionedScriptCache(private val baseDir: File) : CompiledJvmScriptsCache {
    
    private val scriptToFile: (SourceCode, ScriptCompilationConfiguration) -> File? = { script, config ->
        val scriptHash = generateScriptHash(script, config)
        val versionDir = File(baseDir, "v1") // Version for cache format
        versionDir.mkdirs()
        File(versionDir, "$scriptHash.jar")
    }
    
    private val delegate = CompiledScriptJarsCache(scriptToFile)
    
    override fun get(script: SourceCode, scriptCompilationConfiguration: ScriptCompilationConfiguration): CompiledScript? {
        return try {
            delegate.get(script, scriptCompilationConfiguration)
        } catch (e: Exception) {
            // Handle cache corruption or version mismatch
            println("Cache miss due to error: ${e.message}")
            null
        }
    }
    
    override fun store(compiledScript: CompiledScript, script: SourceCode, scriptCompilationConfiguration: ScriptCompilationConfiguration) {
        try {
            delegate.store(compiledScript, script, scriptCompilationConfiguration)
        } catch (e: Exception) {
            println("Failed to cache script: ${e.message}")
        }
    }
}

val versionedCache = VersionedScriptCache(File("versioned-cache"))

Cache Integration with Host

Integrating script caching with the scripting host for automatic cache management.

Cache-Enabled Host Example:

import kotlin.script.experimental.jvmhost.BasicJvmScriptingHost
import kotlin.script.experimental.jvm.*

// Create host configuration with caching
val hostConfigWithCache = ScriptingHostConfiguration {
    jvm {
        compilationCache(CompiledScriptJarsCache { script, config ->
            val scriptName = script.name?.substringBeforeLast('.') ?: "unnamed"
            val hash = generateScriptHash(script, config).take(8)
            File("cache/compiled", "${scriptName}_$hash.jar")
        })
    }
}

// Create host with caching enabled
val cachingHost = BasicJvmScriptingHost(
    baseHostConfiguration = hostConfigWithCache
)

// Scripts will be automatically cached and retrieved
@KotlinScript(fileExtension = "kts")
class CachedScript

val result1 = cachingHost.evalWithTemplate<CachedScript>("println(\"First execution\")".toScriptSource())
val result2 = cachingHost.evalWithTemplate<CachedScript>("println(\"Second execution - cached!\")".toScriptSource())

JAR Storage Format

The cache stores compiled scripts as executable JAR files with embedded metadata and dependency information.

JAR Structure

cached-script.jar
├── META-INF/
│   └── MANIFEST.MF          # Manifest with Main-Class and Class-Path
├── script_metadata.json     # Script compilation metadata
└── compiled/                # Compiled class files
    ├── Script.class         # Main script class
    └── Script$*.class       # Inner classes

Manifest Format

Manifest-Version: 1.0
Created-By: JetBrains Kotlin
Main-Class: Script
Class-Path: /path/to/kotlin-stdlib.jar /path/to/dependency.jar

Loading Scripts from Cache

The cache automatically handles loading scripts from JAR files with dependency resolution and validation.

/**
 * Loads compiled script from JAR file
 * @param checkMissingDependencies Whether to validate all dependencies exist
 * @returns Loaded compiled script or null if invalid
 */
fun File.loadScriptFromJar(checkMissingDependencies: Boolean = true): CompiledScript?

Manual JAR Loading Example:

import kotlin.script.experimental.jvmhost.loadScriptFromJar
import java.io.File

// Load script from cached JAR
val jarFile = File("cache/my-script.jar")
val loadedScript = jarFile.loadScriptFromJar(checkMissingDependencies = true)

when (loadedScript) {
    null -> {
        println("Failed to load script from JAR - possibly corrupted or missing dependencies")
    }
    else -> {
        println("Successfully loaded script from cache")
        
        // Get script class
        val classResult = loadedScript.getClass()
        when (classResult) {
            is ResultWithDiagnostics.Success -> {
                val scriptClass = classResult.value
                println("Loaded script class: ${scriptClass.simpleName}")
                
                // Execute if needed
                val evaluator = BasicJvmScriptEvaluator()
                val evalResult = evaluator(loadedScript, ScriptEvaluationConfiguration.Default)
                // Handle evaluation result...
            }
            is ResultWithDiagnostics.Failure -> {
                println("Failed to get script class from cached JAR")
            }
        }
    }
}

// Load with dependency checking disabled (faster but less safe)
val quickLoadedScript = jarFile.loadScriptFromJar(checkMissingDependencies = false)

Cache Management

Cache Invalidation

Cached scripts are automatically invalidated when:

  • Source script content changes
  • Compilation configuration changes
  • Dependencies are updated or missing
  • JAR file is corrupted or unreadable

Custom Cache Invalidation:

class SmartScriptCache(private val baseDir: File) {
    private val cache = CompiledScriptJarsCache { script, config ->
        File(baseDir, "${script.name}-${config.hashCode()}.jar")
    }
    
    fun invalidateScript(scriptName: String) {
        baseDir.listFiles { _, name -> 
            name.startsWith("$scriptName-") && name.endsWith(".jar")
        }?.forEach { jarFile ->
            if (jarFile.delete()) {
                println("Invalidated cached script: ${jarFile.name}")
            }
        }
    }
    
    fun clearCache() {
        baseDir.listFiles { _, name -> name.endsWith(".jar") }
            ?.forEach { it.delete() }
        println("Cache cleared")
    }
    
    fun getCacheStats(): CacheStats {
        val jarFiles = baseDir.listFiles { _, name -> name.endsWith(".jar") } ?: emptyArray()
        return CacheStats(
            totalFiles = jarFiles.size,
            totalSize = jarFiles.sumOf { it.length() },
            oldestFile = jarFiles.minByOrNull { it.lastModified() }?.lastModified(),
            newestFile = jarFiles.maxByOrNull { it.lastModified() }?.lastModified()
        )
    }
}

data class CacheStats(
    val totalFiles: Int,
    val totalSize: Long,
    val oldestFile: Long?,
    val newestFile: Long?
)

Performance Considerations

  • Cache Hit Rate: Monitor cache effectiveness through hit/miss ratios
  • Storage Space: JAR files include full dependency information, can be large
  • Load Time: Loading from cache is significantly faster than compilation but still involves class loading
  • Dependency Validation: Checking dependencies on each load impacts performance but ensures correctness

Performance Monitoring Example:

class MonitoredScriptCache(private val delegate: CompiledJvmScriptsCache) : CompiledJvmScriptsCache {
    private var hits = 0
    private var misses = 0
    private var stores = 0
    
    override fun get(script: SourceCode, scriptCompilationConfiguration: ScriptCompilationConfiguration): CompiledScript? {
        val result = delegate.get(script, scriptCompilationConfiguration)
        if (result != null) {
            hits++
            println("Cache hit (${hits}/${hits + misses} = ${(hits * 100) / (hits + misses)}%)")
        } else {
            misses++
            println("Cache miss")
        }
        return result
    }
    
    override fun store(compiledScript: CompiledScript, script: SourceCode, scriptCompilationConfiguration: ScriptCompilationConfiguration) {
        delegate.store(compiledScript, script, scriptCompilationConfiguration)
        stores++
        println("Cached script (total stored: $stores)")
    }
    
    fun getStats() = "Cache stats: $hits hits, $misses misses, $stores stored"
}

Install with Tessl CLI

npx tessl i tessl/maven-org-jetbrains-kotlin--kotlin-scripting-jvm-host

docs

core-scripting-host.md

index.md

jsr223-integration.md

repl-support.md

script-caching.md

script-compilation.md

script-persistence.md

tile.json