From 4a39b833a2217c54ba73b19504a8cf9eb4db5415 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20L=C3=A4ubrich?= Date: Sat, 15 Feb 2025 11:45:00 +0100 Subject: [PATCH 1/2] Don't include more bundles if the requirement is already fulfilled Currently PDE includes everything wired in the target platform even though a requirement might already be fulfilled and only has single cardinality. This now tracks all capabilities provided and check if a single cardinality is already fulfilled by some of the bundles chosen. --- .../pde/internal/core/DependencyManager.java | 47 ++++++++++++++++--- 1 file changed, 40 insertions(+), 7 deletions(-) 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 692bbe53321..4f3d72f3b69 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 @@ -20,8 +20,10 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Queue; import java.util.Set; @@ -36,10 +38,13 @@ import org.osgi.framework.Constants; import org.osgi.framework.Version; import org.osgi.framework.namespace.HostNamespace; +import org.osgi.framework.wiring.BundleCapability; import org.osgi.framework.wiring.BundleRequirement; import org.osgi.framework.wiring.BundleRevision; import org.osgi.framework.wiring.BundleWire; import org.osgi.framework.wiring.BundleWiring; +import org.osgi.resource.Capability; +import org.osgi.resource.Namespace; import org.osgi.resource.Resource; /** @@ -173,12 +178,12 @@ public static Set findRequirementsClosure(Collection closure = new HashSet<>(bundles.size() * 4 / 3 + 1); Queue pending = new ArrayDeque<>(bundles.size()); + Map> provided = new HashMap<>(); // initialize with given bundles for (BundleDescription bundle : bundles) { - addNewRequiredBundle(bundle, closure, pending); + addNewRequiredBundle(bundle, closure, pending, provided); } - // perform exhaustive iterative bfs for required wires while (!pending.isEmpty()) { BundleDescription bundle = pending.remove(); @@ -192,7 +197,7 @@ public static Set findRequirementsClosure(Collection findRequirementsClosure(Collection requiredWires = wiring.getRequiredWires(null); for (BundleWire wire : requiredWires) { - BundleRevision declaringBundle = wire.getRequirement().getRevision(); + BundleRequirement requirement = wire.getRequirement(); + if (isSingle(requirement) && isAlreadyProvided(requirement, provided)) { + continue; + } + BundleRevision declaringBundle = requirement.getRevision(); if (declaringBundle != bundle && !closure.contains(declaringBundle)) { // Requirement is declared by an attached fragment, which is // not included into the closure. @@ -221,18 +230,42 @@ public static Set findRequirementsClosure(Collection> provided) { + List list = provided.get(requirement.getNamespace()); + if (list != null && !list.isEmpty()) { + for (BundleCapability bundleCapability : list) { + if (requirement.matches(bundleCapability)) { + return true; + } + } + } + return false; + } + private static void addNewRequiredBundle(BundleDescription bundle, Set requiredBundles, - Queue pending) { + Queue pending, Map> provided) { if (bundle != null && bundle.isResolved() && !bundle.isRemovalPending() && requiredBundles.add(bundle)) { pending.add(bundle); + List capabilities = bundle.getCapabilities(null); + for (Capability capability : capabilities) { + if (capability instanceof BundleCapability bc) { + provided.computeIfAbsent(capability.getNamespace(), nil -> new ArrayList<>()).add(bc); + } + } } } From 197ac6460e611735abe646e0bb2aabb4e19b8f11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20L=C3=A4ubrich?= Date: Sat, 3 May 2025 07:14:31 +0200 Subject: [PATCH 2/2] Use Capabilities Index --- .../pde/internal/core/Capabilities.java | 277 ++++++++++++++++++ .../pde/internal/core/DependencyManager.java | 41 +-- 2 files changed, 288 insertions(+), 30 deletions(-) create mode 100644 ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/Capabilities.java diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/Capabilities.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/Capabilities.java new file mode 100644 index 00000000000..7418a86b9d4 --- /dev/null +++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/Capabilities.java @@ -0,0 +1,277 @@ +package org.eclipse.pde.internal.core; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.osgi.internal.framework.FilterImpl; +import org.eclipse.osgi.util.ManifestElement; +import org.osgi.framework.Filter; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.namespace.AbstractWiringNamespace; +import org.osgi.framework.namespace.BundleNamespace; +import org.osgi.framework.namespace.HostNamespace; +import org.osgi.framework.namespace.PackageNamespace; +import org.osgi.framework.wiring.BundleRevision; +import org.osgi.resource.Capability; +import org.osgi.resource.Namespace; +import org.osgi.resource.Requirement; + +// TEMP adjust and reuse from Equinox +public class Capabilities { + static class NamespaceSet { + private final String name; + private final Map> indexes = new HashMap<>(); + private final Set all = new HashSet<>(); + private final Set nonStringIndexes = new HashSet<>(0); + private final boolean matchMandatory; + + NamespaceSet(String name) { + this.name = name; + this.matchMandatory = PackageNamespace.PACKAGE_NAMESPACE.equals(name) + || BundleNamespace.BUNDLE_NAMESPACE.equals(name) || HostNamespace.HOST_NAMESPACE.equals(name); + } + + void addCapability(Capability capability) { + if (!name.equals(capability.getNamespace())) { + throw new IllegalArgumentException( + "Invalid namespace: " + capability.getNamespace() + ": expecting: " + name); //$NON-NLS-1$ //$NON-NLS-2$ + } + all.add(capability); + // by convention we index by the namespace attribute + Object index = capability.getAttributes().get(name); + if (index == null) { + return; + } + Collection indexCollection = null; + if (index instanceof Collection) { + indexCollection = (Collection) index; + } else if (index.getClass().isArray()) { + indexCollection = Arrays.asList((Object[]) index); + } + if (indexCollection == null) { + addIndex(index, capability); + } else { + for (Object indexKey : indexCollection) { + addIndex(indexKey, capability); + } + } + } + + private void addIndex(Object indexKey, Capability capability) { + if (!(indexKey instanceof String)) { + nonStringIndexes.add(capability); + } else { + Set capabilities = indexes.get(indexKey); + if (capabilities == null) { + capabilities = new HashSet<>(1); + indexes.put((String) indexKey, capabilities); + } + capabilities.add(capability); + } + } + + void removeCapability(Capability capability) { + if (!name.equals(capability.getNamespace())) { + throw new IllegalArgumentException( + "Invalid namespace: " + capability.getNamespace() + ": expecting: " + name); //$NON-NLS-1$//$NON-NLS-2$ + } + all.remove(capability); + // by convention we index by the namespace attribute + Object index = capability.getAttributes().get(name); + if (index == null) { + return; + } + Collection indexCollection = null; + if (index instanceof Collection) { + indexCollection = (Collection) index; + } else if (index.getClass().isArray()) { + indexCollection = Arrays.asList((Object[]) index); + } + if (indexCollection == null) { + removeIndex(index, capability); + } else { + for (Object indexKey : indexCollection) { + removeIndex(indexKey, capability); + } + } + } + + private void removeIndex(Object indexKey, Capability capability) { + if (!(indexKey instanceof String)) { + nonStringIndexes.remove(capability); + } else { + Set capabilities = indexes.get(indexKey); + if (capabilities != null) { + capabilities.remove(capability); + } + } + } + + List findCapabilities(Requirement requirement) { + if (!name.equals(requirement.getNamespace())) { + throw new IllegalArgumentException( + "Invalid namespace: " + requirement.getNamespace() + ": expecting: " + name); //$NON-NLS-1$//$NON-NLS-2$ + } + FilterImpl f = null; + String filterSpec = requirement.getDirectives().get(Namespace.REQUIREMENT_FILTER_DIRECTIVE); + if (filterSpec != null) { + try { + f = FilterImpl.newInstance(filterSpec); + } catch (InvalidSyntaxException e) { + return Collections.emptyList(); + } + } + Object syntheticAttr = requirement.getAttributes().get(SYNTHETIC_REQUIREMENT); + boolean synthetic = syntheticAttr instanceof Boolean ? ((Boolean) syntheticAttr).booleanValue() : false; + + List result; + if (filterSpec == null) { + result = match(null, all, synthetic); + } else { + String indexKey = f.getPrimaryKeyValue(name); + if (indexKey == null) { + result = match(f, all, synthetic); + } else { + Set indexed = indexes.get(indexKey); + if (indexed == null) { + result = new ArrayList<>(0); + } else { + result = match(f, indexed, synthetic); + } + if (!nonStringIndexes.isEmpty()) { + List nonStringResult = match(f, nonStringIndexes, synthetic); + for (Capability capability : nonStringResult) { + if (!result.contains(capability)) { + result.add(capability); + } + } + } + } + } + return result; + } + + private List match(Filter f, Set candidates, boolean synthetic) { + List result = new ArrayList<>(1); + for (Capability candidate : candidates) { + if (matches(f, candidate, !synthetic && matchMandatory)) { + result.add(candidate); + } + } + return result; + } + } + + public static final Pattern MANDATORY_ATTR = Pattern.compile("\\(([^(=<>]+)\\s*[=<>]\\s*[^)]+\\)"); //$NON-NLS-1$ + public static final String SYNTHETIC_REQUIREMENT = "org.eclipse.osgi.container.synthetic"; //$NON-NLS-1$ + + public static boolean matches(Filter f, Capability candidate, boolean matchMandatory) { + if (f != null && !f.matches(candidate.getAttributes())) { + return false; + } + if (matchMandatory) { + // check for mandatory directive + String mandatory = candidate.getDirectives().get(AbstractWiringNamespace.CAPABILITY_MANDATORY_DIRECTIVE); + if (mandatory == null) { + return true; + } + if (f == null) { + return false; + } + Matcher matcher = MANDATORY_ATTR.matcher(f.toString()); + String[] mandatoryAttrs = ManifestElement.getArrayFromList(mandatory, ","); //$NON-NLS-1$ + boolean allPresent = true; + for (String mandatoryAttr : mandatoryAttrs) { + matcher.reset(); + boolean found = false; + while (matcher.find()) { + int numGroups = matcher.groupCount(); + for (int i = 1; i <= numGroups; i++) { + if (mandatoryAttr.equals(matcher.group(i))) { + found = true; + } + } + } + allPresent &= found; + } + return allPresent; + } + return true; + } + + Map namespaceSets = new HashMap<>(); + + /** + * Adds the {@link BundleRevision#getModuleCapabilities(String) + * capabilities} provided by the specified revision to this database. These + * capabilities must become available for lookup with the + * {@link #findCapabilities(Requirement)} method. + * + * @param revision + * the revision which has capabilities to add + * @return a collection of package names added for the osgi.wiring.package + * namespace + */ + public Collection addCapabilities(BundleRevision revision) { + Collection packageNames = null; + for (Capability capability : revision.getCapabilities(null)) { + NamespaceSet namespaceSet = namespaceSets.get(capability.getNamespace()); + if (namespaceSet == null) { + namespaceSet = new NamespaceSet(capability.getNamespace()); + namespaceSets.put(capability.getNamespace(), namespaceSet); + } + namespaceSet.addCapability(capability); + // For the package namespace we return a list of package names. + // This is used to clear the dynamic package miss caches. + if (PackageNamespace.PACKAGE_NAMESPACE.equals(capability.getNamespace())) { + Object packageName = capability.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE); + if (packageName instanceof String) { + if (packageNames == null) { + packageNames = new ArrayList<>(); + } + packageNames.add((String) packageName); + } + } + } + return packageNames == null ? Collections.emptyList() : packageNames; + } + + /** + * Removes the {@link BundleRevision#getCapabilities(String) capabilities} + * provided by the specified revision from this database. These capabilities + * must no longer be available for lookup with the + * {@link #findCapabilities(Requirement)} method. + */ + public void removeCapabilities(BundleRevision revision) { + for (Capability capability : revision.getCapabilities(null)) { + NamespaceSet namespaceSet = namespaceSets.get(capability.getNamespace()); + if (namespaceSet != null) { + namespaceSet.removeCapability(capability); + } + } + } + + /** + * Returns a mutable snapshot of capabilities that are candidates for satisfying + * the specified requirement. + * + * @param requirement the requirement + * @return the candidates for the requirement + */ + public List findCapabilities(Requirement requirement) { + NamespaceSet namespaceSet = namespaceSets.get(requirement.getNamespace()); + if (namespaceSet == null) { + return Collections.emptyList(); + } + return namespaceSet.findCapabilities(requirement); + } +} 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 4f3d72f3b69..563d7596d94 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 @@ -20,10 +20,8 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Queue; import java.util.Set; @@ -38,12 +36,10 @@ import org.osgi.framework.Constants; import org.osgi.framework.Version; import org.osgi.framework.namespace.HostNamespace; -import org.osgi.framework.wiring.BundleCapability; import org.osgi.framework.wiring.BundleRequirement; import org.osgi.framework.wiring.BundleRevision; import org.osgi.framework.wiring.BundleWire; import org.osgi.framework.wiring.BundleWiring; -import org.osgi.resource.Capability; import org.osgi.resource.Namespace; import org.osgi.resource.Resource; @@ -176,13 +172,15 @@ public static Set findRequirementsClosure(Collection closure = new HashSet<>(bundles.size() * 4 / 3 + 1); Queue pending = new ArrayDeque<>(bundles.size()); - Map> provided = new HashMap<>(); + Capabilities capabilities = new Capabilities(); // initialize with given bundles for (BundleDescription bundle : bundles) { - addNewRequiredBundle(bundle, closure, pending, provided); + addNewRequiredBundle(bundle, closure, pending, capabilities); } // perform exhaustive iterative bfs for required wires while (!pending.isEmpty()) { @@ -197,7 +195,7 @@ public static Set findRequirementsClosure(Collection findRequirementsClosure(Collection requiredWires = wiring.getRequiredWires(null); for (BundleWire wire : requiredWires) { BundleRequirement requirement = wire.getRequirement(); - if (isSingle(requirement) && isAlreadyProvided(requirement, provided)) { + if (isSingle(requirement) && capabilities.findCapabilities(requirement).size() > 0) { continue; } BundleRevision declaringBundle = requirement.getRevision(); @@ -231,7 +229,7 @@ public static Set findRequirementsClosure(Collection> provided) { - List list = provided.get(requirement.getNamespace()); - if (list != null && !list.isEmpty()) { - for (BundleCapability bundleCapability : list) { - if (requirement.matches(bundleCapability)) { - return true; - } - } - } - return false; - } - private static void addNewRequiredBundle(BundleDescription bundle, Set requiredBundles, - Queue pending, Map> provided) { - if (bundle != null && bundle.isResolved() && !bundle.isRemovalPending() && requiredBundles.add(bundle)) { + Queue pending, Capabilities capabilities) { + if (bundle != null && bundle.isResolved() && !bundle.isRemovalPending() && requiredBundles.add(bundle) + && !"org.eclipse.osgi".equals(bundle.getSymbolicName())) { //$NON-NLS-1$ pending.add(bundle); - List capabilities = bundle.getCapabilities(null); - for (Capability capability : capabilities) { - if (capability instanceof BundleCapability bc) { - provided.computeIfAbsent(capability.getNamespace(), nil -> new ArrayList<>()).add(bc); - } - } + capabilities.addCapabilities(bundle); } }