Skip to content

Commit 8d99dca

Browse files
committed
WIP
1 parent a6c4ca9 commit 8d99dca

File tree

18 files changed

+242
-69
lines changed

18 files changed

+242
-69
lines changed

README.adoc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ I tried several approaches:
2727
- passing my own classLoader (doesn't work)
2828
- unpacking jars in the SpringBoot fatjar (does work), and
2929
- automatically copying jars from the fatjar to the local file system (will follow).
30+
3031
I will show all approaches here and call for improvements.
3132

3233
## Gradle vs. Maven
@@ -54,3 +55,11 @@ Just run:
5455
1. `./gradlew clean build`
5556
2. `java -jar application/build/libs/application-0.0.1.jar`
5657
3. See the output in the console.
58+
59+
60+
## Jetbrains
61+
62+
Please refer: https://blog.jetbrains.com/kotlin/2024/11/state-of-kotlin-scripting-2024/
63+
64+
- The used BasicScriptingHost here is part of Jetbrains' Kotlin scripting.
65+
- JSR 223 is outdated and will be removed.

application/build.gradle.kts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,14 @@ import org.springframework.boot.gradle.tasks.bundling.BootJar
33
plugins {
44
id("org.springframework.boot") version "3.1.4"
55
id("io.spring.dependency-management") version "1.1.3"
6-
kotlin("jvm") version "2.0.21"
76
kotlin("plugin.spring") version "1.9.10"
87
}
98

109
springBoot {
1110
mainClass.set("de.micromata.kotlinscripting.DemoApplication")
1211
}
1312

14-
val kotlinVersion = "2.0.21"
13+
val kotlinVersion: String by rootProject.extra
1514

1615
val kotlinCompilerDependency = configurations.create("kotlinCompilerDependency")
1716
val kotlinCompilerDependencies = mutableListOf<String>()
Lines changed: 17 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
package de.micromata.kotlinscripting
22

3-
import org.springframework.boot.ExitCodeGenerator
4-
import org.springframework.boot.SpringApplication
3+
import mu.KotlinLogging
54
import org.springframework.boot.autoconfigure.SpringBootApplication
65
import org.springframework.boot.runApplication
7-
import kotlin.script.experimental.api.ResultValue
8-
import kotlin.script.experimental.api.ResultWithDiagnostics
9-
import kotlin.script.experimental.api.valueOrNull
106

7+
private val log = KotlinLogging.logger {}
118

129
@SpringBootApplication
1310
class DemoApplication {
@@ -16,27 +13,24 @@ class DemoApplication {
1613
@JvmStatic
1714
fun main(args: Array<String>) {
1815
runApplication<DemoApplication>(*args)
19-
20-
val scriptExecutor = ScriptExecutor()
21-
val result = scriptExecutor.executeScript()
22-
if (result is ResultWithDiagnostics.Success) {
23-
val returnValue = result.valueOrNull()?.returnValue
24-
val output = if (returnValue is ResultValue.Value) {
25-
returnValue.value
26-
} else {
27-
returnValue
28-
}
29-
println("Script result with success: ${output}")
30-
} else if (result is ResultWithDiagnostics<*>) {
31-
println("*** Script result: ${result.valueOrNull()}")
32-
result.reports.forEach {
33-
println("Script report: ${it.message}")
34-
}
35-
} else {
36-
println("*** Script result: $result")
16+
runScript("Hello world") { SimpleScriptExecutor().executeScript("helloWorld.kts") }
17+
runScript("Simple script with variables") { SimpleScriptExecutor().executeScript("useContext.kts") }
18+
runScript("Simple script using business package") { SimpleScriptExecutor().executeScript("useContextAndBusiness.kts") }
19+
runScript("Simple script using business and common package", "This test will fail on fat jar, because the commons module isn't unpacked.") { SimpleScriptExecutor().executeScript("useContextAndBusinessAndCommons.kts") }
20+
runScript("ScriptExecutorWithCustomizedScriptingHost") {
21+
ScriptExecutorWithCustomizedScriptingHost().executeScript()
3722
}
3823
System.exit(0) // Not an elegant way, but here it is enough.
24+
}
3925

26+
private fun runScript(name: String, msg: String? = null, block: () -> Unit) {
27+
log.info { "******************************************************************************" }
28+
log.info { "*** Running test $name" }
29+
if (msg != null) {
30+
log.info { "*** $msg" }
31+
}
32+
log.info { "******************************************************************************" }
33+
block()
4034
}
4135
}
4236
}

application/src/main/kotlin/de/micromata/kotlinscripting/ScriptExecutor.kt renamed to application/src/main/kotlin/de/micromata/kotlinscripting/ScriptExecutorWithCustomizedScriptingHost.kt

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
package de.micromata.kotlinscripting
22

3+
import de.micromata.kotlinscripting.utils.KotlinScriptUtils
34
import mu.KotlinLogging
45
import java.util.concurrent.Executors
56
import java.util.concurrent.Future
67
import java.util.concurrent.TimeUnit
78
import java.util.concurrent.TimeoutException
89
import kotlin.script.experimental.api.*
910
import kotlin.script.experimental.host.toScriptSource
10-
import kotlin.script.experimental.jvm.baseClassLoader
1111
import kotlin.script.experimental.jvm.dependenciesFromClassloader
1212
import kotlin.script.experimental.jvm.jvm
1313

1414
private val log = KotlinLogging.logger {}
1515

16-
class ScriptExecutor {
16+
class ScriptExecutorWithCustomizedScriptingHost {
1717
private var evalException: Exception? = null
1818

19-
fun executeScript(): ResultWithDiagnostics<EvaluationResult>? {
19+
fun executeScript(): Any? {
2020
//val classLoader = CustomClassLoader(Thread.currentThread().contextClassLoader)
21-
21+
val script = KotlinScriptUtils.loadScript("useContextAndThreadLocal.kts")
2222
val scriptingHost = CustomScriptingHost() // (classLoader)
2323
val compilationConfig = ScriptCompilationConfiguration {
2424
jvm {
@@ -36,14 +36,15 @@ class ScriptExecutor {
3636
}*/
3737
providedProperties("context" to context)
3838
}
39-
val scriptSource = Constants.CHECK_SCRIPT.toScriptSource()
39+
val scriptSource = script.toScriptSource()
4040
val executor = Executors.newSingleThreadExecutor()
4141
var future: Future<ResultWithDiagnostics<EvaluationResult>>? = null
4242
try {
4343
future = executor.submit<ResultWithDiagnostics<EvaluationResult>> {
4444
scriptingHost.eval(scriptSource, compilationConfig, evaluationConfiguration)
4545
}
46-
return future.get(10, TimeUnit.SECONDS) // Timeout
46+
val result = future.get(10, TimeUnit.SECONDS) // Timeout
47+
return KotlinScriptUtils.handleResult(result, script)
4748
} catch (ex: TimeoutException) {
4849
log.info("Script execution was cancelled due to timeout.")
4950
future?.cancel(true) // Attempt to cancel
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package de.micromata.kotlinscripting
2+
3+
import de.micromata.kotlinscripting.utils.KotlinScriptUtils
4+
import kotlin.script.experimental.api.*
5+
import kotlin.script.experimental.host.toScriptSource
6+
import kotlin.script.experimental.jvm.dependenciesFromClassloader
7+
import kotlin.script.experimental.jvm.jvm
8+
import kotlin.script.experimental.jvmhost.BasicJvmScriptingHost
9+
10+
class SimpleScriptExecutor {
11+
fun executeScript(file: String): Any? {
12+
val script = KotlinScriptUtils.loadScript(file)
13+
val scriptingHost = BasicJvmScriptingHost()
14+
val compilationConfig = ScriptCompilationConfiguration {
15+
jvm {
16+
dependenciesFromClassloader(wholeClasspath = true)
17+
}
18+
providedProperties("context" to KotlinScriptContext::class)
19+
compilerOptions.append("-nowarn")
20+
}
21+
val context = KotlinScriptContext()
22+
context.setProperty("testVariable", Constants.TEST_VAR)
23+
val evaluationConfiguration = ScriptEvaluationConfiguration {
24+
providedProperties("context" to context)
25+
}
26+
val scriptSource = script.toScriptSource()
27+
val result = scriptingHost.eval(scriptSource, compilationConfig, evaluationConfiguration)
28+
return KotlinScriptUtils.handleResult(result, script)
29+
}
30+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package de.micromata.kotlinscripting.utils
2+
3+
import mu.KotlinLogging
4+
import org.jetbrains.kotlin.ir.types.IdSignatureValues.result
5+
import kotlin.script.experimental.api.*
6+
7+
private val log = KotlinLogging.logger {}
8+
9+
/**
10+
* Utility class for handling script results.
11+
* Logs the script execution result and reports.
12+
* Formats the script lines with error markers.
13+
* @param result The result of the script execution.
14+
* @param script The script that was executed.
15+
* @param logSeverity The severity level for logging.
16+
* @return The result of the script execution.
17+
*/
18+
internal object KotlinScriptUtils {
19+
fun handleResult(
20+
result: ResultWithDiagnostics<EvaluationResult>?,
21+
script: String,
22+
logSeverity: ScriptDiagnostic.Severity = ScriptDiagnostic.Severity.INFO
23+
): Any? {
24+
if (result == null) {
25+
log.error { "No result (timeout?)" }
26+
return null
27+
}
28+
val scriptLines = script.lines()
29+
result.reports.forEach { report ->
30+
val severity = report.severity
31+
if (severity < logSeverity) {
32+
return@forEach
33+
}
34+
val message = report.message
35+
val location = report.location
36+
var line1 = "[$severity] $message"
37+
var line2: String? = null
38+
location?.let {
39+
// line1 = "[$severity] $message: ${it.start.line}:${it.start.col} to ${it.end?.line}:${it.end?.col}"
40+
line1 = "[$severity] $message: line ${it.start.line} to ${it.end?.line}"
41+
val lineIndex = it.start.line - 1 // Zeilenindex anpassen
42+
if (lineIndex in scriptLines.indices) {
43+
val line = scriptLines[lineIndex]
44+
val startCol = it.start.col - 1
45+
val endCol = (it.end?.col ?: line.length) - 1
46+
47+
// Teile die Zeile auf und füge die Marker ein
48+
val markedLine = buildString {
49+
append(">")
50+
append(line.substring(0, startCol))
51+
append(">>>") // Markierung für Startposition
52+
append(line.substring(startCol, endCol))
53+
append("<<<") // Markierung für Endposition
54+
append(line.substring(endCol))
55+
}
56+
line2 = markedLine
57+
}
58+
}
59+
log(severity, line1)
60+
line2?.let { log(severity, it) }
61+
}
62+
val returnValue = extractResult(result)
63+
if (result is ResultWithDiagnostics.Success) {
64+
println("Script result with success: ${returnValue}")
65+
} else {
66+
println("*** Script result: ${result.valueOrNull()}")
67+
result.reports.forEach {
68+
println("Script report: ${it.message}")
69+
}
70+
}
71+
return returnValue
72+
}
73+
74+
internal fun loadScript(filename: String): String {
75+
return object {}.javaClass.classLoader.getResource(filename)?.readText()
76+
?: throw IllegalArgumentException("Script not found: $filename")
77+
}
78+
79+
private fun extractResult(result: ResultWithDiagnostics<EvaluationResult>): Any? {
80+
val returnValue = result.valueOrNull()?.returnValue
81+
return if (returnValue is ResultValue.Value) {
82+
returnValue.value
83+
} else {
84+
returnValue
85+
}
86+
}
87+
88+
private fun log(serverity: ScriptDiagnostic.Severity, message: String) {
89+
when (serverity) {
90+
ScriptDiagnostic.Severity.FATAL -> log.error { message }
91+
ScriptDiagnostic.Severity.ERROR -> log.error { message }
92+
ScriptDiagnostic.Severity.WARNING -> log.warn { message }
93+
ScriptDiagnostic.Severity.INFO -> log.info { message }
94+
ScriptDiagnostic.Severity.DEBUG -> log.debug { message }
95+
}
96+
}
97+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"Hello world!"
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import de.micromata.kotlinscripting.Constants
2+
3+
val sb = StringBuilder()
4+
sb.appendLine("Hello world!")
5+
val testVar = context.getProperty("testVariable")
6+
if (testVar == Constants.TEST_VAR) {
7+
sb.appendLine("Context: $testVar (OK)")
8+
} else {
9+
sb.appendLine("Context: $testVar (*** ERROR, ${Constants.TEST_VAR} expected)")
10+
}
11+
sb.toString()
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import de.micromata.kotlinscripting.business.Foo
2+
3+
// Using only the business package.
4+
Foo.bar()
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import de.micromata.kotlinscripting.business.Foo
2+
3+
// Foo.useCommons() uses the commons package.
4+
Foo.bar() + ", " + Foo.useCommons()

0 commit comments

Comments
 (0)