diff --git a/tycho-core/src/main/java/org/eclipse/tycho/p2/tools/P2DependencyTreeGenerator.java b/tycho-core/src/main/java/org/eclipse/tycho/p2/tools/P2DependencyTreeGenerator.java
new file mode 100644
index 0000000000..814aadf7c5
--- /dev/null
+++ b/tycho-core/src/main/java/org/eclipse/tycho/p2/tools/P2DependencyTreeGenerator.java
@@ -0,0 +1,209 @@
+/*******************************************************************************
+ * Copyright (c) 2024 Patrick Ziegler and others.
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Patrick Ziegler - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.tycho.p2.tools;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.inject.Inject;
+
+import org.apache.maven.plugin.LegacySupport;
+import org.apache.maven.project.MavenProject;
+import org.codehaus.plexus.component.annotations.Component;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.equinox.p2.metadata.IInstallableUnit;
+import org.eclipse.equinox.p2.metadata.IRequirement;
+import org.eclipse.tycho.ArtifactDescriptor;
+import org.eclipse.tycho.core.TychoProject;
+import org.eclipse.tycho.core.TychoProjectManager;
+import org.eclipse.tycho.core.osgitools.DefaultReactorProject;
+import org.eclipse.tycho.p2maven.InstallableUnitGenerator;
+
+/**
+ * Utility class for converting a flat dependency into a dependency tree. The tree is structured in
+ * such a way that an IU {@code a} is a child of another IU {@code b}, if and only if {@code a} is
+ * required by {@code b}. If {@code b} is required by multiple IUs, the first one is selected.
+ * Used by e.g. the dependency-tree Mojo, in order to mimic the behavior of the native Maven
+ * dependency-tree Mojo.
+ * This class is intended to be use as a Plexus component, so that all required fields are
+ * automatically initialized using DI.
+ */
+@Component(role = P2DependencyTreeGenerator.class)
+public final class P2DependencyTreeGenerator {
+ private final InstallableUnitGenerator generator;
+ private final TychoProjectManager projectManager;
+ private final LegacySupport legacySupport;
+
+ @Inject
+ public P2DependencyTreeGenerator(InstallableUnitGenerator generator, TychoProjectManager projectManager,
+ LegacySupport legacySupport) {
+ this.generator = generator;
+ this.projectManager = projectManager;
+ this.legacySupport = legacySupport;
+ }
+
+ /**
+ * Calculates and returns the dependency tree of the given Maven project. The list that is
+ * returned by this method corresponds to the IUs which are directly required by the given
+ * project.
+ *
+ * @param project
+ * One of the Maven projects of the current reactor build. If this project is not a
+ * Tycho project (e.g. the parent pom), an empty list is returned.
+ * @param unmapped
+ * A set containing all IUs which could not be added to the dependency tree.Meaning
+ * that those units are required by the project but not by any of its IUs. Must be
+ * mutable.
+ * @return as described.
+ * @throws CoreException
+ * if anything goes wrong
+ */
+ public List buildDependencyTree(MavenProject project, Set unmapped)
+ throws CoreException {
+ //TODO maybe we can compute a org.apache.maven.shared.dependency.graph.DependencyNode and reuse org.apache.maven.plugins.dependency.tree.TreeMojo wich has a getSerializingDependencyNodeVisitor
+ Optional tychoProject = projectManager.getTychoProject(project);
+ if (tychoProject.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ List artifacts = tychoProject.get() //
+ .getDependencyArtifacts(DefaultReactorProject.adapt(project)) //
+ .getArtifacts();
+ Set units = artifacts.stream() //
+ .flatMap(d -> d.getInstallableUnits().stream()) //
+ .collect(Collectors.toCollection(HashSet::new));
+ List initial = List
+ .copyOf(generator.getInstallableUnits(project, legacySupport.getSession(), false));
+ units.removeAll(initial);
+
+ return Collections.unmodifiableList(DependencyTreeNode.create(initial, units, unmapped));
+ }
+
+ /**
+ * This class represents a single IU within the dependency tree and holds. Two nodes in this
+ * tree are connected (as in parent and child), if and only if the child IU is required by the
+ * parent. Each IU is unique and must only appear once in the dependency tree.
+ */
+ public static class DependencyTreeNode {
+ private static final Comparator COMPARATOR = Comparator.comparing(IInstallableUnit::getId,
+ String.CASE_INSENSITIVE_ORDER);
+ private final IInstallableUnit iu;
+ private final IRequirement satisfies;
+ private final List children = new ArrayList<>();
+
+ private DependencyTreeNode(IInstallableUnit iu, IRequirement satisfies) {
+ this.iu = iu;
+ this.satisfies = satisfies;
+ }
+
+ public IInstallableUnit getInstallableUnit() {
+ return iu;
+ }
+
+ public IRequirement getRequirement() {
+ return satisfies;
+ }
+
+ public List getChildren() {
+ return Collections.unmodifiableList(children);
+ }
+
+ /**
+ * Returns the IU (if present) that is contained by this node. Primarily used to make
+ * debugging easier.
+ */
+ @Override
+ public String toString() {
+ return Objects.toString(iu);
+ }
+
+ /**
+ * Create the dependency tree based on the {@code initial} IUs. A tree node is created for
+ * each IU of {@code initial}. The children of a node correspond to all IUs that are
+ * (directly) required by the parent IU. Each IU in {@code units} only appears once, even if
+ * it required by multiple IUs.
+ *
+ * @param initial
+ * The "direct" IUs referenced by a given artifact.
+ * @param units
+ * All IUs that are required by a given artifact, excluding {@code initial}.
+ * @param unmapped
+ * A subset of {@code units}, which are not contained in the dependency tree.
+ * Meaning that those IUs are not necessarily required to satisfy the
+ * dependencies of an artifact.
+ * @return A list of dependency tree models. Each model in this list matches an IU of
+ * {@code initial}.
+ */
+ private static List create(List initial, Set units,
+ Set unmapped) {
+ List rootNodes = new ArrayList<>();
+ for (int i = 0; i < initial.size(); ++i) {
+ DependencyTreeNode rootNode = new DependencyTreeNode(initial.get(i), null);
+ create(rootNode, units);
+ rootNodes.add(rootNode);
+ }
+ unmapped.addAll(units);
+ return rootNodes;
+ }
+
+ /**
+ * Internal helper method which recursively goes through IUs that are required by the IU
+ * held by {@code node}. For each IU that satisfies this requirement a new
+ * {@link DependencyTreeNode} is created and added as a child to {@link node}. If such an IU
+ * is found, it is removed from {@code units}, meaning that each IU can only show up once in
+ * the dependency tree. The children of each node are sorted lexicographically according to
+ * {@link #COMPARATOR}.
+ *
+ * @param node
+ * The (intermediate) head of the dependency tree.
+ * @param units
+ * A set of all IUs that are associated with the currently handled project.
+ */
+ private static void create(DependencyTreeNode node, Set units) {
+ List collected = new ArrayList<>();
+ Map requirementsMap = new HashMap<>();
+ IInstallableUnit unit = node.getInstallableUnit();
+ //
+ Stream.concat(unit.getRequirements().stream(), unit.getMetaRequirements().stream()).forEach(requirement -> {
+ for (Iterator iterator = units.iterator(); iterator.hasNext();) {
+ IInstallableUnit other = iterator.next();
+ if (other.satisfies(requirement)) {
+ collected.add(other);
+ requirementsMap.put(other, requirement);
+ iterator.remove();
+ }
+ }
+ });
+ //
+ Collections.sort(collected, COMPARATOR);
+ for (IInstallableUnit iu : collected) {
+ IRequirement satisfies = requirementsMap.get(iu);
+ DependencyTreeNode childNode = new DependencyTreeNode(iu, satisfies);
+ node.children.add(childNode);
+ create(childNode, units);
+ }
+ }
+ }
+}
diff --git a/tycho-p2-plugin/src/main/java/org/eclipse/tycho/plugins/p2/DependenciesTreeMojo.java b/tycho-p2-plugin/src/main/java/org/eclipse/tycho/plugins/p2/DependenciesTreeMojo.java
index d526a4c69a..6109353856 100644
--- a/tycho-p2-plugin/src/main/java/org/eclipse/tycho/plugins/p2/DependenciesTreeMojo.java
+++ b/tycho-p2-plugin/src/main/java/org/eclipse/tycho/plugins/p2/DependenciesTreeMojo.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2022 Christoph Läubrich and others.
+ * Copyright (c) 2022, 2024 Christoph Läubrich and others.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
@@ -10,22 +10,15 @@
package org.eclipse.tycho.plugins.p2;
import java.util.AbstractMap.SimpleEntry;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
import java.util.HashSet;
-import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
-import java.util.stream.Stream;
import org.apache.maven.plugin.AbstractMojo;
-import org.apache.maven.plugin.LegacySupport;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
@@ -41,74 +34,65 @@
import org.eclipse.tycho.core.TychoProject;
import org.eclipse.tycho.core.TychoProjectManager;
import org.eclipse.tycho.core.osgitools.DefaultReactorProject;
-import org.eclipse.tycho.p2maven.InstallableUnitGenerator;
+import org.eclipse.tycho.p2.tools.P2DependencyTreeGenerator;
+import org.eclipse.tycho.p2.tools.P2DependencyTreeGenerator.DependencyTreeNode;
/**
* Similar to dependency:tree outputs a tree of P2 dependencies.
*/
@Mojo(name = "dependency-tree", requiresProject = true, threadSafe = true, requiresDependencyCollection = ResolutionScope.TEST)
public class DependenciesTreeMojo extends AbstractMojo {
-
- private static final Comparator COMPARATOR = Comparator.comparing(IInstallableUnit::getId,
- String.CASE_INSENSITIVE_ORDER);
@Parameter(property = "project")
private MavenProject project;
@Component
- private InstallableUnitGenerator generator;
-
- @Component
- private LegacySupport legacySupport;
+ private P2DependencyTreeGenerator generator;
@Component
private TychoProjectManager projectManager;
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
- //TODO maybe we can compute a org.apache.maven.shared.dependency.graph.DependencyNode and reuse org.apache.maven.plugins.dependency.tree.TreeMojo wich has a getSerializingDependencyNodeVisitor
-
Optional tychoProject = projectManager.getTychoProject(project);
if (tychoProject.isEmpty()) {
return;
}
- Set written = new HashSet();
- written.add(project.getId());
getLog().info(project.getId());
- List artifacts = tychoProject.get()
- .getDependencyArtifacts(DefaultReactorProject.adapt(project)).getArtifacts();
- Map> projectMap = artifacts.stream()
- .filter(a -> a.getMavenProject() != null).flatMap(a -> {
- return a.getInstallableUnits().stream().map(iu -> new SimpleEntry<>(iu, a.getMavenProject()));
- })
+ List artifacts = tychoProject.get() //
+ .getDependencyArtifacts(DefaultReactorProject.adapt(project)) //
+ .getArtifacts();
+ Map> projectMap = artifacts.stream() //
+ .filter(a -> a.getMavenProject() != null) //
+ .flatMap(a -> a.getInstallableUnits().stream().map(iu -> new SimpleEntry<>(iu, a.getMavenProject()))) //
.collect(Collectors.groupingBy(Entry::getKey, Collectors.mapping(Entry::getValue, Collectors.toSet())));
- Set units = artifacts.stream().flatMap(d -> d.getInstallableUnits().stream())
- .collect(Collectors.toCollection(HashSet::new));
- List initial;
+
+ Set unmapped = new HashSet<>();
+ List dependencyTree;
try {
- initial = new ArrayList(
- generator.getInstallableUnits(project, legacySupport.getSession(), false));
+ dependencyTree = generator.buildDependencyTree(project, unmapped);
} catch (CoreException e) {
throw new MojoFailureException(e);
}
- units.removeAll(initial);
- int size = initial.size();
- for (int i = 0; i < size; i++) {
- IInstallableUnit unit = initial.get(i);
- printUnit(unit, null, units, projectMap, 0, i == size - 1);
+
+ for (DependencyTreeNode rootNode : dependencyTree) {
+ printUnit(rootNode, projectMap, 0);
}
- if (!units.isEmpty()) {
+
+ if (!unmapped.isEmpty()) {
getLog().info("Units that cannot be matched to any requirement:");
- for (IInstallableUnit unit : units) {
+ for (IInstallableUnit unit : unmapped) {
getLog().info(unit.toString());
}
}
}
- private void printUnit(IInstallableUnit unit, IRequirement satisfies, Set units,
- Map> projectMap, int indent, boolean last) {
+ private void printUnit(DependencyTreeNode model, Map> projectMap,
+ int indent) {
+ IInstallableUnit unit = model.getInstallableUnit();
+ IRequirement satisfies = model.getRequirement();
StringBuffer line = new StringBuffer();
for (int i = 0; i < indent; i++) {
line.append(" ");
@@ -129,24 +113,8 @@ private void printUnit(IInstallableUnit unit, IRequirement satisfies, Set collected = new ArrayList();
- Map requirementsMap = new HashMap();
- Stream.concat(unit.getRequirements().stream(), unit.getMetaRequirements().stream()).forEach(requirement -> {
- for (Iterator iterator = units.iterator(); iterator.hasNext();) {
- IInstallableUnit other = iterator.next();
- if (other.satisfies(requirement)) {
- collected.add(other);
- requirementsMap.put(other, requirement);
- iterator.remove();
- }
- }
- });
- Collections.sort(collected, COMPARATOR);
- int size = collected.size();
- for (int i = 0; i < size; i++) {
- IInstallableUnit iu = collected.get(i);
- printUnit(iu, requirementsMap.get(iu), units, projectMap, indent + 1, i == size - 1);
+ for (DependencyTreeNode child : model.getChildren()) {
+ printUnit(child, projectMap, indent + 1);
}
}
-
}