[Scripting] Add transitive support for Maven Resolver

This commit is contained in:
Ilya Muradyan
2021-05-14 21:00:00 +03:00
parent b77a822822
commit a0cc88ab5a
4 changed files with 83 additions and 35 deletions
@@ -49,8 +49,6 @@ class MavenDependenciesResolver : ExternalDependenciesResolver {
private fun remoteRepositories() = if (repos.isEmpty()) arrayListOf(mavenCentral) else repos private fun remoteRepositories() = if (repos.isEmpty()) arrayListOf(mavenCentral) else repos
private fun allRepositories() = remoteRepositories() + localRepo
private fun String.toMavenArtifact(): DefaultArtifact? = private fun String.toMavenArtifact(): DefaultArtifact? =
if (this.isNotBlank() && this.count { it == ':' } >= 2) DefaultArtifact(this) if (this.isNotBlank() && this.count { it == ':' } >= 2) DefaultArtifact(this)
else null else null
@@ -63,19 +61,18 @@ class MavenDependenciesResolver : ExternalDependenciesResolver {
val artifactId = artifactCoordinates.toMavenArtifact()!! val artifactId = artifactCoordinates.toMavenArtifact()!!
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 deps = AetherResolveSession( val deps = AetherResolveSession(
localRepo, remoteRepositories() localRepo, remoteRepositories()
).resolve( ).resolve(
artifactId, dependencyScopes.joinToString(",") artifactId, dependencyScopes.joinToString(","), transitive, null
) )
if (deps != null) ResultWithDiagnostics.Success(deps.map { it.file })
return ResultWithDiagnostics.Success(deps.map { it.file })
} catch (e: DependencyResolutionException) { } catch (e: DependencyResolutionException) {
return makeResolveFailureResult(e.message ?: "unknown error", sourceCodeLocation) makeResolveFailureResult(e.message ?: "unknown error", sourceCodeLocation)
} }
return makeResolveFailureResult(allRepositories().map { "$it: $artifactId not found" }, sourceCodeLocation)
} }
private fun tryResolveEnvironmentVariable(str: String) = private fun tryResolveEnvironmentVariable(str: String) =
@@ -25,10 +25,7 @@ import org.eclipse.aether.internal.transport.wagon.PlexusWagonProvider
import org.eclipse.aether.repository.LocalRepository import org.eclipse.aether.repository.LocalRepository
import org.eclipse.aether.repository.Proxy import org.eclipse.aether.repository.Proxy
import org.eclipse.aether.repository.RemoteRepository import org.eclipse.aether.repository.RemoteRepository
import org.eclipse.aether.resolution.ArtifactResult import org.eclipse.aether.resolution.*
import org.eclipse.aether.resolution.DependencyRequest
import org.eclipse.aether.resolution.DependencyResolutionException
import org.eclipse.aether.resolution.DependencyResult
import org.eclipse.aether.spi.connector.RepositoryConnectorFactory import org.eclipse.aether.spi.connector.RepositoryConnectorFactory
import org.eclipse.aether.spi.connector.transport.TransporterFactory import org.eclipse.aether.spi.connector.transport.TransporterFactory
import org.eclipse.aether.transport.file.FileTransporterFactory import org.eclipse.aether.transport.file.FileTransporterFactory
@@ -123,22 +120,54 @@ class AetherResolveSession(
} }
} }
fun resolve(coordinates: String, scope: String, filter: DependencyFilter? = null): List<Artifact>? = fun resolve(coordinates: String, scope: String, filter: DependencyFilter? = null): List<Artifact> =
resolve(DefaultArtifact(coordinates), scope, filter) resolve(DefaultArtifact(coordinates), scope, filter)
fun resolve(root: Artifact, scope: String, filter: DependencyFilter? = null): List<Artifact>? { fun resolve(root: Artifact, scope: String, transitive: Boolean, filter: DependencyFilter?): List<Artifact> {
return if (transitive) resolve(root, scope, filter)
else resolveArtifact(root)
}
fun resolve(root: Artifact, scope: String, filter: DependencyFilter? = null): List<Artifact> {
return fetch( return fetch(
repositorySystem,
repositorySystemSession,
DependencyRequest( DependencyRequest(
request(Dependency(root, scope)), request(Dependency(root, scope)),
filter ?: DependencyFilterUtils.classpathFilter(scope) filter ?: DependencyFilterUtils.classpathFilter(scope)
) ),
{ req -> repositorySystem.resolveDependencies(repositorySystemSession, req).artifactResults },
{ req, ex ->
DependencyResolutionException(
DependencyResult(req),
IllegalArgumentException( //Logger.format(
// "failed to load '%s' from %[list]s into %s",
// req.getCollectRequest().getRoot(),
// Aether.reps(req.getCollectRequest().getRepositories()),
// session.getLocalRepositoryManager()
// .getRepository()
// .getBasedir()
//),
ex
)
)
}
) )
} }
private fun request(root: Dependency): CollectRequest? { private fun resolveArtifact(artifact: Artifact): List<Artifact> {
val request = ArtifactRequest()
request.artifact = artifact
for (repo in remotes) {
request.addRepository(repo)
}
return fetch(
request,
{ req -> listOf(repositorySystem.resolveArtifact(repositorySystemSession, req)) },
{ req, ex -> ArtifactResolutionException(listOf(ArtifactResult(req)), ex.message, IllegalArgumentException(ex)) }
)
}
private fun request(root: Dependency): CollectRequest {
val request = CollectRequest() val request = CollectRequest()
request.root = root request.root = root
for (repo in remotes) { for (repo in remotes) {
@@ -147,32 +176,23 @@ class AetherResolveSession(
return request return request
} }
private fun fetch(system: RepositorySystem, session: RepositorySystemSession, dreq: DependencyRequest): List<Artifact>? { private fun <RequestT> fetch(
request: RequestT,
fetchBody: (RequestT) -> Collection<ArtifactResult>,
wrapException: (RequestT, Exception) -> Exception
): List<Artifact> {
val deps: MutableList<Artifact> = LinkedList() val deps: MutableList<Artifact> = LinkedList()
try { try {
var results: Collection<ArtifactResult> var results: Collection<ArtifactResult>
synchronized(this) { synchronized(this) {
results = system.resolveDependencies(session, dreq) results = fetchBody(request)
.artifactResults
} }
for (res in results) { for (res in results) {
deps.add(res.artifact) deps.add(res.artifact)
} }
// @checkstyle IllegalCatch (1 line) // @checkstyle IllegalCatch (1 line)
} catch (ex: Exception) { } catch (ex: Exception) {
throw DependencyResolutionException( throw wrapException(request, ex)
DependencyResult(dreq),
IllegalArgumentException( //Logger.format(
// "failed to load '%s' from %[list]s into %s",
// dreq.getCollectRequest().getRoot(),
// Aether.reps(dreq.getCollectRequest().getRepositories()),
// session.getLocalRepositoryManager()
// .getRepository()
// .getBasedir()
//),
ex
)
)
} }
return deps return deps
} }
@@ -10,12 +10,15 @@ import org.junit.Assert
import org.junit.Ignore import org.junit.Ignore
import java.io.File import java.io.File
import kotlin.contracts.ExperimentalContracts import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.reflect.full.primaryConstructor import kotlin.reflect.full.primaryConstructor
import kotlin.script.experimental.dependencies.maven.MavenDependenciesResolver import kotlin.script.experimental.dependencies.maven.MavenDependenciesResolver
import kotlin.script.experimental.api.ResultWithDiagnostics import kotlin.script.experimental.api.ResultWithDiagnostics
import kotlin.script.experimental.api.valueOrThrow import kotlin.script.experimental.api.valueOrThrow
import kotlin.script.experimental.dependencies.* import kotlin.script.experimental.dependencies.*
import kotlin.script.experimental.dependencies.impl.DependenciesResolverOptionsName import kotlin.script.experimental.dependencies.impl.DependenciesResolverOptionsName
import kotlin.script.experimental.dependencies.impl.SimpleExternalDependenciesResolverOptionsParser
import kotlin.script.experimental.dependencies.impl.makeExternalDependenciesResolverOptions import kotlin.script.experimental.dependencies.impl.makeExternalDependenciesResolverOptions
import kotlin.script.experimental.dependencies.impl.set import kotlin.script.experimental.dependencies.impl.set
@@ -27,6 +30,9 @@ class MavenResolverTest : ResolversTestBase() {
options: ExternalDependenciesResolver.Options = ExternalDependenciesResolver.Options.Empty, options: ExternalDependenciesResolver.Options = ExternalDependenciesResolver.Options.Empty,
checkBody: (Iterable<File>) -> Boolean = { true } checkBody: (Iterable<File>) -> Boolean = { true }
) { ) {
contract {
callsInPlace(checkBody, InvocationKind.EXACTLY_ONCE)
}
val resolver = MavenDependenciesResolver() val resolver = MavenDependenciesResolver()
val result = runBlocking { resolver.resolve(coordinates, options) } val result = runBlocking { resolver.resolve(coordinates, options) }
if (result is ResultWithDiagnostics.Failure) { if (result is ResultWithDiagnostics.Failure) {
@@ -74,6 +80,31 @@ class MavenResolverTest : ResolversTestBase() {
) )
} }
fun testTransitiveOption() {
val dependency = "junit:junit:4.11"
var transitiveFiles: Iterable<File>
fun parseOptions(options: String) = SimpleExternalDependenciesResolverOptionsParser(options).valueOrThrow()
resolveAndCheck(dependency, options = parseOptions("transitive=true")) { files ->
transitiveFiles = files
true
}
var nonTransitiveFiles: Iterable<File>
resolveAndCheck(dependency, options = parseOptions("transitive=false")) { files ->
nonTransitiveFiles = files
true
}
val tCount = transitiveFiles.count()
val ntCount = nonTransitiveFiles.count()
val artifact = nonTransitiveFiles.single()
assertTrue(ntCount < tCount)
assertEquals("jar", artifact.extension)
}
fun testResolveVersionsRange() { fun testResolveVersionsRange() {
resolveAndCheck("org.jetbrains.kotlin:kotlin-annotations-jvm:(1.3.40,1.3.60)") resolveAndCheck("org.jetbrains.kotlin:kotlin-annotations-jvm:(1.3.40,1.3.60)")
} }
@@ -21,7 +21,7 @@ private val equalsRegex = Regex("^[^\\S\\r\\n]*=")
* And additionally supports flags without any equality statement: * And additionally supports flags without any equality statement:
* `foo bar` * `foo bar`
*/ */
internal object SimpleExternalDependenciesResolverOptionsParser { object SimpleExternalDependenciesResolverOptionsParser {
private sealed class Token { private sealed class Token {
data class Name(val name: String) : Token() data class Name(val name: String) : Token()
data class Value(val value: String) : Token() data class Value(val value: String) : Token()