Skip to content

Commit f3312fa

Browse files
committed
Prefer explicitly included plugins for Eclipse/OSGi launches
When launching an application create a new Equinox resolver state, where explicitly included plugins/bundles are preferred and compute the requirements closure from that state. This avoids adding other providers of a capability if another provider is already (explicitly) included in a launch, just because the former is wired to the requirement in the target-platform state. Potentially this also makes the set of launched plugins more compact. Fixes #1604
1 parent 88117b8 commit f3312fa

2 files changed

Lines changed: 110 additions & 14 deletions

File tree

ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/DependencyManager.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ private static boolean isTestWorkspaceProject(Resource f) {
262262
*
263263
* @return a set of bundle ids
264264
*/
265-
private static Collection<NameVersionDescriptor> getImplicitDependencies() {
265+
public static Collection<NameVersionDescriptor> getImplicitDependencies() {
266266
try {
267267
ITargetPlatformService service = PDECore.getDefault().acquireService(ITargetPlatformService.class);
268268
if (service != null) {

ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/BundleLauncherHelper.java

Lines changed: 109 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.util.ArrayDeque;
2929
import java.util.ArrayList;
3030
import java.util.Arrays;
31+
import java.util.Collection;
3132
import java.util.Collections;
3233
import java.util.Comparator;
3334
import java.util.HashMap;
@@ -50,7 +51,10 @@
5051
import org.eclipse.core.runtime.Status;
5152
import org.eclipse.debug.core.ILaunchConfiguration;
5253
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
54+
import org.eclipse.osgi.service.resolver.BaseDescription;
5355
import org.eclipse.osgi.service.resolver.BundleDescription;
56+
import org.eclipse.osgi.service.resolver.State;
57+
import org.eclipse.osgi.service.resolver.StateObjectFactory;
5458
import org.eclipse.osgi.util.NLS;
5559
import org.eclipse.pde.core.plugin.IMatchRules;
5660
import org.eclipse.pde.core.plugin.IPluginBase;
@@ -59,6 +63,7 @@
5963
import org.eclipse.pde.core.plugin.PluginRegistry;
6064
import org.eclipse.pde.core.target.ITargetDefinition;
6165
import org.eclipse.pde.core.target.ITargetPlatformService;
66+
import org.eclipse.pde.internal.build.BundleHelper;
6267
import org.eclipse.pde.internal.build.IPDEBuildConstants;
6368
import org.eclipse.pde.internal.core.DependencyManager;
6469
import org.eclipse.pde.internal.core.FeatureModelManager;
@@ -75,7 +80,6 @@
7580
import org.eclipse.pde.internal.launching.PDEMessages;
7681
import org.eclipse.pde.launching.IPDELauncherConstants;
7782
import org.osgi.framework.Version;
78-
import org.osgi.resource.Resource;
7983

8084
public class BundleLauncherHelper {
8185

@@ -166,12 +170,7 @@ private static void addRequiredBundles(Map<IPluginModelBase, String> bundle2star
166170
RequirementHelper.addApplicationLaunchRequirements(appRequirements, configuration, bundle2startLevel);
167171

168172
boolean includeOptional = configuration.getAttribute(IPDELauncherConstants.INCLUDE_OPTIONAL, true);
169-
Set<BundleDescription> requiredDependencies = includeOptional //
170-
? DependencyManager.getDependencies(bundle2startLevel.keySet(), DependencyManager.Options.INCLUDE_OPTIONAL_DEPENDENCIES)
171-
: DependencyManager.getDependencies(bundle2startLevel.keySet());
172-
173-
requiredDependencies.stream().map(Resource.class::cast) //
174-
.map(PluginRegistry::findModel).filter(Objects::nonNull) //
173+
computeDependencies(bundle2startLevel.keySet(), includeOptional, IPDELauncherConstants.LOCATION_WORKSPACE) //
175174
.forEach(p -> addDefaultStartingBundle(bundle2startLevel, p));
176175
}
177176

@@ -227,12 +226,8 @@ private static Map<IPluginModelBase, String> getMergedBundleMapFeatureBased(ILau
227226
List<String> appRequirements = RequirementHelper.getApplicationLaunchRequirements(configuration);
228227
RequirementHelper.addApplicationLaunchRequirements(appRequirements, configuration, launchPlugins, launchPlugins::add);
229228

230-
// Get all required plugins
231-
Set<BundleDescription> additionalBundles = DependencyManager.getDependencies(launchPlugins);
232-
for (BundleDescription bundle : additionalBundles) {
233-
IPluginModelBase plugin = getRequiredPlugin(bundle.getSymbolicName(), bundle.getVersion().toString(), IMatchRules.PERFECT, defaultPluginResolution);
234-
launchPlugins.add(Objects.requireNonNull(plugin));// should never be null
235-
}
229+
computeDependencies(launchPlugins, false, defaultPluginResolution) //
230+
.map(Objects::requireNonNull).forEach(launchPlugins::add);
236231
}
237232

238233
// Create the start levels for the selected plugins and add them to the map
@@ -546,6 +541,107 @@ private static void addBundleToMap(Map<IPluginModelBase, String> map, IPluginMod
546541
map.put(bundle, startData);
547542
}
548543

544+
// --- dependency resolution ---
545+
546+
private static Stream<IPluginModelBase> computeDependencies(Set<IPluginModelBase> includedPlugins, boolean includeOptional, String defaultPluginResolution) {
547+
if (includedPlugins.isEmpty()) {
548+
return Stream.empty();
549+
}
550+
Set<BundleDescription> launchBundles = resolveBundlesInNewStatePreferringIncludedBundles(includedPlugins, defaultPluginResolution);
551+
State launchState = launchBundles.iterator().next().getContainingState();
552+
553+
DependencyManager.getImplicitDependencies().stream().map(descriptor -> {
554+
String versionStr = descriptor.getVersion();
555+
Version version = versionStr != null ? Version.parseVersion(versionStr) : null;
556+
return launchState.getBundle(descriptor.getId(), version);
557+
}).forEach(launchBundles::add);
558+
559+
DependencyManager.Options[] options = includeOptional //
560+
? new DependencyManager.Options[] {DependencyManager.Options.INCLUDE_OPTIONAL_DEPENDENCIES}
561+
: new DependencyManager.Options[] {};
562+
Set<BundleDescription> closure = DependencyManager.findRequirementsClosure(launchBundles, options);
563+
return closure.stream() //
564+
.map(bundle -> {
565+
// Ensure that the preferred plugin-location is respected (therefore not just use PluginRegistry.findModel)
566+
//TODO: incorporate this when creating the new state?!
567+
return getRequiredPlugin(bundle.getSymbolicName(), bundle.getVersion().toString(), IMatchRules.PERFECT, defaultPluginResolution);
568+
}).map(Objects::requireNonNull) //TODO: check if this can ever be null?!
569+
.filter(p -> !includedPlugins.contains(p));
570+
}
571+
572+
private static Set<BundleDescription> resolveBundlesInNewStatePreferringIncludedBundles(Collection<IPluginModelBase> includedPlugins, String defaultPluginResolution) {
573+
Set<BundleDescription> includedTPBundles = new HashSet<>();
574+
Set<BundleDescription> launchBundles = new HashSet<>();
575+
State tpState = TargetPlatformHelper.getState();
576+
577+
StateObjectFactory factory = BundleHelper.getPlatformAdmin().getFactory();
578+
State launchState = factory.createState(true);
579+
//TODO: ensure this is correct?!
580+
launchState.setPlatformProperties(tpState.getPlatformProperties());
581+
582+
Comparator<BaseDescription> selectionPolicy = Comparator.<BaseDescription, Boolean> //
583+
comparing(d -> launchBundles.contains(d.getSupplier())).reversed() //false<true
584+
.thenComparing(tpState.getResolver().getSelectionPolicy());
585+
launchState.getResolver().setSelectionPolicy(selectionPolicy);
586+
587+
// Collect all bundles explicitly included in the launch
588+
for (IPluginModelBase plugin : includedPlugins) {
589+
BundleDescription bundle = plugin.getBundleDescription();
590+
if (bundle != null) {
591+
if (bundle.getContainingState() != tpState) { // consistency check
592+
throw new IllegalStateException("Plugins have different TP state"); //$NON-NLS-1$
593+
}
594+
includedTPBundles.add(bundle);
595+
BundleDescription launchBundle = factory.createBundleDescription(bundle);
596+
if (!launchState.addBundle(launchBundle)) {
597+
throw new IllegalStateException("Failed to add bundle to launch state: " + launchBundle); //$NON-NLS-1$
598+
}
599+
if (!launchBundles.add(launchBundle)) {
600+
throw new IllegalStateException("Duplicated launch bundle for plugin: " + plugin); //$NON-NLS-1$
601+
}
602+
}
603+
}
604+
// Add all other bundles from the tpState and resolve the set of explicitly included bundles against them
605+
for (BundleDescription bundle : tpState.getBundles()) {
606+
if (!includedTPBundles.contains(bundle)) {
607+
BundleDescription launchBundle = factory.createBundleDescription(bundle);
608+
if (!launchState.addBundle(launchBundle)) {
609+
throw new IllegalStateException("Failed to add bundle to launch state: " + launchBundle); //$NON-NLS-1$
610+
}
611+
}
612+
}
613+
launchState.resolve(false);
614+
return launchBundles;
615+
}
616+
617+
// -- start data ---
618+
619+
// private static Comparator<BaseDescription> getDefaultSelectionPolicy(Dictionary<?, ?>[] platformProperties) {
620+
// Object preferSystem = platformProperties.length == 0 ? null : platformProperties[0].get("osgi.resolver.preferSystemPackages"); //$NON-NLS-1$
621+
// boolean preferSystemPackages = preferSystem == null || Boolean.parseBoolean(preferSystem.toString());
622+
// String systemBundle = Optional.ofNullable(platformProperties.length == 0 ? null : (String) platformProperties[0].get("osgi.system.bundle")).orElse(EquinoxContainer.NAME); //$NON-NLS-1$
623+
//
624+
// return (BaseDescription d1, BaseDescription d2) -> {
625+
// // Assume BaseDescription.getName() == BundleDescription.getSymbolicName()
626+
// if (preferSystemPackages) {
627+
// if (systemBundle.equals(d1.getSupplier().getSymbolicName()) && !systemBundle.equals(d2.getSupplier().getSymbolicName())) {
628+
// return -1;
629+
// } else if (!systemBundle.equals(d1.getSupplier().getSymbolicName()) && systemBundle.equals(d2.getSupplier().getSymbolicName())) {
630+
// return 1;
631+
// }
632+
// }
633+
// ;
634+
// if (d1.getSupplier().isResolved() != d2.getSupplier().isResolved()) {
635+
// return d1.getSupplier().isResolved() ? -1 : 1;
636+
// }
637+
// int versionCompare = -(d1.getVersion().compareTo(d2.getVersion()));
638+
// if (versionCompare != 0) {
639+
// return versionCompare;
640+
// }
641+
// return Long.compare(d1.getSupplier().getBundleId(), d2.getSupplier().getBundleId());
642+
// };
643+
// }
644+
549645
public static String getStartData(BundleDescription desc, String defaultStartData) {
550646
String runLevel = resolveSystemRunLevelText(desc);
551647
String auto = resolveSystemAutoText(desc);

0 commit comments

Comments
 (0)