[Scripting] Add partial transitive resolution to Maven resolver

This commit is contained in:
Ilya Muradyan
2022-09-09 19:46:23 +02:00
parent a78d43c1ec
commit 551a481dc5
4 changed files with 123 additions and 26 deletions
@@ -17,6 +17,7 @@ import kotlin.script.experimental.dependencies.ExternalDependenciesResolver
import kotlin.script.experimental.dependencies.RepositoryCoordinates import kotlin.script.experimental.dependencies.RepositoryCoordinates
import kotlin.script.experimental.dependencies.impl.* import kotlin.script.experimental.dependencies.impl.*
import kotlin.script.experimental.dependencies.maven.impl.AetherResolveSession import kotlin.script.experimental.dependencies.maven.impl.AetherResolveSession
import kotlin.script.experimental.dependencies.maven.impl.ResolutionKind
import kotlin.script.experimental.dependencies.maven.impl.mavenCentral import kotlin.script.experimental.dependencies.maven.impl.mavenCentral
@Deprecated( @Deprecated(
@@ -58,15 +59,20 @@ class MavenDependenciesResolver : ExternalDependenciesResolver {
return try { return try {
val dependencyScopes = options.dependencyScopes ?: listOf(JavaScopes.COMPILE, JavaScopes.RUNTIME) 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 classifier = options.classifier
val extension = options.extension val extension = options.extension
val deps = AetherResolveSession( AetherResolveSession(
null, remoteRepositories() null, remoteRepositories()
).resolve( ).resolve(
artifactId, dependencyScopes.joinToString(","), transitive, null, classifier, extension artifactId, dependencyScopes.joinToString(","), kind, null, classifier, extension
) )
ResultWithDiagnostics.Success(deps.map { it.file })
} catch (e: RepositoryException) { } catch (e: RepositoryException) {
makeResolveFailureResult(e, sourceCodeLocation) makeResolveFailureResult(e, sourceCodeLocation)
} }
@@ -39,10 +39,23 @@ import org.eclipse.aether.util.repository.AuthenticationBuilder
import org.eclipse.aether.util.repository.DefaultMirrorSelector import org.eclipse.aether.util.repository.DefaultMirrorSelector
import org.eclipse.aether.util.repository.DefaultProxySelector import org.eclipse.aether.util.repository.DefaultProxySelector
import java.io.File 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() 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( internal class AetherResolveSession(
localRepoDirectory: File? = null, localRepoDirectory: File? = null,
remoteRepos: List<RemoteRepository> = listOf(mavenCentral) remoteRepos: List<RemoteRepository> = listOf(mavenCentral)
@@ -133,22 +146,63 @@ internal class AetherResolveSession(
fun resolve( fun resolve(
root: Artifact, root: Artifact,
scope: String, scope: String,
transitive: Boolean, kind: ResolutionKind,
filter: DependencyFilter?, filter: DependencyFilter?,
classifier: String? = null, classifier: String? = null,
extension: String? = null, extension: String? = null,
): List<Artifact> { ): ResultWithDiagnostics<List<File>> {
return if (transitive) resolveDependencies(root, scope, filter, classifier, extension) if (kind == ResolutionKind.NON_TRANSITIVE) return resolveArtifact(root).asSuccess()
else resolveArtifact(root)
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, root: Artifact,
scope: String, scope: String,
filter: DependencyFilter?, filter: DependencyFilter?,
classifier: String?, classifier: String?,
extension: String?, extension: String?,
): List<Artifact> { ): Collection<ArtifactRequest> {
return fetch( return fetch(
request(Dependency(root, scope)), request(Dependency(root, scope)),
{ req -> { req ->
@@ -163,8 +217,7 @@ internal class AetherResolveSession(
) )
) )
val requests = requestsBuilder.requests requestsBuilder.requests
repositorySystem.resolveArtifacts(repositorySystemSession, requests)
}, },
{ req, ex -> { req, ex ->
DependencyCollectionException( 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() val request = ArtifactRequest()
request.artifact = artifact request.artifact = artifact
for (repo in remotes) { for (repo in remotes) {
@@ -187,7 +258,7 @@ internal class AetherResolveSession(
request, request,
{ req -> listOf(repositorySystem.resolveArtifact(repositorySystemSession, req)) }, { req -> listOf(repositorySystem.resolveArtifact(repositorySystemSession, req)) },
{ req, ex -> ArtifactResolutionException(listOf(ArtifactResult(req)), ex.message, IllegalArgumentException(ex)) } { req, ex -> ArtifactResolutionException(listOf(ArtifactResult(req)), ex.message, IllegalArgumentException(ex)) }
) ).toFiles()
} }
private fun request(root: Dependency): CollectRequest { private fun request(root: Dependency): CollectRequest {
@@ -199,25 +270,19 @@ internal class AetherResolveSession(
return request return request
} }
private fun <RequestT> fetch( private fun <RequestT, ResultT> fetch(
request: RequestT, request: RequestT,
fetchBody: (RequestT) -> Collection<ArtifactResult>, fetchBody: (RequestT) -> ResultT,
wrapException: (RequestT, Exception) -> Exception wrapException: (RequestT, Exception) -> Exception
): List<Artifact> { ): ResultT {
val deps: MutableList<Artifact> = LinkedList() return try {
try {
var results: Collection<ArtifactResult>
synchronized(this) { synchronized(this) {
results = fetchBody(request) fetchBody(request)
}
for (res in results) {
deps.add(res.artifact)
} }
// @checkstyle IllegalCatch (1 line) // @checkstyle IllegalCatch (1 line)
} catch (ex: Exception) { } catch (ex: Exception) {
throw wrapException(request, ex) throw wrapException(request, ex)
} }
return deps
} }
private fun getMirrorSelector(): DefaultMirrorSelector { private fun getMirrorSelector(): DefaultMirrorSelector {
@@ -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 // Ignored - tests with custom repos often break the CI due to the caching issues
// TODO: find a way to enable it back // TODO: find a way to enable it back
@Ignore @Ignore
@@ -23,6 +23,7 @@ operator fun MutableMap<String, String>.set(key: DependenciesResolverOptionsName
*/ */
enum class DependenciesResolverOptionsName(optionName: String? = null) { enum class DependenciesResolverOptionsName(optionName: String? = null) {
TRANSITIVE, TRANSITIVE,
PARTIAL_RESOLUTION,
SCOPE, SCOPE,
USERNAME, USERNAME,
PASSWORD, PASSWORD,
@@ -37,6 +38,13 @@ enum class DependenciesResolverOptionsName(optionName: String? = null) {
val ExternalDependenciesResolver.Options.transitive val ExternalDependenciesResolver.Options.transitive
get() = flag(DependenciesResolverOptionsName.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 val ExternalDependenciesResolver.Options.dependencyScopes
get() = value(DependenciesResolverOptionsName.SCOPE)?.split(",") get() = value(DependenciesResolverOptionsName.SCOPE)?.split(",")