diff --git a/tycho-core/pom.xml b/tycho-core/pom.xml
index 512fad0dcb..ea6f9bae5a 100644
--- a/tycho-core/pom.xml
+++ b/tycho-core/pom.xml
@@ -312,7 +312,18 @@
org.eclipse.jdt.core
3.33.0
-
+
+ org.eclipse.pde
+ org.eclipse.pde.core
+ 3.16.100
+ test
+
+
+ *
+ *
+
+
+
\ No newline at end of file
diff --git a/tycho-core/src/test/java/org/eclipse/m2e/pde/target/tests/AbstractMavenTargetTest.java b/tycho-core/src/test/java/org/eclipse/m2e/pde/target/tests/AbstractMavenTargetTest.java
new file mode 100644
index 0000000000..cb260a500f
--- /dev/null
+++ b/tycho-core/src/test/java/org/eclipse/m2e/pde/target/tests/AbstractMavenTargetTest.java
@@ -0,0 +1,225 @@
+/*******************************************************************************
+ * Copyright (c) 2023, 2023 Hannes Wellmann and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Hannes Wellmann - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.m2e.pde.target.tests;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.ServiceLoader;
+import java.util.ServiceLoader.Provider;
+import java.util.Set;
+import java.util.function.BiPredicate;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+import java.util.stream.Collectors;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.URIUtil;
+import org.eclipse.equinox.frameworkadmin.BundleInfo;
+import org.eclipse.m2e.pde.target.tests.spi.TargetLocationLoader;
+import org.eclipse.pde.core.target.ITargetLocation;
+import org.eclipse.pde.core.target.NameVersionDescriptor;
+import org.eclipse.pde.core.target.TargetBundle;
+import org.eclipse.pde.core.target.TargetFeature;
+import org.junit.Rule;
+import org.junit.rules.TemporaryFolder;
+
+public abstract class AbstractMavenTargetTest {
+ static final String SOURCE_BUNDLE_SUFFIX = ".source";
+ static final TargetBundle[] EMPTY = {};
+
+ @Rule
+ public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+ private static ServiceLoader LOCATION_LOADER = ServiceLoader.load(TargetLocationLoader.class,
+ AbstractMavenTargetTest.class.getClassLoader());
+ private static TargetLocationLoader loader;
+
+ ITargetLocation resolveMavenTarget(String targetXML) throws Exception {
+ return getLoader().resolveMavenTarget(targetXML, temporaryFolder.newFolder());
+ }
+
+ private static TargetLocationLoader getLoader() {
+ if (loader == null) {
+ Provider provider = LOCATION_LOADER.stream().sorted().findFirst().orElseThrow(
+ () -> new IllegalStateException("No TargetLocationLoader found on classpath of test!"));
+ loader = provider.get();
+ }
+ return loader;
+ }
+
+ protected static void assertStatusOk(IStatus status) {
+ if (!status.isOK()) {
+ throw new AssertionError(status.toString(), status.getException());
+ }
+ }
+
+ // --- common assertion utilities ---
+
+ private static Map assertTargetContent(List expectedUnits, T[] allUnit,
+ BiPredicate matcher, Function getLocation, Predicate isSourceUnit,
+ Function getSourceTarget, Function toString) {
+
+ Map units = new HashMap<>();
+ List allElements = new ArrayList<>(Arrays.asList(allUnit));
+ for (U expectedUnit : expectedUnits) {
+ List matchingUnits = allElements.stream().filter(u -> matcher.test(expectedUnit, u)).toList();
+
+ if (matchingUnits.isEmpty()) {
+ fail("Expected unit is missing: " + expectedUnit);
+ } else if (matchingUnits.size() == 1) {
+ T targetUnit = matchingUnits.get(0);
+ allElements.remove(targetUnit);
+
+ assertEquals("Unexpected 'original' state of " + targetUnit, expectedUnit.isOriginal(),
+ isOriginalArtifact(expectedUnit, targetUnit, getLocation));
+ assertEquals("Unexpected 'isSource' state of " + targetUnit, expectedUnit.isSourceBundle(),
+ isSourceUnit.test(targetUnit));
+ if (expectedUnit.isSourceBundle()) {
+ String expectedSourceTarget = expectedUnit.id().substring(0,
+ expectedUnit.id().length() - SOURCE_BUNDLE_SUFFIX.length());
+ assertEquals("Source target id", expectedSourceTarget, getSourceTarget.apply(targetUnit));
+ } else {
+ assertNull(getSourceTarget.apply(targetUnit));
+ }
+ units.put(expectedUnit, targetUnit);
+ } else {
+ fail("Expected bundle contaiend multiple times:" + expectedUnit);
+ }
+ }
+ if (!allElements.isEmpty()) {
+ String unepxectedBundlesList = allElements.stream().map(u -> " " + toString.apply(u))
+ .collect(Collectors.joining("\n"));
+ fail("Encoutnered the following unexpected bundles:" + unepxectedBundlesList);
+ }
+ return units;
+ }
+
+ private static boolean isOriginalArtifact(ExpectedUnit expectedUnit, T unit, Function getLocation) {
+ ArtifactKey key = expectedUnit.key();
+ if (key == null) {
+ return false;
+ }
+ URI location = getLocation.apply(unit);
+ String expectedPathSuffix = "/" + String.join("/", ".m2", "repository", key.groupId().replace('.', '/'),
+ key.artifactId(), key.version(), key.artifactId() + "-" + key.version() + ".jar");
+ return location.toASCIIString().endsWith(expectedPathSuffix);
+ }
+
+ // --- assertion utilities for Bundles in target ---
+
+ static ExpectedBundle originalOSGiBundle(String bsn, String version, String groupArtifact) {
+ return originalOSGiBundle(bsn, version, groupArtifact, version);
+ }
+
+ static ExpectedBundle originalOSGiBundle(String bsn, String version, String groupArtifact, String mavenVersion) {
+ return new ExpectedBundle(bsn, version, false, true,
+ ArtifactKey.fromPortableString(groupArtifact + ":" + mavenVersion + "::"));
+ }
+
+ static ExpectedBundle generatedBundle(String bsn, String version, String groupArtifact) {
+ return new ExpectedBundle(bsn, version, false, false,
+ ArtifactKey.fromPortableString(groupArtifact + ":" + version + "::"));
+ }
+
+ static List withSourceBundles(List mainBundles) {
+ return mainBundles.stream().mapMulti((unit, downStream) -> {
+ downStream.accept(unit);
+ String sourceId = unit.bsn() + SOURCE_BUNDLE_SUFFIX;
+ ExpectedBundle sourceUnit = new ExpectedBundle(sourceId, unit.version(), true, false, unit.key());
+ downStream.accept(sourceUnit);
+ }).toList();
+ }
+
+ static Attributes getManifestMainAttributes(TargetBundle targetBundle) throws IOException {
+ BundleInfo bundleInfo = targetBundle.getBundleInfo();
+ File file = URIUtil.toFile(bundleInfo.getLocation());
+ try (var jar = new JarFile(file)) {
+ return jar.getManifest().getMainAttributes();
+ }
+ }
+
+ static void assertTargetBundles(ITargetLocation target, List expectedUnits) {
+ assertTargetContent(expectedUnits, target.getBundles(), //
+ (expectedBundle, bundle) -> {
+ BundleInfo info = bundle.getBundleInfo();
+ return expectedBundle.bsn().equals(info.getSymbolicName())
+ && expectedBundle.version().equals(info.getVersion());
+ }, //
+ tb -> tb.getBundleInfo().getLocation(), //
+ tb -> tb.isSourceBundle(),
+ tb -> tb.getSourceTarget() != null ? tb.getSourceTarget().getSymbolicName() : null,
+ tb -> tb.getBundleInfo().getSymbolicName() + ":" + tb.getBundleInfo().getVersion());
+ }
+
+ // --- assertion utilities for Features in a target ---
+
+ static ExpectedFeature originalFeature(String id, String version, String groupArtifact,
+ List containedPlugins) {
+ ArtifactKey key = ArtifactKey.fromPortableString(groupArtifact + ":" + version + "::");
+ return new ExpectedFeature(id, version, false, true, key, containedPlugins);
+ }
+
+ static ExpectedFeature generatedFeature(String id, String version, List containedPlugins) {
+ return new ExpectedFeature(id, version, false, false, null, containedPlugins);
+ }
+
+ static NameVersionDescriptor featurePlugin(String bsn, String version) {
+ return new NameVersionDescriptor(bsn, version);
+ }
+
+ static List withSourceFeatures(List mainFeatures) {
+ return mainFeatures.stream().mapMulti((feature, downStream) -> {
+ downStream.accept(feature);
+ String sourceId = feature.id() + SOURCE_BUNDLE_SUFFIX;
+ List sourcePlugins = feature.containedPlugins().stream()
+ .map(d -> featurePlugin(d.getId() + SOURCE_BUNDLE_SUFFIX, d.getVersion())).toList();
+ ExpectedFeature sourceUnit = new ExpectedFeature(sourceId, feature.version(), true, false, feature.key(),
+ sourcePlugins);
+ downStream.accept(sourceUnit);
+ }).toList();
+ }
+
+ static Map assertTargetFeatures(ITargetLocation target,
+ List expectedFeatures) {
+ var encounteredFeatures = assertTargetContent(expectedFeatures, target.getFeatures(), //
+ (expectedFeature, feature) -> expectedFeature.id().equals(feature.getId())
+ && expectedFeature.version().equals(feature.getVersion()), //
+ f -> Path.of(f.getLocation()).toUri(), //
+ f -> isSourceFeature(f), //
+ f -> isSourceFeature(f) ? f.getId().substring(0, f.getId().length() - SOURCE_BUNDLE_SUFFIX.length())
+ : null, //
+ f -> f.getId() + ":" + f.getVersion());
+ encounteredFeatures.forEach((expectedFeature, feature) -> {
+ assertEquals(Set.copyOf(expectedFeature.containedPlugins()), Set.of(feature.getPlugins()));
+ });
+ return encounteredFeatures;
+ }
+
+ private static boolean isSourceFeature(TargetFeature f) {
+ return f.getId().endsWith(SOURCE_BUNDLE_SUFFIX)
+ && Arrays.stream(f.getPlugins()).allMatch(d -> d.getId().endsWith(SOURCE_BUNDLE_SUFFIX));
+ }
+}
diff --git a/tycho-core/src/test/java/org/eclipse/m2e/pde/target/tests/ArtifactKey.java b/tycho-core/src/test/java/org/eclipse/m2e/pde/target/tests/ArtifactKey.java
new file mode 100644
index 0000000000..098af99152
--- /dev/null
+++ b/tycho-core/src/test/java/org/eclipse/m2e/pde/target/tests/ArtifactKey.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Copyright (c) 2008-2022 Sonatype, Inc.
+ * All rights reserved. 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:
+ * Sonatype, Inc. - initial API and implementation
+ * Hannes Wellmann - Convert to record
+ *******************************************************************************/
+
+package org.eclipse.m2e.pde.target.tests;
+
+import java.io.Serializable;
+
+import org.eclipse.osgi.util.NLS;
+
+public record ArtifactKey(String groupId, String artifactId, String version, String classifier)
+ implements Serializable {
+ public static ArtifactKey fromPortableString(String str) {
+ int p = 0;
+ int c = nextColonIndex(str, p);
+ String groupId = substring(str, p, c);
+
+ p = c + 1;
+ c = nextColonIndex(str, p);
+ String artifactId = substring(str, p, c);
+
+ p = c + 1;
+ c = nextColonIndex(str, p);
+ String version = substring(str, p, c);
+
+ p = c + 1;
+ c = nextColonIndex(str, p);
+ String classifier = substring(str, p, c);
+
+ return new ArtifactKey(groupId, artifactId, version, classifier);
+ }
+
+ private static String substring(String str, int start, int end) {
+ String substring = str.substring(start, end);
+ return "".equals(substring) ? null : substring; //$NON-NLS-1$
+ }
+
+ private static int nextColonIndex(String str, int pos) {
+ int idx = str.indexOf(':', pos);
+ if (idx < 0) {
+ throw new IllegalArgumentException(NLS.bind("Invalid portable string: {0}", str));
+ }
+ return idx;
+ }
+}
diff --git a/tycho-core/src/test/java/org/eclipse/m2e/pde/target/tests/DependencyExclusionTest.java b/tycho-core/src/test/java/org/eclipse/m2e/pde/target/tests/DependencyExclusionTest.java
new file mode 100644
index 0000000000..28613dddb0
--- /dev/null
+++ b/tycho-core/src/test/java/org/eclipse/m2e/pde/target/tests/DependencyExclusionTest.java
@@ -0,0 +1,171 @@
+/*******************************************************************************
+ * Copyright (c) 2023, 2023 Hannes Wellmann and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Hannes Wellmann - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.m2e.pde.target.tests;
+
+import static org.junit.Assert.assertArrayEquals;
+
+import java.util.List;
+
+import org.eclipse.pde.core.target.ITargetLocation;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class DependencyExclusionTest extends AbstractMavenTargetTest {
+ @Parameter(0)
+ public Boolean includeSource;
+
+ @Parameters(name = "includeSource={0}")
+ public static List dependencyConfigurations() {
+ return List.of(false, true);
+ }
+
+ @Test
+ public void testExclusionOfDirectRequirement() throws Exception {
+ ITargetLocation target = resolveMavenTarget(String.format(
+ """
+
+
+
+ org.junit.platform
+ junit-platform-commons
+ 1.9.3
+ jar
+
+
+ org.apiguardian:apiguardian-api:1.1.2
+
+ """,
+ 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 testExclusionOfDirectAndTransitivRequirement() throws Exception {
+ ITargetLocation target = resolveMavenTarget(String
+ .format(
+ """
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ 5.9.3
+ jar
+
+
+ org.apiguardian:apiguardian-api:1.1.2
+
+ """,
+ includeSource));
+ assertStatusOk(target.getStatus());
+ assertArrayEquals(EMPTY, target.getFeatures());
+ List expectedBundles = List.of(//
+ junitJupiterAPI(), //
+ junitPlatformCommons("1.9.3"), //
+ opentest4j());
+ assertTargetBundles(target, includeSource ? withSourceBundles(expectedBundles) : expectedBundles);
+
+ }
+
+ @Test
+ public void testExclusionOfMultipleVersions() throws Exception {
+ ITargetLocation target = resolveMavenTarget(String
+ .format(
+ """
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ 5.9.3
+ jar
+
+
+ org.junit.platform
+ junit-platform-commons
+ 1.7.2
+ jar
+
+
+ org.apiguardian:apiguardian-api:1.1.2
+ org.apiguardian:apiguardian-api:1.1.0
+
+ """,
+ includeSource));
+ assertStatusOk(target.getStatus());
+ assertArrayEquals(EMPTY, target.getFeatures());
+ List expectedBundles = List.of(//
+ junitJupiterAPI(), //
+ junitPlatformCommons("1.9.3"), //
+ junitPlatformCommons("1.7.2"), //
+ opentest4j());
+ assertTargetBundles(target, includeSource ? withSourceBundles(expectedBundles) : expectedBundles);
+ }
+
+ @Test
+ public void testExclusionOfDifferentVersions() throws Exception {
+ ITargetLocation target = resolveMavenTarget(String
+ .format(
+ """
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ 5.9.3
+ jar
+
+
+ org.junit.platform
+ junit-platform-commons
+ 1.7.2
+ jar
+
+
+ org.apiguardian:apiguardian-api:1.1.0
+
+ """,
+ includeSource));
+ assertStatusOk(target.getStatus());
+ assertArrayEquals(EMPTY, target.getFeatures());
+ List expectedBundles = List.of(//
+ junitJupiterAPI(), //
+ junitPlatformCommons("1.9.3"), //
+ junitPlatformCommons("1.7.2"), //
+ 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");
+ }
+
+ private static ExpectedBundle junitJupiterAPI() {
+ return originalOSGiBundle("junit-jupiter-api", "5.9.3", "org.junit.jupiter:junit-jupiter-api");
+ }
+
+ private static ExpectedBundle apiGuardian(String version) {
+ return originalOSGiBundle("org.apiguardian.api", version, "org.apiguardian:apiguardian-api");
+ }
+
+ private static ExpectedBundle opentest4j() {
+ return originalOSGiBundle("org.opentest4j", "1.2.0", "org.opentest4j:opentest4j");
+ }
+}
diff --git a/tycho-core/src/test/java/org/eclipse/m2e/pde/target/tests/ExpectedBundle.java b/tycho-core/src/test/java/org/eclipse/m2e/pde/target/tests/ExpectedBundle.java
new file mode 100644
index 0000000000..eea73c5a39
--- /dev/null
+++ b/tycho-core/src/test/java/org/eclipse/m2e/pde/target/tests/ExpectedBundle.java
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * Copyright (c) 2023, 2023 Hannes Wellmann and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Hannes Wellmann - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.m2e.pde.target.tests;
+
+public record ExpectedBundle(String bsn, String version, boolean isSourceBundle, boolean isOriginal,
+ ArtifactKey key) implements ExpectedUnit {
+
+ @Override
+ public String id() {
+ return bsn();
+ }
+
+ @Override
+ public String toString() {
+ return bsn + ":" + version;
+ }
+}
\ No newline at end of file
diff --git a/tycho-core/src/test/java/org/eclipse/m2e/pde/target/tests/ExpectedFeature.java b/tycho-core/src/test/java/org/eclipse/m2e/pde/target/tests/ExpectedFeature.java
new file mode 100644
index 0000000000..d5bc4a18ac
--- /dev/null
+++ b/tycho-core/src/test/java/org/eclipse/m2e/pde/target/tests/ExpectedFeature.java
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ * Copyright (c) 2023, 2023 Hannes Wellmann and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Hannes Wellmann - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.m2e.pde.target.tests;
+
+import java.util.List;
+
+import org.eclipse.pde.core.target.NameVersionDescriptor;
+
+public record ExpectedFeature(String id, String version, boolean isSourceBundle, boolean isOriginal,
+ ArtifactKey key, List containedPlugins) implements ExpectedUnit {
+
+ @Override
+ public String toString() {
+ return id + ":" + version;
+ }
+}
\ No newline at end of file
diff --git a/tycho-core/src/test/java/org/eclipse/m2e/pde/target/tests/ExpectedUnit.java b/tycho-core/src/test/java/org/eclipse/m2e/pde/target/tests/ExpectedUnit.java
new file mode 100644
index 0000000000..0f06e28c4e
--- /dev/null
+++ b/tycho-core/src/test/java/org/eclipse/m2e/pde/target/tests/ExpectedUnit.java
@@ -0,0 +1,23 @@
+/*******************************************************************************
+ * Copyright (c) 2023, 2023 Hannes Wellmann and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Hannes Wellmann - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.m2e.pde.target.tests;
+interface ExpectedUnit {
+
+ String id();
+
+ boolean isSourceBundle();
+
+ boolean isOriginal();
+
+ ArtifactKey key();
+}
\ No newline at end of file
diff --git a/tycho-core/src/test/java/org/eclipse/m2e/pde/target/tests/MavenFeatureTest.java b/tycho-core/src/test/java/org/eclipse/m2e/pde/target/tests/MavenFeatureTest.java
new file mode 100644
index 0000000000..0e92df62b0
--- /dev/null
+++ b/tycho-core/src/test/java/org/eclipse/m2e/pde/target/tests/MavenFeatureTest.java
@@ -0,0 +1,187 @@
+/*******************************************************************************
+ * Copyright (c) 2023, 2023 Hannes Wellmann and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Hannes Wellmann - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.m2e.pde.target.tests;
+
+import java.util.List;
+
+import org.eclipse.pde.core.target.ITargetLocation;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class MavenFeatureTest extends AbstractMavenTargetTest {
+ @Parameter(0)
+ public Boolean includeSource;
+
+ @Parameters(name = "includeSource={0}")
+ public static List dependencyConfigurations() {
+ return List.of(false, true);
+ }
+
+ @Test
+ public void testLocationContentFeatureGeneration() throws Exception {
+ ITargetLocation target = resolveMavenTarget(String
+ .format(
+ """
+
+
+
+ This is a great feature
+
+
+ My copyright description.
+
+
+ No license granted.
+
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ 5.9.3
+ jar
+
+
+
+ """,
+ includeSource));
+ assertStatusOk(target.getStatus());
+ List expectedBundles = List.of( //
+ junitJupiterAPI(), //
+ junitPlatformCommons(), //
+ apiGuardian(), opentest4j());
+ assertTargetBundles(target, includeSource ? withSourceBundles(expectedBundles) : expectedBundles);
+ List expectedFeature = List.of(generatedFeature("my.junit.feature", "1.2.3.qualifier", List.of(//
+ featurePlugin("junit-jupiter-api", "5.9.3"), //
+ featurePlugin("junit-platform-commons", "1.9.3"), //
+ featurePlugin("org.apiguardian.api", "1.1.2"), //
+ featurePlugin("org.opentest4j", "1.2.0"), //
+ featurePlugin("slf4j.api", null))));
+ List expectedFeatures = includeSource ? withSourceFeatures(expectedFeature) : expectedFeature;
+ expectedFeatures = expectedFeatures.stream().map(f -> {
+ if (f.containedPlugins().stream().noneMatch(d -> d.getId().equals("slf4j.api.source"))) {
+ return f;
+ }
+ // Explicitly listed Plug-ins are just removed for a Source-Feature, if they are
+ // (probably) not a source Plug-in and are not mapped to a source Plug-in.
+ return new ExpectedFeature(f.id(), f.version(), f.isSourceBundle(), f.isOriginal(), f.key(),
+ f.containedPlugins().stream().filter(d -> !d.getId().equals("slf4j.api.source")).toList());
+ }).toList();
+ assertTargetFeatures(target, expectedFeatures);
+ }
+
+ @Test
+ public void testPomArtifactFeatureGeneration() throws Exception {
+ ITargetLocation target = resolveMavenTarget(String
+ .format(
+ """
+
+
+
+ com.sun.xml.bind
+ jaxb-ri
+ 4.0.2
+ pom
+
+
+
+ """,
+ includeSource));
+ assertStatusOk(target.getStatus());
+ List expectedBundles = List.of( //
+ originalOSGiBundle("com.sun.xml.bind.jaxb-core", "4.0.2", "com.sun.xml.bind:jaxb-core"),
+ originalOSGiBundle("com.sun.xml.bind.jaxb-impl", "4.0.2", "com.sun.xml.bind:jaxb-impl"),
+ originalOSGiBundle("com.sun.xml.bind.jaxb-jxc", "4.0.2", "com.sun.xml.bind:jaxb-jxc"),
+ originalOSGiBundle("com.sun.xml.bind.jaxb-xjc", "4.0.2", "com.sun.xml.bind:jaxb-xjc"),
+ originalOSGiBundle("com.sun.xml.fastinfoset.FastInfoset", "2.1.0",
+ "com.sun.xml.fastinfoset:FastInfoset"),
+ originalOSGiBundle("jakarta.activation-api", "2.1.1", "jakarta.activation:jakarta.activation-api"),
+ originalOSGiBundle("jakarta.xml.bind-api", "4.0.0", "jakarta.xml.bind:jakarta.xml.bind-api"),
+ originalOSGiBundle("org.jvnet.staxex.stax-ex", "2.1.0", "org.jvnet.staxex:stax-ex"));
+ assertTargetBundles(target, includeSource ? withSourceBundles(expectedBundles) : expectedBundles);
+ List expectedFeature = List.of(generatedFeature("com.sun.xml.bind.jaxb-ri.pom", "0.0.1",
+ List.of(//
+ featurePlugin("com.sun.xml.bind.jaxb-core", "4.0.2"),
+ featurePlugin("com.sun.xml.bind.jaxb-impl", "4.0.2"),
+ featurePlugin("com.sun.xml.bind.jaxb-jxc", "4.0.2"),
+ featurePlugin("com.sun.xml.bind.jaxb-xjc", "4.0.2"),
+ featurePlugin("com.sun.xml.fastinfoset.FastInfoset", "2.1.0"),
+ featurePlugin("jakarta.activation-api", "2.1.1"),
+ featurePlugin("jakarta.xml.bind-api", "4.0.0"),
+ featurePlugin("org.jvnet.staxex.stax-ex", "2.1.0"))));
+ assertTargetFeatures(target, includeSource ? withSourceFeatures(expectedFeature) : expectedFeature);
+
+ }
+
+ @Test
+ public void testFeatureArtifact() throws Exception {
+ // TODO: For real feature artifacts, which don't have a source-artifact, a
+ // source feature is not generated (yet).
+ Assume.assumeFalse(includeSource);
+ ITargetLocation target = resolveMavenTarget(String
+ .format(
+ """
+
+
+
+ org.eclipse.vorto
+ org.eclipse.vorto.feature
+ 1.0.0
+ jar
+
+
+
+ """,
+ includeSource));
+ assertStatusOk(target.getStatus());
+ assertTargetBundles(target, List.of());
+ List expectedFeature = List.of(generatedFeature("org.eclipse.vorto.feature", "1.0.0", List.of(//
+ featurePlugin("org.eclipse.vorto.core", "1.0.0"), //
+ featurePlugin("org.eclipse.vorto.editor", "1.0.0"), //
+ featurePlugin("org.eclipse.vorto.editor.datatype", "1.0.0"),
+ featurePlugin("org.eclipse.vorto.editor.datatype.ide", "1.0.0"),
+ featurePlugin("org.eclipse.vorto.editor.datatype.ui", "1.0.0"),
+ featurePlugin("org.eclipse.vorto.editor.functionblock", "1.0.0"),
+ featurePlugin("org.eclipse.vorto.editor.functionblock.ide", "1.0.0"),
+ featurePlugin("org.eclipse.vorto.editor.functionblock.ui", "1.0.0"),
+ featurePlugin("org.eclipse.vorto.editor.infomodel", "1.0.0"),
+ featurePlugin("org.eclipse.vorto.editor.infomodel.ide", "1.0.0"),
+ featurePlugin("org.eclipse.vorto.editor.infomodel.ui", "1.0.0"),
+ featurePlugin("org.eclipse.vorto.editor.mapping", "1.0.0"),
+ featurePlugin("org.eclipse.vorto.editor.mapping.ide", "1.0.0"),
+ featurePlugin("org.eclipse.vorto.editor.mapping.ui", "1.0.0"))));
+ assertTargetFeatures(target, includeSource ? withSourceFeatures(expectedFeature) : expectedFeature);
+ }
+
+ private static ExpectedBundle junitPlatformCommons() {
+ return originalOSGiBundle("junit-platform-commons", "1.9.3", "org.junit.platform:junit-platform-commons");
+ }
+
+ private static ExpectedBundle junitJupiterAPI() {
+ return originalOSGiBundle("junit-jupiter-api", "5.9.3", "org.junit.jupiter:junit-jupiter-api");
+ }
+
+ private static ExpectedBundle apiGuardian() {
+ return originalOSGiBundle("org.apiguardian.api", "1.1.2", "org.apiguardian:apiguardian-api");
+ }
+
+ private static ExpectedBundle opentest4j() {
+ return originalOSGiBundle("org.opentest4j", "1.2.0", "org.opentest4j:opentest4j");
+ }
+}
diff --git a/tycho-core/src/test/java/org/eclipse/m2e/pde/target/tests/MavenTargetsTransitiveDependenciesTest.java b/tycho-core/src/test/java/org/eclipse/m2e/pde/target/tests/MavenTargetsTransitiveDependenciesTest.java
new file mode 100644
index 0000000000..1efd5b8435
--- /dev/null
+++ b/tycho-core/src/test/java/org/eclipse/m2e/pde/target/tests/MavenTargetsTransitiveDependenciesTest.java
@@ -0,0 +1,140 @@
+/*******************************************************************************
+ * Copyright (c) 2023, 2023 Hannes Wellmann and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Hannes Wellmann - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.m2e.pde.target.tests;
+
+import static org.junit.Assert.assertArrayEquals;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.pde.core.target.ITargetLocation;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class MavenTargetsTransitiveDependenciesTest extends AbstractMavenTargetTest {
+
+ @Parameter(0)
+ public String dependencyDepth;
+ @Parameter(1)
+ public String dependencyScopes;
+ @Parameter(2)
+ public String sources;
+ @Parameter(3)
+ public List expectedBundles;
+
+ @Parameters(name = "includeDependencyDepth={0} - includeDependencyScopes={1} - includeSource={2}")
+ public static Collection