mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2025-12-19 15:09:18 +00:00
add shared class loader support for script dependencies
This commit is contained in:
@@ -1,10 +1,9 @@
|
||||
package com.volmit.iris.core.scripting.kotlin.base
|
||||
|
||||
import com.volmit.iris.core.scripting.kotlin.runner.configureMavenDepsOnAnnotations
|
||||
import com.volmit.iris.core.scripting.kotlin.runner.configure
|
||||
import kotlin.script.experimental.annotations.KotlinScript
|
||||
import kotlin.script.experimental.api.ScriptCompilationConfiguration
|
||||
import kotlin.script.experimental.api.defaultImports
|
||||
import kotlin.script.experimental.api.refineConfiguration
|
||||
import kotlin.script.experimental.dependencies.DependsOn
|
||||
import kotlin.script.experimental.dependencies.Repository
|
||||
import kotlin.script.experimental.jvm.dependenciesFromClassContext
|
||||
@@ -28,9 +27,7 @@ object SimpleScriptDefinition : ScriptCompilationConfiguration({
|
||||
dependenciesFromClassContext(SimpleScript::class, wholeClasspath = true)
|
||||
}
|
||||
|
||||
refineConfiguration {
|
||||
onAnnotations(DependsOn::class, Repository::class, handler = ::configureMavenDepsOnAnnotations)
|
||||
}
|
||||
configure()
|
||||
}) {
|
||||
private fun readResolve(): Any = SimpleScriptDefinition
|
||||
}
|
||||
@@ -5,9 +5,10 @@ import java.io.File
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.script.experimental.annotations.KotlinScript
|
||||
import kotlin.script.experimental.api.*
|
||||
import kotlin.script.experimental.dependencies.DependsOn
|
||||
import kotlin.script.experimental.dependencies.Repository
|
||||
import kotlin.script.experimental.api.KotlinType
|
||||
import kotlin.script.experimental.api.ResultWithDiagnostics
|
||||
import kotlin.script.experimental.api.ScriptCompilationConfiguration
|
||||
import kotlin.script.experimental.api.SourceCode
|
||||
import kotlin.script.experimental.host.FileScriptSource
|
||||
import kotlin.script.experimental.host.createCompilationConfigurationFromTemplate
|
||||
import kotlin.script.experimental.host.toScriptSource
|
||||
@@ -25,6 +26,7 @@ class ScriptRunner(
|
||||
|
||||
private val configs = ConcurrentHashMap<KClass<*>, ScriptCompilationConfiguration>()
|
||||
private val hostConfig = host.baseHostConfiguration.withDefaultsFrom(defaultJvmScriptingHostConfiguration)
|
||||
private val sharedClassLoader = SharedClassLoader()
|
||||
private var resolver = createResolver(baseDir)
|
||||
|
||||
fun compile(type: KClass<*>, raw: String, name: String? = null) = compile(type, raw.toScriptSource(name))
|
||||
@@ -50,7 +52,8 @@ class ScriptRunner(
|
||||
) {
|
||||
dependencyResolver(resolver)
|
||||
packDirectory(baseDir)
|
||||
|
||||
sharedClassloader(sharedClassLoader)
|
||||
|
||||
if (SimpleScript::class.java.isAssignableFrom(type.java))
|
||||
return@createCompilationConfigurationFromTemplate
|
||||
|
||||
@@ -60,8 +63,6 @@ class ScriptRunner(
|
||||
dependenciesFromClassContext(KotlinScript::class, wholeClasspath = true)
|
||||
}
|
||||
|
||||
refineConfiguration {
|
||||
onAnnotations(DependsOn::class, Repository::class, handler = ::configureMavenDepsOnAnnotations)
|
||||
}
|
||||
configure()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,18 @@ package com.volmit.iris.core.scripting.kotlin.runner
|
||||
import com.volmit.iris.core.scripting.kotlin.runner.resolver.CompoundDependenciesResolver
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.io.File
|
||||
import java.net.URLClassLoader
|
||||
import kotlin.script.experimental.api.*
|
||||
import kotlin.script.experimental.dependencies.resolveFromScriptSourceAnnotations
|
||||
import kotlin.script.experimental.jvm.updateClasspath
|
||||
import kotlin.script.experimental.dependencies.DependsOn
|
||||
import kotlin.script.experimental.dependencies.ExternalDependenciesResolver
|
||||
import kotlin.script.experimental.dependencies.Repository
|
||||
import kotlin.script.experimental.dependencies.addRepository
|
||||
import kotlin.script.experimental.dependencies.impl.SimpleExternalDependenciesResolverOptionsParser
|
||||
import kotlin.script.experimental.jvm.JvmDependency
|
||||
import kotlin.script.experimental.jvm.JvmDependencyFromClassLoader
|
||||
import kotlin.script.experimental.jvm.util.classpathFromClassloader
|
||||
import kotlin.script.experimental.util.PropertiesCollection
|
||||
import kotlin.script.experimental.util.filterByAnnotationType
|
||||
|
||||
internal fun <T, R> ResultWithDiagnostics<T>.map(transformer: (T) -> R): ResultWithDiagnostics<R> = when (this) {
|
||||
is ResultWithDiagnostics.Success -> ResultWithDiagnostics.Success(transformer(value), reports)
|
||||
@@ -25,11 +32,14 @@ private val workDir = File(".").normalize()
|
||||
internal fun createResolver(baseDir: File = workDir) = CompoundDependenciesResolver(baseDir)
|
||||
|
||||
private val resolver = createResolver()
|
||||
internal fun configureMavenDepsOnAnnotations(context: ScriptConfigurationRefinementContext): ResultWithDiagnostics<ScriptCompilationConfiguration> {
|
||||
private val loader = SharedClassLoader()
|
||||
|
||||
private fun configureMavenDepsOnAnnotations(context: ScriptConfigurationRefinementContext): ResultWithDiagnostics<ScriptCompilationConfiguration> = runCatching {
|
||||
val annotations = context.collectedData?.get(ScriptCollectedData.collectedAnnotations)?.takeIf { it.isNotEmpty() }
|
||||
?: return context.compilationConfiguration.asSuccess()
|
||||
|
||||
val reports = mutableListOf<ScriptDiagnostic>()
|
||||
val loader = context.compilationConfiguration[ScriptCompilationConfiguration.sharedClassloader] ?: loader
|
||||
val resolver = context.compilationConfiguration[ScriptCompilationConfiguration.dependencyResolver] ?: resolver
|
||||
context.compilationConfiguration[ScriptCompilationConfiguration.packDirectory]
|
||||
?.addPack(resolver)
|
||||
@@ -55,22 +65,82 @@ internal fun configureMavenDepsOnAnnotations(context: ScriptConfigurationRefinem
|
||||
}
|
||||
|
||||
return runBlocking {
|
||||
resolver.resolveFromScriptSourceAnnotations(annotations)
|
||||
}.onSuccess {
|
||||
resolver.resolveDependencies(annotations)
|
||||
}.onSuccess { classpath ->
|
||||
context.compilationConfiguration.with {
|
||||
updateClasspath(it)
|
||||
val newClasspath = classpath.filterNewClasspath(this[ScriptCompilationConfiguration.dependencies])
|
||||
?: return@with
|
||||
val shared = classpath.mapNotNull { p -> p.first.takeIf { p.second } }
|
||||
if (shared.isNotEmpty()) loader.addFiles(shared)
|
||||
|
||||
val regular = newClasspath
|
||||
.map { p -> p.first }
|
||||
.let { JvmDependency(it) }
|
||||
ScriptCompilationConfiguration.dependencies.append(regular)
|
||||
}.asSuccess()
|
||||
}.appendReports(reports)
|
||||
}.getOrElse { ResultWithDiagnostics.Failure(it.asDiagnostics()) }
|
||||
|
||||
private fun Collection<Pair<File, Boolean>>.filterNewClasspath(known: Collection<ScriptDependency>?): List<Pair<File, Boolean>>? {
|
||||
if (isEmpty()) return null
|
||||
|
||||
val knownClasspath = known?.flatMapTo(hashSetOf()) {
|
||||
(it as? JvmDependency)?.classpath ?: emptyList()
|
||||
}
|
||||
|
||||
return filterNot { knownClasspath?.contains(it.first) == true }.takeIf { it.isNotEmpty() }
|
||||
}
|
||||
|
||||
private suspend fun ExternalDependenciesResolver.resolveDependencies(
|
||||
annotations: Iterable<ScriptSourceAnnotation<*>>
|
||||
): ResultWithDiagnostics<List<Pair<File, Boolean>>> {
|
||||
val reports = mutableListOf<ScriptDiagnostic>()
|
||||
annotations.forEach { (annotation, locationWithId) ->
|
||||
when (annotation) {
|
||||
is Repository -> {
|
||||
val options = SimpleExternalDependenciesResolverOptionsParser(*annotation.options, locationWithId = locationWithId)
|
||||
.valueOr { return it }
|
||||
|
||||
for (coordinates in annotation.repositoriesCoordinates) {
|
||||
val added = addRepository(coordinates, options, locationWithId)
|
||||
.also { reports.addAll(it.reports) }
|
||||
.valueOr { return it }
|
||||
|
||||
if (!added)
|
||||
return reports + makeFailureResult(
|
||||
"Unrecognized repository coordinates: $coordinates",
|
||||
locationWithId = locationWithId
|
||||
)
|
||||
}
|
||||
}
|
||||
is DependsOn -> {}
|
||||
else -> return reports + makeFailureResult("Unknown annotation ${annotation.javaClass}", locationWithId = locationWithId)
|
||||
}
|
||||
}
|
||||
|
||||
return reports + annotations.filterByAnnotationType<DependsOn>()
|
||||
.flatMapSuccess { (annotation, locationWithId) ->
|
||||
SimpleExternalDependenciesResolverOptionsParser(
|
||||
*annotation.options,
|
||||
locationWithId = locationWithId
|
||||
).onSuccess { options ->
|
||||
annotation.artifactsCoordinates.asIterable().flatMapSuccess { artifactCoordinates ->
|
||||
resolve(artifactCoordinates, options, locationWithId)
|
||||
}.map { files -> files.map { it to (options.shared ?: false) } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val ExternalDependenciesResolver.Options.shared get() = flag("shared")
|
||||
internal val ClassLoader.classpath get() = classpathFromClassloader(this) ?: emptyList()
|
||||
|
||||
fun <R> ResultWithDiagnostics<R>.valueOrThrow(message: CharSequence): R = valueOr {
|
||||
internal fun <R> ResultWithDiagnostics<R>.valueOrThrow(message: CharSequence): R = valueOr {
|
||||
throw RuntimeException(it.reports.joinToString("\n", "$message\n") { r -> r.render(withStackTrace = true) })
|
||||
}
|
||||
|
||||
val ScriptCompilationConfigurationKeys.dependencyResolver by PropertiesCollection.key(resolver)
|
||||
val ScriptCompilationConfigurationKeys.packDirectory by PropertiesCollection.key<File>()
|
||||
internal val ScriptCompilationConfigurationKeys.dependencyResolver by PropertiesCollection.key(resolver, true)
|
||||
internal val ScriptCompilationConfigurationKeys.packDirectory by PropertiesCollection.key(workDir, true)
|
||||
internal val ScriptCompilationConfigurationKeys.sharedClassloader by PropertiesCollection.key(loader, true)
|
||||
|
||||
private fun File.addPack(resolver: CompoundDependenciesResolver) = resolver.addPack(this)
|
||||
private fun <R> ResultWithDiagnostics<R>.appendReports(reports : Collection<ScriptDiagnostic>) =
|
||||
@@ -78,4 +148,26 @@ private fun <R> ResultWithDiagnostics<R>.appendReports(reports : Collection<Scri
|
||||
else when (this) {
|
||||
is ResultWithDiagnostics.Success -> ResultWithDiagnostics.Success(value, this.reports + reports)
|
||||
is ResultWithDiagnostics.Failure -> ResultWithDiagnostics.Failure(this.reports + reports)
|
||||
}
|
||||
}
|
||||
|
||||
internal class SharedClassLoader(parent: ClassLoader = SharedClassLoader::class.java.classLoader) : URLClassLoader(arrayOf(), parent) {
|
||||
val dependency = JvmDependencyFromClassLoader { this }
|
||||
|
||||
fun addFiles(files: List<File>) {
|
||||
files.forEach { addURL(it.toURI().toURL()) }
|
||||
}
|
||||
}
|
||||
|
||||
internal fun ScriptCompilationConfiguration.Builder.configure() {
|
||||
refineConfiguration {
|
||||
beforeParsing { context -> try {
|
||||
context.compilationConfiguration.with {
|
||||
ScriptCompilationConfiguration.dependencies.append((this[ScriptCompilationConfiguration.sharedClassloader] ?: loader).dependency)
|
||||
}.asSuccess()
|
||||
} catch (e: Throwable) {
|
||||
ResultWithDiagnostics.Failure(e.asDiagnostics())
|
||||
}}
|
||||
|
||||
onAnnotations(DependsOn::class, Repository::class, handler = ::configureMavenDepsOnAnnotations)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user