Scripting, KT-61727: Make it possible to resolve multiple artifacts at once
For MavenDependenciesResolver it improves performance drastically depending on how strong transitive dependencies of the roots interfere ^KT-61727 Fixed
This commit is contained in:
committed by
Space Team
parent
0d628c9e59
commit
841cc8eb43
@@ -308,3 +308,37 @@ fun <R> ResultWithDiagnostics<R>.valueOrThrow(): R = valueOr {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class IterableResultsCollector<T> {
|
||||||
|
private val diagnostics = mutableListOf<ScriptDiagnostic>()
|
||||||
|
private val values = mutableListOf<T>()
|
||||||
|
|
||||||
|
fun add(result: ResultWithDiagnostics<Iterable<T>>) {
|
||||||
|
diagnostics.addAll(result.reports)
|
||||||
|
if (result is ResultWithDiagnostics.Success) {
|
||||||
|
values.addAll(result.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addValue(value: T) {
|
||||||
|
values.add(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addDiagnostic(diagnostic: ScriptDiagnostic) {
|
||||||
|
diagnostics.add(diagnostic)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getResult(): ResultWithDiagnostics<List<T>> {
|
||||||
|
return if (values.isEmpty()) {
|
||||||
|
ResultWithDiagnostics.Failure(diagnostics)
|
||||||
|
} else {
|
||||||
|
ResultWithDiagnostics.Success(values, diagnostics)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> Iterable<ResultWithDiagnostics<Iterable<T>>>.asSuccessIfAny(): ResultWithDiagnostics<List<T>> {
|
||||||
|
return IterableResultsCollector<T>().run {
|
||||||
|
forEach { add(it) }
|
||||||
|
getResult()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+9
-7
@@ -13,6 +13,7 @@ import org.eclipse.aether.util.artifact.JavaScopes
|
|||||||
import org.eclipse.aether.util.repository.AuthenticationBuilder
|
import org.eclipse.aether.util.repository.AuthenticationBuilder
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import kotlin.script.experimental.api.*
|
import kotlin.script.experimental.api.*
|
||||||
|
import kotlin.script.experimental.dependencies.ArtifactWithLocation
|
||||||
import kotlin.script.experimental.dependencies.ExternalDependenciesResolver
|
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.*
|
||||||
@@ -50,12 +51,13 @@ class MavenDependenciesResolver : ExternalDependenciesResolver {
|
|||||||
else null
|
else null
|
||||||
|
|
||||||
override suspend fun resolve(
|
override suspend fun resolve(
|
||||||
artifactCoordinates: String,
|
artifactsWithLocations: List<ArtifactWithLocation>,
|
||||||
options: ExternalDependenciesResolver.Options,
|
options: ExternalDependenciesResolver.Options
|
||||||
sourceCodeLocation: SourceCode.LocationWithId?
|
|
||||||
): ResultWithDiagnostics<List<File>> {
|
): ResultWithDiagnostics<List<File>> {
|
||||||
|
val firstArtifactWithLocation = artifactsWithLocations.firstOrNull() ?: return ResultWithDiagnostics.Success(emptyList())
|
||||||
val artifactId = artifactCoordinates.toMavenArtifact()!!
|
val artifactIds = artifactsWithLocations.map {
|
||||||
|
it.artifact.toMavenArtifact()!!
|
||||||
|
}
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
val dependencyScopes = options.dependencyScopes ?: listOf(JavaScopes.COMPILE, JavaScopes.RUNTIME)
|
val dependencyScopes = options.dependencyScopes ?: listOf(JavaScopes.COMPILE, JavaScopes.RUNTIME)
|
||||||
@@ -71,10 +73,10 @@ class MavenDependenciesResolver : ExternalDependenciesResolver {
|
|||||||
AetherResolveSession(
|
AetherResolveSession(
|
||||||
null, remoteRepositories()
|
null, remoteRepositories()
|
||||||
).resolve(
|
).resolve(
|
||||||
artifactId, dependencyScopes.joinToString(","), kind, null, classifier, extension
|
artifactIds, dependencyScopes.joinToString(","), kind, null, classifier, extension
|
||||||
)
|
)
|
||||||
} catch (e: RepositoryException) {
|
} catch (e: RepositoryException) {
|
||||||
makeResolveFailureResult(e, sourceCodeLocation)
|
makeResolveFailureResult(e, firstArtifactWithLocation.sourceCodeLocation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+14
-16
@@ -144,16 +144,16 @@ internal class AetherResolveSession(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun resolve(
|
fun resolve(
|
||||||
root: Artifact,
|
roots: List<Artifact>,
|
||||||
scope: String,
|
scope: String,
|
||||||
kind: ResolutionKind,
|
kind: ResolutionKind,
|
||||||
filter: DependencyFilter?,
|
filter: DependencyFilter?,
|
||||||
classifier: String? = null,
|
classifier: String? = null,
|
||||||
extension: String? = null,
|
extension: String? = null,
|
||||||
): ResultWithDiagnostics<List<File>> {
|
): ResultWithDiagnostics<List<File>> {
|
||||||
if (kind == ResolutionKind.NON_TRANSITIVE) return resolveArtifact(root).asSuccess()
|
if (kind == ResolutionKind.NON_TRANSITIVE) return resolveArtifacts(roots).asSuccess()
|
||||||
|
|
||||||
val requests = resolveTree(root, scope, filter, classifier, extension)
|
val requests = resolveTree(roots, scope, filter, classifier, extension)
|
||||||
|
|
||||||
@Suppress("KotlinConstantConditions")
|
@Suppress("KotlinConstantConditions")
|
||||||
return when (kind) {
|
return when (kind) {
|
||||||
@@ -197,14 +197,14 @@ internal class AetherResolveSession(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun resolveTree(
|
private fun resolveTree(
|
||||||
root: Artifact,
|
roots: List<Artifact>,
|
||||||
scope: String,
|
scope: String,
|
||||||
filter: DependencyFilter?,
|
filter: DependencyFilter?,
|
||||||
classifier: String?,
|
classifier: String?,
|
||||||
extension: String?,
|
extension: String?,
|
||||||
): Collection<ArtifactRequest> {
|
): Collection<ArtifactRequest> {
|
||||||
return fetch(
|
return fetch(
|
||||||
request(Dependency(root, scope)),
|
request(roots.map { root -> Dependency(root, scope) }),
|
||||||
{ req ->
|
{ req ->
|
||||||
val requestsBuilder = ArtifactRequestBuilder(classifier, extension)
|
val requestsBuilder = ArtifactRequestBuilder(classifier, extension)
|
||||||
val collectionResult = repositorySystem.collectDependencies(repositorySystemSession, req)
|
val collectionResult = repositorySystem.collectDependencies(repositorySystemSession, req)
|
||||||
@@ -247,23 +247,21 @@ internal class AetherResolveSession(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resolveArtifact(artifact: Artifact): List<File> {
|
private fun resolveArtifacts(artifacts: List<Artifact>): List<File> {
|
||||||
val request = ArtifactRequest()
|
val requests = artifacts.map { artifact ->
|
||||||
request.artifact = artifact
|
ArtifactRequest(artifact, remotes, "")
|
||||||
for (repo in remotes) {
|
|
||||||
request.addRepository(repo)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return fetch(
|
return fetch(
|
||||||
request,
|
requests,
|
||||||
{ req -> listOf(repositorySystem.resolveArtifact(repositorySystemSession, req)) },
|
{ reqs -> listOf(repositorySystem.resolveArtifacts(repositorySystemSession, reqs)) },
|
||||||
{ req, ex -> ArtifactResolutionException(listOf(ArtifactResult(req)), ex.message, IllegalArgumentException(ex)) }
|
{ reqs, ex -> ArtifactResolutionException(reqs.map { req -> ArtifactResult(req) }, ex.message, IllegalArgumentException(ex)) }
|
||||||
).toFiles()
|
).flatMap { it.toFiles() }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun request(root: Dependency): CollectRequest {
|
private fun request(roots: List<Dependency>): CollectRequest {
|
||||||
val request = CollectRequest()
|
val request = CollectRequest()
|
||||||
request.root = root
|
request.dependencies = roots
|
||||||
for (repo in remotes) {
|
for (repo in remotes) {
|
||||||
request.addRepository(repo)
|
request.addRepository(repo)
|
||||||
}
|
}
|
||||||
|
|||||||
+37
@@ -9,6 +9,7 @@ import kotlinx.coroutines.runBlocking
|
|||||||
import org.junit.Assert
|
import org.junit.Assert
|
||||||
import org.junit.Ignore
|
import org.junit.Ignore
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.util.*
|
||||||
import kotlin.contracts.ExperimentalContracts
|
import kotlin.contracts.ExperimentalContracts
|
||||||
import kotlin.contracts.InvocationKind
|
import kotlin.contracts.InvocationKind
|
||||||
import kotlin.contracts.contract
|
import kotlin.contracts.contract
|
||||||
@@ -22,6 +23,7 @@ import kotlin.script.experimental.dependencies.impl.makeExternalDependenciesReso
|
|||||||
import kotlin.script.experimental.dependencies.impl.set
|
import kotlin.script.experimental.dependencies.impl.set
|
||||||
import kotlin.script.experimental.dependencies.maven.MavenDependenciesResolver
|
import kotlin.script.experimental.dependencies.maven.MavenDependenciesResolver
|
||||||
import kotlin.script.experimental.dependencies.maven.impl.createMavenSettings
|
import kotlin.script.experimental.dependencies.maven.impl.createMavenSettings
|
||||||
|
import kotlin.system.measureTimeMillis
|
||||||
|
|
||||||
@ExperimentalContracts
|
@ExperimentalContracts
|
||||||
class MavenResolverTest : ResolversTestBase() {
|
class MavenResolverTest : ResolversTestBase() {
|
||||||
@@ -183,6 +185,41 @@ class MavenResolverTest : ResolversTestBase() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun testMultipleDependencies() {
|
||||||
|
val resolver = MavenDependenciesResolver()
|
||||||
|
val sourceOptions = buildOptions(
|
||||||
|
DependenciesResolverOptionsName.PARTIAL_RESOLUTION to "true",
|
||||||
|
DependenciesResolverOptionsName.CLASSIFIER to "sources",
|
||||||
|
DependenciesResolverOptionsName.EXTENSION to "jar",
|
||||||
|
)
|
||||||
|
val multipleDependencies = listOf(
|
||||||
|
"commons-io:commons-io:2.11.0",
|
||||||
|
"org.jetbrains.kotlin:kotlin-reflect:1.8.20",
|
||||||
|
"org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.20",
|
||||||
|
).map { ArtifactWithLocation(it, null) }
|
||||||
|
|
||||||
|
val result = TreeSet<String>()
|
||||||
|
fun addToResult(resultFiles: ResultWithDiagnostics<List<File>>) {
|
||||||
|
if (resultFiles is ResultWithDiagnostics.Success) {
|
||||||
|
for (file in resultFiles.value) {
|
||||||
|
result.add(file.absolutePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val timeMs = measureTimeMillis {
|
||||||
|
runBlocking {
|
||||||
|
addToResult(resolver.resolve(multipleDependencies))
|
||||||
|
addToResult(resolver.resolve(multipleDependencies, sourceOptions))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println("Test time: $timeMs ms")
|
||||||
|
println("Deps size: ${result.size}")
|
||||||
|
println(result.joinToString("\n"))
|
||||||
|
|
||||||
|
assertEquals(14, result.size)
|
||||||
|
}
|
||||||
|
|
||||||
@Ignore("ignored because spark is a very heavy dependency")
|
@Ignore("ignored because spark is a very heavy dependency")
|
||||||
fun ignore_testPartialResolution() {
|
fun ignore_testPartialResolution() {
|
||||||
val resolver = MavenDependenciesResolver()
|
val resolver = MavenDependenciesResolver()
|
||||||
|
|||||||
+47
-18
@@ -6,10 +6,7 @@
|
|||||||
package kotlin.script.experimental.dependencies
|
package kotlin.script.experimental.dependencies
|
||||||
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import kotlin.script.experimental.api.ResultWithDiagnostics
|
import kotlin.script.experimental.api.*
|
||||||
import kotlin.script.experimental.api.ScriptDiagnostic
|
|
||||||
import kotlin.script.experimental.api.SourceCode
|
|
||||||
import kotlin.script.experimental.api.asSuccess
|
|
||||||
import kotlin.script.experimental.dependencies.impl.makeResolveFailureResult
|
import kotlin.script.experimental.dependencies.impl.makeResolveFailureResult
|
||||||
|
|
||||||
class CompoundDependenciesResolver(private val resolvers: List<ExternalDependenciesResolver>) : ExternalDependenciesResolver {
|
class CompoundDependenciesResolver(private val resolvers: List<ExternalDependenciesResolver>) : ExternalDependenciesResolver {
|
||||||
@@ -57,25 +54,57 @@ class CompoundDependenciesResolver(private val resolvers: List<ExternalDependenc
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun resolve(
|
override suspend fun resolve(
|
||||||
artifactCoordinates: String,
|
artifactsWithLocations: List<ArtifactWithLocation>,
|
||||||
options: ExternalDependenciesResolver.Options,
|
options: ExternalDependenciesResolver.Options
|
||||||
sourceCodeLocation: SourceCode.LocationWithId?
|
|
||||||
): ResultWithDiagnostics<List<File>> {
|
): ResultWithDiagnostics<List<File>> {
|
||||||
val reports = mutableListOf<ScriptDiagnostic>()
|
val resultsCollector = IterableResultsCollector<File>()
|
||||||
|
|
||||||
for (resolver in resolvers) {
|
val artifactToResolverIndex = mutableMapOf<ArtifactWithLocation, Int>().apply {
|
||||||
if (resolver.acceptsArtifact(artifactCoordinates)) {
|
for (artifactWithLocation in artifactsWithLocations) {
|
||||||
when (val resolveResult = resolver.resolve(artifactCoordinates, options, sourceCodeLocation)) {
|
put(artifactWithLocation, -1)
|
||||||
is ResultWithDiagnostics.Failure -> reports.addAll(resolveResult.reports)
|
}
|
||||||
else -> return resolveResult
|
}
|
||||||
|
|
||||||
|
while (artifactToResolverIndex.isNotEmpty()) {
|
||||||
|
val resolverGroups = mutableMapOf<Int, MutableList<ArtifactWithLocation>>()
|
||||||
|
|
||||||
|
for ((artifactWithLocation, resolverIndex) in artifactToResolverIndex) {
|
||||||
|
val (artifact, sourceCodeLocation) = artifactWithLocation
|
||||||
|
|
||||||
|
var currentIndex = resolverIndex + 1
|
||||||
|
while (currentIndex < resolvers.size) {
|
||||||
|
if (resolvers[currentIndex].acceptsArtifact(artifact)) break
|
||||||
|
++currentIndex
|
||||||
|
}
|
||||||
|
if (currentIndex == resolvers.size) {
|
||||||
|
if (resolverIndex == -1) {
|
||||||
|
// We add this diagnostic only if there were no resolution attempts made
|
||||||
|
resultsCollector.addDiagnostic(
|
||||||
|
"No suitable dependency resolver found for artifact '$artifact'"
|
||||||
|
.asErrorDiagnostics(locationWithId = sourceCodeLocation)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resolverGroups
|
||||||
|
.getOrPut(currentIndex) { mutableListOf() }
|
||||||
|
.add(artifactWithLocation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
artifactToResolverIndex.clear()
|
||||||
|
for ((resolverIndex, artifacts) in resolverGroups) {
|
||||||
|
val resolver = resolvers[resolverIndex]
|
||||||
|
val resolveResult = resolver.resolve(artifacts, options)
|
||||||
|
resultsCollector.add(resolveResult)
|
||||||
|
if (resolveResult.reports.isNotEmpty()) {
|
||||||
|
for (artifact in artifacts) {
|
||||||
|
artifactToResolverIndex[artifact] = resolverIndex
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return if (reports.count() == 0) {
|
|
||||||
makeResolveFailureResult("No suitable dependency resolver found for artifact '$artifactCoordinates'", sourceCodeLocation)
|
return resultsCollector.getResult()
|
||||||
} else {
|
|
||||||
ResultWithDiagnostics.Failure(reports)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+10
-2
@@ -11,6 +11,8 @@ import kotlin.script.experimental.dependencies.ExternalDependenciesResolver.Opti
|
|||||||
|
|
||||||
open class RepositoryCoordinates(val string: String)
|
open class RepositoryCoordinates(val string: String)
|
||||||
|
|
||||||
|
data class ArtifactWithLocation(val artifact: String, val sourceCodeLocation: SourceCode.LocationWithId?)
|
||||||
|
|
||||||
interface ExternalDependenciesResolver {
|
interface ExternalDependenciesResolver {
|
||||||
interface Options {
|
interface Options {
|
||||||
object Empty : Options {
|
object Empty : Options {
|
||||||
@@ -25,11 +27,18 @@ interface ExternalDependenciesResolver {
|
|||||||
fun acceptsRepository(repositoryCoordinates: RepositoryCoordinates): Boolean
|
fun acceptsRepository(repositoryCoordinates: RepositoryCoordinates): Boolean
|
||||||
fun acceptsArtifact(artifactCoordinates: String): Boolean
|
fun acceptsArtifact(artifactCoordinates: String): Boolean
|
||||||
|
|
||||||
|
// Override one of the following methods
|
||||||
suspend fun resolve(
|
suspend fun resolve(
|
||||||
artifactCoordinates: String,
|
artifactCoordinates: String,
|
||||||
options: Options = Options.Empty,
|
options: Options = Options.Empty,
|
||||||
sourceCodeLocation: SourceCode.LocationWithId? = null
|
sourceCodeLocation: SourceCode.LocationWithId? = null
|
||||||
): ResultWithDiagnostics<List<File>>
|
): ResultWithDiagnostics<List<File>> = resolve(listOf(ArtifactWithLocation(artifactCoordinates, sourceCodeLocation)), options)
|
||||||
|
|
||||||
|
suspend fun resolve(
|
||||||
|
artifactsWithLocations: List<ArtifactWithLocation>,
|
||||||
|
options: Options = Options.Empty,
|
||||||
|
): ResultWithDiagnostics<List<File>> =
|
||||||
|
artifactsWithLocations.map { (artifact, location) -> resolve(artifact, options, location) }.asSuccessIfAny()
|
||||||
|
|
||||||
fun addRepository(
|
fun addRepository(
|
||||||
repositoryCoordinates: RepositoryCoordinates,
|
repositoryCoordinates: RepositoryCoordinates,
|
||||||
@@ -46,4 +55,3 @@ fun ExternalDependenciesResolver.addRepository(
|
|||||||
options: Options = Options.Empty,
|
options: Options = Options.Empty,
|
||||||
sourceCodeLocation: SourceCode.LocationWithId? = null
|
sourceCodeLocation: SourceCode.LocationWithId? = null
|
||||||
) = addRepository(RepositoryCoordinates(repositoryCoordinates), options, sourceCodeLocation)
|
) = addRepository(RepositoryCoordinates(repositoryCoordinates), options, sourceCodeLocation)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -39,7 +39,7 @@ abstract class ResolversTestBase : TestCase() {
|
|||||||
fun ExternalDependenciesResolver.assertNotResolve(expectedReportsCount: Int, path: String) {
|
fun ExternalDependenciesResolver.assertNotResolve(expectedReportsCount: Int, path: String) {
|
||||||
val result = runBlocking { resolve(path) }
|
val result = runBlocking { resolve(path) }
|
||||||
assertIsFailure(result)
|
assertIsFailure(result)
|
||||||
assertEquals(expectedReportsCount, result.reports.count())
|
assertEquals("Actual reports:\n${result.reports.joinToString("\n")}", expectedReportsCount, result.reports.count())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ExternalDependenciesResolver.assertAcceptsArtifact(path: String) = assertTrue(acceptsArtifact(path))
|
fun ExternalDependenciesResolver.assertAcceptsArtifact(path: String) = assertTrue(acceptsArtifact(path))
|
||||||
|
|||||||
Reference in New Issue
Block a user