mirror of
https://github.com/rife2/bld
synced 2026-07-24 16:37:48 +02:00
Give scope-local BOMs precedence and warn about conflicting BOM versions
Some checks are pending
bld-ci / build (./bld, 17, macos-latest, false) (push) Waiting to run
bld-ci / build (./bld, 21, macos-latest, false) (push) Waiting to run
bld-ci / build (./bld, 25, macos-latest, false) (push) Waiting to run
bld-ci / build (./bld, 26, macos-latest, false) (push) Waiting to run
bld-ci / build (.\bld.bat, 17, windows-latest, false) (push) Waiting to run
bld-ci / build (.\bld.bat, 21, windows-latest, false) (push) Waiting to run
bld-ci / build (.\bld.bat, 25, windows-latest, false) (push) Waiting to run
bld-ci / build (.\bld.bat, 26, windows-latest, false) (push) Waiting to run
bld-ci / build (unittests, password, unittests, mariadb:10.9, mysql:8, gvenzl/oracle-free:latest, gvenzl/oracle-xe:18-slim, postgres:15, ./bld, 17, ubuntu-latest, true) (push) Waiting to run
bld-ci / build (unittests, password, unittests, mariadb:10.9, mysql:8, gvenzl/oracle-free:latest, gvenzl/oracle-xe:18-slim, postgres:15, ./bld, 21, ubuntu-latest, true) (push) Waiting to run
bld-ci / build (unittests, password, unittests, mariadb:10.9, mysql:8, gvenzl/oracle-free:latest, gvenzl/oracle-xe:18-slim, postgres:15, ./bld, 25, ubuntu-latest, true) (push) Waiting to run
bld-ci / build (unittests, password, unittests, mariadb:10.9, mysql:8, gvenzl/oracle-free:latest, gvenzl/oracle-xe:18-slim, postgres:15, ./bld, 26, ubuntu-latest, true) (push) Waiting to run
javadocs-pages / deploy (push) Waiting to run
Some checks are pending
bld-ci / build (./bld, 17, macos-latest, false) (push) Waiting to run
bld-ci / build (./bld, 21, macos-latest, false) (push) Waiting to run
bld-ci / build (./bld, 25, macos-latest, false) (push) Waiting to run
bld-ci / build (./bld, 26, macos-latest, false) (push) Waiting to run
bld-ci / build (.\bld.bat, 17, windows-latest, false) (push) Waiting to run
bld-ci / build (.\bld.bat, 21, windows-latest, false) (push) Waiting to run
bld-ci / build (.\bld.bat, 25, windows-latest, false) (push) Waiting to run
bld-ci / build (.\bld.bat, 26, windows-latest, false) (push) Waiting to run
bld-ci / build (unittests, password, unittests, mariadb:10.9, mysql:8, gvenzl/oracle-free:latest, gvenzl/oracle-xe:18-slim, postgres:15, ./bld, 17, ubuntu-latest, true) (push) Waiting to run
bld-ci / build (unittests, password, unittests, mariadb:10.9, mysql:8, gvenzl/oracle-free:latest, gvenzl/oracle-xe:18-slim, postgres:15, ./bld, 21, ubuntu-latest, true) (push) Waiting to run
bld-ci / build (unittests, password, unittests, mariadb:10.9, mysql:8, gvenzl/oracle-free:latest, gvenzl/oracle-xe:18-slim, postgres:15, ./bld, 25, ubuntu-latest, true) (push) Waiting to run
bld-ci / build (unittests, password, unittests, mariadb:10.9, mysql:8, gvenzl/oracle-free:latest, gvenzl/oracle-xe:18-slim, postgres:15, ./bld, 26, ubuntu-latest, true) (push) Waiting to run
javadocs-pages / deploy (push) Waiting to run
This commit is contained in:
parent
c9298d3de6
commit
d7d176eef6
|
|
@ -7,6 +7,7 @@ package rife.bld.dependencies;
|
|||
import rife.ioc.HierarchicalProperties;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
|
||||
|
|
@ -75,10 +76,13 @@ public class DependencyScopes extends LinkedHashMap<Scope, DependencySet> {
|
|||
* {@code compile} scope BOMs also apply to the {@code provided},
|
||||
* {@code runtime} and {@code test} scopes, and the {@code runtime} and
|
||||
* {@code provided} scope BOMs also apply to the {@code test} scope.
|
||||
* The BOMs are returned in classpath order, for the {@code test} scope
|
||||
* that is {@code compile}, {@code provided}, {@code runtime},
|
||||
* {@code test}, and this order determines their precedence when
|
||||
* several manage the same dependency.
|
||||
* The BOMs are returned with the scope's own BOMs first, followed by
|
||||
* the inherited ones from the more specific to the more general scope,
|
||||
* for the {@code test} scope that is {@code test}, {@code runtime},
|
||||
* {@code provided}, {@code compile}. This order determines their
|
||||
* precedence when several manage the same dependency: a BOM declared
|
||||
* in the scope where a dependency is used takes precedence over a BOM
|
||||
* that is inherited from a broader scope.
|
||||
* <p>
|
||||
* The {@code standalone} scope only uses its own BOMs, and its BOMs
|
||||
* deliberately never apply to any other scope.
|
||||
|
|
@ -100,9 +104,9 @@ public class DependencyScopes extends LinkedHashMap<Scope, DependencySet> {
|
|||
|
||||
private static Scope[] bomScopes(Scope scope) {
|
||||
return switch (scope) {
|
||||
case provided -> new Scope[]{Scope.compile, Scope.provided};
|
||||
case runtime -> new Scope[]{Scope.compile, Scope.runtime};
|
||||
case test -> new Scope[]{Scope.compile, Scope.provided, Scope.runtime, Scope.test};
|
||||
case provided -> new Scope[]{Scope.provided, Scope.compile};
|
||||
case runtime -> new Scope[]{Scope.runtime, Scope.compile};
|
||||
case test -> new Scope[]{Scope.test, Scope.runtime, Scope.provided, Scope.compile};
|
||||
default -> new Scope[]{scope};
|
||||
};
|
||||
}
|
||||
|
|
@ -142,6 +146,37 @@ public class DependencyScopes extends LinkedHashMap<Scope, DependencySet> {
|
|||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the version conflicts between the BOMs that apply to the
|
||||
* scopes, where more than one applicable BOM manages the same
|
||||
* dependency at a different version.
|
||||
* <p>
|
||||
* Each conflict is reported once, the versions are keyed by their BOM
|
||||
* in precedence order so that the first is the version that is used.
|
||||
*
|
||||
* @param properties the properties to use to get artifacts
|
||||
* @param retriever the retriever to use to get artifacts
|
||||
* @param repositories the repositories to use for the BOM resolution
|
||||
* @return the version conflicts between the applicable BOMs
|
||||
* @since 2.4.0
|
||||
*/
|
||||
public List<VersionResolution.BomVersionConflict> bomVersionConflicts(HierarchicalProperties properties, ArtifactRetriever retriever, List<Repository> repositories) {
|
||||
var result = new ArrayList<VersionResolution.BomVersionConflict>();
|
||||
var seen = new HashSet<String>();
|
||||
for (var scope : keySet()) {
|
||||
var effective_boms = effectiveBoms(scope);
|
||||
if (effective_boms.size() < 2) {
|
||||
continue;
|
||||
}
|
||||
for (var conflict : VersionResolution.resolveBomVersionConflicts(properties, retriever, repositories, effective_boms)) {
|
||||
if (seen.add(conflict.dependency() + conflict.bomVersions())) {
|
||||
result.add(conflict);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the transitive set of dependencies that would be used for the compile scope in a project.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -6,8 +6,10 @@ package rife.bld.dependencies;
|
|||
|
||||
import rife.ioc.HierarchicalProperties;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Logger;
|
||||
|
|
@ -175,6 +177,62 @@ public class VersionResolution {
|
|||
return bom_versions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes a version conflict between bills of materials, where more
|
||||
* than one applicable BOM manages the same dependency at a different
|
||||
* version.
|
||||
*
|
||||
* @param dependency the group and artifact identifiers of the
|
||||
* dependency that is managed at conflicting versions
|
||||
* @param bomVersions the versions that the BOMs manage the dependency
|
||||
* at, keyed by the BOM, in precedence order so that
|
||||
* the first entry is the version that is used
|
||||
* @since 2.4.0
|
||||
*/
|
||||
public record BomVersionConflict(String dependency, Map<String, Version> bomVersions) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the version conflicts between the provided bills of
|
||||
* materials, where more than one of them manages the same dependency
|
||||
* at a different version.
|
||||
*
|
||||
* @param properties the properties to use to get artifacts
|
||||
* @param retriever the retriever to use to get the BOMs
|
||||
* @param repositories the repositories to resolve the BOMs in
|
||||
* @param boms the BOMs to check, in precedence order
|
||||
* @return the version conflicts between the BOMs
|
||||
* @since 2.4.0
|
||||
*/
|
||||
public static List<BomVersionConflict> resolveBomVersionConflicts(HierarchicalProperties properties, ArtifactRetriever retriever, List<Repository> repositories, Collection<Bom> boms) {
|
||||
var base = new VersionResolution(properties);
|
||||
var versions_by_key = new LinkedHashMap<String, LinkedHashMap<String, Version>>();
|
||||
var dependency_by_key = new LinkedHashMap<String, String>();
|
||||
if (boms != null) {
|
||||
for (var bom : boms) {
|
||||
var pom = new DependencyResolver(base, retriever, repositories, bom).getMavenPom(bom);
|
||||
for (var managed : pom.getManagedDependencies()) {
|
||||
if (managed.version() != null && !managed.version().isBlank()) {
|
||||
var dependency = managed.convertToDependency();
|
||||
var key = managedKey(dependency);
|
||||
versions_by_key.computeIfAbsent(key, k -> new LinkedHashMap<>())
|
||||
.putIfAbsent(bom.toArtifactString(), dependency.version());
|
||||
dependency_by_key.putIfAbsent(key, dependency.toArtifactString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var conflicts = new ArrayList<BomVersionConflict>();
|
||||
for (var entry : versions_by_key.entrySet()) {
|
||||
var bom_versions = entry.getValue();
|
||||
if (bom_versions.values().stream().distinct().count() > 1) {
|
||||
conflicts.add(new BomVersionConflict(dependency_by_key.get(entry.getKey()), new LinkedHashMap<>(bom_versions)));
|
||||
}
|
||||
}
|
||||
return conflicts;
|
||||
}
|
||||
|
||||
// builds the identity that dependency management entries are matched
|
||||
// on, mirroring Maven this includes the type and the classifier, the
|
||||
// modular and forced-classpath JAR types match the plain jar entries
|
||||
|
|
|
|||
|
|
@ -86,6 +86,28 @@ public class DownloadOperation extends AbstractOperation<DownloadOperation> {
|
|||
for (var dependency : dependencies().versionlessDependenciesWithoutBom(properties(), artifactRetriever(), repositories())) {
|
||||
System.out.println("Warning: '" + dependency.toArtifactString() + "' isn't covered by a BOM, its latest version will be used");
|
||||
}
|
||||
for (var conflict : dependencies().bomVersionConflicts(properties(), artifactRetriever(), repositories())) {
|
||||
System.out.println(formatBomVersionConflict(conflict));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a warning message for a BOM version conflict.
|
||||
*
|
||||
* @param conflict the BOM version conflict to format
|
||||
* @return the formatted warning message
|
||||
* @since 2.4.0
|
||||
*/
|
||||
protected static String formatBomVersionConflict(rife.bld.dependencies.VersionResolution.BomVersionConflict conflict) {
|
||||
var entries = conflict.bomVersions().entrySet().iterator();
|
||||
var used = entries.next();
|
||||
var message = new StringBuilder("Warning: '" + conflict.dependency() + "' is managed by multiple BOMs, using " +
|
||||
used.getValue() + " from '" + used.getKey() + "'");
|
||||
while (entries.hasNext()) {
|
||||
var other = entries.next();
|
||||
message.append(", not ").append(other.getValue()).append(" from '").append(other.getKey()).append('\'');
|
||||
}
|
||||
return message.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -78,6 +78,9 @@ public class PurgeOperation extends AbstractOperation<PurgeOperation> {
|
|||
for (var dependency : dependencies().versionlessDependenciesWithoutBom(properties(), artifactRetriever(), repositories())) {
|
||||
System.out.println("Warning: '" + dependency.toArtifactString() + "' isn't covered by a BOM, its latest version will be used");
|
||||
}
|
||||
for (var conflict : dependencies().bomVersionConflicts(properties(), artifactRetriever(), repositories())) {
|
||||
System.out.println(DownloadOperation.formatBomVersionConflict(conflict));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -410,10 +410,11 @@ public class TestBom {
|
|||
.include(new Bom("com.example", "bom2", new VersionNumber(1, 0, 0)))
|
||||
.include(new Dependency("com.example", "a"));
|
||||
|
||||
// when several applicable BOMs manage the same dependency, the
|
||||
// first one in classpath composition order determines the version
|
||||
// a BOM declared in the dependency's own scope takes precedence
|
||||
// over a BOM inherited from a broader scope, so the test scope
|
||||
// bom2 wins over the compile scope bom1
|
||||
var test_resolved = scopes.resolveTestDependencies(new HierarchicalProperties(), ArtifactRetriever.cachingInstance(), serverRepositories(server));
|
||||
assertEquals(Version.parse("1.4.0"), test_resolved.get(new Dependency("com.example", "a")).version());
|
||||
assertEquals(Version.parse("2.2.0"), test_resolved.get(new Dependency("com.example", "a")).version());
|
||||
} finally {
|
||||
server.stop(0);
|
||||
}
|
||||
|
|
@ -470,6 +471,41 @@ public class TestBom {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBomVersionConflictsAreDetected() throws Exception {
|
||||
var server = createArtifactServer(Map.of(
|
||||
"bom1:1.0.0", bomPom("bom1", "1.0.0", managed("a", "1.4.0") + managed("b", "3.0.0")),
|
||||
"bom2:1.0.0", bomPom("bom2", "1.0.0", managed("a", "2.2.0"))),
|
||||
Map.of());
|
||||
server.start();
|
||||
try {
|
||||
var scopes = new DependencyScopes();
|
||||
scopes.scope(compile)
|
||||
.include(new Bom("com.example", "bom1", new VersionNumber(1, 0, 0)));
|
||||
scopes.scope(Scope.test)
|
||||
.include(new Bom("com.example", "bom2", new VersionNumber(1, 0, 0)));
|
||||
|
||||
var conflicts = scopes.bomVersionConflicts(new HierarchicalProperties(), ArtifactRetriever.cachingInstance(), serverRepositories(server));
|
||||
// only 'a' is managed by both BOMs at different versions, 'b'
|
||||
// is managed by a single BOM so it isn't a conflict
|
||||
assertEquals(1, conflicts.size());
|
||||
var conflict = conflicts.get(0);
|
||||
assertEquals("com.example:a", conflict.dependency());
|
||||
|
||||
var versions = conflict.bomVersions().entrySet().iterator();
|
||||
// the test scope bom2 wins and is reported first, the compile
|
||||
// scope bom1 is the alternative
|
||||
var used = versions.next();
|
||||
assertEquals("com.example:bom2", used.getKey());
|
||||
assertEquals(Version.parse("2.2.0"), used.getValue());
|
||||
var other = versions.next();
|
||||
assertEquals("com.example:bom1", other.getKey());
|
||||
assertEquals(Version.parse("1.4.0"), other.getValue());
|
||||
} finally {
|
||||
server.stop(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEffectiveBomsComposition() {
|
||||
var compile_bom = new Bom("com.example", "compile-bom", new VersionNumber(1, 0, 0));
|
||||
|
|
@ -485,11 +521,12 @@ public class TestBom {
|
|||
scopes.scope(Scope.test).include(test_bom);
|
||||
scopes.scope(Scope.standalone).include(standalone_bom);
|
||||
|
||||
// the scope's own BOMs come first, then the inherited ones from
|
||||
// the more specific to the more general scope
|
||||
assertEquals(List.of(compile_bom), scopes.effectiveBoms(compile));
|
||||
assertEquals(List.of(compile_bom, provided_bom), scopes.effectiveBoms(Scope.provided));
|
||||
assertEquals(List.of(compile_bom, runtime_bom), scopes.effectiveBoms(Scope.runtime));
|
||||
// the test scope order matches the test classpath order
|
||||
assertEquals(List.of(compile_bom, provided_bom, runtime_bom, test_bom), scopes.effectiveBoms(Scope.test));
|
||||
assertEquals(List.of(provided_bom, compile_bom), scopes.effectiveBoms(Scope.provided));
|
||||
assertEquals(List.of(runtime_bom, compile_bom), scopes.effectiveBoms(Scope.runtime));
|
||||
assertEquals(List.of(test_bom, runtime_bom, provided_bom, compile_bom), scopes.effectiveBoms(Scope.test));
|
||||
assertEquals(List.of(standalone_bom), scopes.effectiveBoms(Scope.standalone));
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue