mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2025-12-24 01:29:16 +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
|
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.annotations.KotlinScript
|
||||||
import kotlin.script.experimental.api.ScriptCompilationConfiguration
|
import kotlin.script.experimental.api.ScriptCompilationConfiguration
|
||||||
import kotlin.script.experimental.api.defaultImports
|
import kotlin.script.experimental.api.defaultImports
|
||||||
import kotlin.script.experimental.api.refineConfiguration
|
|
||||||
import kotlin.script.experimental.dependencies.DependsOn
|
import kotlin.script.experimental.dependencies.DependsOn
|
||||||
import kotlin.script.experimental.dependencies.Repository
|
import kotlin.script.experimental.dependencies.Repository
|
||||||
import kotlin.script.experimental.jvm.dependenciesFromClassContext
|
import kotlin.script.experimental.jvm.dependenciesFromClassContext
|
||||||
@@ -28,9 +27,7 @@ object SimpleScriptDefinition : ScriptCompilationConfiguration({
|
|||||||
dependenciesFromClassContext(SimpleScript::class, wholeClasspath = true)
|
dependenciesFromClassContext(SimpleScript::class, wholeClasspath = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
refineConfiguration {
|
configure()
|
||||||
onAnnotations(DependsOn::class, Repository::class, handler = ::configureMavenDepsOnAnnotations)
|
|
||||||
}
|
|
||||||
}) {
|
}) {
|
||||||
private fun readResolve(): Any = SimpleScriptDefinition
|
private fun readResolve(): Any = SimpleScriptDefinition
|
||||||
}
|
}
|
||||||
@@ -5,9 +5,10 @@ import java.io.File
|
|||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
import kotlin.script.experimental.annotations.KotlinScript
|
import kotlin.script.experimental.annotations.KotlinScript
|
||||||
import kotlin.script.experimental.api.*
|
import kotlin.script.experimental.api.KotlinType
|
||||||
import kotlin.script.experimental.dependencies.DependsOn
|
import kotlin.script.experimental.api.ResultWithDiagnostics
|
||||||
import kotlin.script.experimental.dependencies.Repository
|
import kotlin.script.experimental.api.ScriptCompilationConfiguration
|
||||||
|
import kotlin.script.experimental.api.SourceCode
|
||||||
import kotlin.script.experimental.host.FileScriptSource
|
import kotlin.script.experimental.host.FileScriptSource
|
||||||
import kotlin.script.experimental.host.createCompilationConfigurationFromTemplate
|
import kotlin.script.experimental.host.createCompilationConfigurationFromTemplate
|
||||||
import kotlin.script.experimental.host.toScriptSource
|
import kotlin.script.experimental.host.toScriptSource
|
||||||
@@ -25,6 +26,7 @@ class ScriptRunner(
|
|||||||
|
|
||||||
private val configs = ConcurrentHashMap<KClass<*>, ScriptCompilationConfiguration>()
|
private val configs = ConcurrentHashMap<KClass<*>, ScriptCompilationConfiguration>()
|
||||||
private val hostConfig = host.baseHostConfiguration.withDefaultsFrom(defaultJvmScriptingHostConfiguration)
|
private val hostConfig = host.baseHostConfiguration.withDefaultsFrom(defaultJvmScriptingHostConfiguration)
|
||||||
|
private val sharedClassLoader = SharedClassLoader()
|
||||||
private var resolver = createResolver(baseDir)
|
private var resolver = createResolver(baseDir)
|
||||||
|
|
||||||
fun compile(type: KClass<*>, raw: String, name: String? = null) = compile(type, raw.toScriptSource(name))
|
fun compile(type: KClass<*>, raw: String, name: String? = null) = compile(type, raw.toScriptSource(name))
|
||||||
@@ -50,6 +52,7 @@ class ScriptRunner(
|
|||||||
) {
|
) {
|
||||||
dependencyResolver(resolver)
|
dependencyResolver(resolver)
|
||||||
packDirectory(baseDir)
|
packDirectory(baseDir)
|
||||||
|
sharedClassloader(sharedClassLoader)
|
||||||
|
|
||||||
if (SimpleScript::class.java.isAssignableFrom(type.java))
|
if (SimpleScript::class.java.isAssignableFrom(type.java))
|
||||||
return@createCompilationConfigurationFromTemplate
|
return@createCompilationConfigurationFromTemplate
|
||||||
@@ -60,8 +63,6 @@ class ScriptRunner(
|
|||||||
dependenciesFromClassContext(KotlinScript::class, wholeClasspath = true)
|
dependenciesFromClassContext(KotlinScript::class, wholeClasspath = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
refineConfiguration {
|
configure()
|
||||||
onAnnotations(DependsOn::class, Repository::class, handler = ::configureMavenDepsOnAnnotations)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,18 @@ package com.volmit.iris.core.scripting.kotlin.runner
|
|||||||
import com.volmit.iris.core.scripting.kotlin.runner.resolver.CompoundDependenciesResolver
|
import com.volmit.iris.core.scripting.kotlin.runner.resolver.CompoundDependenciesResolver
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.net.URLClassLoader
|
||||||
import kotlin.script.experimental.api.*
|
import kotlin.script.experimental.api.*
|
||||||
import kotlin.script.experimental.dependencies.resolveFromScriptSourceAnnotations
|
import kotlin.script.experimental.dependencies.DependsOn
|
||||||
import kotlin.script.experimental.jvm.updateClasspath
|
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.jvm.util.classpathFromClassloader
|
||||||
import kotlin.script.experimental.util.PropertiesCollection
|
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) {
|
internal fun <T, R> ResultWithDiagnostics<T>.map(transformer: (T) -> R): ResultWithDiagnostics<R> = when (this) {
|
||||||
is ResultWithDiagnostics.Success -> ResultWithDiagnostics.Success(transformer(value), reports)
|
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)
|
internal fun createResolver(baseDir: File = workDir) = CompoundDependenciesResolver(baseDir)
|
||||||
|
|
||||||
private val resolver = createResolver()
|
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() }
|
val annotations = context.collectedData?.get(ScriptCollectedData.collectedAnnotations)?.takeIf { it.isNotEmpty() }
|
||||||
?: return context.compilationConfiguration.asSuccess()
|
?: return context.compilationConfiguration.asSuccess()
|
||||||
|
|
||||||
val reports = mutableListOf<ScriptDiagnostic>()
|
val reports = mutableListOf<ScriptDiagnostic>()
|
||||||
|
val loader = context.compilationConfiguration[ScriptCompilationConfiguration.sharedClassloader] ?: loader
|
||||||
val resolver = context.compilationConfiguration[ScriptCompilationConfiguration.dependencyResolver] ?: resolver
|
val resolver = context.compilationConfiguration[ScriptCompilationConfiguration.dependencyResolver] ?: resolver
|
||||||
context.compilationConfiguration[ScriptCompilationConfiguration.packDirectory]
|
context.compilationConfiguration[ScriptCompilationConfiguration.packDirectory]
|
||||||
?.addPack(resolver)
|
?.addPack(resolver)
|
||||||
@@ -55,22 +65,82 @@ internal fun configureMavenDepsOnAnnotations(context: ScriptConfigurationRefinem
|
|||||||
}
|
}
|
||||||
|
|
||||||
return runBlocking {
|
return runBlocking {
|
||||||
resolver.resolveFromScriptSourceAnnotations(annotations)
|
resolver.resolveDependencies(annotations)
|
||||||
}.onSuccess {
|
}.onSuccess { classpath ->
|
||||||
context.compilationConfiguration.with {
|
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()
|
}.asSuccess()
|
||||||
}.appendReports(reports)
|
}.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()
|
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) })
|
throw RuntimeException(it.reports.joinToString("\n", "$message\n") { r -> r.render(withStackTrace = true) })
|
||||||
}
|
}
|
||||||
|
|
||||||
val ScriptCompilationConfigurationKeys.dependencyResolver by PropertiesCollection.key(resolver)
|
internal val ScriptCompilationConfigurationKeys.dependencyResolver by PropertiesCollection.key(resolver, true)
|
||||||
val ScriptCompilationConfigurationKeys.packDirectory by PropertiesCollection.key<File>()
|
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 File.addPack(resolver: CompoundDependenciesResolver) = resolver.addPack(this)
|
||||||
private fun <R> ResultWithDiagnostics<R>.appendReports(reports : Collection<ScriptDiagnostic>) =
|
private fun <R> ResultWithDiagnostics<R>.appendReports(reports : Collection<ScriptDiagnostic>) =
|
||||||
@@ -79,3 +149,25 @@ private fun <R> ResultWithDiagnostics<R>.appendReports(reports : Collection<Scri
|
|||||||
is ResultWithDiagnostics.Success -> ResultWithDiagnostics.Success(value, this.reports + reports)
|
is ResultWithDiagnostics.Success -> ResultWithDiagnostics.Success(value, this.reports + reports)
|
||||||
is ResultWithDiagnostics.Failure -> ResultWithDiagnostics.Failure(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