Bug 522332 - Add Required Plug-ins: resolve unsatisfied constraints
Collect dependencies recursively until no additional dependency can be
computed.
Then perform a validation operation to detect unsatisfied constraints
and try to resolve bundles from the target platform state that are
satisfying these
constraints.
Change-Id: Ib4a1bf310240bc2df5db8993639aa478ea1d8cb9
Signed-off-by: Karsten Thoms <karsten.thoms@itemis.de>
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/DependencyManager.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/DependencyManager.java
index 8fe37c5..eb4756d 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/DependencyManager.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/DependencyManager.java
@@ -7,14 +7,16 @@
*
* Contributors:
* IBM Corporation - initial API and implementation
+ * Karsten Thoms <karsten.thoms@itemis.de> - Bug 522332
*******************************************************************************/
package org.eclipse.pde.internal.core;
import java.util.*;
+import java.util.stream.Collectors;
import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.osgi.service.resolver.*;
-import org.eclipse.pde.core.plugin.IPluginExtension;
-import org.eclipse.pde.core.plugin.IPluginModelBase;
+import org.eclipse.pde.core.plugin.*;
import org.eclipse.pde.core.target.ITargetPlatformService;
import org.eclipse.pde.core.target.NameVersionDescriptor;
import org.osgi.framework.Constants;
@@ -27,7 +29,6 @@
* @noinstantiate This class is not intended to be instantiated by clients.
*/
public class DependencyManager {
-
/**
* Returns a {@link Set} of bundle ids for the dependents of the given
* {@link IPluginModelBase}. The set includes the id of the given model base
@@ -106,29 +107,46 @@
}
/**
- * Returns a {@link Set} of bundle ids for the dependents of the given
- * objects from the given {@link State}.
- * The set additionally only includes the given set of implicit dependencies.
+ * Returns a {@link Set} of bundle ids for the dependents of the given objects
+ * from the given {@link State}. The set additionally only includes the given
+ * set of implicit dependencies.
*
- * @param selected selected the group of objects to compute dependencies for. Any items
- * in this array that are not {@link IPluginModelBase}s are ignored.
- * @param implicit the array of additional implicit dependencies to add to the {@link Set}
- * @param state the {@link State} to compute the dependencies in
- * @param removeSelf if the id of one of the bundles were are computing dependencies for should be
- * included in the result {@link Set} or not
- * @param includeOptional if optional bundle ids should be included
- * @param excludeFragments a collection of <b>fragment</b> bundle symbolic names to exclude from the dependency resolution
+ * @param selected
+ * selected the group of objects to compute dependencies for. Any
+ * items in this array that are not {@link IPluginModelBase}s are
+ * ignored.
+ * @param implicit
+ * the array of additional implicit dependencies to add to the
+ * {@link Set}
+ * @param state
+ * the {@link State} to compute the dependencies in
+ * @param removeSelf
+ * if the id of one of the bundles were are computing dependencies
+ * for should be included in the result {@link Set} or not
+ * @param includeOptional
+ * if optional bundle ids should be included
+ * @param excludeFragments
+ * a collection of <b>fragment</b> bundle symbolic names to exclude
+ * from the dependency resolution
* @return a set of bundle IDs
*/
- private static Set<String> getDependencies(Object[] selected, String[] implicit, State state, boolean removeSelf, boolean includeOptional, Set<String> excludeFragments) {
+ private static Set<String> getDependencies(Object[] selected, String[] implicit, State state, boolean removeSelf,
+ boolean includeOptional, Set<String> excludeFragments) {
Set<String> set = new TreeSet<>();
+ Set<IPluginModelBase> models = new HashSet<>();
+
+ // For all selected bundles add their bundle dependencies.
+ // Also consider plugin extensions and their dependencies.
for (int i = 0; i < selected.length; i++) {
if (!(selected[i] instanceof IPluginModelBase))
continue;
IPluginModelBase model = (IPluginModelBase) selected[i];
+ models.add(model);
addBundleAndDependencies(model.getBundleDescription(), set, includeOptional, excludeFragments);
IPluginExtension[] extensions = model.getPluginBase().getExtensions();
for (IPluginExtension extension : extensions) {
+ // TODO: this loop might be useless, because dependencies are already defined in
+ // the manifest
String point = extension.getPoint();
if (point != null) {
int dot = point.lastIndexOf('.');
@@ -153,6 +171,29 @@
set.remove(model.getPluginBase().getId());
}
}
+
+ if (!set.isEmpty()) {
+ // validate all models and try to add bundles that resolve constraint violations
+ for (IPluginModelBase model : DependencyManager.getDependencies(TargetPlatformHelper.getState(),
+ models.toArray(new IPluginModelBase[models.size()]))) {
+ set.add(model.getBundleDescription().getSymbolicName());
+ }
+
+ // build array with all selected plus calculated dependencies and recurse
+ // loop ends when no more additional dependencies are calculated
+ for (String id : set) {
+ ModelEntry entry = PluginRegistry.findEntry(id);
+ if (entry != null) {
+ models.add(entry.getModel());
+ }
+ }
+
+ Set<String> additionalIds = getDependencies(models.toArray(), implicit, state, removeSelf, includeOptional,
+ excludeFragments);
+ set.addAll(additionalIds);
+ }
+
+
return set;
}
@@ -221,4 +262,73 @@
}
}
+ /**
+ * Validates the given models and retrieves bundle IDs that satisfy violated
+ * constraints. This method uses the {@link BundleValidationOperation} to
+ * determine unsatisfied constraints for the given plugin models.
+ *
+ * @param state
+ * the {@link State} to compute the dependencies in
+ * @param models
+ * the array of {@link IPluginModelBase}s to compute dependencies for
+ *
+ * @return a set of bundle IDs
+ */
+ private static Set<IPluginModelBase> getDependencies(State state, IPluginModelBase[] models) {
+ Set<IPluginModelBase> dependencies = new HashSet<>();
+ BundleValidationOperation operation = new BundleValidationOperation(models);
+ try {
+ operation.run(new NullProgressMonitor());
+ Map<Object, Object[]> input = operation.getResolverErrors();
+ // extract the unsatisfied constraints from the operation's result structure
+ VersionConstraint[] unsatisfiedConstraints = input.values().stream()
+ .filter(ResolverError[].class::isInstance)
+ .map(ResolverError[].class::cast)
+ .flatMap(arr -> Arrays.stream(arr))
+ .filter(err -> err.getUnsatisfiedConstraint() != null)
+ .map(err -> err.getUnsatisfiedConstraint())
+ .toArray(VersionConstraint[]::new);
+
+ for (VersionConstraint constraint : unsatisfiedConstraints) {
+ // first try to find a solution in the set of additionally computed
+ // bundles that satisfy constraints.
+ if (dependencies.stream()
+ .anyMatch(pmb -> satisfiesConstraint(pmb.getBundleDescription(), constraint))) {
+ continue;
+ }
+ // determine all bundles from the target platform state that satisfy the current
+ // constraint
+ List<BundleDescription> satisfyingBundles = Arrays.stream(state.getBundles())
+ .filter(desc -> satisfiesConstraint(desc, constraint)).collect(Collectors.toList());
+
+ // It is possible to have none, exactly one, or in rare cases multiple bundles
+ // that satisfy the constraint.
+ for (BundleDescription bundle : satisfyingBundles) {
+ ModelEntry entry = PluginRegistry.findEntry(bundle.getSymbolicName());
+ if (entry != null) {
+ dependencies.add(entry.getModel());
+ }
+ }
+ }
+ return dependencies;
+ } catch (CoreException e) {
+ PDECore.log(e);
+ return Collections.emptySet();
+ }
+ }
+
+ private static boolean satisfiesConstraint(BundleDescription desc, VersionConstraint constraint) {
+ if (constraint instanceof GenericSpecification) {
+ for (GenericDescription description : desc.getGenericCapabilities()) {
+ if (constraint.isSatisfiedBy(description)) {
+ return true;
+ }
+ }
+ } else if (constraint instanceof BundleSpecification) {
+ return constraint.getName().equals(desc.getName())
+ && constraint.getVersionRange().isIncluded(desc.getVersion());
+ }
+ return false;
+ }
+
}
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 d3f47ec..2d7450a 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
@@ -10,6 +10,7 @@
* Ian Bull <irbull@cs.uvic.ca> - bug 204404 and bug 207064
* EclipseSource Corporation - ongoing enhancements
* Lars Vogel <Lars.Vogel@vogella.com> - Bug 487943
+ * Karsten Thoms <karsten.thoms@itemis.de> - Bug 522332
*******************************************************************************/
package org.eclipse.pde.internal.ui.launcher;
@@ -17,6 +18,7 @@
import java.util.*;
import java.util.List;
+import java.util.stream.Collectors;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.*;
@@ -828,31 +830,19 @@
*/
protected void addRequiredPlugins() {
Object[] checked = fPluginTreeViewer.getCheckedElements();
- ArrayList<Object> toCheck = new ArrayList<>(checked.length);
- for (Object checkedElement : checked) {
- if (checkedElement instanceof IPluginModelBase) {
- toCheck.add(checkedElement);
- }
- }
+ Set<IPluginModelBase> toCheck = Arrays.stream(checked).filter(IPluginModelBase.class::isInstance)
+ .map(IPluginModelBase.class::cast).collect(Collectors.toSet());
- Set<?> additionalIds = DependencyManager.getDependencies(checked, fIncludeOptionalButton.getSelection(), null);
+ Set<String> additionalIds = DependencyManager.getDependencies(toCheck.toArray(),
+ fIncludeOptionalButton.getSelection(), null);
- Iterator<?> it = additionalIds.iterator();
- while (it.hasNext()) {
- String id = (String) it.next();
- if (findPlugin(id) == null) {
- ModelEntry entry = PluginRegistry.findEntry(id);
- if (entry != null) {
- IPluginModelBase model = entry.getModel();
- if (model != null) {
- toCheck.add(model);
- }
- }
- }
- }
+ additionalIds.stream().map(id -> PluginRegistry.findEntry(id))
+ .filter(Objects::nonNull).map(entry -> entry.getModel())
+ .forEach(model -> toCheck.add(model));
checked = toCheck.toArray();
setCheckedElements(checked);
+
fNumExternalChecked = 0;
fNumWorkspaceChecked = 0;
for (Object checkedElement : checked) {