Skip to content

Commit 45f3d8b

Browse files
authored
Fixes regarding nearest/highest strategies and test (#1565)
And some other cleanup. Fixed highest strategy (introduced in 2.0.0). Added UT with some examples to compare differences of behaviour of two strategies (nearest vs highest). And finally, there is some minor cleanup in demo snippets, better naming local reposes. This PR now makes Resolver able to: * dynamically choose conflict resolver based on session config (def is new "path") * dynamically choose version selection strategy for conflict resolution, based on session config (def is "nearest") * dynamically choose skipper type: versionless (default) or versioned (must be used with "highest") Fixes #1562 Fixes #1566
1 parent b6ee3da commit 45f3d8b

File tree

17 files changed

+508
-60
lines changed

17 files changed

+508
-60
lines changed

maven-resolver-demos/maven-resolver-demo-snippets/src/main/java/org/apache/maven/resolver/examples/AllResolverDemos.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public class AllResolverDemos {
3030
* @throws Exception
3131
*/
3232
public static void main(String[] args) throws Exception {
33+
// examples
3334
FindAvailableVersions.main(args);
3435
FindNewestVersion.main(args);
3536
GetDirectDependencies.main(args);
@@ -42,6 +43,10 @@ public static void main(String[] args) throws Exception {
4243
InstallArtifacts.main(args);
4344
DeployArtifacts.main(args);
4445

46+
// experimental
47+
GetDependencyHierarchyWithConflictsStrategies.main(args);
48+
49+
// resolver demo
4550
ResolverDemo.main(args);
4651
}
4752
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.maven.resolver.examples;
20+
21+
import java.util.List;
22+
23+
import org.apache.maven.resolver.examples.util.Booter;
24+
import org.eclipse.aether.RepositorySystem;
25+
import org.eclipse.aether.RepositorySystemSession.CloseableSession;
26+
import org.eclipse.aether.RepositorySystemSession.SessionBuilder;
27+
import org.eclipse.aether.artifact.DefaultArtifact;
28+
import org.eclipse.aether.collection.CollectRequest;
29+
import org.eclipse.aether.collection.CollectResult;
30+
import org.eclipse.aether.graph.Dependency;
31+
import org.eclipse.aether.graph.DependencyNode;
32+
import org.eclipse.aether.internal.impl.collect.bf.BfDependencyCollector;
33+
import org.eclipse.aether.util.graph.manager.DependencyManagerUtils;
34+
import org.eclipse.aether.util.graph.transformer.ChainedDependencyGraphTransformer;
35+
import org.eclipse.aether.util.graph.transformer.ConfigurableVersionSelector;
36+
import org.eclipse.aether.util.graph.transformer.ConflictResolver;
37+
import org.eclipse.aether.util.graph.transformer.JavaDependencyContextRefiner;
38+
import org.eclipse.aether.util.graph.transformer.JavaScopeDeriver;
39+
import org.eclipse.aether.util.graph.transformer.JavaScopeSelector;
40+
import org.eclipse.aether.util.graph.transformer.SimpleOptionalitySelector;
41+
import org.eclipse.aether.util.graph.visitor.DependencyGraphDumper;
42+
43+
/**
44+
* Demo of "nearest" vs "highest" winner selection.
45+
*/
46+
public class GetDependencyHierarchyWithConflictsStrategies {
47+
48+
/**
49+
* Main.
50+
* @param args
51+
* @throws Exception
52+
*/
53+
public static void main(String[] args) throws Exception {
54+
System.out.println("------------------------------------------------------------");
55+
System.out.println(GetDependencyHierarchyWithConflictsStrategies.class.getSimpleName());
56+
57+
runItWithStrategy(args, ConfigurableVersionSelector.NEAREST_SELECTION_STRATEGY);
58+
runItWithStrategy(args, ConfigurableVersionSelector.HIGHEST_SELECTION_STRATEGY);
59+
}
60+
61+
private static void runItWithStrategy(String[] args, String selectionStrategy) throws Exception {
62+
System.out.println();
63+
System.out.println(selectionStrategy);
64+
try (RepositorySystem system = Booter.newRepositorySystem(Booter.selectFactory(args))) {
65+
SessionBuilder sessionBuilder = Booter.newRepositorySystemSession(system);
66+
sessionBuilder.setConfigProperty(ConflictResolver.CONFIG_PROP_VERBOSE, ConflictResolver.Verbosity.FULL);
67+
sessionBuilder.setConfigProperty(DependencyManagerUtils.CONFIG_PROP_VERBOSE, true);
68+
sessionBuilder.setConfigProperty(
69+
ConfigurableVersionSelector.CONFIG_PROP_SELECTION_STRATEGY, selectionStrategy);
70+
sessionBuilder.setConfigProperty(
71+
BfDependencyCollector.CONFIG_PROP_SKIPPER, BfDependencyCollector.VERSIONED_SKIPPER);
72+
try (CloseableSession session = sessionBuilder
73+
.setDependencyGraphTransformer(new ChainedDependencyGraphTransformer(
74+
new ConflictResolver(
75+
new ConfigurableVersionSelector(),
76+
new JavaScopeSelector(),
77+
new SimpleOptionalitySelector(),
78+
new JavaScopeDeriver()),
79+
new JavaDependencyContextRefiner()))
80+
.setRepositoryListener(null)
81+
.setTransferListener(null)
82+
.build()) {
83+
84+
CollectRequest collectRequest = new CollectRequest();
85+
collectRequest.setRootArtifact(new DefaultArtifact("demo:demo:1.0"));
86+
collectRequest.setDependencies(List.of(
87+
new Dependency(new DefaultArtifact("com.squareup.okhttp3:okhttp:jar:4.12.0"), "compile")));
88+
collectRequest.setRepositories(Booter.newRepositories(system, session));
89+
90+
CollectResult result = system.collectDependencies(session, collectRequest);
91+
System.out.println("tree:");
92+
result.getRoot().accept(new DependencyGraphDumper(System.out::println));
93+
94+
List<DependencyNode> selected =
95+
system.flattenDependencyNodes(session, result.getRoot(), (node, parents) -> !node.getData()
96+
.containsKey(ConflictResolver.NODE_DATA_WINNER));
97+
System.out.println("cp:");
98+
selected.forEach(System.out::println);
99+
}
100+
}
101+
}
102+
}

maven-resolver-demos/maven-resolver-demo-snippets/src/main/java/org/apache/maven/resolver/examples/resolver/ResolverDemo.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ public static void main(String[] args) throws Exception {
3838
System.out.println("------------------------------------------------------------");
3939
System.out.println(ResolverDemo.class.getSimpleName());
4040

41-
Resolver resolver =
42-
new Resolver(Booter.selectFactory(args), "https://repo.maven.apache.org/maven2/", "target/aether-repo");
41+
Resolver resolver = new Resolver(
42+
Booter.selectFactory(args), "https://repo.maven.apache.org/maven2/", "target/resolver-demo-repo");
4343
ResolverResult result = resolver.resolve("junit", "junit", "4.13.2");
4444

4545
System.out.println("Result:");

maven-resolver-demos/maven-resolver-demo-snippets/src/main/java/org/apache/maven/resolver/examples/util/Booter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public static RepositorySystem newRepositorySystem(final String factory) {
6464
public static SessionBuilder newRepositorySystemSession(RepositorySystem system) {
6565
SessionBuilder result = new SessionBuilderSupplier(system)
6666
.get()
67-
.withLocalRepositoryBaseDirectories(Path.of("target/local-repo"))
67+
.withLocalRepositoryBaseDirectories(Path.of("target/example-snippets-repo"))
6868
.setRepositoryListener(new ConsoleRepositoryListener())
6969
.setTransferListener(new ConsoleTransferListener())
7070
.setConfigProperty("aether.generator.gpg.enabled", Boolean.TRUE.toString())

maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/bf/BfDependencyCollector.java

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.io.Closeable;
2626
import java.util.ArrayDeque;
2727
import java.util.ArrayList;
28+
import java.util.Arrays;
2829
import java.util.Collections;
2930
import java.util.LinkedHashMap;
3031
import java.util.List;
@@ -36,6 +37,7 @@
3637
import java.util.concurrent.CompletableFuture;
3738
import java.util.concurrent.ConcurrentHashMap;
3839
import java.util.concurrent.atomic.AtomicReference;
40+
import java.util.function.Supplier;
3941
import java.util.stream.Collectors;
4042
import java.util.stream.Stream;
4143

@@ -89,21 +91,29 @@ public class BfDependencyCollector extends DependencyCollectorDelegate {
8991

9092
/**
9193
* The key in the repository session's {@link RepositorySystemSession#getConfigProperties()
92-
* configuration properties} used to store a {@link Boolean} flag controlling the resolver's skip mode.
94+
* configuration properties} used to store a {@link String} flag controlling the resolver's skip mode.
95+
* Supported modes are "versionless" (default), "versioned" and "false" to not use skipper. The first two modes
96+
* are defining "function" how to map artifact coordinates to (String) key while deciding "skip" logic.
97+
* The "versionless" uses {@code G:A:C:E} coordinate elements only (without version), while "versioned" uses
98+
* all {@code G:A:C:E:V} artifact coordinates.
9399
*
94100
* @since 1.8.0
95101
* @configurationSource {@link RepositorySystemSession#getConfigProperties()}
96-
* @configurationType {@link java.lang.Boolean}
102+
* @configurationType {@link java.lang.String}
97103
* @configurationDefaultValue {@link #DEFAULT_SKIPPER}
98104
*/
99105
public static final String CONFIG_PROP_SKIPPER = CONFIG_PROPS_PREFIX + "skipper";
100106

107+
public static final String NONE_SKIPPER = "false";
108+
public static final String VERSIONLESS_SKIPPER = "versionless";
109+
public static final String VERSIONED_SKIPPER = "versioned";
110+
101111
/**
102112
* The default value for {@link #CONFIG_PROP_SKIPPER}, {@code true}.
103113
*
104114
* @since 1.8.0
105115
*/
106-
public static final boolean DEFAULT_SKIPPER = true;
116+
public static final String DEFAULT_SKIPPER = VERSIONLESS_SKIPPER;
107117

108118
/**
109119
* The count of threads to be used when collecting POMs in parallel.
@@ -146,15 +156,23 @@ protected void doCollectDependencies(
146156
List<Dependency> managedDependencies,
147157
Results results)
148158
throws DependencyCollectionException {
149-
boolean useSkip = ConfigUtils.getBoolean(session, DEFAULT_SKIPPER, CONFIG_PROP_SKIPPER);
150-
151-
if (useSkip) {
152-
logger.debug("Collector skip mode enabled");
159+
String skipperMode = ConfigUtils.getString(session, DEFAULT_SKIPPER, CONFIG_PROP_SKIPPER);
160+
Supplier<DependencyResolutionSkipper> skipperSupplier;
161+
if (NONE_SKIPPER.equals(skipperMode)) {
162+
logger.debug("Collector skip mode disabled");
163+
skipperSupplier = DependencyResolutionSkipper::neverSkipper;
164+
} else if (VERSIONLESS_SKIPPER.equals(skipperMode)) {
165+
logger.debug("Collector skip mode enabled: {} (key function GACE)", skipperMode);
166+
skipperSupplier = DependencyResolutionSkipper::defaultGACESkipper;
167+
} else if (VERSIONED_SKIPPER.equals(skipperMode)) {
168+
logger.debug("Collector skip mode enabled: {} (key function GACEV)", skipperMode);
169+
skipperSupplier = DependencyResolutionSkipper::defaultGACEVSkipper;
170+
} else {
171+
throw new IllegalArgumentException("Unknown skipper mode: " + skipperMode + "; known are "
172+
+ Arrays.asList(VERSIONLESS_SKIPPER, VERSIONED_SKIPPER, NONE_SKIPPER));
153173
}
154174

155-
try (DependencyResolutionSkipper skipper = useSkip
156-
? DependencyResolutionSkipper.defaultSkipper()
157-
: DependencyResolutionSkipper.neverSkipper();
175+
try (DependencyResolutionSkipper skipper = skipperSupplier.get();
158176
ParallelDescriptorResolver parallelDescriptorResolver =
159177
new ParallelDescriptorResolver(SmartExecutorUtils.smartExecutor(
160178
session,

maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/bf/DependencyResolutionSkipper.java

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,16 @@
2424
import java.util.List;
2525
import java.util.Map;
2626
import java.util.concurrent.atomic.AtomicInteger;
27+
import java.util.function.Function;
2728

2829
import org.eclipse.aether.artifact.Artifact;
2930
import org.eclipse.aether.graph.DependencyNode;
3031
import org.eclipse.aether.util.artifact.ArtifactIdUtils;
3132
import org.slf4j.Logger;
3233
import org.slf4j.LoggerFactory;
3334

35+
import static java.util.Objects.requireNonNull;
36+
3437
/**
3538
* A skipper that determines whether to skip resolving given node during the dependency collection.
3639
* Internal helper for {@link BfDependencyCollector}.
@@ -63,12 +66,21 @@ abstract class DependencyResolutionSkipper implements Closeable {
6366
public abstract void close();
6467

6568
/**
66-
* Returns new instance of "default" skipper.
69+
* Returns new instance of "default" GACE skipper.
70+
*
71+
* Note: type is specialized for testing purposes.
72+
*/
73+
public static DefaultDependencyResolutionSkipper defaultGACESkipper() {
74+
return new DefaultDependencyResolutionSkipper(ArtifactIdUtils::toVersionlessId);
75+
}
76+
77+
/**
78+
* Returns new instance of "default" GACEV skipper.
6779
*
6880
* Note: type is specialized for testing purposes.
6981
*/
70-
public static DefaultDependencyResolutionSkipper defaultSkipper() {
71-
return new DefaultDependencyResolutionSkipper();
82+
public static DefaultDependencyResolutionSkipper defaultGACEVSkipper() {
83+
return new DefaultDependencyResolutionSkipper(ArtifactIdUtils::toId);
7284
}
7385

7486
/**
@@ -97,14 +109,20 @@ public void close() {}
97109
}
98110

99111
/**
100-
* Visible for testing.
112+
* Default implementation with selectable key function. Visible for testing.
101113
*/
102114
static final class DefaultDependencyResolutionSkipper extends DependencyResolutionSkipper {
103115
private static final Logger LOGGER = LoggerFactory.getLogger(DependencyResolutionSkipper.class);
104116

105-
private final Map<DependencyNode, DependencyResolutionResult> results = new LinkedHashMap<>(256);
106-
private final CacheManager cacheManager = new CacheManager();
107-
private final CoordinateManager coordinateManager = new CoordinateManager();
117+
private final Map<DependencyNode, DependencyResolutionResult> results;
118+
private final CacheManager cacheManager;
119+
private final CoordinateManager coordinateManager;
120+
121+
private DefaultDependencyResolutionSkipper(Function<Artifact, String> keyFunction) {
122+
this.results = new LinkedHashMap<>(256);
123+
this.cacheManager = new CacheManager(keyFunction);
124+
this.coordinateManager = new CoordinateManager();
125+
}
108126

109127
@Override
110128
public boolean skipResolution(DependencyNode node, List<DependencyNode> parents) {
@@ -213,15 +231,26 @@ private static final class CacheManager {
213231
/**
214232
* artifact -> node
215233
*/
216-
private final Map<Artifact, DependencyNode> winners = new HashMap<>(256);
234+
private final Map<Artifact, DependencyNode> winners;
217235

218236
/**
219237
* versionLessId -> Artifact, only cache winners
220238
*/
221-
private final Map<String, Artifact> winnerGAs = new HashMap<>(256);
239+
private final Map<String, Artifact> winnerGAs;
240+
241+
/**
242+
* artifact -> key function (GACE or GACEV is what makes sense primarily)
243+
*/
244+
private final Function<Artifact, String> keyFunction;
245+
246+
private CacheManager(Function<Artifact, String> keyFunction) {
247+
this.winners = new HashMap<>(256);
248+
this.winnerGAs = new HashMap<>(256);
249+
this.keyFunction = requireNonNull(keyFunction);
250+
}
222251

223252
boolean isVersionConflict(DependencyNode node) {
224-
String ga = ArtifactIdUtils.toVersionlessId(node.getArtifact());
253+
String ga = keyFunction.apply(node.getArtifact());
225254
if (winnerGAs.containsKey(ga)) {
226255
Artifact result = winnerGAs.get(ga);
227256
return !node.getArtifact().getVersion().equals(result.getVersion());
@@ -232,7 +261,7 @@ boolean isVersionConflict(DependencyNode node) {
232261

233262
void cacheWinner(DependencyNode node) {
234263
winners.put(node.getArtifact(), node);
235-
winnerGAs.put(ArtifactIdUtils.toVersionlessId(node.getArtifact()), node.getArtifact());
264+
winnerGAs.put(keyFunction.apply(node.getArtifact()), node.getArtifact());
236265
}
237266

238267
boolean isDuplicate(DependencyNode node) {

maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/scope/ScopeManagerImpl.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
import org.eclipse.aether.util.graph.selector.ExclusionDependencySelector;
5151
import org.eclipse.aether.util.graph.transformer.ChainedDependencyGraphTransformer;
5252
import org.eclipse.aether.util.graph.transformer.ConfigurableVersionSelector;
53-
import org.eclipse.aether.util.graph.transformer.PathConflictResolver;
53+
import org.eclipse.aether.util.graph.transformer.ConflictResolver;
5454
import org.eclipse.aether.util.graph.transformer.SimpleOptionalitySelector;
5555
import org.eclipse.aether.util.graph.visitor.CloningDependencyVisitor;
5656
import org.eclipse.aether.util.graph.visitor.FilteringDependencyVisitor;
@@ -159,9 +159,8 @@ public DependencySelector getDependencySelector(ResolutionScope resolutionScope)
159159

160160
@Override
161161
public DependencyGraphTransformer getDependencyGraphTransformer(ResolutionScope resolutionScope) {
162-
// TODO: this should be parameterized!
163162
return new ChainedDependencyGraphTransformer(
164-
new PathConflictResolver(
163+
new ConflictResolver(
165164
new ConfigurableVersionSelector(), new ManagedScopeSelector(this),
166165
new SimpleOptionalitySelector(), new ManagedScopeDeriver(this)),
167166
new ManagedDependencyContextRefiner(this));

maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/collect/bf/DependencyResolutionSkipperTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ void testSkipVersionConflict() {
7575

7676
// follow the BFS resolve sequence
7777
try (DependencyResolutionSkipper.DefaultDependencyResolutionSkipper skipper =
78-
DependencyResolutionSkipper.defaultSkipper()) {
78+
DependencyResolutionSkipper.defaultGACESkipper()) {
7979
assertFalse(skipper.skipResolution(aNode, new ArrayList<>()));
8080
skipper.cache(aNode, new ArrayList<>());
8181
assertFalse(skipper.skipResolution(bNode, mutableList(aNode)));
@@ -120,7 +120,7 @@ void testSkipDeeperDuplicateNode() {
120120

121121
// follow the BFS resolve sequence
122122
try (DependencyResolutionSkipper.DefaultDependencyResolutionSkipper skipper =
123-
DependencyResolutionSkipper.defaultSkipper()) {
123+
DependencyResolutionSkipper.defaultGACESkipper()) {
124124
assertFalse(skipper.skipResolution(aNode, new ArrayList<>()));
125125
skipper.cache(aNode, new ArrayList<>());
126126
assertFalse(skipper.skipResolution(bNode, mutableList(aNode)));
@@ -170,7 +170,7 @@ void testForceResolution() {
170170

171171
// follow the BFS resolve sequence
172172
try (DependencyResolutionSkipper.DefaultDependencyResolutionSkipper skipper =
173-
DependencyResolutionSkipper.defaultSkipper()) {
173+
DependencyResolutionSkipper.defaultGACESkipper()) {
174174
assertFalse(skipper.skipResolution(aNode, new ArrayList<>()));
175175
skipper.cache(aNode, new ArrayList<>());
176176
assertFalse(skipper.skipResolution(bNode, mutableList(aNode)));

0 commit comments

Comments
 (0)