diff --git a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/base/SimpleScript.kt b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/base/SimpleScript.kt index d97732c31..5839bbcc0 100644 --- a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/base/SimpleScript.kt +++ b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/base/SimpleScript.kt @@ -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 } \ No newline at end of file diff --git a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/runner/ScriptRunner.kt b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/runner/ScriptRunner.kt index 04b3b535d..593ac4db1 100644 --- a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/runner/ScriptRunner.kt +++ b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/runner/ScriptRunner.kt @@ -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, 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() } } diff --git a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/runner/Utils.kt b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/runner/Utils.kt index 926ecf2b9..fc0fdd1a6 100644 --- a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/runner/Utils.kt +++ b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/runner/Utils.kt @@ -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 ResultWithDiagnostics.map(transformer: (T) -> R): ResultWithDiagnostics = 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 { +private val loader = SharedClassLoader() + +private fun configureMavenDepsOnAnnotations(context: ScriptConfigurationRefinementContext): ResultWithDiagnostics = runCatching { val annotations = context.collectedData?.get(ScriptCollectedData.collectedAnnotations)?.takeIf { it.isNotEmpty() } ?: return context.compilationConfiguration.asSuccess() val reports = mutableListOf() + 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>.filterNewClasspath(known: Collection?): List>? { + 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> +): ResultWithDiagnostics>> { + val reports = mutableListOf() + 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() + .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 ResultWithDiagnostics.valueOrThrow(message: CharSequence): R = valueOr { +internal fun ResultWithDiagnostics.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() +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 ResultWithDiagnostics.appendReports(reports : Collection) = @@ -78,4 +148,26 @@ private fun ResultWithDiagnostics.appendReports(reports : Collection ResultWithDiagnostics.Success(value, this.reports + reports) is ResultWithDiagnostics.Failure -> ResultWithDiagnostics.Failure(this.reports + reports) - } \ No newline at end of file + } + +internal class SharedClassLoader(parent: ClassLoader = SharedClassLoader::class.java.classLoader) : URLClassLoader(arrayOf(), parent) { + val dependency = JvmDependencyFromClassLoader { this } + + fun addFiles(files: List) { + 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) + } +} \ No newline at end of file