diff --git a/src/it/override-test-dependencies-useUpperBounds/invoker.properties b/src/it/override-test-dependencies-useUpperBounds/invoker.properties
deleted file mode 100644
index 384bd1fafb..0000000000
--- a/src/it/override-test-dependencies-useUpperBounds/invoker.properties
+++ /dev/null
@@ -1 +0,0 @@
-invoker.goals=-ntp test
diff --git a/src/it/override-test-dependencies-useUpperBounds/pom.xml b/src/it/override-test-dependencies-useUpperBounds/pom.xml
deleted file mode 100644
index c703d7fc5b..0000000000
--- a/src/it/override-test-dependencies-useUpperBounds/pom.xml
+++ /dev/null
@@ -1,69 +0,0 @@
-
-
- 4.0.0
-
- org.jenkins-ci.plugins
- plugin
- 4.86
-
-
- org.jenkins-ci.tools.hpi.its
- override-test-dependencies-useUpperBounds
- 1.0-SNAPSHOT
- hpi
-
- 2.361.4
- @project.version@
-
- org.jenkins-ci.plugins.workflow:workflow-cps:2.33
- true
- SampleTest
- false
- 0
- 2.9
- false
-
-
-
- org.jenkins-ci.plugins
- structs
- 1.6
-
-
- org.jenkins-ci.plugins.workflow
- workflow-step-api
- ${workflow-step-api-plugin.version}
-
-
- org.jenkins-ci.plugins.workflow
- workflow-cps
- 2.30
- test
-
-
- antlr
- antlr
-
-
-
-
- org.jenkins-ci.plugins.workflow
- workflow-step-api
- ${workflow-step-api-plugin.version}
- tests
- test
-
-
-
-
- repo.jenkins-ci.org
- https://repo.jenkins-ci.org/public/
-
-
-
-
- repo.jenkins-ci.org
- https://repo.jenkins-ci.org/public/
-
-
-
diff --git a/src/it/override-test-dependencies-useUpperBounds/src/main/resources/index.jelly b/src/it/override-test-dependencies-useUpperBounds/src/main/resources/index.jelly
deleted file mode 100644
index 2f655e510a..0000000000
--- a/src/it/override-test-dependencies-useUpperBounds/src/main/resources/index.jelly
+++ /dev/null
@@ -1,2 +0,0 @@
-
-
diff --git a/src/it/override-test-dependencies-useUpperBounds/src/test/java/test/SampleTest.java b/src/it/override-test-dependencies-useUpperBounds/src/test/java/test/SampleTest.java
deleted file mode 100644
index 548bc826ff..0000000000
--- a/src/it/override-test-dependencies-useUpperBounds/src/test/java/test/SampleTest.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package test;
-
-import static org.junit.Assert.*;
-
-import com.google.common.collect.ImmutableMap;
-import hudson.remoting.Which;
-import java.io.InputStream;
-import java.net.URL;
-import java.util.Enumeration;
-import java.util.Map;
-import java.util.jar.Manifest;
-import org.jenkinsci.plugins.workflow.steps.StepConfigTester;
-import org.junit.Rule;
-import org.junit.Test;
-import org.jvnet.hudson.test.JenkinsRule;
-
-public class SampleTest {
-
- @Rule
- public JenkinsRule r = new JenkinsRule();
-
- @Test
- public void smokes() throws Exception {
- Map expectedVersions = ImmutableMap.of(
- "structs", "1.7", "workflow-step-api", "2.10", "workflow-api", "2.16", "workflow-cps", "2.33");
- Enumeration manifests = SampleTest.class.getClassLoader().getResources("META-INF/MANIFEST.MF");
- while (manifests.hasMoreElements()) {
- URL url = manifests.nextElement();
- try (InputStream is = url.openStream()) {
- Manifest mf = new Manifest(is);
- String pluginName = mf.getMainAttributes().getValue("Short-Name");
- String expectedVersion = expectedVersions.get(pluginName);
- if (expectedVersion != null) {
- assertEquals(
- "wrong version for " + pluginName + " as classpath entry",
- expectedVersion,
- mf.getMainAttributes().getValue("Plugin-Version"));
- }
- }
- }
- for (Map.Entry entry : expectedVersions.entrySet()) {
- assertEquals(
- "wrong version for " + entry.getKey() + " as plugin",
- entry.getValue(),
- r.jenkins.pluginManager.getPlugin(entry.getKey()).getVersion());
- }
- assertEquals(
- "workflow-step-api-2.10-tests.jar",
- Which.jarFile(StepConfigTester.class).getName());
- }
-}
diff --git a/src/it/override-test-dependencies-useUpperBounds/verify.groovy b/src/it/override-test-dependencies-useUpperBounds/verify.groovy
deleted file mode 100644
index a4b7f510d8..0000000000
--- a/src/it/override-test-dependencies-useUpperBounds/verify.groovy
+++ /dev/null
@@ -1,3 +0,0 @@
-def log = new File(basedir, 'build.log').text
-// TODO add anything needed, or delete this file
-true
\ No newline at end of file
diff --git a/src/main/java/org/jenkinsci/maven/plugins/hpi/TestDependencyMojo.java b/src/main/java/org/jenkinsci/maven/plugins/hpi/TestDependencyMojo.java
index 7a4e2191cd..1e5a77a4a8 100644
--- a/src/main/java/org/jenkinsci/maven/plugins/hpi/TestDependencyMojo.java
+++ b/src/main/java/org/jenkinsci/maven/plugins/hpi/TestDependencyMojo.java
@@ -20,8 +20,10 @@
import java.util.Map;
import java.util.NavigableMap;
import java.util.NavigableSet;
+import java.util.Optional;
import java.util.Properties;
import java.util.Set;
+import java.util.Stack;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.jar.JarEntry;
@@ -34,7 +36,6 @@
import org.apache.commons.io.FileUtils;
import org.apache.maven.RepositoryUtils;
import org.apache.maven.artifact.Artifact;
-import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.ComparableVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
@@ -58,11 +59,13 @@
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuildingRequest;
import org.apache.maven.project.ProjectDependenciesResolver;
-import org.apache.maven.shared.dependency.graph.DependencyCollectorBuilder;
-import org.apache.maven.shared.dependency.graph.DependencyCollectorBuilderException;
-import org.apache.maven.shared.dependency.graph.DependencyNode;
-import org.apache.maven.shared.dependency.graph.traversal.DependencyNodeVisitor;
import org.eclipse.aether.DefaultRepositorySystemSession;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.artifact.ArtifactTypeRegistry;
+import org.eclipse.aether.collection.CollectRequest;
+import org.eclipse.aether.collection.DependencyCollectionException;
+import org.eclipse.aether.graph.DependencyNode;
+import org.eclipse.aether.graph.DependencyVisitor;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.resolution.ArtifactDescriptorException;
import org.eclipse.aether.resolution.ArtifactDescriptorRequest;
@@ -70,6 +73,7 @@
import org.eclipse.aether.resolution.ArtifactRequest;
import org.eclipse.aether.resolution.ArtifactResolutionException;
import org.eclipse.aether.resolution.ArtifactResult;
+import org.eclipse.aether.util.graph.manager.DependencyManagerUtils;
import org.twdata.maven.mojoexecutor.MojoExecutor;
/**
@@ -93,9 +97,6 @@ public class TestDependencyMojo extends AbstractHpiMojo {
@Component
private BuildPluginManager pluginManager;
- @Component
- private DependencyCollectorBuilder dependencyCollectorBuilder;
-
@Component
private ProjectDependenciesResolver dependenciesResolver;
@@ -132,6 +133,8 @@ public class TestDependencyMojo extends AbstractHpiMojo {
@Parameter(property = "upperBoundsExcludes")
private List upperBoundsExcludes;
+ private RequireUpperBoundDepsVisitor upperBoundDepsVisitor;
+
@Override
public void execute() throws MojoExecutionException {
Map overrides = overrideVersions != null ? parseOverrides(overrideVersions) : Map.of();
@@ -215,19 +218,40 @@ public void execute() throws MojoExecutionException {
*/
DependencyNode node;
try {
- ProjectBuildingRequest buildingRequest =
- new DefaultProjectBuildingRequest(session.getProjectBuildingRequest());
- buildingRequest.setProject(shadow);
- buildingRequest.setRemoteRepositories(shadow.getRemoteArtifactRepositories());
- ArtifactFilter filter = null; // Evaluate all scopes
- node = dependencyCollectorBuilder.collectDependencyGraph(buildingRequest, filter);
- } catch (DependencyCollectorBuilderException e) {
+ RepositorySystemSession repositorySystemSession = session.getRepositorySession();
+
+ ArtifactTypeRegistry artifactTypeRegistry =
+ session.getRepositorySession().getArtifactTypeRegistry();
+
+ List dependencies = shadow.getDependencies().stream()
+ .filter(d -> !d.isOptional())
+ .filter(d -> !List.of(Artifact.SCOPE_TEST, Artifact.SCOPE_PROVIDED)
+ .contains(d.getScope()))
+ .map(d -> RepositoryUtils.toDependency(d, artifactTypeRegistry))
+ .collect(Collectors.toList());
+
+ List managedDependencies = Optional.ofNullable(
+ shadow.getDependencyManagement())
+ .map(DependencyManagement::getDependencies)
+ .map(list -> list.stream()
+ .map(d -> RepositoryUtils.toDependency(d, artifactTypeRegistry))
+ .collect(Collectors.toList()))
+ .orElse(null);
+
+ CollectRequest collectRequest = new CollectRequest(
+ dependencies, managedDependencies, shadow.getRemoteProjectRepositories());
+ collectRequest.setRootArtifact(RepositoryUtils.toArtifact(shadow.getArtifact()));
+
+ node = repositorySystem
+ .collectDependencies(repositorySystemSession, collectRequest)
+ .getRoot();
+ } catch (DependencyCollectionException e) {
throw new MojoExecutionException("Failed to analyze dependency tree for useUpperBounds", e);
}
- RequireUpperBoundDepsVisitor visitor = new RequireUpperBoundDepsVisitor();
- node.accept(visitor);
+ upperBoundDepsVisitor = new RequireUpperBoundDepsVisitor();
+ node.accept(upperBoundDepsVisitor);
String self = String.format("%s:%s", shadow.getGroupId(), shadow.getArtifactId());
- upperBounds = visitor.upperBounds(upperBoundsExcludes, self);
+ upperBounds = upperBoundDepsVisitor.upperBounds(upperBoundsExcludes, self);
if (upperBounds.isEmpty()) {
converged = true;
@@ -787,24 +811,27 @@ private Set resolveDependencies(MavenProject project) throws MojoExecu
}
}
- // Adapted from RequireUpperBoundDeps @ 731ea7a693a0986f2054b6a73a86a31373df59ec.
- private class RequireUpperBoundDepsVisitor implements DependencyNodeVisitor {
+ // Adapted from RequireUpperBoundDeps @ 78488535e0cfc37e26707c12d944ff8437b94fc4.
+ private class RequireUpperBoundDepsVisitor implements DependencyVisitor, ParentNodeProvider {
- private Map> keyToPairsMap = new LinkedHashMap<>();
+ private final ParentsVisitor parentsVisitor = new ParentsVisitor();
+
+ private final Map> keyToPairsMap = new HashMap<>();
@Override
- public boolean visit(DependencyNode node) {
- DependencyNodeHopCountPair pair = new DependencyNodeHopCountPair(node);
+ public boolean visitEnter(DependencyNode node) {
+ parentsVisitor.visitEnter(node);
+ DependencyNodeHopCountPair pair = new DependencyNodeHopCountPair(node, this);
String key = pair.constructKey();
- List pairs = keyToPairsMap.computeIfAbsent(key, unused -> new ArrayList<>());
- pairs.add(pair);
- Collections.sort(pairs);
+
+ keyToPairsMap.computeIfAbsent(key, k1 -> new ArrayList<>()).add(pair);
+ keyToPairsMap.get(key).sort(DependencyNodeHopCountPair::compareTo);
return true;
}
@Override
- public boolean endVisit(DependencyNode node) {
- return true;
+ public boolean visitLeave(DependencyNode node) {
+ return parentsVisitor.visitLeave(node);
}
// added for TestDependencyMojo in place of getConflicts/containsConflicts
@@ -812,20 +839,12 @@ public Map upperBounds(List upperBoundsExcludes, String
Map r = new HashMap<>();
for (List pairs : keyToPairsMap.values()) {
DependencyNodeHopCountPair resolvedPair = pairs.get(0);
-
- // search for artifact with lowest hopCount
- for (DependencyNodeHopCountPair hopPair : pairs.subList(1, pairs.size())) {
- if (hopPair.getHopCount() < resolvedPair.getHopCount()) {
- resolvedPair = hopPair;
- }
- }
-
ArtifactVersion resolvedVersion = resolvedPair.extractArtifactVersion(false);
for (DependencyNodeHopCountPair pair : pairs) {
ArtifactVersion version = pair.extractArtifactVersion(true);
if (resolvedVersion.compareTo(version) < 0) {
- Artifact artifact = resolvedPair.node.getArtifact();
+ Artifact artifact = toArtifact(resolvedPair.node);
String key = toKey(artifact);
if (!key.equals(self)
&& (!r.containsKey(key)
@@ -850,30 +869,35 @@ public Map upperBounds(List upperBoundsExcludes, String
}
return r;
}
+
+ @Override
+ public DependencyNode getParent(DependencyNode node) {
+ return parentsVisitor.getParent(node);
+ }
}
private static class DependencyNodeHopCountPair implements Comparable {
-
- private DependencyNode node;
-
+ private final DependencyNode node;
private int hopCount;
+ private final ParentNodeProvider parentNodeProvider;
- private DependencyNodeHopCountPair(DependencyNode node) {
+ private DependencyNodeHopCountPair(DependencyNode node, ParentNodeProvider parentNodeProvider) {
+ this.parentNodeProvider = parentNodeProvider;
this.node = node;
countHops();
}
private void countHops() {
hopCount = 0;
- DependencyNode parent = node.getParent();
+ DependencyNode parent = parentNodeProvider.getParent(node);
while (parent != null) {
hopCount++;
- parent = parent.getParent();
+ parent = parentNodeProvider.getParent(parent);
}
}
private String constructKey() {
- Artifact artifact = node.getArtifact();
+ Artifact artifact = toArtifact(node);
return toKey(artifact);
}
@@ -882,11 +906,11 @@ public DependencyNode getNode() {
}
private ArtifactVersion extractArtifactVersion(boolean usePremanagedVersion) {
- if (usePremanagedVersion && node.getPremanagedVersion() != null) {
- return new DefaultArtifactVersion(node.getPremanagedVersion());
+ if (usePremanagedVersion && DependencyManagerUtils.getPremanagedVersion(node) != null) {
+ return new DefaultArtifactVersion(DependencyManagerUtils.getPremanagedVersion(node));
}
- Artifact artifact = node.getArtifact();
+ Artifact artifact = toArtifact(node);
String version = artifact.getBaseVersion();
if (version != null) {
return new DefaultArtifactVersion(version);
@@ -912,35 +936,75 @@ public int compareTo(DependencyNodeHopCountPair other) {
}
}
- private static String buildErrorMessage(List conflict) {
+ /**
+ * Provides the information about {@link DependencyNode} parent nodes
+ */
+ private interface ParentNodeProvider {
+ /**
+ * Returns the parent node of the given node
+ * @param node node to get the information for
+ * @return parent node or {@code null} is no information is known
+ */
+ DependencyNode getParent(DependencyNode node);
+ }
+
+ /**
+ * A {@link DependencyVisitor} building a map of parent nodes
+ */
+ private static class ParentsVisitor implements DependencyVisitor, ParentNodeProvider {
+
+ private final Map parents = new HashMap<>();
+ private final Stack parentStack = new Stack<>();
+
+ @Override
+ public DependencyNode getParent(DependencyNode node) {
+ return parents.get(node);
+ }
+
+ @Override
+ public boolean visitEnter(DependencyNode node) {
+ parents.put(node, parentStack.isEmpty() ? null : parentStack.peek());
+ parentStack.push(node);
+ return true;
+ }
+
+ @Override
+ public boolean visitLeave(DependencyNode node) {
+ parentStack.pop();
+ return true;
+ }
+ }
+
+ private String buildErrorMessage(List conflict) {
StringBuilder errorMessage = new StringBuilder();
- errorMessage.append("Require upper bound dependencies error for "
- + getFullArtifactName(conflict.get(0), false)
- + " paths to dependency are:"
- + System.lineSeparator());
+ errorMessage
+ .append("Require upper bound dependencies error for ")
+ .append(getFullArtifactName(conflict.get(0), false))
+ .append(" paths to dependency are:")
+ .append(System.lineSeparator());
if (conflict.size() > 0) {
errorMessage.append(buildTreeString(conflict.get(0)));
}
for (DependencyNode node : conflict.subList(1, conflict.size())) {
- errorMessage.append("and" + System.lineSeparator());
+ errorMessage.append("and").append(System.lineSeparator());
errorMessage.append(buildTreeString(node));
}
return errorMessage.toString();
}
- private static StringBuilder buildTreeString(DependencyNode node) {
+ private StringBuilder buildTreeString(DependencyNode node) {
List loc = new ArrayList<>();
DependencyNode currentNode = node;
while (currentNode != null) {
StringBuilder line = new StringBuilder(getFullArtifactName(currentNode, false));
- if (currentNode.getPremanagedVersion() != null) {
+ if (DependencyManagerUtils.getPremanagedVersion(currentNode) != null) {
line.append(" (managed) <-- ");
line.append(getFullArtifactName(currentNode, true));
}
loc.add(line.toString());
- currentNode = currentNode.getParent();
+ currentNode = upperBoundDepsVisitor.getParent(currentNode);
}
Collections.reverse(loc);
StringBuilder builder = new StringBuilder();
@@ -953,9 +1017,9 @@ private static StringBuilder buildTreeString(DependencyNode node) {
}
private static String getFullArtifactName(DependencyNode node, boolean usePremanaged) {
- Artifact artifact = node.getArtifact();
+ Artifact artifact = toArtifact(node);
- String version = node.getPremanagedVersion();
+ String version = DependencyManagerUtils.getPremanagedVersion(node);
if (!usePremanaged || version == null) {
version = artifact.getBaseVersion();
}
@@ -967,13 +1031,31 @@ private static String getFullArtifactName(DependencyNode node, boolean usePreman
}
String scope = artifact.getScope();
- if (scope != null) {
+ if (scope != null && !"compile".equals(scope)) {
result += " [" + scope + ']';
}
return result;
}
+ /**
+ * Converts {@link DependencyNode} to {@link Artifact}; in comparison
+ * to {@link RepositoryUtils#toArtifact(org.eclipse.aether.artifact.Artifact)}, this method
+ * assigns {@link Artifact#getScope()} and {@link Artifact#isOptional()} based on
+ * the dependency information from the node.
+ *
+ * @param node {@link DependencyNode} to convert to {@link Artifact}
+ * @return target artifact
+ */
+ private static Artifact toArtifact(DependencyNode node) {
+ Artifact artifact = RepositoryUtils.toArtifact(node.getArtifact());
+ Optional.ofNullable(node.getDependency()).ifPresent(dependency -> {
+ Optional.ofNullable(dependency.getScope()).ifPresent(artifact::setScope);
+ artifact.setOptional(dependency.isOptional());
+ });
+ return artifact;
+ }
+
private static String toKey(Artifact artifact) {
return artifact.getGroupId() + ":" + artifact.getArtifactId();
}