diff --git a/libraries/scripting/dependencies-maven/build.gradle.kts b/libraries/scripting/dependencies-maven/build.gradle.kts index 2de7325c734..f7c466d10cd 100644 --- a/libraries/scripting/dependencies-maven/build.gradle.kts +++ b/libraries/scripting/dependencies-maven/build.gradle.kts @@ -9,11 +9,20 @@ jvmTarget = "1.6" dependencies { compile(kotlinStdlib()) compile(project(":kotlin-scripting-dependencies")) - compile("org.jetbrains.kotlin:jcabi-aether:1.0-dev-3") - compile("org.sonatype.aether:aether-api:1.13.1") - testCompile("org.apache.maven:maven-settings-builder:3.0.5") + compile("org.eclipse.aether:aether-api:1.1.0") + compile("org.eclipse.aether:aether-impl:1.1.0") + compile("org.eclipse.aether:aether-util:1.1.0") + compile("org.eclipse.aether:aether-connector-basic:1.1.0") + compile("org.eclipse.aether:aether-transport-wagon:1.1.0") + compile("org.eclipse.aether:aether-transport-http:1.1.0") + compile("org.eclipse.aether:aether-transport-file:1.1.0") + compile("org.apache.maven:maven-core:3.6.3") + compile("org.apache.maven:maven-settings-builder:3.6.3") + compile("org.apache.maven:maven-aether-provider:3.3.9") + compile("org.apache.maven.wagon:wagon-provider-api:3.3.4") testCompile(projectTests(":kotlin-scripting-dependencies")) testCompile(commonDep("junit")) + testRuntimeOnly("org.slf4j:slf4j-nop:1.7.30") } sourceSets { diff --git a/libraries/scripting/dependencies-maven/src/kotlin/script/experimental/dependencies/maven/MavenDependenciesResolver.kt b/libraries/scripting/dependencies-maven/src/kotlin/script/experimental/dependencies/maven/MavenDependenciesResolver.kt index 9f40045c2b1..9c72ca3f8d5 100644 --- a/libraries/scripting/dependencies-maven/src/kotlin/script/experimental/dependencies/maven/MavenDependenciesResolver.kt +++ b/libraries/scripting/dependencies-maven/src/kotlin/script/experimental/dependencies/maven/MavenDependenciesResolver.kt @@ -5,12 +5,11 @@ package kotlin.script.experimental.dependencies.maven -import com.jcabi.aether.Aether -import org.sonatype.aether.repository.Authentication -import org.sonatype.aether.repository.RemoteRepository -import org.sonatype.aether.resolution.DependencyResolutionException -import org.sonatype.aether.util.artifact.DefaultArtifact -import org.sonatype.aether.util.artifact.JavaScopes +import org.eclipse.aether.artifact.DefaultArtifact +import org.eclipse.aether.repository.RemoteRepository +import org.eclipse.aether.resolution.DependencyResolutionException +import org.eclipse.aether.util.artifact.JavaScopes +import org.eclipse.aether.util.repository.AuthenticationBuilder import java.io.File import java.util.* import kotlin.script.experimental.api.ResultWithDiagnostics @@ -18,8 +17,9 @@ import kotlin.script.experimental.dependencies.ExternalDependenciesResolver import kotlin.script.experimental.dependencies.RepositoryCoordinates import kotlin.script.experimental.dependencies.impl.makeResolveFailureResult import kotlin.script.experimental.dependencies.impl.toRepositoryUrlOrNull +import kotlin.script.experimental.dependencies.maven.impl.AetherResolveSession +import kotlin.script.experimental.dependencies.maven.impl.mavenCentral -val mavenCentral = RemoteRepository("maven-central", "default", "https://repo.maven.apache.org/maven2/") class MavenRepositoryCoordinates( url: String, @@ -45,7 +45,7 @@ class MavenDependenciesResolver : ExternalDependenciesResolver { private fun remoteRepositories() = if (repos.isEmpty()) arrayListOf(mavenCentral) else repos - private fun allRepositories() = remoteRepositories().map { it.url!!.toString() } + localRepo.toString() + private fun allRepositories() = remoteRepositories() + localRepo private fun String.toMavenArtifact(): DefaultArtifact? = if (this.isNotBlank() && this.count { it == ':' } == 2) DefaultArtifact(this) @@ -56,11 +56,11 @@ class MavenDependenciesResolver : ExternalDependenciesResolver { val artifactId = artifactCoordinates.toMavenArtifact()!! try { - val deps = Aether(remoteRepositories(), localRepo).resolve(artifactId, JavaScopes.RUNTIME) + val deps = AetherResolveSession(localRepo, remoteRepositories()).resolve(artifactId, JavaScopes.RUNTIME) if (deps != null) return ResultWithDiagnostics.Success(deps.map { it.file }) } catch (e: DependencyResolutionException) { - + return makeResolveFailureResult(e.message ?: "unknown error") } return makeResolveFailureResult(allRepositories().map { "$it: $artifactId not found" }) } @@ -72,7 +72,7 @@ class MavenDependenciesResolver : ExternalDependenciesResolver { override fun addRepository(repositoryCoordinates: RepositoryCoordinates) { val url = repositoryCoordinates.toRepositoryUrlOrNull() ?: throw IllegalArgumentException("Invalid Maven repository URL: ${repositoryCoordinates}") - val repo = RemoteRepository( + val repo = RemoteRepository.Builder( repositoryCoordinates.string, "default", url.toString() @@ -80,8 +80,16 @@ class MavenDependenciesResolver : ExternalDependenciesResolver { if (repositoryCoordinates is MavenRepositoryCoordinates) { val username = repositoryCoordinates.username?.let(::tryResolveEnvironmentVariable) val password = repositoryCoordinates.password?.let(::tryResolveEnvironmentVariable) - repo.authentication = Authentication(username, password, repositoryCoordinates.privateKeyFile, repositoryCoordinates.passPhrase) + if (username != null) { + val auth = AuthenticationBuilder().apply { + addUsername(username) + if (password != null) { + addPassword(password) + } + } + repo.setAuthentication(auth.build()) + } } - repos.add(repo) + repos.add(repo.build()) } } \ No newline at end of file diff --git a/libraries/scripting/dependencies-maven/src/kotlin/script/experimental/dependencies/maven/impl/aether.kt b/libraries/scripting/dependencies-maven/src/kotlin/script/experimental/dependencies/maven/impl/aether.kt new file mode 100644 index 00000000000..5c6176a24e5 --- /dev/null +++ b/libraries/scripting/dependencies-maven/src/kotlin/script/experimental/dependencies/maven/impl/aether.kt @@ -0,0 +1,227 @@ +/* + * 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.commons.io.filefilter.NameFileFilter +import org.apache.maven.repository.internal.MavenRepositorySystemUtils +import org.apache.maven.settings.Settings +import org.apache.maven.settings.SettingsUtils +import org.apache.maven.settings.TrackableBase +import org.apache.maven.settings.building.* +import org.eclipse.aether.RepositorySystem +import org.eclipse.aether.RepositorySystemSession +import org.eclipse.aether.artifact.Artifact +import org.eclipse.aether.artifact.DefaultArtifact +import org.eclipse.aether.collection.CollectRequest +import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory +import org.eclipse.aether.graph.Dependency +import org.eclipse.aether.graph.DependencyFilter +import org.eclipse.aether.repository.LocalRepository +import org.eclipse.aether.repository.Proxy +import org.eclipse.aether.repository.RemoteRepository +import org.eclipse.aether.resolution.ArtifactResult +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.transport.TransporterFactory +import org.eclipse.aether.transport.file.FileTransporterFactory +import org.eclipse.aether.transport.http.HttpTransporterFactory +import org.eclipse.aether.util.filter.DependencyFilterUtils +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.io.FileFilter +import java.util.* + +val mavenCentral: RemoteRepository = RemoteRepository.Builder("maven central", "default", "https://repo.maven.apache.org/maven2/").build() + +class AetherResolveSession( + val localRepo: File = File(File(System.getProperty("user.home")!!, ".m2"), "repository"), + remoteRepos: List = listOf(mavenCentral) +) { + + val remotes by lazy { + val proxySelector = settings.getActiveProxy()?.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 + } + val mirrorSelector = getMirrorSelector() + remoteRepos.mapNotNull { + val builder = RemoteRepository.Builder(it) + 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 { + mirrorSelector.getMirror(built) ?: 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, + HttpTransporterFactory::class.java + ) + locator.getService(RepositorySystem::class.java) + } + + private val repositorySystemSession: RepositorySystemSession by lazy { + val localRepo = LocalRepository(localRepo.absolutePath) + MavenRepositorySystemUtils.newSession().also { + it.localRepositoryManager = repositorySystem.newLocalRepositoryManager(it, localRepo) + } + } + + fun resolve(coordinates: String, scope: String, filter: DependencyFilter? = null): List? = + resolve(DefaultArtifact(coordinates), scope, filter) + + fun resolve(root: Artifact, scope: String, filter: DependencyFilter? = null): List? { + + return fetch( + repositorySystem, + repositorySystemSession, + DependencyRequest( + request(Dependency(root, scope)), + filter ?: DependencyFilterUtils.classpathFilter(scope) + ) + ) + } + + private fun request(root: Dependency): CollectRequest? { + val request = CollectRequest() + request.root = root + for (repo in remotes) { + request.addRepository(repo) + } + return request + } + + private fun fetch(system: RepositorySystem, session: RepositorySystemSession, dreq: DependencyRequest): List? { + val deps: MutableList = LinkedList() + try { + var results: Collection + synchronized(this) { + results = system.resolveDependencies(session, dreq) + .artifactResults + } + for (res in results) { + deps.add(res.artifact) + } + // @checkstyle IllegalCatch (1 line) + } catch (ex: Exception) { + throw DependencyResolutionException( + 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 + } + + 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, + mirror.mirrorOf, mirror.mirrorOfLayouts + ) + } + } + return selector + } + + private val settings: Settings by lazy { + val builder: SettingsBuilder = DefaultSettingsBuilderFactory().newInstance() + val request: SettingsBuildingRequest = DefaultSettingsBuildingRequest() + val user = System.getProperty("org.apache.maven.user-settings") + if (user == null) { + request.userSettingsFile = File( + File(System.getProperty("user.home")).absoluteFile, + "/.m2/settings.xml" + ) + } else { + request.userSettingsFile = File(user) + } + val global = System.getProperty("org.apache.maven.global-settings") + if (global != null) { + request.globalSettingsFile = File(global) + } + val result: SettingsBuildingResult + result = try { + builder.build(request) + } catch (ex: SettingsBuildingException) { + throw IllegalStateException(ex) + } + this.invokers(builder, result) + } + + private fun invokers( + builder: SettingsBuilder, + result: SettingsBuildingResult + ): Settings { + var main = result.effectiveSettings + val files = File(System.getProperty("user.dir")) + .parentFile.listFiles( + NameFileFilter("interpolated-settings.xml") as FileFilter + ) + if (files.size == 1) { + val irequest = + DefaultSettingsBuildingRequest() + irequest.userSettingsFile = files[0] + main = try { + val isettings = builder.build(irequest) + .effectiveSettings + SettingsUtils.merge(isettings, main, TrackableBase.USER_LEVEL) + isettings + } catch (ex: SettingsBuildingException) { + throw java.lang.IllegalStateException(ex) + } + } + return main + } +} \ No newline at end of file