make depencency resolvers more predictable

This commit is contained in:
Julian Krings 2025-08-01 04:51:58 +02:00
parent 96efc15c36
commit 05193bd0d9
No known key found for this signature in database
GPG Key ID: 208C6E08C3B718D2
5 changed files with 254 additions and 23 deletions

View File

@ -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<ScriptCompilationConfiguration> {
val annotations = context.collectedData?.get(ScriptCollectedData.collectedAnnotations)?.takeIf { it.isNotEmpty() }
?: return context.compilationConfiguration.asSuccess()
val reports = mutableListOf<ScriptDiagnostic>()
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 <R> ResultWithDiagnostics<R>.valueOrThrow(message: CharSequence): R = valueO
}
val ScriptCompilationConfigurationKeys.dependencyResolver by PropertiesCollection.key(resolver)
val ScriptCompilationConfigurationKeys.packDirectory by PropertiesCollection.key<File>()
val ScriptCompilationConfigurationKeys.packDirectory by PropertiesCollection.key<File>()
private fun File.addPack(resolver: CompoundDependenciesResolver) = resolver.addPack(this)
private fun <R> ResultWithDiagnostics<R>.appendReports(reports : Collection<ScriptDiagnostic>) =
if (reports.isEmpty()) this
else when (this) {
is ResultWithDiagnostics.Success -> ResultWithDiagnostics.Success(value, this.reports + reports)
is ResultWithDiagnostics.Failure -> ResultWithDiagnostics.Failure(this.reports + reports)
}

View File

@ -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<Boolean> {
var success = false
var repositoryAdded = false
val reports = mutableListOf<ScriptDiagnostic>()
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<ArtifactWithLocation>,
options: ExternalDependenciesResolver.Options
): ResultWithDiagnostics<List<File>> {
val resultsCollector = IterableResultsCollector<File>()
val artifactToResolverIndex = mutableMapOf<ArtifactWithLocation, Int>().apply {
for (artifactWithLocation in artifactsWithLocations) {
put(artifactWithLocation, -1)
}
}
while (artifactToResolverIndex.isNotEmpty()) {
val resolverGroups = mutableMapOf<Int, MutableList<ArtifactWithLocation>>()
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) }
}
}

View File

@ -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)
}

View File

@ -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<File>(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)
}
}

View File

@ -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<ArtifactWithLocation>,
options: ExternalDependenciesResolver.Options
): ResultWithDiagnostics<List<File>> {
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")
}
}
}