Bug 544838 - Option to automatically include requirements at launch

Change-Id: I3bf1acec1fb96d5bf6c14f4609ca08ac00f9e28f
Signed-off-by: Hannes Wellmann <wellmann.hannes1@gmx.net>
Reviewed-on: https://git.eclipse.org/r/c/pde/eclipse.pde.ui/+/186833
Reviewed-by: Julian Honnen <julian.honnen@vector.com>
Tested-by: PDE Bot <pde-bot@eclipse.org>
diff --git a/ui/org.eclipse.pde.launching/META-INF/MANIFEST.MF b/ui/org.eclipse.pde.launching/META-INF/MANIFEST.MF
index 76ae491..6b18bb1 100644
--- a/ui/org.eclipse.pde.launching/META-INF/MANIFEST.MF
+++ b/ui/org.eclipse.pde.launching/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %name
 Bundle-SymbolicName: org.eclipse.pde.launching;singleton:=true
-Bundle-Version: 3.9.700.qualifier
+Bundle-Version: 3.10.0.qualifier
 Bundle-RequiredExecutionEnvironment: JavaSE-11
 Bundle-Vendor: %provider-name
 Require-Bundle: org.eclipse.jdt.junit.core;bundle-version="[3.6.0,4.0.0)",
diff --git a/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/BundleLauncherHelper.java b/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/BundleLauncherHelper.java
index 35b302b..8af476d 100644
--- a/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/BundleLauncherHelper.java
+++ b/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/BundleLauncherHelper.java
@@ -17,6 +17,7 @@
  *     Hannes Wellmann - Bug 576887 - Handle multiple versions of features and plug-ins for feature-launches
  *     Hannes Wellmann - Bug 576888, Bug 576889 - Consider included child-features and required dependency-features for feature-launches
  *     Hannes Wellmann - Bug 576890 - Ignore included features/plug-ins not matching target-environment
+ *     Hannes Wellmann - Bug 544838 - Option to automatically add requirements at launch
  *******************************************************************************/
 package org.eclipse.pde.internal.launching.launcher;
 
@@ -93,7 +94,12 @@
 			return getMergedBundleMapFeatureBased(wc);
 		}
 
-		return getAllSelectedPluginBundles(wc);
+		Map<IPluginModelBase, String> selectedBundles = getAllSelectedPluginBundles(wc);
+		boolean autoAddRequirements = configuration.getAttribute(IPDELauncherConstants.AUTOMATIC_INCLUDE_REQUIREMENTS, false);
+		if (autoAddRequirements) {
+			addRequiredBundles(selectedBundles, configuration);
+		}
+		return selectedBundles;
 	}
 
 	public static Map<IPluginModelBase, String> getAllSelectedPluginBundles(ILaunchConfiguration config) throws CoreException {
@@ -103,14 +109,29 @@
 		return map;
 	}
 
+	private static void addRequiredBundles(Map<IPluginModelBase, String> bundle2startLevel, ILaunchConfiguration configuration) throws CoreException {
+
+		RequirementHelper.addApplicationLaunchRequirements(bundle2startLevel, configuration);
+
+		boolean includeOptional = configuration.getAttribute(IPDELauncherConstants.INCLUDE_OPTIONAL, true);
+		Set<BundleDescription> requiredDependencies = includeOptional //
+				? DependencyManager.getDependencies(bundle2startLevel.keySet(), DependencyManager.Options.INCLUDE_OPTIONAL_DEPENDENCIES)
+				: DependencyManager.getDependencies(bundle2startLevel.keySet());
+
+		requiredDependencies.stream() //
+				.map(PluginRegistry::findModel).filter(Objects::nonNull) //
+				.forEach(p -> addDefaultStartingBundle(bundle2startLevel, p));
+	}
+
 	// --- feature based launches ---
 
 	private static Map<IPluginModelBase, String> getMergedBundleMapFeatureBased(ILaunchConfiguration configuration) throws CoreException {
 
 		String defaultPluginResolution = configuration.getAttribute(IPDELauncherConstants.FEATURE_PLUGIN_RESOLUTION, IPDELauncherConstants.LOCATION_WORKSPACE);
 		ITargetDefinition target = PDECore.getDefault().acquireService(ITargetPlatformService.class).getWorkspaceTargetDefinition();
+		boolean addRequirements = configuration.getAttribute(IPDELauncherConstants.AUTOMATIC_INCLUDE_REQUIREMENTS, true);
 
-		Map<IFeature, String> feature2resolution = getSelectedFeatures(configuration, target);
+		Map<IFeature, String> feature2resolution = getSelectedFeatures(configuration, target, addRequirements);
 
 		// Get the feature model for each selected feature id and resolve its plugins
 		Set<IPluginModelBase> launchPlugins = new HashSet<>();
@@ -128,12 +149,14 @@
 					}
 				}
 			}
-			IFeatureImport[] featureImports = feature.getImports();
-			for (IFeatureImport featureImport : featureImports) {
-				if (featureImport.getType() == IFeatureImport.PLUGIN) {
-					IPluginModelBase plugin = getRequiredPlugin(featureImport.getId(), featureImport.getVersion(), featureImport.getMatch(), pluginResolution);
-					if (plugin != null) {
-						launchPlugins.add(plugin);
+			if (addRequirements) {
+				IFeatureImport[] featureImports = feature.getImports();
+				for (IFeatureImport featureImport : featureImports) {
+					if (featureImport.getType() == IFeatureImport.PLUGIN) {
+						IPluginModelBase plugin = getRequiredPlugin(featureImport.getId(), featureImport.getVersion(), featureImport.getMatch(), pluginResolution);
+						if (plugin != null) {
+							launchPlugins.add(plugin);
+						}
 					}
 				}
 			}
@@ -142,20 +165,17 @@
 		Map<IPluginModelBase, AdditionalPluginData> additionalPlugins = getAdditionalPlugins(configuration, true);
 		launchPlugins.addAll(additionalPlugins.keySet());
 
-		// Get any plug-ins required by the application/product set on the config
-		List<String> applicationIds = RequirementHelper.getApplicationLaunchRequirements(configuration);
-		for (String applicationId : applicationIds) {
-			IPluginModelBase plugin = getLatestPlugin(applicationId, defaultPluginResolution);
-			if (plugin != null) {
-				launchPlugins.add(plugin);
+		if (addRequirements) {
+			// Add all missing  plug-ins required by the application/product set in the config
+			RequirementHelper.addApplicationLaunchRequirements(configuration, launchPlugins, launchPlugins::add);
+
+			// Get all required plugins
+			Set<BundleDescription> additionalBundles = DependencyManager.getDependencies(launchPlugins);
+			for (BundleDescription bundle : additionalBundles) {
+				IPluginModelBase plugin = getRequiredPlugin(bundle.getSymbolicName(), bundle.getVersion().toString(), IMatchRules.PERFECT, defaultPluginResolution);
+				launchPlugins.add(Objects.requireNonNull(plugin));// should never be null
 			}
 		}
-		// Get all required plugins
-		Set<BundleDescription> additionalBundles = DependencyManager.getDependencies(launchPlugins);
-		for (BundleDescription bundle : additionalBundles) {
-			IPluginModelBase plugin = getRequiredPlugin(bundle.getSymbolicName(), bundle.getVersion().toString(), IMatchRules.PERFECT, defaultPluginResolution);
-			launchPlugins.add(Objects.requireNonNull(plugin));// should never be null
-		}
 
 		// Create the start levels for the selected plugins and add them to the map
 		Map<IPluginModelBase, String> map = new LinkedHashMap<>();
@@ -167,7 +187,7 @@
 		return map;
 	}
 
-	private static Map<IFeature, String> getSelectedFeatures(ILaunchConfiguration configuration, ITargetDefinition target) throws CoreException {
+	private static Map<IFeature, String> getSelectedFeatures(ILaunchConfiguration configuration, ITargetDefinition target, boolean addRequirements) throws CoreException {
 		String featureLocation = configuration.getAttribute(IPDELauncherConstants.FEATURE_DEFAULT_LOCATION, IPDELauncherConstants.LOCATION_WORKSPACE);
 
 		Predicate<IFeature> targetEnvironmentFilter = f -> f.matchesEnvironment(target);
@@ -201,11 +221,13 @@
 				}
 			}
 
-			IFeatureImport[] featureImports = feature.getImports();
-			for (IFeatureImport featureImport : featureImports) {
-				if (featureImport.getType() == IFeatureImport.FEATURE) {
-					IFeature dependency = getRequiredFeature(featureImport.getId(), featureImport.getVersion(), featureImport.getMatch(), targetEnvironmentFilter, featureMaps);
-					addFeatureIfAbsent(dependency, pluginResolution, feature2pluginResolution, pendingFeatures);
+			if (addRequirements) {
+				IFeatureImport[] featureImports = feature.getImports();
+				for (IFeatureImport featureImport : featureImports) {
+					if (featureImport.getType() == IFeatureImport.FEATURE) {
+						IFeature dependency = getRequiredFeature(featureImport.getId(), featureImport.getVersion(), featureImport.getMatch(), targetEnvironmentFilter, featureMaps);
+						addFeatureIfAbsent(dependency, pluginResolution, feature2pluginResolution, pendingFeatures);
+					}
 				}
 			}
 		}
@@ -453,15 +475,17 @@
 		BundleDescription desc = bundle.getBundleDescription();
 		boolean defaultsl = startData == null || startData.equals(DEFAULT_START_LEVELS);
 		if (desc != null && defaultsl) {
-			String runLevelText = resolveSystemRunLevelText(bundle);
-			String autoText = resolveSystemAutoText(bundle);
-			if (runLevelText != null && autoText != null) {
-				startData = runLevelText + AUTO_START_SEPARATOR + autoText;
-			}
+			startData = getStartData(desc, startData);
 		}
 		map.put(bundle, startData);
 	}
 
+	public static String getStartData(BundleDescription desc, String defaultStartData) {
+		String runLevel = resolveSystemRunLevelText(desc);
+		String auto = resolveSystemAutoText(desc);
+		return runLevel != null && auto != null ? (runLevel + AUTO_START_SEPARATOR + auto) : defaultStartData;
+	}
+
 	public static void addDefaultStartingBundle(Map<IPluginModelBase, String> map, IPluginModelBase bundle) {
 		addBundleToMap(map, bundle, DEFAULT_START_LEVELS);
 	}
@@ -474,13 +498,11 @@
 			Map.entry(IPDEBuildConstants.BUNDLE_CORE_RUNTIME, DEFAULT), //
 			Map.entry(IPDEBuildConstants.BUNDLE_FELIX_SCR, "1")); //$NON-NLS-1$
 
-	public static String resolveSystemRunLevelText(IPluginModelBase model) {
-		BundleDescription description = model.getBundleDescription();
+	public static String resolveSystemRunLevelText(BundleDescription description) {
 		return AUTO_STARTED_BUNDLE_LEVELS.get(description.getSymbolicName());
 	}
 
-	public static String resolveSystemAutoText(IPluginModelBase model) {
-		BundleDescription description = model.getBundleDescription();
+	public static String resolveSystemAutoText(BundleDescription description) {
 		return AUTO_STARTED_BUNDLE_LEVELS.containsKey(description.getSymbolicName()) ? "true" : null; //$NON-NLS-1$
 	}
 
diff --git a/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/RequirementHelper.java b/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/RequirementHelper.java
index 26d9ad7..b4050e2 100644
--- a/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/RequirementHelper.java
+++ b/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/RequirementHelper.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2010, 2018 IBM Corporation and others.
+ * Copyright (c) 2010, 2022 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -11,12 +11,14 @@
  * Contributors:
  *     IBM Corporation - initial API and implementation
  *     Karsten Thoms (itemis) - Bug 530406
+ *     Hannes Wellmann - Bug 576860 - Specify all launch-type requirements in RequirementHelper
  *******************************************************************************/
 package org.eclipse.pde.internal.launching.launcher;
 
 import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
+import java.util.function.Consumer;
 import java.util.stream.Stream;
 import org.eclipse.core.runtime.*;
 import org.eclipse.debug.core.*;
@@ -77,6 +79,11 @@
 	}
 
 	public static boolean addApplicationLaunchRequirements(Map<IPluginModelBase, String> bundle2startLevel, ILaunchConfiguration configuration) throws CoreException {
+		Consumer<IPluginModelBase> addPlugin = b -> BundleLauncherHelper.addDefaultStartingBundle(bundle2startLevel, b);
+		return addApplicationLaunchRequirements(configuration, bundle2startLevel.keySet(), addPlugin);
+	}
+
+	public static boolean addApplicationLaunchRequirements(ILaunchConfiguration configuration, Set<IPluginModelBase> containedPlugins, Consumer<IPluginModelBase> addPlugin) throws CoreException {
 		boolean isFeatureBasedLaunch = configuration.getAttribute(IPDELauncherConstants.USE_CUSTOM_FEATURES, false);
 		String pluginResolution = isFeatureBasedLaunch ? configuration.getAttribute(IPDELauncherConstants.FEATURE_PLUGIN_RESOLUTION, IPDELauncherConstants.LOCATION_WORKSPACE) : IPDELauncherConstants.LOCATION_WORKSPACE;
 
@@ -87,9 +94,9 @@
 			if (entry != null) {
 				// add required plug-in if not yet already included
 				var allPluginsWithId = Stream.of(entry.getWorkspaceModels(), entry.getExternalModels()).flatMap(Arrays::stream);
-				if (allPluginsWithId.noneMatch(bundle2startLevel::containsKey)) {
+				if (allPluginsWithId.noneMatch(containedPlugins::contains)) {
 					IPluginModelBase plugin = BundleLauncherHelper.getLatestPlugin(requiredBundleId, pluginResolution);
-					BundleLauncherHelper.addDefaultStartingBundle(bundle2startLevel, plugin);
+					addPlugin.accept(plugin);
 				}
 			} else {
 				missingRequirements = true;
diff --git a/ui/org.eclipse.pde.launching/src/org/eclipse/pde/launching/IPDELauncherConstants.java b/ui/org.eclipse.pde.launching/src/org/eclipse/pde/launching/IPDELauncherConstants.java
index 0ff5b1d..b9ae5c2 100644
--- a/ui/org.eclipse.pde.launching/src/org/eclipse/pde/launching/IPDELauncherConstants.java
+++ b/ui/org.eclipse.pde.launching/src/org/eclipse/pde/launching/IPDELauncherConstants.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2005, 2019 IBM Corporation and others.
+ * Copyright (c) 2005, 2022 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -219,6 +219,15 @@
 	String INCLUDE_OPTIONAL = "includeOptional"; //$NON-NLS-1$
 
 	/**
+	 * Launch configuration attribute key. The value is a boolean specifying
+	 * whether required plug-ins and/or features should be added automatically
+	 * to the list of plug-ins to launch with.
+	 * 
+	 * @since 3.10 
+	 */
+	String AUTOMATIC_INCLUDE_REQUIREMENTS = "automaticIncludeRequirements"; //$NON-NLS-1$ 
+
+	/**
 	 * Launch configuration attribute key. The value is a boolean indicating
 	 * whether tracing is enabled or disabled.
 	 */
diff --git a/ui/org.eclipse.pde.ui.tests/META-INF/MANIFEST.MF b/ui/org.eclipse.pde.ui.tests/META-INF/MANIFEST.MF
index fdcb898..ac06478 100644
--- a/ui/org.eclipse.pde.ui.tests/META-INF/MANIFEST.MF
+++ b/ui/org.eclipse.pde.ui.tests/META-INF/MANIFEST.MF
@@ -40,7 +40,9 @@
  org.eclipse.ui.views.log,
  org.eclipse.debug.core,
  org.eclipse.pde.genericeditor.extension,
- org.eclipse.equinox.simpleconfigurator.manipulator;bundle-version="2.1.300"
+ org.eclipse.equinox.simpleconfigurator.manipulator;bundle-version="2.1.300",
+ org.eclipse.platform,
+ org.eclipse.ui.ide.application
 Import-Package: org.assertj.core.api;version="3.14.0",
  org.assertj.core.presentation;version="3.21.0",
  org.junit.jupiter.api.function;version="5.8.1"
diff --git a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/launcher/FeatureBasedLaunchTest.java b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/launcher/FeatureBasedLaunchTest.java
index f16235f..da78771 100644
--- a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/launcher/FeatureBasedLaunchTest.java
+++ b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/launcher/FeatureBasedLaunchTest.java
@@ -17,11 +17,15 @@
 package org.eclipse.pde.ui.tests.launcher;
 
 import static java.util.Collections.emptySet;
+import static java.util.Map.entry;
 import static java.util.Map.ofEntries;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.eclipse.pde.internal.core.ICoreConstants.DEFAULT_VERSION;
 import static org.eclipse.pde.ui.tests.util.ProjectUtils.createPluginProject;
 import static org.eclipse.pde.ui.tests.util.TargetPlatformUtil.bundle;
+import static org.eclipse.pde.ui.tests.util.TargetPlatformUtil.resolution;
+import static org.osgi.framework.Constants.REQUIRE_BUNDLE;
+import static org.osgi.framework.Constants.RESOLUTION_OPTIONAL;
 
 import java.io.PrintWriter;
 import java.nio.file.Files;
@@ -33,17 +37,17 @@
 import org.eclipse.core.runtime.*;
 import org.eclipse.debug.core.*;
 import org.eclipse.jface.operation.IRunnableWithProgress;
-import org.eclipse.pde.core.plugin.IMatchRules;
-import org.eclipse.pde.core.plugin.IPluginModelBase;
+import org.eclipse.osgi.service.resolver.BundleDescription;
+import org.eclipse.pde.core.plugin.*;
 import org.eclipse.pde.core.target.NameVersionDescriptor;
-import org.eclipse.pde.internal.core.FeatureModelManager;
-import org.eclipse.pde.internal.core.PDECore;
+import org.eclipse.pde.internal.core.*;
 import org.eclipse.pde.internal.core.feature.FeatureChild;
 import org.eclipse.pde.internal.core.feature.WorkspaceFeatureModel;
 import org.eclipse.pde.internal.core.ifeature.*;
 import org.eclipse.pde.internal.launching.launcher.BundleLauncherHelper;
 import org.eclipse.pde.internal.ui.wizards.feature.AbstractCreateFeatureOperation;
 import org.eclipse.pde.internal.ui.wizards.feature.FeatureData;
+import org.eclipse.pde.launching.EclipseApplicationLaunchConfiguration;
 import org.eclipse.pde.launching.IPDELauncherConstants;
 import org.eclipse.pde.ui.tests.util.TargetPlatformUtil;
 import org.junit.*;
@@ -1302,6 +1306,84 @@
 				workspaceBundle("plugin.d", "1.0.0")));
 	}
 
+	@Test
+	public void testGetMergedBundleMap_automaticallyAddRequirements() throws Throwable {
+
+		var targetBundles = Map.ofEntries( //
+				bundle("plugin.b", "1.0.0"), //
+				bundle("plugin.c", "1.0.0"), //
+
+				bundle("plugin.z", "1.0.0", //
+						entry(REQUIRE_BUNDLE, "plugin.x,plugin.w" + resolution(RESOLUTION_OPTIONAL))),
+				bundle("plugin.y", "1.0.0"), //
+				bundle("plugin.x", "1.0.0"), //
+				bundle("plugin.w", "1.0.0"));
+
+		List<NameVersionDescriptor> targetFeatures = List.of( //
+				targetFeature("feature.a", "1.0.0", f -> {
+					addIncludedFeature(f, "feature.b", "1.0.0");
+					addRequiredFeature(f, "feature.c", "", IMatchRules.COMPATIBLE);
+					addIncludedPlugin(f, "plugin.z", "1.0.0");
+					addRequiredPlugin(f, "plugin.y", "", IMatchRules.COMPATIBLE);
+				}), //
+				targetFeature("feature.b", "1.0.0", f -> {
+					addIncludedPlugin(f, "plugin.b", "1.0.0");
+				}), //
+				targetFeature("feature.c", "1.0.0", f -> {
+					addIncludedPlugin(f, "plugin.c", "1.0.0");
+				}));
+
+		// Gather requirements of the product used below
+		Map<BundleLocationDescriptor, String> requiredRPBundles = getEclipseAppRequirementClosureForRunningPlatform();
+
+		TargetPlatformUtil.setRunningPlatformWithDummyBundlesAsTarget(null, targetBundles, targetFeatures,
+				tpJarDirectory);
+
+		ILaunchConfigurationWorkingCopy wc = createFeatureLaunchConfig();
+		wc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.a:external"));
+		wc.setAttribute(IPDELauncherConstants.USE_PRODUCT, true);
+		wc.setAttribute(IPDELauncherConstants.PRODUCT, "org.eclipse.platform.ide");
+
+		// test AUTOMATIC_ADD_REQUIREMENTS=true and its default (true)
+		for (Boolean autoAddRequirements : Arrays.asList(true, null)) {
+			wc.setAttribute(IPDELauncherConstants.AUTOMATIC_INCLUDE_REQUIREMENTS, autoAddRequirements);
+			assertGetMergedBundleMap(wc, concat(requiredRPBundles, toDefaultStartData(Set.of(//
+					targetBundle("plugin.b", "1.0.0"), //
+					targetBundle("plugin.c", "1.0.0"), //
+					targetBundle("plugin.z", "1.0.0"), //
+					targetBundle("plugin.y", "1.0.0"), //
+					targetBundle("plugin.x", "1.0.0")))));
+		}
+
+		// test AUTOMATIC_ADD_REQUIREMENTS=false
+		wc.setAttribute(IPDELauncherConstants.AUTOMATIC_INCLUDE_REQUIREMENTS, false);
+		assertGetMergedBundleMap(wc, Set.of( //
+				targetBundle("plugin.b", "1.0.0"), //
+				targetBundle("plugin.z", "1.0.0")));
+	}
+
+	static Map<BundleLocationDescriptor, String> getEclipseAppRequirementClosureForRunningPlatform(
+			DependencyManager.Options... closureOptions) throws Exception {
+		// ensure app requirements are registered (done at class initialization)
+		new EclipseApplicationLaunchConfiguration();
+		@SuppressWarnings("unused") // prevent bundle removal
+		org.eclipse.platform.internal.LaunchUpdateIntroAction a;
+		@SuppressWarnings("unused") // prevent bundle removal
+		org.eclipse.ui.internal.ide.application.IDEApplication app;
+
+		TargetPlatformUtil.setRunningPlatformAsTarget();
+
+		List<String> productPlugins = List.of("org.eclipse.platform", "org.eclipse.ui.ide.application");
+		Set<BundleDescription> appBundles = productPlugins.stream().map(PluginRegistry::findModel)
+				.map(IPluginModelBase::getBundleDescription).collect(Collectors.toSet());
+
+		Set<BundleDescription> appBundleClosure = DependencyManager.findRequirementsClosure(appBundles, closureOptions);
+		assertThat(appBundleClosure).hasSizeGreaterThanOrEqualTo(productPlugins.size());
+		return DependencyManager.findRequirementsClosure(appBundles, closureOptions).stream().collect(Collectors.toMap( //
+				d -> targetBundle(d.getSymbolicName(), d.getVersion().toString()),
+				d -> BundleLauncherHelper.getStartData(d, "default:default")));
+	}
+
 	// --- utility methods ---
 
 	private static interface CoreConsumer<E> {
@@ -1444,10 +1526,11 @@
 
 	private static void assertGetMergedBundleMap(String message, ILaunchConfiguration launchConfig,
 			Set<BundleLocationDescriptor> expectedBundles) throws Exception {
+		assertGetMergedBundleMap(message, launchConfig, toDefaultStartData(expectedBundles));
+	}
 
-		Map<BundleLocationDescriptor, String> expectedBundleMap = expectedBundles.stream()
-				.collect(Collectors.toMap(b -> b, b -> "default:default"));
-		assertGetMergedBundleMap(message, launchConfig, expectedBundleMap);
+	static Map<BundleLocationDescriptor, String> toDefaultStartData(Collection<BundleLocationDescriptor> bundles) {
+		return bundles.stream().collect(Collectors.toMap(b -> b, b -> "default:default"));
 	}
 
 	private static void assertGetMergedBundleMap(ILaunchConfiguration launchConfig,
@@ -1467,4 +1550,11 @@
 
 		assertPluginMapsEquals(message, expectedPluginMap, bundleMap);
 	}
+
+	@SafeVarargs
+	static <K, V> Map<K, V> concat(Map<K, V>... maps) {
+		Map<K, V> map = new HashMap<>();
+		Arrays.stream(maps).forEach(map::putAll);
+		return map;
+	}
 }
diff --git a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/launcher/PluginBasedLaunchTest.java b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/launcher/PluginBasedLaunchTest.java
index 8cc1837..dbf8237 100644
--- a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/launcher/PluginBasedLaunchTest.java
+++ b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/launcher/PluginBasedLaunchTest.java
@@ -13,9 +13,15 @@
  *******************************************************************************/
 package org.eclipse.pde.ui.tests.launcher;
 
+import static java.util.Map.entry;
 import static java.util.Map.ofEntries;
+import static org.eclipse.pde.ui.tests.launcher.FeatureBasedLaunchTest.concat;
+import static org.eclipse.pde.ui.tests.launcher.FeatureBasedLaunchTest.toDefaultStartData;
 import static org.eclipse.pde.ui.tests.util.TargetPlatformUtil.bundle;
+import static org.eclipse.pde.ui.tests.util.TargetPlatformUtil.resolution;
 import static org.junit.Assert.assertEquals;
+import static org.osgi.framework.Constants.REQUIRE_BUNDLE;
+import static org.osgi.framework.Constants.RESOLUTION_OPTIONAL;
 
 import java.nio.file.Path;
 import java.util.*;
@@ -25,6 +31,8 @@
 import org.eclipse.debug.core.*;
 import org.eclipse.pde.core.plugin.IPluginModelBase;
 import org.eclipse.pde.core.target.NameVersionDescriptor;
+import org.eclipse.pde.internal.core.DependencyManager;
+import org.eclipse.pde.internal.launching.IPDEConstants;
 import org.eclipse.pde.internal.launching.launcher.BundleLauncherHelper;
 import org.eclipse.pde.launching.IPDELauncherConstants;
 import org.eclipse.pde.ui.tests.util.ProjectUtils;
@@ -731,6 +739,68 @@
 		assertGetMergedBundleMap(workspacePlugins, targetPlatformBundles, launchConfigSetup, expectedBundles);
 	}
 
+	// --- miscellaneous cases ---
+
+	@Test
+	public void testGetMergedBundleMap_automaticallyAddRequirements() throws Exception {
+
+		var targetPlatformBundles = Map.ofEntries( //
+				bundle("plugin.a", "1.0.0", //
+						entry(REQUIRE_BUNDLE, "plugin.b,plugin.c" + resolution(RESOLUTION_OPTIONAL))),
+				bundle("plugin.b", "1.0.0"), //
+				bundle("plugin.c", "1.0.0"));
+
+		Map<BundleLocationDescriptor, String> requiredRPBundlesWithoutOptional = FeatureBasedLaunchTest
+				.getEclipseAppRequirementClosureForRunningPlatform();
+
+		Map<BundleLocationDescriptor, String> requiredRPBundlesWithOptional = FeatureBasedLaunchTest
+				.getEclipseAppRequirementClosureForRunningPlatform(
+						DependencyManager.Options.INCLUDE_OPTIONAL_DEPENDENCIES);
+
+		TargetPlatformUtil.setRunningPlatformWithDummyBundlesAsTarget(null, targetPlatformBundles, List.of(),
+				tpJarDirectory);
+
+		Consumer<ILaunchConfigurationWorkingCopy> basicSetup = wc -> {
+			wc.setAttribute(IPDELauncherConstants.SELECTED_TARGET_BUNDLES, Set.of("plugin.a*1.0.0"));
+			wc.setAttribute(IPDELauncherConstants.USE_PRODUCT, true);
+			wc.setAttribute(IPDELauncherConstants.PRODUCT, "org.eclipse.platform.ide");
+			// prevent BundleLaunchHelper.migrateLaunchConfiguration() from
+			// adding more target plug-ins:
+			wc.setAttribute(IPDEConstants.LAUNCHER_PDE_VERSION, "3.3");
+		};
+
+		// test automatic-add-requirements disabled (which is the default)
+		for (Boolean autoAddRequirements : Arrays.asList(false, null)) {
+			assertGetMergedBundleMap(wc -> {
+				basicSetup.accept(wc);
+				wc.setAttribute(IPDELauncherConstants.AUTOMATIC_INCLUDE_REQUIREMENTS, autoAddRequirements);
+			}, toDefaultStartData(Set.of( //
+					targetBundle("plugin.a", "1.0.0"))));
+		}
+
+		// test enabled automatic-add-requirements
+		// with optional requirements enabled (which is the default)
+		for (Boolean includeOptional : Arrays.asList(true, null)) {
+			assertGetMergedBundleMap(wc -> {
+				basicSetup.accept(wc);
+				wc.setAttribute(IPDELauncherConstants.AUTOMATIC_INCLUDE_REQUIREMENTS, true);
+				wc.setAttribute(IPDELauncherConstants.INCLUDE_OPTIONAL, includeOptional);
+			}, concat(requiredRPBundlesWithOptional, toDefaultStartData(Set.of( //
+					targetBundle("plugin.a", "1.0.0"), //
+					targetBundle("plugin.b", "1.0.0"), //
+					targetBundle("plugin.c", "1.0.0")))));
+		}
+
+		// test automatic-add-requirements enabled without optional requirements
+		assertGetMergedBundleMap(wc -> {
+			basicSetup.accept(wc);
+			wc.setAttribute(IPDELauncherConstants.AUTOMATIC_INCLUDE_REQUIREMENTS, true);
+			wc.setAttribute(IPDELauncherConstants.INCLUDE_OPTIONAL, false);
+		}, concat(requiredRPBundlesWithoutOptional, toDefaultStartData(Set.of( //
+				targetBundle("plugin.a", "1.0.0"), //
+				targetBundle("plugin.b", "1.0.0")))));
+	}
+
 	// --- test cases for writeBundleEntry() ----
 
 	@Test
@@ -828,6 +898,11 @@
 
 		setUpWorkspace(workspacePlugins, targetPlugins);
 
+		assertGetMergedBundleMap(launchConfigPreparer, expectedBundleMap);
+	}
+
+	private static void assertGetMergedBundleMap(Consumer<ILaunchConfigurationWorkingCopy> launchConfigPreparer,
+			Map<BundleLocationDescriptor, String> expectedBundleMap) throws CoreException {
 		ILaunchConfigurationWorkingCopy wc = createPluginLaunchConfig("plugin-based-Eclipse-app");
 		launchConfigPreparer.accept(wc);
 
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/PDEUIMessages.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/PDEUIMessages.java
index a34dce8..25516c7 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/PDEUIMessages.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/PDEUIMessages.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2014, 2021 IBM Corporation and others.
+ * Copyright (c) 2014, 2022 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -1055,6 +1055,10 @@
 	public static String AdvancedLauncherTab_subset;
 	public static String AdvancedLauncherTab_subset_plugins;
 	public static String AdvancedLauncherTab_subset_bundles;
+	public static String AdvancedLauncherTab_autoIncludeRequirements_bundles;
+	public static String AdvancedLauncherTab_autoIncludeRequirements_plugins;
+	public static String AdvancedLauncherTab_autoIncludeRequirements_features_withBundles;
+	public static String AdvancedLauncherTab_autoIncludeRequirements_features_withPlugins;
 	public static String AdvancedLauncherTab_addNew;
 	public static String AdvancedLauncherTab_addNew_plugins;
 	public static String AdvancedLauncherTab_addNew_bundles;
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/launcher/AbstractPluginBlock.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/launcher/AbstractPluginBlock.java
index e79d6cf..9873f9e 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/launcher/AbstractPluginBlock.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/launcher/AbstractPluginBlock.java
@@ -54,8 +54,7 @@
 import org.eclipse.swt.SWTException;
 import org.eclipse.swt.custom.CCombo;
 import org.eclipse.swt.custom.TreeEditor;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.*;
 import org.eclipse.swt.graphics.Image;
 import org.eclipse.swt.graphics.Point;
 import org.eclipse.swt.layout.GridData;
@@ -78,6 +77,8 @@
 	protected int fNumExternalChecked;
 	protected int fNumWorkspaceChecked;
 
+	private Button fAutoIncludeRequirementsButton;
+	private boolean fAutoIncludeRequirementsButtonChanged;
 	private Button fIncludeOptionalButton;
 	protected Button fAddWorkspaceButton;
 	private Button fAutoValidate;
@@ -312,6 +313,15 @@
 	public void createControl(Composite parent, int span, int indent) {
 		createPluginViewer(parent, span - 1, indent);
 		createButtonContainer(parent);
+
+		if (fTab instanceof PluginsTab) {
+			fAutoIncludeRequirementsButton = createButton(parent, span, indent, PDEUIMessages.AdvancedLauncherTab_autoIncludeRequirements_plugins );
+		} else if (fTab instanceof BundlesTab) {
+			fAutoIncludeRequirementsButton = createButton(parent, span, indent, PDEUIMessages.AdvancedLauncherTab_autoIncludeRequirements_bundles);
+		}
+		fAutoIncludeRequirementsButton.addSelectionListener(
+				SelectionListener.widgetSelectedAdapter(e -> this.fAutoIncludeRequirementsButtonChanged = true));
+
 		if (fTab instanceof PluginsTab) {
 			fIncludeOptionalButton = createButton(parent, span, indent,PDEUIMessages.AdvancedLauncherTab_includeOptional_plugins);
 		}else if (fTab instanceof BundlesTab) {
@@ -652,10 +662,10 @@
 			autoText = autoText == null || autoText.length() == 0 ? "default" : autoText; //$NON-NLS-1$
 
 			// Replace run levels and auto start values for certain important system bundles
-			String systemValue = BundleLauncherHelper.resolveSystemRunLevelText(model);
+			String systemValue = BundleLauncherHelper.resolveSystemRunLevelText(model.getBundleDescription());
 			levelText = systemValue != null ? systemValue : levelText;
 
-			systemValue = BundleLauncherHelper.resolveSystemAutoText(model);
+			systemValue = BundleLauncherHelper.resolveSystemAutoText(model.getBundleDescription());
 			autoText = systemValue != null ? systemValue : autoText;
 
 			// Recache the values in case they changed.  I believe the code to only recache
@@ -791,6 +801,8 @@
 	}
 
 	protected void initializeButtonsFrom(ILaunchConfiguration config) throws CoreException {
+		boolean autoIncludeRequired = config.getAttribute(IPDELauncherConstants.AUTOMATIC_INCLUDE_REQUIREMENTS, false);
+		fAutoIncludeRequirementsButton.setSelection(autoIncludeRequired);
 		fIncludeOptionalButton.setSelection(config.getAttribute(IPDELauncherConstants.INCLUDE_OPTIONAL, true));
 		fAddWorkspaceButton.setSelection(config.getAttribute(IPDELauncherConstants.AUTOMATIC_ADD, true));
 		fAutoValidate.setSelection(config.getAttribute(IPDELauncherConstants.AUTOMATIC_VALIDATE, true));
@@ -876,6 +888,11 @@
 	}
 
 	public void performApply(ILaunchConfigurationWorkingCopy config) {
+		if (fAutoIncludeRequirementsButtonChanged) {
+			boolean includeRequirements = fAutoIncludeRequirementsButton.getSelection();
+			config.setAttribute(IPDELauncherConstants.AUTOMATIC_INCLUDE_REQUIREMENTS, includeRequirements);
+			fAutoIncludeRequirementsButtonChanged = false;
+		}
 		config.setAttribute(IPDELauncherConstants.INCLUDE_OPTIONAL, fIncludeOptionalButton.getSelection());
 		config.setAttribute(IPDELauncherConstants.AUTOMATIC_ADD, fAddWorkspaceButton.getSelection());
 		config.setAttribute(IPDELauncherConstants.AUTOMATIC_VALIDATE, fAutoValidate.getSelection());
@@ -900,6 +917,7 @@
 		fWorkingSetButton.setEnabled(enable);
 		fSelectAllButton.setEnabled(enable);
 		fDeselectButton.setEnabled(enable);
+		fAutoIncludeRequirementsButton.setEnabled(enable);
 		fIncludeOptionalButton.setEnabled(enable);
 		fAddWorkspaceButton.setEnabled(enable);
 		fCounter.setEnabled(enable);
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/launcher/FeatureBlock.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/launcher/FeatureBlock.java
index c41a9eb..fd0700d 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/launcher/FeatureBlock.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/launcher/FeatureBlock.java
@@ -798,6 +798,8 @@
 	private Button fSelectAllButton;
 	private Button fDeselectAllButton;
 
+	private Button fAutoIncludeRequirementsButton;
+	private boolean fAutoIncludeRequirementsButtonChanged;
 	private Button fWorkspacePluginButton;
 	private Button fExternalPluginButton;
 	private Button fFilterButton;
@@ -850,6 +852,15 @@
 		createCheckBoxTree(treeGroup);
 		createButtonContainer(treeGroup, 10);
 
+		if (fTab instanceof PluginsTab) {
+			fAutoIncludeRequirementsButton = SWTFactory.createCheckButton(treeGroup, PDEUIMessages.AdvancedLauncherTab_autoIncludeRequirements_features_withPlugins, null, false, 1);
+		} else if (fTab instanceof BundlesTab) {
+			fAutoIncludeRequirementsButton = SWTFactory.createCheckButton(treeGroup, PDEUIMessages.AdvancedLauncherTab_autoIncludeRequirements_features_withBundles, null, false, 1);
+		}
+		fAutoIncludeRequirementsButton.addSelectionListener(fListener);
+		fAutoIncludeRequirementsButton.addSelectionListener(
+				SelectionListener.widgetSelectedAdapter(e -> this.fAutoIncludeRequirementsButtonChanged = true));
+
 		fFeatureWorkspaceButton = SWTFactory.createCheckButton(treeGroup, PDEUIMessages.FeatureBlock_UseWorkspaceFeatures, null, true, 2);
 		fFeatureWorkspaceButton.addSelectionListener(fListener);
 
@@ -1114,6 +1125,8 @@
 		setInput(config, fTree);
 
 		// Setup other buttons
+		boolean autoIncludeRequired = config.getAttribute(IPDELauncherConstants.AUTOMATIC_INCLUDE_REQUIREMENTS, true);
+		fAutoIncludeRequirementsButton.setSelection(autoIncludeRequired);
 		String pluginResolution = config.getAttribute(IPDELauncherConstants.FEATURE_PLUGIN_RESOLUTION, IPDELauncherConstants.LOCATION_WORKSPACE);
 		if (pluginResolution.equalsIgnoreCase(IPDELauncherConstants.LOCATION_WORKSPACE)) {
 			fWorkspacePluginButton.setSelection(true);
@@ -1147,6 +1160,11 @@
 	}
 
 	public void performApply(ILaunchConfigurationWorkingCopy config) {
+		if (fAutoIncludeRequirementsButtonChanged) {
+			boolean includeRequirements = fAutoIncludeRequirementsButton.getSelection();
+			config.setAttribute(IPDELauncherConstants.AUTOMATIC_INCLUDE_REQUIREMENTS, includeRequirements);
+			fAutoIncludeRequirementsButtonChanged = false;
+		}
 		config.setAttribute(IPDELauncherConstants.SHOW_SELECTED_ONLY, fFilterButton.getSelection());
 		config.setAttribute(IPDELauncherConstants.FEATURE_DEFAULT_LOCATION, fFeatureWorkspaceButton.getSelection() ? IPDELauncherConstants.LOCATION_WORKSPACE : IPDELauncherConstants.LOCATION_EXTERNAL);
 		config.setAttribute(IPDELauncherConstants.FEATURE_PLUGIN_RESOLUTION, fWorkspacePluginButton.getSelection() ? IPDELauncherConstants.LOCATION_WORKSPACE : IPDELauncherConstants.LOCATION_EXTERNAL);
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/pderesources.properties b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/pderesources.properties
index b197d49..7075fa7 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/pderesources.properties
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/pderesources.properties
@@ -569,6 +569,10 @@
 AdvancedLauncherTab_subset=Add Re&quired {0}
 AdvancedLauncherTab_subset_bundles=Select Re&quired
 AdvancedLauncherTab_subset_plugins=Select Re&quired
+AdvancedLauncherTab_autoIncludeRequirements_bundles=Include required Bundles automatically while launching
+AdvancedLauncherTab_autoIncludeRequirements_plugins=Include required Plug-ins automatically while launching
+AdvancedLauncherTab_autoIncludeRequirements_features_withBundles=Include required Features and Bundles automatically while launching
+AdvancedLauncherTab_autoIncludeRequirements_features_withPlugins=Include required Features and Plug-ins automatically while launching
 AdvancedLauncherTab_selectedBundles = Only s&how selected
 AdvancedLauncherTab_addNew=&Add new workspace {0} to this launch configuration automatically
 AdvancedLauncherTab_addNew_plugins=&Add new workspace Plug-ins to this launch configuration automatically