From 05193bd0d91783f242a3db63e7d33c9f0bfbff7a Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Fri, 1 Aug 2025 04:51:58 +0200 Subject: [PATCH] make depencency resolvers more predictable --- .../core/scripting/kotlin/runner/Utils.kt | 56 +++++--- .../resolver/CompoundDependenciesResolver.kt | 120 ++++++++++++++++++ .../runner/resolver/DependenciesResolver.kt | 8 ++ .../FileDependenciesResolver.kt | 8 +- .../LocalMavenDependenciesResolver.kt | 85 +++++++++++++ 5 files changed, 254 insertions(+), 23 deletions(-) create mode 100644 core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/runner/resolver/CompoundDependenciesResolver.kt create mode 100644 core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/runner/resolver/DependenciesResolver.kt rename core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/runner/{ => resolver}/FileDependenciesResolver.kt (94%) create mode 100644 core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/runner/resolver/LocalMavenDependenciesResolver.kt 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 dd329b6dd..926ecf2b9 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 @@ -1,11 +1,9 @@ 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 kotlin.script.experimental.api.* -import kotlin.script.experimental.dependencies.CompoundDependenciesResolver -import kotlin.script.experimental.dependencies.addRepository -import kotlin.script.experimental.dependencies.maven.MavenDependenciesResolver import kotlin.script.experimental.dependencies.resolveFromScriptSourceAnnotations import kotlin.script.experimental.jvm.updateClasspath import kotlin.script.experimental.jvm.util.classpathFromClassloader @@ -24,37 +22,45 @@ internal fun ResultValue.valueOrNull(): Any? = } private val workDir = File(".").normalize() -internal fun createResolver(baseDir: File = workDir) = CompoundDependenciesResolver(FileDependenciesResolver(baseDir), MavenDependenciesResolver()) +internal fun createResolver(baseDir: File = workDir) = CompoundDependenciesResolver(baseDir) private val resolver = createResolver() internal fun configureMavenDepsOnAnnotations(context: ScriptConfigurationRefinementContext): ResultWithDiagnostics { val annotations = context.collectedData?.get(ScriptCollectedData.collectedAnnotations)?.takeIf { it.isNotEmpty() } ?: return context.compilationConfiguration.asSuccess() + val reports = mutableListOf() val resolver = context.compilationConfiguration[ScriptCompilationConfiguration.dependencyResolver] ?: resolver - val packDirectory = context.compilationConfiguration[ScriptCompilationConfiguration.packDirectory] ?: context.script.locationId?.let(::File)?.takeIf { it.exists() }?.run { - val parts = normalize().absolutePath.split(File.separatorChar) - - var packDir: File? = null - for (i in parts.size - 1 downTo 1) { - if (parts[i] != "scripts") continue - val pack = File(parts.subList(0, i).joinToString(File.separator)) - if (!File(pack, "dimensions${File.separator}${parts[i - 1]}.json").exists()) - continue - packDir = pack - break - } - packDir - } ?: workDir + context.compilationConfiguration[ScriptCompilationConfiguration.packDirectory] + ?.addPack(resolver) + ?: context.script.locationId + ?.let(::File) + ?.takeIf { it.exists() } + ?.run { + val location = SourceCode.LocationWithId(context.script.locationId!!, SourceCode.Location(SourceCode.Position(0, 0))) + val parts = normalize().absolutePath.split(File.separatorChar) + for (i in parts.size - 1 downTo 1) { + if (parts[i] != "scripts") continue + val pack = File(parts.subList(0, i).joinToString(File.separator)) + if (!File(pack, "dimensions${File.separator}${parts[i - 1]}.json").exists()) + continue + pack.addPack(resolver) + reports.add(ScriptDiagnostic( + ScriptDiagnostic.unspecifiedInfo, + "Adding pack \"$pack\"", + ScriptDiagnostic.Severity.INFO, + location + )) + } + } return runBlocking { - resolver.addRepository(packDirectory.toURI().toURL().toString()) resolver.resolveFromScriptSourceAnnotations(annotations) }.onSuccess { context.compilationConfiguration.with { updateClasspath(it) }.asSuccess() - } + }.appendReports(reports) } internal val ClassLoader.classpath get() = classpathFromClassloader(this) ?: emptyList() @@ -64,4 +70,12 @@ fun ResultWithDiagnostics.valueOrThrow(message: CharSequence): R = valueO } val ScriptCompilationConfigurationKeys.dependencyResolver by PropertiesCollection.key(resolver) -val ScriptCompilationConfigurationKeys.packDirectory by PropertiesCollection.key() \ No newline at end of file +val ScriptCompilationConfigurationKeys.packDirectory by PropertiesCollection.key() + +private fun File.addPack(resolver: CompoundDependenciesResolver) = resolver.addPack(this) +private fun ResultWithDiagnostics.appendReports(reports : Collection) = + if (reports.isEmpty()) this + else when (this) { + is ResultWithDiagnostics.Success -> ResultWithDiagnostics.Success(value, this.reports + reports) + is ResultWithDiagnostics.Failure -> ResultWithDiagnostics.Failure(this.reports + reports) + } \ No newline at end of file diff --git a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/runner/resolver/CompoundDependenciesResolver.kt b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/runner/resolver/CompoundDependenciesResolver.kt new file mode 100644 index 000000000..37098e9bb --- /dev/null +++ b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/runner/resolver/CompoundDependenciesResolver.kt @@ -0,0 +1,120 @@ +package com.volmit.iris.core.scripting.kotlin.runner.resolver + +import java.io.File +import kotlin.collections.component1 +import kotlin.collections.component2 +import kotlin.collections.iterator +import kotlin.collections.set +import kotlin.script.experimental.api.IterableResultsCollector +import kotlin.script.experimental.api.ResultWithDiagnostics +import kotlin.script.experimental.api.ScriptDiagnostic +import kotlin.script.experimental.api.SourceCode +import kotlin.script.experimental.api.asErrorDiagnostics +import kotlin.script.experimental.api.asSuccess +import kotlin.script.experimental.dependencies.ArtifactWithLocation +import kotlin.script.experimental.dependencies.ExternalDependenciesResolver +import kotlin.script.experimental.dependencies.RepositoryCoordinates +import kotlin.script.experimental.dependencies.impl.makeResolveFailureResult + +class CompoundDependenciesResolver( + private val baseDir: File +) : DependenciesResolver { + private val resolvers = listOf(FileDependenciesResolver(baseDir), LocalMavenDependenciesResolver()) + + override fun acceptsRepository(repositoryCoordinates: RepositoryCoordinates): Boolean { + return resolvers.any { it.acceptsRepository(repositoryCoordinates) } + } + + override fun acceptsArtifact(artifactCoordinates: String): Boolean { + return resolvers.any { it.acceptsArtifact(artifactCoordinates) } + } + + override fun addRepository( + repositoryCoordinates: RepositoryCoordinates, + options: ExternalDependenciesResolver.Options, + sourceCodeLocation: SourceCode.LocationWithId? + ): ResultWithDiagnostics { + var success = false + var repositoryAdded = false + val reports = mutableListOf() + + for (resolver in resolvers) { + when (val result = resolver.addRepository(repositoryCoordinates, options, sourceCodeLocation)) { + is ResultWithDiagnostics.Success -> { + success = true + repositoryAdded = repositoryAdded || result.value + reports.addAll(result.reports) + } + is ResultWithDiagnostics.Failure -> reports.addAll(result.reports) + } + } + + return when { + success -> repositoryAdded.asSuccess(reports) + reports.isEmpty() -> makeResolveFailureResult( + "No dependency resolver found that recognizes the repository coordinates '$repositoryCoordinates'", + sourceCodeLocation + ) + else -> ResultWithDiagnostics.Failure(reports) + } + } + + override suspend fun resolve( + artifactsWithLocations: List, + options: ExternalDependenciesResolver.Options + ): ResultWithDiagnostics> { + val resultsCollector = IterableResultsCollector() + + val artifactToResolverIndex = mutableMapOf().apply { + for (artifactWithLocation in artifactsWithLocations) { + put(artifactWithLocation, -1) + } + } + + while (artifactToResolverIndex.isNotEmpty()) { + val resolverGroups = mutableMapOf>() + + for ((artifactWithLocation, resolverIndex) in artifactToResolverIndex) { + val (artifact, sourceCodeLocation) = artifactWithLocation + + var currentIndex = resolverIndex + 1 + while (currentIndex < resolvers.size) { + if (resolvers[currentIndex].acceptsArtifact(artifact)) break + ++currentIndex + } + if (currentIndex == resolvers.size) { + if (resolverIndex == -1) { + resultsCollector.addDiagnostic( + "No suitable dependency resolver found for artifact '$artifact'" + .asErrorDiagnostics(locationWithId = sourceCodeLocation) + ) + } + } else { + resolverGroups + .getOrPut(currentIndex) { mutableListOf() } + .add(artifactWithLocation) + } + } + + artifactToResolverIndex.clear() + for ((resolverIndex, artifacts) in resolverGroups) { + val resolver = resolvers[resolverIndex] + val resolveResult = resolver.resolve(artifacts, options) + resultsCollector.add(resolveResult) + if (resolveResult.reports.isNotEmpty()) { + for (artifact in artifacts) { + artifactToResolverIndex[artifact] = resolverIndex + } + } + } + } + + return resultsCollector.getResult() + } + + override fun addPack(directory: File) { + if (!directory.normalize().absolutePath.startsWith(baseDir.normalize().absolutePath)) + return + resolvers.forEach { it.addPack(directory) } + } +} diff --git a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/runner/resolver/DependenciesResolver.kt b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/runner/resolver/DependenciesResolver.kt new file mode 100644 index 000000000..c6ee1e29c --- /dev/null +++ b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/runner/resolver/DependenciesResolver.kt @@ -0,0 +1,8 @@ +package com.volmit.iris.core.scripting.kotlin.runner.resolver + +import java.io.File +import kotlin.script.experimental.dependencies.ExternalDependenciesResolver + +interface DependenciesResolver : ExternalDependenciesResolver { + fun addPack(directory: File) +} \ No newline at end of file diff --git a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/runner/FileDependenciesResolver.kt b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/runner/resolver/FileDependenciesResolver.kt similarity index 94% rename from core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/runner/FileDependenciesResolver.kt rename to core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/runner/resolver/FileDependenciesResolver.kt index 82cfdba21..ea9dac401 100644 --- a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/runner/FileDependenciesResolver.kt +++ b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/runner/resolver/FileDependenciesResolver.kt @@ -1,4 +1,4 @@ -package com.volmit.iris.core.scripting.kotlin.runner +package com.volmit.iris.core.scripting.kotlin.runner.resolver import java.io.File import java.util.concurrent.ConcurrentHashMap @@ -12,7 +12,7 @@ import kotlin.script.experimental.dependencies.impl.toRepositoryUrlOrNull class FileDependenciesResolver( private val baseDir: File, -) : ExternalDependenciesResolver { +) : DependenciesResolver { private val localRepos = ConcurrentHashMap.newKeySet(1).also { it.add(baseDir) } private fun String.toRepositoryFileOrNull(): File? = @@ -62,4 +62,8 @@ class FileDependenciesResolver( override fun acceptsRepository(repositoryCoordinates: RepositoryCoordinates): Boolean = repositoryCoordinates.toFilePath() != null + override fun addPack(directory: File) { + localRepos.add(directory) + } + } \ No newline at end of file diff --git a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/runner/resolver/LocalMavenDependenciesResolver.kt b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/runner/resolver/LocalMavenDependenciesResolver.kt new file mode 100644 index 000000000..c24ed18d3 --- /dev/null +++ b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/runner/resolver/LocalMavenDependenciesResolver.kt @@ -0,0 +1,85 @@ +package com.volmit.iris.core.scripting.kotlin.runner.resolver + +import com.volmit.iris.util.io.IO +import org.dom4j.Document +import org.dom4j.DocumentFactory +import org.dom4j.io.SAXReader +import java.io.File +import kotlin.script.experimental.api.ResultWithDiagnostics +import kotlin.script.experimental.api.SourceCode +import kotlin.script.experimental.dependencies.ArtifactWithLocation +import kotlin.script.experimental.dependencies.ExternalDependenciesResolver +import kotlin.script.experimental.dependencies.RepositoryCoordinates +import kotlin.script.experimental.dependencies.maven.MavenDependenciesResolver + +class LocalMavenDependenciesResolver : DependenciesResolver { + private lateinit var localRepo: File + private val maven = MavenDependenciesResolver(true) + + override fun acceptsRepository(repositoryCoordinates: RepositoryCoordinates) = maven.acceptsRepository(repositoryCoordinates) + override fun acceptsArtifact(artifactCoordinates: String) = maven.acceptsArtifact(artifactCoordinates) + + override fun addRepository( + repositoryCoordinates: RepositoryCoordinates, + options: ExternalDependenciesResolver.Options, + sourceCodeLocation: SourceCode.LocationWithId? + ) = maven.addRepository(repositoryCoordinates, options, sourceCodeLocation) + + override suspend fun resolve( + artifactsWithLocations: List, + options: ExternalDependenciesResolver.Options + ): ResultWithDiagnostics> { + val userOld: String? = System.getProperty("org.apache.maven.user-settings") + val globalOld: String? = System.getProperty("org.apache.maven.global-settings") + + try { + System.setProperty("org.apache.maven.user-settings", createSettings(userOld)) + System.clearProperty("org.apache.maven.global-settings") + + return maven.resolve(artifactsWithLocations, options) + } finally { + setProperty("org.apache.maven.user-settings", userOld) + setProperty("org.apache.maven.global-settings", globalOld) + } + } + + private fun createSettings(user: String?): String { + val settingsFile = File(localRepo, "settings.xml") + val document = readSettings(user) + val node = document.selectSingleNode("//localRepository") + ?: document.rootElement.addElement("localRepository") + + if (node.text != localRepo.absolutePath) { + node.text = localRepo.absolutePath + + IO.write(settingsFile, document) + } + return settingsFile.absolutePath + } + + private fun readSettings(user: String?): Document { + val baseFile = user?.let(::File)?.takeIf { it.exists() } ?: File( + System.getProperty("user.home"), + ".m2/settings.xml" + ).takeIf { it.exists() }?.let { return SAXReader().read(it) } + return if (baseFile != null) SAXReader().read(baseFile) else DocumentFactory.getInstance().createDocument().also { + it.addElement("settings") + .addAttribute("xmlns", "http://maven.apache.org/SETTINGS/1.0.0") + .addAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance") + .addAttribute("xsi:schemaLocation", "http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd") + } + } + + private fun setProperty(name: String, value: String?) { + when(value) { + null -> System.clearProperty(name) + else -> System.setProperty(name, value) + } + } + + override fun addPack(directory: File) { + if (!::localRepo.isInitialized) { + localRepo = directory.resolve(".libs") + } + } +} \ No newline at end of file