diff --git a/org.eclipse.m2e.pde.target.tests/src/org/eclipse/m2e/pde/target/tests/DependencyExclusionTest.java b/org.eclipse.m2e.pde.target.tests/src/org/eclipse/m2e/pde/target/tests/DependencyExclusionTest.java index 28613dddb..4810bfafc 100644 --- a/org.eclipse.m2e.pde.target.tests/src/org/eclipse/m2e/pde/target/tests/DependencyExclusionTest.java +++ b/org.eclipse.m2e.pde.target.tests/src/org/eclipse/m2e/pde/target/tests/DependencyExclusionTest.java @@ -153,6 +153,74 @@ public void testExclusionOfDifferentVersions() throws Exception { assertTargetBundles(target, includeSource ? withSourceBundles(expectedBundles) : expectedBundles); } + @Test + public void testMavenExclusionSyntaxOnDependency() throws Exception { + ITargetLocation target = resolveMavenTarget(String.format( + """ + + + + org.junit.platform + junit-platform-commons + 1.9.3 + jar + + + org.apiguardian + apiguardian-api + + + + + + """, + includeSource)); + assertStatusOk(target.getStatus()); + assertArrayEquals(EMPTY, target.getFeatures()); + List expectedBundles = List.of(junitPlatformCommons("1.9.3")); + assertTargetBundles(target, includeSource ? withSourceBundles(expectedBundles) : expectedBundles); + } + + @Test + public void testMavenExclusionSyntaxIsPerDependency() throws Exception { + ITargetLocation target = resolveMavenTarget(String.format( + """ + + + + org.junit.jupiter + junit-jupiter-api + 5.9.3 + jar + + + org.apiguardian + apiguardian-api + + + + + org.junit.platform + junit-platform-commons + 1.9.3 + jar + + + + """, + includeSource)); + assertStatusOk(target.getStatus()); + assertArrayEquals(EMPTY, target.getFeatures()); + // apiguardian excluded from junit-jupiter-api's transitive graph, + // but junit-platform-commons (without exclusion) still pulls it in + List expectedBundles = List.of(// + junitJupiterAPI(), // + junitPlatformCommons("1.9.3"), // + apiGuardian("1.1.2"), // + opentest4j()); + assertTargetBundles(target, includeSource ? withSourceBundles(expectedBundles) : expectedBundles); + } + private static ExpectedBundle junitPlatformCommons(String version) { return originalOSGiBundle("junit-platform-commons", version, "org.junit.platform:junit-platform-commons"); } diff --git a/org.eclipse.m2e.pde.target/src/org/eclipse/m2e/pde/target/MavenTargetDependency.java b/org.eclipse.m2e.pde.target/src/org/eclipse/m2e/pde/target/MavenTargetDependency.java index c55252d07..c4cae6881 100644 --- a/org.eclipse.m2e.pde.target/src/org/eclipse/m2e/pde/target/MavenTargetDependency.java +++ b/org.eclipse.m2e.pde.target/src/org/eclipse/m2e/pde/target/MavenTargetDependency.java @@ -12,10 +12,12 @@ *******************************************************************************/ package org.eclipse.m2e.pde.target; +import java.util.ArrayList; import java.util.List; import java.util.Objects; import org.apache.maven.model.Dependency; +import org.apache.maven.model.Exclusion; import org.eclipse.aether.graph.DependencyNode; public final class MavenTargetDependency extends Dependency { @@ -66,7 +68,13 @@ public void bind(MavenTargetLocation mavenTargetLocation) { } public MavenTargetDependency copy() { - return new MavenTargetDependency(getGroupId(), getArtifactId(), getVersion(), getType(), getClassifier()); + MavenTargetDependency copy = new MavenTargetDependency(getGroupId(), getArtifactId(), getVersion(), getType(), + getClassifier()); + List exclusions = getExclusions(); + if (!exclusions.isEmpty()) { + copy.setExclusions(new ArrayList<>(exclusions)); + } + return copy; } public boolean matches(Dependency other) { diff --git a/org.eclipse.m2e.pde.target/src/org/eclipse/m2e/pde/target/MavenTargetLocation.java b/org.eclipse.m2e.pde.target/src/org/eclipse/m2e/pde/target/MavenTargetLocation.java index 37b0ec788..ee2f84677 100644 --- a/org.eclipse.m2e.pde.target/src/org/eclipse/m2e/pde/target/MavenTargetLocation.java +++ b/org.eclipse.m2e.pde.target/src/org/eclipse/m2e/pde/target/MavenTargetLocation.java @@ -48,6 +48,7 @@ import org.eclipse.aether.artifact.Artifact; import org.eclipse.aether.artifact.DefaultArtifact; import org.eclipse.aether.graph.DependencyNode; +import org.eclipse.aether.graph.Exclusion; import org.eclipse.aether.repository.RemoteRepository; import org.eclipse.aether.resolution.VersionRangeRequest; import org.eclipse.aether.resolution.VersionRangeResolutionException; @@ -97,6 +98,8 @@ public class MavenTargetLocation extends AbstractBundleContainer { public static final String ELEMENT_GROUP_ID = "groupId"; public static final String ELEMENT_INSTRUCTIONS = "instructions"; public static final String ELEMENT_EXCLUDED = "exclude"; + public static final String ELEMENT_EXCLUSIONS = "exclusions"; + public static final String ELEMENT_EXCLUSION = "exclusion"; public static final String ELEMENT_DEPENDENCY = "dependency"; public static final String ELEMENT_DEPENDENCIES = "dependencies"; public static final String ELEMENT_REPOSITORY = "repository"; @@ -251,8 +254,10 @@ private Artifact resolveDependency(MavenTargetDependency root, IMaven maven, Lis root.getVersion(), root.getType(), root.getClassifier(), repositories, subMonitor.split(80))); } if (artifact != null) { + List exclusions = root.getExclusions().stream() + .map(ex -> new Exclusion(ex.getGroupId(), ex.getArtifactId(), null, null)).toList(); MavenRootDependency dependency = new MavenRootDependency(artifact.getGroupId(), artifact.getArtifactId(), - artifact.getVersion(), artifact.getClassifier(), artifact.getExtension()); + artifact.getVersion(), artifact.getClassifier(), artifact.getExtension(), exclusions); DependencyDepth depth = MavenDependencyCollector.getEffectiveDepth(dependency, dependencyDepth); SubMonitor split = subMonitor.split(20); @@ -600,6 +605,17 @@ public String serialize() { element(xml, ELEMENT_VERSION, dependency.getVersion()); element(xml, ELEMENT_TYPE, dependency.getType()); element(xml, ELEMENT_CLASSIFIER, dependency.getClassifier()); + List depExclusions = dependency.getExclusions(); + if (!depExclusions.isEmpty()) { + xml.append("<" + ELEMENT_EXCLUSIONS + ">"); + depExclusions.forEach(ex -> { + xml.append("<" + ELEMENT_EXCLUSION + ">"); + element(xml, ELEMENT_GROUP_ID, ex.getGroupId()); + element(xml, ELEMENT_ARTIFACT_ID, ex.getArtifactId()); + xml.append(""); + }); + xml.append(""); + } xml.append(""); }); xml.append(""); diff --git a/org.eclipse.m2e.pde.target/src/org/eclipse/m2e/pde/target/MavenTargetLocationFactory.java b/org.eclipse.m2e.pde.target/src/org/eclipse/m2e/pde/target/MavenTargetLocationFactory.java index e31e02a5a..f7e128a25 100644 --- a/org.eclipse.m2e.pde.target/src/org/eclipse/m2e/pde/target/MavenTargetLocationFactory.java +++ b/org.eclipse.m2e.pde.target/src/org/eclipse/m2e/pde/target/MavenTargetLocationFactory.java @@ -15,6 +15,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashSet; import java.util.List; @@ -130,7 +131,19 @@ private static MavenTargetDependency parseDependency(Element element) { String version = getText(MavenTargetLocation.ELEMENT_VERSION, element); String artifactType = getText(MavenTargetLocation.ELEMENT_TYPE, element); String classifier = getText(MavenTargetLocation.ELEMENT_CLASSIFIER, element); - return new MavenTargetDependency(groupId, artifactId, version, artifactType, classifier); + MavenTargetDependency dependency = new MavenTargetDependency(groupId, artifactId, version, artifactType, + classifier); + List exclusions = descendants(element, + MavenTargetLocation.ELEMENT_EXCLUSION).map(exclusionElement -> { + org.apache.maven.model.Exclusion exclusion = new org.apache.maven.model.Exclusion(); + exclusion.setGroupId(getText(MavenTargetLocation.ELEMENT_GROUP_ID, exclusionElement)); + exclusion.setArtifactId(getText(MavenTargetLocation.ELEMENT_ARTIFACT_ID, exclusionElement)); + return exclusion; + }).toList(); + if (!exclusions.isEmpty()) { + dependency.setExclusions(new ArrayList<>(exclusions)); + } + return dependency; } private static String getText(String tagName, Element location) { diff --git a/org.eclipse.m2e.pde.target/src/org/eclipse/m2e/pde/target/shared/MavenDependencyCollector.java b/org.eclipse.m2e.pde.target/src/org/eclipse/m2e/pde/target/shared/MavenDependencyCollector.java index ccf5846a2..af69512b0 100644 --- a/org.eclipse.m2e.pde.target/src/org/eclipse/m2e/pde/target/shared/MavenDependencyCollector.java +++ b/org.eclipse.m2e.pde.target/src/org/eclipse/m2e/pde/target/shared/MavenDependencyCollector.java @@ -30,6 +30,7 @@ import org.eclipse.aether.graph.DefaultDependencyNode; import org.eclipse.aether.graph.Dependency; import org.eclipse.aether.graph.DependencyNode; +import org.eclipse.aether.graph.Exclusion; import org.eclipse.aether.repository.RemoteRepository; import org.eclipse.aether.repository.RepositoryPolicy; import org.eclipse.aether.resolution.ArtifactDescriptorRequest; @@ -91,6 +92,7 @@ public DependencyResult collect(MavenRootDependency root) throws RepositoryExcep "Invalid root dependency: " + root + " allowed types are " + VALID_EXTENSIONS); } DependencyDepth depth = getEffectiveDepth(root, dependencyDepth); + List exclusions = root.exclusions(); List artifacts = new ArrayList<>(); List nodes = new ArrayList<>(); ArtifactDescriptor rootDescriptor = readArtifactDescriptor(new Dependency( @@ -101,7 +103,9 @@ public DependencyResult collect(MavenRootDependency root) throws RepositoryExcep } if (depth == DependencyDepth.DIRECT) { for (Dependency dependency : rootDescriptor.dependencies()) { - readArtifactDescriptor(dependency, rootDescriptor.node(), artifacts, nodes); + if (!isExcluded(dependency, exclusions)) { + readArtifactDescriptor(dependency, rootDescriptor.node(), artifacts, nodes); + } } return new DependencyResult(depth, artifacts, rootDescriptor.node(), nodes); } @@ -113,7 +117,7 @@ public DependencyResult collect(MavenRootDependency root) throws RepositoryExcep while (!queue.isEmpty()) { ArtifactDescriptor current = queue.poll(); for (Dependency dependency : current.dependencies()) { - if (isValidDependency(dependency) && isValidScope(dependency)) { + if (isValidDependency(dependency) && isValidScope(dependency) && !isExcluded(dependency, exclusions)) { if (isVersionRanged(dependency)) { ArtifactDescriptor dependencyDescriptor = resolveHighestVersion(dependency, current.node(), artifacts, nodes); @@ -134,6 +138,25 @@ public DependencyResult collect(MavenRootDependency root) throws RepositoryExcep return new DependencyResult(depth, artifacts, rootDescriptor.node(), nodes); } + private static boolean isExcluded(Dependency dependency, List exclusions) { + if (exclusions.isEmpty()) { + return false; + } + String groupId = dependency.getArtifact().getGroupId(); + String artifactId = dependency.getArtifact().getArtifactId(); + for (Exclusion exclusion : exclusions) { + if (matchesPattern(exclusion.getGroupId(), groupId) + && matchesPattern(exclusion.getArtifactId(), artifactId)) { + return true; + } + } + return false; + } + + private static boolean matchesPattern(String pattern, String value) { + return "*".equals(pattern) || pattern.equals(value); + } + private ArtifactDescriptor resolveHighestVersion(Dependency dependency, DependencyNode parent, Collection artifacts, List nodes) throws VersionRangeResolutionException { diff --git a/org.eclipse.m2e.pde.target/src/org/eclipse/m2e/pde/target/shared/MavenRootDependency.java b/org.eclipse.m2e.pde.target/src/org/eclipse/m2e/pde/target/shared/MavenRootDependency.java index 1575b31c2..ff63c4125 100644 --- a/org.eclipse.m2e.pde.target/src/org/eclipse/m2e/pde/target/shared/MavenRootDependency.java +++ b/org.eclipse.m2e.pde.target/src/org/eclipse/m2e/pde/target/shared/MavenRootDependency.java @@ -12,13 +12,22 @@ *******************************************************************************/ package org.eclipse.m2e.pde.target.shared; -public record MavenRootDependency(String groupId, String artifactId, String version, String classifier, String type) { +import java.util.List; - public String getType() { - if (type == null || type.isBlank()) { - return "jar"; - } - return type; - } +import org.eclipse.aether.graph.Exclusion; + +public record MavenRootDependency(String groupId, String artifactId, String version, String classifier, String type, + List exclusions) { + + public MavenRootDependency(String groupId, String artifactId, String version, String classifier, String type) { + this(groupId, artifactId, version, classifier, type, List.of()); + } + + public String getType() { + if (type == null || type.isBlank()) { + return "jar"; + } + return type; + } }