[Scripting] Add partial transitive resolution to Maven resolver
This commit is contained in:
+10
-4
@@ -17,6 +17,7 @@ import kotlin.script.experimental.dependencies.ExternalDependenciesResolver
|
||||
import kotlin.script.experimental.dependencies.RepositoryCoordinates
|
||||
import kotlin.script.experimental.dependencies.impl.*
|
||||
import kotlin.script.experimental.dependencies.maven.impl.AetherResolveSession
|
||||
import kotlin.script.experimental.dependencies.maven.impl.ResolutionKind
|
||||
import kotlin.script.experimental.dependencies.maven.impl.mavenCentral
|
||||
|
||||
@Deprecated(
|
||||
@@ -58,15 +59,20 @@ class MavenDependenciesResolver : ExternalDependenciesResolver {
|
||||
|
||||
return try {
|
||||
val dependencyScopes = options.dependencyScopes ?: listOf(JavaScopes.COMPILE, JavaScopes.RUNTIME)
|
||||
val transitive = options.transitive ?: true
|
||||
val kind = when (options.partialResolution) {
|
||||
true -> ResolutionKind.TRANSITIVE_PARTIAL
|
||||
false, null -> when(options.transitive) {
|
||||
true, null -> ResolutionKind.TRANSITIVE
|
||||
false -> ResolutionKind.NON_TRANSITIVE
|
||||
}
|
||||
}
|
||||
val classifier = options.classifier
|
||||
val extension = options.extension
|
||||
val deps = AetherResolveSession(
|
||||
AetherResolveSession(
|
||||
null, remoteRepositories()
|
||||
).resolve(
|
||||
artifactId, dependencyScopes.joinToString(","), transitive, null, classifier, extension
|
||||
artifactId, dependencyScopes.joinToString(","), kind, null, classifier, extension
|
||||
)
|
||||
ResultWithDiagnostics.Success(deps.map { it.file })
|
||||
} catch (e: RepositoryException) {
|
||||
makeResolveFailureResult(e, sourceCodeLocation)
|
||||
}
|
||||
|
||||
+87
-22
@@ -39,10 +39,23 @@ import org.eclipse.aether.util.repository.AuthenticationBuilder
|
||||
import org.eclipse.aether.util.repository.DefaultMirrorSelector
|
||||
import org.eclipse.aether.util.repository.DefaultProxySelector
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import kotlin.script.experimental.api.ResultWithDiagnostics
|
||||
import kotlin.script.experimental.api.ScriptDiagnostic
|
||||
import kotlin.script.experimental.api.asSuccess
|
||||
|
||||
val mavenCentral: RemoteRepository = RemoteRepository.Builder("maven central", "default", "https://repo.maven.apache.org/maven2/").build()
|
||||
|
||||
internal enum class ResolutionKind {
|
||||
NON_TRANSITIVE,
|
||||
TRANSITIVE,
|
||||
|
||||
// Partial resolution is successful in case if dependency tree was built,
|
||||
// but may return non-complete list of dependencies - i.e. while requesting sources, some libraries may lack sources artifacts.
|
||||
// Resolution errors will be attached as reports.
|
||||
// Also, might be slightly slower than usual transitive resolution.
|
||||
TRANSITIVE_PARTIAL
|
||||
}
|
||||
|
||||
internal class AetherResolveSession(
|
||||
localRepoDirectory: File? = null,
|
||||
remoteRepos: List<RemoteRepository> = listOf(mavenCentral)
|
||||
@@ -133,22 +146,63 @@ internal class AetherResolveSession(
|
||||
fun resolve(
|
||||
root: Artifact,
|
||||
scope: String,
|
||||
transitive: Boolean,
|
||||
kind: ResolutionKind,
|
||||
filter: DependencyFilter?,
|
||||
classifier: String? = null,
|
||||
extension: String? = null,
|
||||
): List<Artifact> {
|
||||
return if (transitive) resolveDependencies(root, scope, filter, classifier, extension)
|
||||
else resolveArtifact(root)
|
||||
): ResultWithDiagnostics<List<File>> {
|
||||
if (kind == ResolutionKind.NON_TRANSITIVE) return resolveArtifact(root).asSuccess()
|
||||
|
||||
val requests = resolveTree(root, scope, filter, classifier, extension)
|
||||
|
||||
@Suppress("KotlinConstantConditions")
|
||||
return when (kind) {
|
||||
ResolutionKind.TRANSITIVE -> resolveDependencies(requests) {
|
||||
repositorySystem.resolveArtifacts(
|
||||
repositorySystemSession,
|
||||
requests
|
||||
).toFiles().asSuccess()
|
||||
}
|
||||
|
||||
ResolutionKind.TRANSITIVE_PARTIAL -> resolveDependencies(requests) {
|
||||
val reports = mutableListOf<ScriptDiagnostic>()
|
||||
val results = mutableListOf<File>()
|
||||
for (req in requests) {
|
||||
try {
|
||||
results.add(
|
||||
repositorySystem.resolveArtifact(
|
||||
repositorySystemSession,
|
||||
req
|
||||
).artifact.file
|
||||
)
|
||||
} catch (e: ArtifactResolutionException) {
|
||||
reports.add(
|
||||
ScriptDiagnostic(
|
||||
ScriptDiagnostic.unspecifiedError,
|
||||
e.message.orEmpty(),
|
||||
ScriptDiagnostic.Severity.WARNING,
|
||||
exception = e
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ResultWithDiagnostics.Success(results, reports)
|
||||
}
|
||||
|
||||
ResolutionKind.NON_TRANSITIVE -> {
|
||||
error("This statement is not reachable")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveDependencies(
|
||||
private fun resolveTree(
|
||||
root: Artifact,
|
||||
scope: String,
|
||||
filter: DependencyFilter?,
|
||||
classifier: String?,
|
||||
extension: String?,
|
||||
): List<Artifact> {
|
||||
): Collection<ArtifactRequest> {
|
||||
return fetch(
|
||||
request(Dependency(root, scope)),
|
||||
{ req ->
|
||||
@@ -163,8 +217,7 @@ internal class AetherResolveSession(
|
||||
)
|
||||
)
|
||||
|
||||
val requests = requestsBuilder.requests
|
||||
repositorySystem.resolveArtifacts(repositorySystemSession, requests)
|
||||
requestsBuilder.requests
|
||||
},
|
||||
{ req, ex ->
|
||||
DependencyCollectionException(
|
||||
@@ -176,7 +229,25 @@ internal class AetherResolveSession(
|
||||
)
|
||||
}
|
||||
|
||||
private fun resolveArtifact(artifact: Artifact): List<Artifact> {
|
||||
private fun Collection<ArtifactResult>.toFiles() = map { it.artifact.file }
|
||||
|
||||
private fun resolveDependencies(
|
||||
requests: Collection<ArtifactRequest>,
|
||||
resolveAction: (Collection<ArtifactRequest>) -> ResultWithDiagnostics<List<File>>
|
||||
): ResultWithDiagnostics<List<File>> {
|
||||
return fetch(
|
||||
requests,
|
||||
resolveAction
|
||||
) { _, ex ->
|
||||
DependencyCollectionException(
|
||||
null,
|
||||
ex.message,
|
||||
ex
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveArtifact(artifact: Artifact): List<File> {
|
||||
val request = ArtifactRequest()
|
||||
request.artifact = artifact
|
||||
for (repo in remotes) {
|
||||
@@ -187,7 +258,7 @@ internal class AetherResolveSession(
|
||||
request,
|
||||
{ req -> listOf(repositorySystem.resolveArtifact(repositorySystemSession, req)) },
|
||||
{ req, ex -> ArtifactResolutionException(listOf(ArtifactResult(req)), ex.message, IllegalArgumentException(ex)) }
|
||||
)
|
||||
).toFiles()
|
||||
}
|
||||
|
||||
private fun request(root: Dependency): CollectRequest {
|
||||
@@ -199,25 +270,19 @@ internal class AetherResolveSession(
|
||||
return request
|
||||
}
|
||||
|
||||
private fun <RequestT> fetch(
|
||||
private fun <RequestT, ResultT> fetch(
|
||||
request: RequestT,
|
||||
fetchBody: (RequestT) -> Collection<ArtifactResult>,
|
||||
fetchBody: (RequestT) -> ResultT,
|
||||
wrapException: (RequestT, Exception) -> Exception
|
||||
): List<Artifact> {
|
||||
val deps: MutableList<Artifact> = LinkedList()
|
||||
try {
|
||||
var results: Collection<ArtifactResult>
|
||||
): ResultT {
|
||||
return try {
|
||||
synchronized(this) {
|
||||
results = fetchBody(request)
|
||||
}
|
||||
for (res in results) {
|
||||
deps.add(res.artifact)
|
||||
fetchBody(request)
|
||||
}
|
||||
// @checkstyle IllegalCatch (1 line)
|
||||
} catch (ex: Exception) {
|
||||
throw wrapException(request, ex)
|
||||
}
|
||||
return deps
|
||||
}
|
||||
|
||||
private fun getMirrorSelector(): DefaultMirrorSelector {
|
||||
|
||||
+18
@@ -183,6 +183,24 @@ class MavenResolverTest : ResolversTestBase() {
|
||||
)
|
||||
}
|
||||
|
||||
@Ignore("ignored because spark is a very heavy dependency")
|
||||
fun ignore_testPartialResolution() {
|
||||
val resolver = MavenDependenciesResolver()
|
||||
val options = buildOptions(
|
||||
DependenciesResolverOptionsName.PARTIAL_RESOLUTION to "true",
|
||||
DependenciesResolverOptionsName.CLASSIFIER to "sources",
|
||||
DependenciesResolverOptionsName.EXTENSION to "jar",
|
||||
)
|
||||
|
||||
val result = runBlocking {
|
||||
resolver.resolve("org.jetbrains.kotlinx.spark:kotlin-spark-api_3.3.0_2.13:1.2.1", options)
|
||||
}
|
||||
|
||||
result as ResultWithDiagnostics.Success
|
||||
assertTrue(result.reports.isNotEmpty())
|
||||
assertTrue(result.value.isNotEmpty())
|
||||
}
|
||||
|
||||
// Ignored - tests with custom repos often break the CI due to the caching issues
|
||||
// TODO: find a way to enable it back
|
||||
@Ignore
|
||||
|
||||
+8
@@ -23,6 +23,7 @@ operator fun MutableMap<String, String>.set(key: DependenciesResolverOptionsName
|
||||
*/
|
||||
enum class DependenciesResolverOptionsName(optionName: String? = null) {
|
||||
TRANSITIVE,
|
||||
PARTIAL_RESOLUTION,
|
||||
SCOPE,
|
||||
USERNAME,
|
||||
PASSWORD,
|
||||
@@ -37,6 +38,13 @@ enum class DependenciesResolverOptionsName(optionName: String? = null) {
|
||||
val ExternalDependenciesResolver.Options.transitive
|
||||
get() = flag(DependenciesResolverOptionsName.TRANSITIVE)
|
||||
|
||||
/**
|
||||
* Enables partial resolution of transitive dependencies.
|
||||
* When this flag is enabled, resolver ignores [transitive] flag.
|
||||
*/
|
||||
val ExternalDependenciesResolver.Options.partialResolution
|
||||
get() = flag(DependenciesResolverOptionsName.PARTIAL_RESOLUTION)
|
||||
|
||||
val ExternalDependenciesResolver.Options.dependencyScopes
|
||||
get() = value(DependenciesResolverOptionsName.SCOPE)?.split(",")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user