Parallelize update checks and wrapper extension resolution through new ParallelDependencyResolver

This commit is contained in:
Geert Bevin 2026-07-12 22:14:37 -04:00
parent f3f4799190
commit 4447610d02
6 changed files with 141 additions and 53 deletions

View file

@ -9,7 +9,6 @@ import rife.tools.exceptions.FileUtilsErrorException;
import java.io.*;
import java.util.*;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import static rife.bld.dependencies.Dependency.*;
@ -180,46 +179,6 @@ public class DependencyResolver {
return result;
}
/**
* Resolves the transitive dependencies of multiple root dependencies,
* merging the results in the order of the provided roots.
* <p>
* The roots are resolved in parallel according to the resolution
* parallelism, while a shared {@code PomPrefetcher} speculatively warms
* the retriever cache across all of them. The merged result is identical
* to resolving each root sequentially.
*
* @param resolution the version resolution state that can be cached
* @param retriever the retriever to use to get artifacts
* @param repositories the repositories to use for the resolution
* @param roots the root dependencies to resolve
* @param scopes the scopes to return the transitive dependencies for
* @return the merged transitive dependencies of all the roots
* @since 2.3.1
*/
static DependencySet resolveAllDependencies(VersionResolution resolution, ArtifactRetriever retriever, List<Repository> repositories, Collection<Dependency> roots, Scope... scopes) {
var result = new DependencySet();
if (roots.isEmpty()) {
return result;
}
var prefetcher = PomPrefetcher.create(resolution, retriever, repositories);
try {
var resolutions = new ArrayList<Supplier<DependencySet>>(roots.size());
for (var root : roots) {
resolutions.add(() -> new DependencyResolver(resolution, retriever, repositories, root).getAllDependencies(prefetcher, scopes));
}
for (var dependencies : ParallelExecution.execute(resolutions, resolution.resolutionParallelism())) {
result.addAll(dependencies);
}
} finally {
if (prefetcher != null) {
prefetcher.shutdown();
}
}
return result;
}
private boolean matchesExclusions(Dependency context, PomDependency checked) {
while (context != null) {
if (context.exclusions() != null) {

View file

@ -155,7 +155,7 @@ public class DependencyScopes extends LinkedHashMap<Scope, DependencySet> {
roots.addAll(scoped_dependencies);
}
}
var dependencies = DependencyResolver.resolveAllDependencies(resolution, retriever, repositories, roots, transitiveScopes);
var dependencies = new ParallelDependencyResolver(resolution, retriever, repositories).resolveAllDependencies(roots, transitiveScopes);
if (excluded != null) {
dependencies.removeAll(excluded);
}

View file

@ -226,7 +226,7 @@ public class DependencySet extends AbstractSet<Dependency> implements Set<Depend
* @since 2.0
*/
public String generateTransitiveDependencyTree(VersionResolution resolution, ArtifactRetriever retriever, List<Repository> repositories, Scope... scopes) {
return DependencyResolver.resolveAllDependencies(resolution, retriever, repositories, this, scopes).generateDependencyTree();
return new ParallelDependencyResolver(resolution, retriever, repositories).resolveAllDependencies(this, scopes).generateDependencyTree();
}
/**

View file

@ -0,0 +1,118 @@
/*
* Copyright 2001-2026 Geert Bevin (gbevin[remove] at uwyn dot com)
* Licensed under the Apache License, Version 2.0 (the "License")
*/
package rife.bld.dependencies;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Supplier;
/**
* Resolves multiple dependencies in parallel within a list of
* Maven-compatible repositories.
* <p>
* The parallelism is determined by {@link VersionResolution#resolutionParallelism()},
* setting it to {@code 1} makes the resolution sequential. The results are
* always identical to resolving each dependency sequentially with a
* {@link DependencyResolver}, in the same order.
*
* @author Geert Bevin (gbevin[remove] at uwyn dot com)
* @since 2.3.1
*/
public class ParallelDependencyResolver {
private final VersionResolution resolution_;
private final ArtifactRetriever retriever_;
private final List<Repository> repositories_;
/**
* Creates a new parallel resolver.
* <p>
* The repositories will be checked in the order they're listed.
*
* @param resolution the version resolution state that can be cached
* @param retriever the retriever to use to get artifacts
* @param repositories the repositories to use for the resolution
* @since 2.3.1
*/
public ParallelDependencyResolver(VersionResolution resolution, ArtifactRetriever retriever, List<Repository> repositories) {
resolution_ = resolution;
retriever_ = retriever;
if (repositories == null) {
repositories = List.of();
}
repositories_ = repositories;
}
/**
* Resolves the transitive dependencies of multiple root dependencies,
* merging the results in the order of the provided roots.
* <p>
* The roots are resolved in parallel while a shared prefetcher
* speculatively warms the retriever cache across all of them.
*
* @param roots the root dependencies to resolve
* @param scopes the scopes to return the transitive dependencies for
* @return the merged transitive dependencies of all the roots
* @since 2.3.1
*/
public DependencySet resolveAllDependencies(Collection<Dependency> roots, Scope... scopes) {
var result = new DependencySet();
if (roots.isEmpty()) {
return result;
}
var prefetcher = PomPrefetcher.create(resolution_, retriever_, repositories_);
try {
var resolutions = new ArrayList<Supplier<DependencySet>>(roots.size());
for (var root : roots) {
resolutions.add(() -> new DependencyResolver(resolution_, retriever_, repositories_, root).getAllDependencies(prefetcher, scopes));
}
for (var dependencies : ParallelExecution.execute(resolutions, resolution_.resolutionParallelism())) {
result.addAll(dependencies);
}
} finally {
if (prefetcher != null) {
prefetcher.shutdown();
}
}
return result;
}
/**
* Resolves the latest versions of multiple dependencies, returning them
* in the same order as the provided dependencies.
*
* @param dependencies the dependencies to resolve the latest versions of
* @return the latest versions in the order of the provided dependencies
* @since 2.3.1
*/
public List<Version> resolveLatestVersions(List<Dependency> dependencies) {
var resolutions = new ArrayList<Supplier<Version>>(dependencies.size());
for (var dependency : dependencies) {
resolutions.add(() -> new DependencyResolver(resolution_, retriever_, repositories_, dependency).latestVersion());
}
return ParallelExecution.execute(resolutions, resolution_.resolutionParallelism());
}
/**
* Returns the version resolution state that can be cached.
*
* @return the version resolution state
* @since 2.3.1
*/
public VersionResolution resolution() {
return resolution_;
}
/**
* Retrieve the repositories that are used by this resolver.
*
* @return the resolver's repositories
* @since 2.3.1
*/
public List<Repository> repositories() {
return repositories_;
}
}

View file

@ -31,16 +31,26 @@ public class UpdatesOperation extends AbstractOperation<UpdatesOperation> {
*/
public void execute() {
var resolution = new VersionResolution(properties());
var result = new DependencyScopes();
var scopes = new ArrayList<Scope>();
var dependencies = new ArrayList<Dependency>();
for (var entry : dependencies_.entrySet()) {
var scope = entry.getKey();
for (var dependency : entry.getValue()) {
var latest = new DependencyResolver(resolution, artifactRetriever(), repositories(), dependency).latestVersion();
if (latest.compareTo(dependency.version()) > 0) {
var latest_dependency = new Dependency(dependency.groupId(), dependency.artifactId(), latest,
dependency.classifier(), dependency.type());
result.scope(scope).include(latest_dependency);
}
scopes.add(entry.getKey());
dependencies.add(dependency);
}
}
var latest_versions = new ParallelDependencyResolver(resolution, artifactRetriever(), repositories()).resolveLatestVersions(dependencies);
var result = new DependencyScopes();
for (var i = 0; i < dependencies.size(); ++i) {
var dependency = dependencies.get(i);
var latest = latest_versions.get(i);
if (latest.compareTo(dependency.version()) > 0) {
var latest_dependency = new Dependency(dependency.groupId(), dependency.artifactId(), latest,
dependency.classifier(), dependency.type());
result.scope(scopes.get(i)).include(latest_dependency);
}
}

View file

@ -89,12 +89,13 @@ public class WrapperExtensionResolver {
private Set<String> transferExtensionDependencies() {
var filenames = new HashSet<String>();
var dependencies = new DependencySet();
var roots = new ArrayList<Dependency>();
for (var d : dependencies_) {
if (d != null) {
dependencies.addAll(new DependencyResolver(resolution_, retriever_, repositories_, d).getAllDependencies(Scope.compile, Scope.runtime));
roots.add(d);
}
}
var dependencies = new ParallelDependencyResolver(resolution_, retriever_, repositories_).resolveAllDependencies(roots, Scope.compile, Scope.runtime);
if (!dependencies.isEmpty()) {
ensurePrintedHeader();