
kotlin.script.experimental.dependencies.maven.impl.aether.kt Maven / Gradle / Ivy
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package kotlin.script.experimental.dependencies.maven.impl
import org.apache.maven.repository.internal.MavenRepositorySystemUtils
import org.apache.maven.settings.Settings
import org.apache.maven.wagon.Wagon
import org.codehaus.plexus.DefaultContainerConfiguration
import org.codehaus.plexus.DefaultPlexusContainer
import org.codehaus.plexus.classworlds.ClassWorld
import org.eclipse.aether.RepositorySystem
import org.eclipse.aether.RepositorySystemSession
import org.eclipse.aether.artifact.Artifact
import org.eclipse.aether.collection.CollectRequest
import org.eclipse.aether.collection.CollectResult
import org.eclipse.aether.collection.DependencyCollectionException
import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory
import org.eclipse.aether.graph.Dependency
import org.eclipse.aether.graph.DependencyFilter
import org.eclipse.aether.internal.transport.wagon.PlexusWagonConfigurator
import org.eclipse.aether.internal.transport.wagon.PlexusWagonProvider
import org.eclipse.aether.repository.LocalRepository
import org.eclipse.aether.repository.Proxy
import org.eclipse.aether.repository.RemoteRepository
import org.eclipse.aether.resolution.ArtifactRequest
import org.eclipse.aether.resolution.ArtifactResolutionException
import org.eclipse.aether.resolution.ArtifactResult
import org.eclipse.aether.spi.connector.RepositoryConnectorFactory
import org.eclipse.aether.spi.connector.transport.TransporterFactory
import org.eclipse.aether.transport.file.FileTransporterFactory
import org.eclipse.aether.transport.wagon.WagonConfigurator
import org.eclipse.aether.transport.wagon.WagonProvider
import org.eclipse.aether.transport.wagon.WagonTransporterFactory
import org.eclipse.aether.util.filter.DependencyFilterUtils
import org.eclipse.aether.util.graph.visitor.FilteringDependencyVisitor
import org.eclipse.aether.util.graph.visitor.TreeDependencyVisitor
import org.eclipse.aether.util.repository.AuthenticationBuilder
import org.eclipse.aether.util.repository.DefaultAuthenticationSelector
import org.eclipse.aether.util.repository.DefaultMirrorSelector
import org.eclipse.aether.util.repository.DefaultProxySelector
import java.io.File
import kotlin.script.experimental.api.IterableResultsCollector
import kotlin.script.experimental.api.ResultWithDiagnostics
import kotlin.script.experimental.api.ScriptDiagnostic
import kotlin.script.experimental.api.asSuccess
import kotlin.script.experimental.dependencies.impl.makeResolveFailureResult
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 = listOf(mavenCentral),
settingsFactory: () -> Settings = ::createMavenSettings,
) {
private val localRepoPath by lazy {
localRepoDirectory?.absolutePath ?: settings.localRepository
}
private val remotes by lazy {
val proxySelector = settings.activeProxy?.let { proxy ->
val selector = DefaultProxySelector()
val auth = with(AuthenticationBuilder()) {
addUsername(proxy.username)
addPassword(proxy.password)
build()
}
selector.add(
Proxy(
proxy.protocol,
proxy.host,
proxy.port,
auth
), proxy.nonProxyHosts
)
selector
}
remoteRepos.mapNotNull { originalRepo ->
val builder = RemoteRepository.Builder(originalRepo)
if (proxySelector != null) {
builder.setProxy(proxySelector.getProxy(builder.build()))
}
val built = builder.build()
if (!built.protocol.matches(Regex("https?|file"))) {
//Logger.warn(
// this,
// "%s ignored (only S3, HTTP/S, and FILE are supported)",
// repo
//);
null
} else {
resolveRemote(built)
}
}
}
private val repositorySystem: RepositorySystem by lazy {
val locator = MavenRepositorySystemUtils.newServiceLocator()
locator.addService(
RepositoryConnectorFactory::class.java,
BasicRepositoryConnectorFactory::class.java
)
locator.addService(
TransporterFactory::class.java,
FileTransporterFactory::class.java
)
locator.addService(
TransporterFactory::class.java,
WagonTransporterFactory::class.java
)
val container = DefaultPlexusContainer(DefaultContainerConfiguration().apply {
val realmId = "wagon"
classWorld = ClassWorld(realmId, Wagon::class.java.classLoader)
realm = classWorld.getRealm(realmId)
})
locator.setServices(
WagonProvider::class.java,
PlexusWagonProvider(container)
)
locator.setServices(
WagonConfigurator::class.java,
PlexusWagonConfigurator(container)
)
locator.getService(RepositorySystem::class.java)
}
private val repositorySystemSession: RepositorySystemSession by lazy {
val localRepo = LocalRepository(localRepoPath)
MavenRepositorySystemUtils.newSession().also {
it.localRepositoryManager = repositorySystem.newLocalRepositoryManager(it, localRepo)
}
}
fun resolve(
roots: List,
scope: String,
kind: ResolutionKind,
filter: DependencyFilter?,
classifier: String? = null,
extension: String? = null,
): ResultWithDiagnostics> {
if (kind == ResolutionKind.NON_TRANSITIVE) return resolveArtifacts(roots).asSuccess()
val isOptional = kind == ResolutionKind.TRANSITIVE_PARTIAL
val requests = resolveTree(roots, scope, isOptional, filter, classifier, extension)
val artifactResults = try {
synchronized(this) {
repositorySystem.resolveArtifacts(
repositorySystemSession,
requests
)
}
} catch (resolutionException: ArtifactResolutionException) {
if (isOptional) {
resolutionException.results
} else {
return makeResolveFailureResult(listOf(resolutionException.message.orEmpty()), null, resolutionException)
}
}
return IterableResultsCollector().run {
for (artifactResult in artifactResults) {
if (artifactResult.isResolved) {
addValue(artifactResult.artifact.file)
} else {
for (exception in artifactResult.exceptions) {
addDiagnostic(
ScriptDiagnostic(
ScriptDiagnostic.unspecifiedError,
"Unable to resolve artifact ${artifactResult.request.artifact}",
exception = exception
)
)
}
}
}
getResult()
}
}
private fun resolveTree(
roots: List,
scope: String,
isOptional: Boolean,
filter: DependencyFilter?,
classifier: String?,
extension: String?,
): Collection {
return fetch(
request(roots.map { root -> Dependency(root, scope, isOptional) }),
{ req ->
val requestsBuilder = ArtifactRequestBuilder(classifier, extension)
val collectionResult = repositorySystem.collectDependencies(repositorySystemSession, req)
collectionResult.root.accept(
TreeDependencyVisitor(
FilteringDependencyVisitor(
requestsBuilder,
filter ?: DependencyFilterUtils.classpathFilter(scope)
)
)
)
requestsBuilder.requests
},
{ req, ex ->
DependencyCollectionException(
CollectResult(req),
ex.message,
ex
)
}
)
}
private fun Collection.toFiles() = map { it.artifact.file }
private fun resolveArtifacts(artifacts: List): List {
val requests = artifacts.map { artifact ->
ArtifactRequest(artifact, remotes, "")
}
return fetch(
requests,
{ reqs -> listOf(repositorySystem.resolveArtifacts(repositorySystemSession, reqs)) },
{ reqs, ex -> ArtifactResolutionException(reqs.map { req -> ArtifactResult(req) }, ex.message, IllegalArgumentException(ex)) }
).flatMap { it.toFiles() }
}
private fun request(roots: List): CollectRequest {
val request = CollectRequest()
request.dependencies = roots
for (repo in remotes) {
request.addRepository(repo)
}
return request
}
private fun fetch(
request: RequestT,
fetchBody: (RequestT) -> ResultT,
wrapException: (RequestT, Exception) -> Exception,
): ResultT {
return try {
synchronized(this) {
fetchBody(request)
}
// @checkstyle IllegalCatch (1 line)
} catch (ex: Exception) {
throw wrapException(request, ex)
}
}
private fun getMirrorSelector(): DefaultMirrorSelector {
val selector = DefaultMirrorSelector()
val mirrors = settings.mirrors
if (mirrors != null) {
for (mirror in mirrors) {
selector.add(
mirror.id, mirror.url, mirror.layout, false, false,
mirror.mirrorOf, mirror.mirrorOfLayouts
)
}
}
return selector
}
private fun getAuthenticationSelector(): DefaultAuthenticationSelector {
val selector = DefaultAuthenticationSelector()
val servers = settings.servers
if (servers != null) {
val validServers = servers.filter { it.id != null }
for (server in validServers) {
selector.add(
server.id, AuthenticationBuilder()
.addUsername(server.username)
.addPassword(server.password)
.addPrivateKey(server.privateKey, server.passphrase)
.build()
)
}
}
return selector
}
internal fun resolveRemote(
remote: RemoteRepository,
): RemoteRepository {
val mirrorSelector = getMirrorSelector()
val authenticationSelector = getAuthenticationSelector()
val finalServer = mirrorSelector.getMirror(remote) ?: remote
return if (finalServer.authentication == null) {
RemoteRepository.Builder(finalServer)
.setAuthentication(authenticationSelector.getAuthentication(finalServer))
.build()
} else {
finalServer
}
}
private val settings: Settings by lazy {
settingsFactory()
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy