Add new script dependency resolvers

Changes in new API for dependency resolvers (GenericDependenciesResolver) comparing to an old one (GenericResolver:
- Add ability to fast check whether artifact or repository is suitable for current resolver to distinguish between unsuitable resolvers and resolution failures
- Return all artifact resolution failures in ResultWithDiagnostics
- Use single string for artifact coordinates
- Add compound resolver that combines several resolvers
- Merge Direct and FlatLib resolver into single FileSystemDependencyResolver
- Mark resolve() method as suspend to indicate long operation
- Add credentials support for maven resolver (https://youtrack.jetbrains.com/issue/KT-27701)
This commit is contained in:
Anatoly Nikitin
2019-09-27 14:01:02 +03:00
committed by Ilya Chernikov
parent 81e2e119e2
commit c7e9a6c5d9
12 changed files with 535 additions and 0 deletions
@@ -0,0 +1,32 @@
plugins {
kotlin("jvm")
id("jps-compatible")
}
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")
testCompile(projectTests(":kotlin-scripting-dependencies"))
testCompile(commonDep("junit"))
}
sourceSets {
"main" { projectDefault() }
"test" { projectDefault() }
}
tasks.withType<org.jetbrains.kotlin.gradle.dsl.KotlinCompile<*>> {
kotlinOptions.freeCompilerArgs += "-Xallow-kotlin-package"
}
publish()
runtimeJar()
sourcesJar()
javadocJar()
@@ -0,0 +1,87 @@
/*
* Copyright 2010-2019 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
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 java.io.File
import java.util.*
import kotlin.script.experimental.api.ResultWithDiagnostics
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
val mavenCentral = RemoteRepository("maven-central", "default", "https://repo.maven.apache.org/maven2/")
class MavenRepositoryCoordinates(
url: String,
val username: String?,
val password: String?,
val privateKeyFile: String?,
val passPhrase: String?
) : RepositoryCoordinates(url)
class MavenDependenciesResolver : ExternalDependenciesResolver {
override fun acceptsArtifact(artifactCoordinates: String): Boolean =
artifactCoordinates.toMavenArtifact() != null
override fun acceptsRepository(repositoryCoordinates: RepositoryCoordinates): Boolean {
return repositoryCoordinates.toRepositoryUrlOrNull() != null
}
// TODO: make robust
val localRepo = File(File(System.getProperty("user.home")!!, ".m2"), "repository")
val repos: ArrayList<RemoteRepository> = arrayListOf()
private fun remoteRepositories() = if (repos.isEmpty()) arrayListOf(mavenCentral) else repos
private fun allRepositories() = remoteRepositories().map { it.url!!.toString() } + localRepo.toString()
private fun String.toMavenArtifact(): DefaultArtifact? =
if (this.isNotBlank() && this.count { it == ':' } == 2) DefaultArtifact(this)
else null
override suspend fun resolve(artifactCoordinates: String): ResultWithDiagnostics<List<File>> {
val artifactId = artifactCoordinates.toMavenArtifact()!!
try {
val deps = Aether(remoteRepositories(), localRepo).resolve(artifactId, JavaScopes.RUNTIME)
if (deps != null)
return ResultWithDiagnostics.Success(deps.map { it.file })
} catch (e: DependencyResolutionException) {
}
return makeResolveFailureResult(allRepositories().map { "$it: $artifactId not found" })
}
private fun tryResolveEnvironmentVariable(str: String) =
if (str.startsWith("$")) System.getenv(str.substring(1)) ?: str
else str
override fun addRepository(repositoryCoordinates: RepositoryCoordinates) {
val url = repositoryCoordinates.toRepositoryUrlOrNull()
?: throw IllegalArgumentException("Invalid Maven repository URL: ${repositoryCoordinates}")
val repo = RemoteRepository(
repositoryCoordinates.string,
"default",
url.toString()
)
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)
}
repos.add(repo)
}
}
@@ -0,0 +1,25 @@
/*
* Copyright 2010-2019 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.test
import kotlinx.coroutines.runBlocking
import org.junit.Assert
import kotlin.contracts.ExperimentalContracts
import kotlin.script.experimental.dependencies.maven.MavenDependenciesResolver
import kotlin.script.experimental.api.ResultWithDiagnostics
import kotlin.script.experimental.api.valueOrThrow
@ExperimentalContracts
class MavenResolverTest : ResolversTestBase() {
fun testResolve() {
val resolver = MavenDependenciesResolver()
val result = runBlocking { resolver.resolve("org.jetbrains.kotlin:kotlin-annotations-jvm:1.3.50") }
Assert.assertTrue(result is ResultWithDiagnostics.Success)
val files = result.valueOrThrow()
files.forEach { it.delete() }
}
}