Upgrades Kotlin to 1.8.22 and fixes Kotlin scripting

fixes gh-1902
fixes gh-1792
This commit is contained in:
Marcin Grzejszczak
2023-07-05 15:45:38 +02:00
parent a6f0e9a4f5
commit c130aec7b4
4 changed files with 83 additions and 62 deletions

View File

@@ -67,7 +67,7 @@
<InMemoryJavaCompiler.version>1.3.0</InMemoryJavaCompiler.version>
<thin-jar.version>1.0.30.RELEASE</thin-jar.version>
<contract.kotlin.version>1.6.21</contract.kotlin.version>
<contract.kotlin.version>1.8.22</contract.kotlin.version>
<commons-beanutils.version>1.9.4</commons-beanutils.version>
<sisu-inject-plexus.version>2.6.0</sisu-inject-plexus.version>

View File

@@ -26,6 +26,18 @@
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-compiler-embeddable</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-scripting-common</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-scripting-jvm</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-scripting-jvm-host</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-scripting-compiler-embeddable</artifactId>

View File

@@ -22,7 +22,14 @@ import org.springframework.cloud.contract.spec.ContractConverter
import java.io.File
import java.net.URLClassLoader.newInstance
import java.util.concurrent.atomic.AtomicInteger
import javax.script.ScriptEngineManager
import kotlin.script.experimental.api.EvaluationResult
import kotlin.script.experimental.api.ResultValue
import kotlin.script.experimental.api.ResultWithDiagnostics
import kotlin.script.experimental.api.ScriptCompilationConfiguration
import kotlin.script.experimental.host.toScriptSource
import kotlin.script.experimental.jvm.dependenciesFromCurrentContext
import kotlin.script.experimental.jvm.jvm
import kotlin.script.experimental.jvmhost.BasicJvmScriptingHost
/**
* Converter that will convert the Kotlin DSL to Java DSL.
@@ -32,62 +39,73 @@ import javax.script.ScriptEngineManager
*/
class KotlinContractConverter : ContractConverter<List<Contract>> {
private val ext = "kts"
private val ext = "kts"
constructor() {
// Sets an {@code idea.use.native.fs.for.win} system property to {@code false}
// to disable a native engine discovery for Windows: may be resolved in the future Kotlin versions.
System.setProperty("idea.use.native.fs.for.win", "false")
}
constructor() {
// Sets an {@code idea.use.native.fs.for.win} system property to {@code false}
// to disable a native engine discovery for Windows: may be resolved in the future Kotlin versions.
System.setProperty("idea.use.native.fs.for.win", "false")
}
override fun isAccepted(file: File): Boolean {
return ext == file.extension
}
override fun isAccepted(file: File): Boolean {
return ext == file.extension
}
override fun convertFrom(file: File): Collection<Contract> {
val eval = withUpdatedClassloader(file) {
file.reader().use {
// Get a new engine every time we need to process a file.
// Reusing the script engine could leak context and will fail subsequent evals
ScriptEngineManager().getEngineByExtension(ext).eval(it)
}
}
val contracts = when (eval) {
is Contract -> listOf(eval)
is Iterable<*> -> eval.filterIsInstance(Contract::class.java)
is Array<*> -> eval.filterIsInstance(Contract::class.java)
else -> emptyList()
}
override fun convertFrom(file: File): Collection<Contract> {
val eval = withUpdatedClassloader(file) {
BasicJvmScriptingHost().eval(file.toScriptSource(), ScriptWithCurrentClasspathConfiguration(), null)
}
when (eval) {
is ResultWithDiagnostics.Success<*> -> {
val contracts = when (val parsedValue = ((eval.value as EvaluationResult).returnValue as ResultValue.Value).value) {
is Contract -> listOf(parsedValue)
is Iterable<*> -> parsedValue.filterIsInstance(Contract::class.java)
is Array<*> -> parsedValue.filterIsInstance(Contract::class.java)
else -> emptyList()
}
return withName(file, contracts)
}
return withName(file, contracts)
}
private fun withName(file: File, contracts: Collection<Contract>): Collection<Contract> {
val counter = AtomicInteger(0)
return contracts.onEach { contract ->
if (ObjectUtils.isEmpty(contract.name)) {
contract.name = defaultContractName(file, contracts, counter.get())
}
counter.incrementAndGet()
}
}
else -> throw IllegalStateException("Failed to parse kotlin script due to ${(eval as ResultWithDiagnostics.Failure).reports}")
}
}
private fun defaultContractName(file: File, contracts: Collection<*>, counter: Int): String {
val lastIndexOfDot = file.name.lastIndexOf(".")
val tillExtension = file.name.substring(0, lastIndexOfDot)
return tillExtension + if (counter > 0 || contracts.size > 1) "_$counter" else ""
}
private fun withName(file: File, contracts: Collection<Contract>): Collection<Contract> {
val counter = AtomicInteger(0)
return contracts.onEach { contract ->
if (ObjectUtils.isEmpty(contract.name)) {
contract.name = defaultContractName(file, contracts, counter.get())
}
counter.incrementAndGet()
}
}
override fun convertTo(contract: Collection<Contract>) = contract.toList()
private fun defaultContractName(file: File, contracts: Collection<*>, counter: Int): String {
val lastIndexOfDot = file.name.lastIndexOf(".")
val tillExtension = file.name.substring(0, lastIndexOfDot)
return tillExtension + if (counter > 0 || contracts.size > 1) "_$counter" else ""
}
private fun withUpdatedClassloader(file: File, block: ClassLoader.() -> Any): Any {
val currentClassLoader = Thread.currentThread().contextClassLoader
try {
val tempClassLoader = newInstance(arrayOf(file.parentFile.toURI().toURL()), currentClassLoader)
Thread.currentThread().contextClassLoader = tempClassLoader
return tempClassLoader.block()
} finally {
Thread.currentThread().contextClassLoader = currentClassLoader
}
}
}
override fun convertTo(contract: Collection<Contract>) = contract.toList()
private fun withUpdatedClassloader(file: File, block: ClassLoader.() -> Any): Any {
val currentClassLoader = Thread.currentThread().contextClassLoader
try {
val tempClassLoader = newInstance(arrayOf(file.parentFile.toURI().toURL()), currentClassLoader)
Thread.currentThread().contextClassLoader = tempClassLoader
return tempClassLoader.block()
} finally {
Thread.currentThread().contextClassLoader = currentClassLoader
}
}
class ScriptWithCurrentClasspathConfiguration : ScriptCompilationConfiguration(
{
jvm {
// Extract the whole classpath from context classloader and use it as dependencies
dependenciesFromCurrentContext(wholeClasspath = true)
}
}
)
}

View File

@@ -56,15 +56,6 @@
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<excludes>
<exclude>**/PluginUnitTest.java</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>