Skip to content

Commit 22fc5a4

Browse files
committed
Fix transitive artifact collection in m2e targets and make sharable
Currently m2e uses the usual maven resolver to find transitive dependencies but this works differently than what we want here. Instead we want all dependencies and their dependencies and then filter for the given scopes provided if any match.As this is a non trivial operation, it seems useful to make the code also sharable with Tycho so we ensure there is always the same result in Tycho as in m2e. This now creates a new MavenDependencyCollector that can be shared with Tycho that traverses the dependency tree in a way we need here and collects everything along the way. This also has the advantage, that if we choose to only collect direct dependencies, not the whole tree has to be traversed as before.
1 parent 9bab8e8 commit 22fc5a4

File tree

10 files changed

+264
-32
lines changed

10 files changed

+264
-32
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package org.eclipse.m2e.pde.target.tests;
2+
3+
import org.eclipse.pde.core.target.ITargetLocation;
4+
import org.eclipse.pde.core.target.TargetBundle;
5+
import org.junit.Test;
6+
7+
public class IncludedContentTest extends AbstractMavenTargetTest {
8+
@Test
9+
public void testIncludeProvided() throws Exception {
10+
ITargetLocation target = resolveMavenTarget(
11+
"""
12+
<location includeDependencyDepth="infinite" includeDependencyScopes="provided" includeSource="false" missingManifest="ignore" type="Maven">
13+
<dependencies>
14+
<dependency>
15+
<groupId>org.osgi</groupId>
16+
<artifactId>org.osgi.test.common</artifactId>
17+
<version>1.3.0</version>
18+
<type>jar</type>
19+
</dependency>
20+
</dependencies>
21+
</location>
22+
""");
23+
assertStatusOk(getTargetStatus(target));
24+
TargetBundle[] allBundles = target.getBundles();
25+
for (TargetBundle targetBundle : allBundles) {
26+
System.out.println(targetBundle);
27+
}
28+
}
29+
}

org.eclipse.m2e.pde.target/src/org/eclipse/m2e/pde/target/DependencyNodeGenerator.java

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,51 +18,41 @@
1818
import org.apache.maven.RepositoryUtils;
1919
import org.apache.maven.artifact.repository.ArtifactRepository;
2020
import org.eclipse.aether.RepositoryException;
21-
import org.eclipse.aether.RepositorySystem;
2221
import org.eclipse.aether.artifact.Artifact;
23-
import org.eclipse.aether.collection.CollectRequest;
2422
import org.eclipse.aether.graph.Dependency;
2523
import org.eclipse.aether.graph.DependencyNode;
26-
import org.eclipse.aether.resolution.DependencyRequest;
27-
import org.eclipse.aether.util.graph.visitor.PreorderNodeListGenerator;
2824
import org.eclipse.core.runtime.CoreException;
29-
import org.eclipse.core.runtime.IProgressMonitor;
3025
import org.eclipse.core.runtime.Status;
3126
import org.eclipse.m2e.core.embedder.ICallable;
32-
import org.eclipse.m2e.core.embedder.IMavenExecutionContext;
3327
import org.eclipse.m2e.core.internal.MavenPluginActivator;
28+
import org.eclipse.m2e.pde.target.shared.DependencyDepth;
29+
import org.eclipse.m2e.pde.target.shared.DependencyResult;
30+
import org.eclipse.m2e.pde.target.shared.MavenDependencyCollector;
3431

3532
final class DependencyNodeGenerator {
3633
private DependencyNodeGenerator() {
3734
}
3835

39-
static ICallable<PreorderNodeListGenerator> create(MavenTargetDependency root, Artifact artifact,
40-
DependencyDepth dependencyDepth, Collection<String> dependencyScopes, List<ArtifactRepository> repositories,
36+
static ICallable<DependencyResult> create(MavenTargetDependency root, Artifact artifact,
37+
DependencyDepth dependencyDepth, Collection<String> dependencyScopes,
38+
@SuppressWarnings("deprecation") List<ArtifactRepository> repositories,
4139
MavenTargetLocation parent) {
42-
43-
return (IMavenExecutionContext context, IProgressMonitor monitor) -> {
40+
return (context, monitor) -> {
4441
try {
45-
CollectRequest collectRequest = new CollectRequest();
46-
collectRequest.setRoot(new Dependency(artifact, null));
47-
collectRequest.setRepositories(RepositoryUtils.toRepos(repositories));
48-
49-
RepositorySystem repoSystem = MavenPluginActivator.getDefault().getRepositorySystem();
50-
DependencyNode node = repoSystem.collectDependencies(context.getRepositorySession(), collectRequest)
51-
.getRoot();
42+
MavenDependencyCollector collector = new MavenDependencyCollector(
43+
MavenPluginActivator.getDefault().getRepositorySystem(), context.getRepositorySession(),
44+
RepositoryUtils.toRepos(repositories), dependencyDepth, dependencyScopes);
45+
DependencyResult result = collector.collect(new Dependency(artifact, null));
46+
DependencyNode node = result.root();
5247
node.setData(MavenTargetLocation.DEPENDENCYNODE_PARENT, parent);
5348
node.setData(MavenTargetLocation.DEPENDENCYNODE_ROOT, root);
54-
DependencyRequest dependencyRequest = new DependencyRequest();
55-
dependencyRequest.setRoot(node);
56-
dependencyRequest.setFilter(new MavenTargetDependencyFilter(dependencyDepth, dependencyScopes));
57-
repoSystem.resolveDependencies(context.getRepositorySession(), dependencyRequest);
58-
PreorderNodeListGenerator nlg = new PreorderNodeListGenerator();
59-
node.accept(nlg);
60-
return nlg;
49+
return result;
6150
} catch (RepositoryException e) {
6251
throw new CoreException(Status.error("Resolving dependencies failed", e));
6352
} catch (RuntimeException e) {
6453
throw new CoreException(Status.error("Internal error", e));
6554
}
6655
};
6756
}
57+
6858
}

org.eclipse.m2e.pde.target/src/org/eclipse/m2e/pde/target/MavenTargetDependencyFilter.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@
2121
import java.util.Collection;
2222
import java.util.List;
2323

24+
import org.eclipse.aether.artifact.Artifact;
2425
import org.eclipse.aether.graph.Dependency;
2526
import org.eclipse.aether.graph.DependencyFilter;
2627
import org.eclipse.aether.graph.DependencyNode;
28+
import org.eclipse.m2e.pde.target.shared.DependencyDepth;
2729

2830
public class MavenTargetDependencyFilter implements DependencyFilter {
2931

@@ -43,7 +45,15 @@ public boolean accept(DependencyNode node, List<DependencyNode> parents) {
4345
// only for a valid extension...
4446
if (valid.equalsIgnoreCase(extension) && (dependencyDepth == DependencyDepth.INFINITE
4547
|| (dependencyDepth == DependencyDepth.DIRECT && parents.size() <= 1))) {
46-
return isValidScope(node.getDependency());
48+
Dependency dependency = node.getDependency();
49+
if (dependency == null) {
50+
Artifact artifact = node.getArtifact();
51+
if (artifact == null) {
52+
return false;
53+
}
54+
dependency = new Dependency(artifact, null);
55+
}
56+
return isValidScope(dependency);
4757
}
4858
}
4959
return false;

org.eclipse.m2e.pde.target/src/org/eclipse/m2e/pde/target/MavenTargetLocation.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@
5151
import org.eclipse.aether.resolution.VersionRangeRequest;
5252
import org.eclipse.aether.resolution.VersionRangeResolutionException;
5353
import org.eclipse.aether.resolution.VersionRangeResult;
54-
import org.eclipse.aether.util.graph.visitor.PreorderNodeListGenerator;
5554
import org.eclipse.aether.version.Version;
5655
import org.eclipse.core.runtime.CoreException;
5756
import org.eclipse.core.runtime.ILog;
@@ -69,6 +68,8 @@
6968
import org.eclipse.m2e.core.internal.MavenPluginActivator;
7069
import org.eclipse.m2e.core.project.IMavenProjectFacade;
7170
import org.eclipse.m2e.core.project.IMavenProjectRegistry;
71+
import org.eclipse.m2e.pde.target.shared.DependencyDepth;
72+
import org.eclipse.m2e.pde.target.shared.DependencyResult;
7273
import org.eclipse.pde.core.IModel;
7374
import org.eclipse.pde.core.target.ITargetDefinition;
7475
import org.eclipse.pde.core.target.TargetBundle;
@@ -254,15 +255,15 @@ private Artifact resolveDependency(MavenTargetDependency root, IMaven maven, Lis
254255
}
255256
SubMonitor split = subMonitor.split(20);
256257
if (depth == DependencyDepth.DIRECT || depth == DependencyDepth.INFINITE) {
257-
ICallable<PreorderNodeListGenerator> callable = DependencyNodeGenerator.create(root, artifact, depth,
258+
ICallable<DependencyResult> callable = DependencyNodeGenerator.create(root, artifact, depth,
258259
dependencyScopes, repositories, this);
259-
PreorderNodeListGenerator dependecies;
260+
DependencyResult dependecies;
260261
if (workspaceProject == null) {
261262
dependecies = maven.createExecutionContext().execute(callable, subMonitor);
262263
} else {
263264
dependecies = registry.execute(workspaceProject, callable, subMonitor);
264265
}
265-
List<Artifact> artifacts = dependecies.getArtifacts(true);
266+
List<Artifact> artifacts = dependecies.artifacts();
266267
split.setWorkRemaining(artifacts.size());
267268
for (Artifact a : artifacts) {
268269
if (a.getFile() == null) {
@@ -271,7 +272,7 @@ private Artifact resolveDependency(MavenTargetDependency root, IMaven maven, Lis
271272
}
272273
addBundleForArtifact(a, cacheManager, maven, targetBundles, split.split(1));
273274
}
274-
targetBundles.dependencyNodes.put(root, dependecies.getNodes());
275+
targetBundles.dependencyNodes.put(root, dependecies.nodes());
275276
} else {
276277
addBundleForArtifact(artifact, cacheManager, maven, targetBundles, split);
277278
}

org.eclipse.m2e.pde.target/src/org/eclipse/m2e/pde/target/MavenTargetLocationFactory.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
import org.eclipse.core.runtime.CoreException;
3232
import org.eclipse.core.runtime.Status;
33+
import org.eclipse.m2e.pde.target.shared.DependencyDepth;
3334
import org.eclipse.pde.core.target.ITargetLocationFactory;
3435
import org.eclipse.pde.internal.core.ifeature.IFeature;
3536
import org.w3c.dom.Document;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Christoph Läubrich and others
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License 2.0 which is available at
6+
* https://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*
10+
* Contributors:
11+
* Christoph Läubrich - initial API and implementation
12+
*******************************************************************************/
13+
package org.eclipse.m2e.pde.target.shared;
14+
15+
import java.util.List;
16+
17+
import org.eclipse.aether.graph.Dependency;
18+
import org.eclipse.aether.graph.DependencyNode;
19+
20+
public record ArtifactDescriptor(DependencyNode node, List<Dependency> dependencies,
21+
List<Dependency> managedDependencies) {
22+
23+
}

org.eclipse.m2e.pde.target/src/org/eclipse/m2e/pde/target/DependencyDepth.java renamed to org.eclipse.m2e.pde.target/src/org/eclipse/m2e/pde/target/shared/DependencyDepth.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
* Contributors:
1111
* Christoph Läubrich - initial API and implementation
1212
*******************************************************************************/
13-
package org.eclipse.m2e.pde.target;
13+
package org.eclipse.m2e.pde.target.shared;
1414

1515
public enum DependencyDepth {
1616
NONE, DIRECT, INFINITE;
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Christoph Läubrich and others
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License 2.0 which is available at
6+
* https://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*
10+
* Contributors:
11+
* Christoph Läubrich - initial API and implementation
12+
*******************************************************************************/
13+
package org.eclipse.m2e.pde.target.shared;
14+
15+
import java.util.List;
16+
17+
import org.eclipse.aether.artifact.Artifact;
18+
import org.eclipse.aether.graph.DependencyNode;
19+
20+
public record DependencyResult(List<Artifact> artifacts, DependencyNode root, List<DependencyNode> nodes) {
21+
22+
}
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Christoph Läubrich and others
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License 2.0 which is available at
6+
* https://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*
10+
* Contributors:
11+
* Christoph Läubrich - initial API and implementation
12+
*******************************************************************************/
13+
package org.eclipse.m2e.pde.target.shared;
14+
15+
import java.util.ArrayList;
16+
import java.util.Collection;
17+
import java.util.HashSet;
18+
import java.util.LinkedList;
19+
import java.util.List;
20+
import java.util.Queue;
21+
import java.util.Set;
22+
23+
import org.eclipse.aether.RepositoryException;
24+
import org.eclipse.aether.RepositorySystem;
25+
import org.eclipse.aether.RepositorySystemSession;
26+
import org.eclipse.aether.artifact.Artifact;
27+
import org.eclipse.aether.graph.DefaultDependencyNode;
28+
import org.eclipse.aether.graph.Dependency;
29+
import org.eclipse.aether.graph.DependencyNode;
30+
import org.eclipse.aether.repository.RemoteRepository;
31+
import org.eclipse.aether.resolution.ArtifactDescriptorRequest;
32+
import org.eclipse.aether.resolution.ArtifactDescriptorResult;
33+
import org.eclipse.aether.resolution.ArtifactRequest;
34+
import org.eclipse.aether.resolution.ArtifactResult;
35+
36+
/**
37+
* Collector to collect (and filter) all transitive dependencies of a maven
38+
* target location
39+
*/
40+
public class MavenDependencyCollector {
41+
42+
private static final Set<String> VALID_EXTENSIONS = Set.of("jar", "pom");
43+
44+
private RepositorySystem repoSystem;
45+
private RepositorySystemSession repositorySession;
46+
private List<RemoteRepository> repositories;
47+
private DependencyDepth depth;
48+
private Collection<String> dependencyScopes;
49+
50+
public MavenDependencyCollector(RepositorySystem repoSystem, RepositorySystemSession repositorySession,
51+
List<RemoteRepository> repositories, DependencyDepth depth, Collection<String> dependencyScopes) {
52+
this.repoSystem = repoSystem;
53+
this.repositorySession = repositorySession;
54+
this.repositories = repositories;
55+
this.depth = depth;
56+
this.dependencyScopes = dependencyScopes;
57+
}
58+
59+
public DependencyResult collect(Dependency root) throws RepositoryException {
60+
if (!isValidDependency(root)) {
61+
throw new RepositoryException(
62+
"Invalid root dependency: " + root + " allowed extensions are " + VALID_EXTENSIONS);
63+
}
64+
List<Artifact> artifacts = new ArrayList<>();
65+
List<DependencyNode> nodes = new ArrayList<>();
66+
ArtifactDescriptor rootDescriptor = readArtifactDescriptor(root, null, artifacts, nodes);
67+
if (depth == DependencyDepth.NONE) {
68+
return new DependencyResult(artifacts, rootDescriptor.node(), nodes);
69+
}
70+
if (depth == DependencyDepth.DIRECT) {
71+
for (Dependency dependency : rootDescriptor.dependencies()) {
72+
readArtifactDescriptor(dependency, rootDescriptor.node(), artifacts, nodes);
73+
}
74+
return new DependencyResult(artifacts, rootDescriptor.node(), nodes);
75+
}
76+
// Add all dependencies with BFS method
77+
Set<String> collected = new HashSet<>();
78+
collected.add(getId(rootDescriptor.node().getDependency()));
79+
Queue<ArtifactDescriptor> queue = new LinkedList<>();
80+
queue.add(rootDescriptor);
81+
while (!queue.isEmpty()) {
82+
ArtifactDescriptor current = queue.poll();
83+
for (Dependency dependency : current.dependencies()) {
84+
if (isValidDependency(dependency) && collected.add(getId(dependency))) {
85+
ArtifactDescriptor dependencyDescriptor = readArtifactDescriptor(dependency, current.node(),
86+
artifacts, nodes);
87+
if (dependencyDescriptor != null) {
88+
queue.add(dependencyDescriptor);
89+
}
90+
}
91+
}
92+
}
93+
return new DependencyResult(artifacts, rootDescriptor.node(), nodes);
94+
}
95+
96+
private String getId(Dependency dependency) {
97+
Artifact artifact = dependency.getArtifact();
98+
// This does not include the version so we always ever only collect one version
99+
// of an (transitive) artifact
100+
return artifact.getGroupId() + ":" + artifact.getArtifactId() + ":"
101+
+ artifact.getClassifier();
102+
}
103+
104+
/**
105+
* This method reads the artifact descriptor and resolves the artifact.
106+
*
107+
* @param artifact the artifact to read its descriptor
108+
* @param artifacts
109+
* @param nodes
110+
* @return the resolved artifact and the list of (managed) dependencies
111+
* @throws RepositoryException
112+
*/
113+
private ArtifactDescriptor readArtifactDescriptor(Dependency dependency, DependencyNode parent,
114+
Collection<Artifact> artifacts,
115+
List<DependencyNode> nodes) throws RepositoryException {
116+
if (isValidScope(dependency) && isValidDependency(dependency)) {
117+
ArtifactDescriptorRequest descriptorRequest = new ArtifactDescriptorRequest();
118+
descriptorRequest.setArtifact(dependency.getArtifact());
119+
descriptorRequest.setRepositories(repositories);
120+
ArtifactDescriptorResult result = repoSystem.readArtifactDescriptor(repositorySession, descriptorRequest);
121+
ArtifactRequest artifactRequest = new ArtifactRequest();
122+
artifactRequest.setArtifact(result.getArtifact());
123+
artifactRequest.setRepositories(repositories);
124+
ArtifactResult artifactResult = repoSystem.resolveArtifact(repositorySession, artifactRequest);
125+
Artifact resolved = artifactResult.getArtifact();
126+
artifacts.add(resolved);
127+
DefaultDependencyNode dependencyNode = new DefaultDependencyNode(
128+
new Dependency(resolved, dependency.getScope()));
129+
nodes.add(dependencyNode);
130+
if (parent != null) {
131+
parent.getChildren().add(dependencyNode);
132+
}
133+
return new ArtifactDescriptor(dependencyNode, result.getDependencies(), result.getManagedDependencies());
134+
}
135+
return null;
136+
}
137+
138+
private boolean isValidDependency(Dependency dependency) {
139+
if (dependency.isOptional()) {
140+
// optional in maven means do not include in transitive dependency chains see
141+
// https://maven.apache.org/guides/introduction/introduction-to-optional-and-excludes-dependencies.html
142+
return false;
143+
}
144+
return VALID_EXTENSIONS.contains(dependency.getArtifact().getExtension());
145+
}
146+
147+
private boolean isValidScope(Dependency dependency) {
148+
String scope = dependency.getScope();
149+
if (scope == null || scope.isEmpty()) {
150+
return true;
151+
152+
}
153+
return dependencyScopes.contains(scope);
154+
}
155+
156+
}

0 commit comments

Comments
 (0)