| /******************************************************************************* |
| * 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 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * Karsten Thoms <karsten.thoms@itemis.de> - Bug 522332 |
| * Hannes Wellmann - Bug 539637 major rework to consider versions and improve runtime behavior |
| *******************************************************************************/ |
| package org.eclipse.pde.internal.core; |
| |
| import java.util.ArrayDeque; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Queue; |
| import java.util.Set; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.osgi.service.resolver.BundleDescription; |
| import org.eclipse.osgi.service.resolver.State; |
| import org.eclipse.pde.core.plugin.IPluginModelBase; |
| import org.eclipse.pde.core.plugin.PluginRegistry; |
| import org.eclipse.pde.core.target.ITargetPlatformService; |
| import org.eclipse.pde.core.target.NameVersionDescriptor; |
| import org.osgi.framework.Constants; |
| import org.osgi.framework.Version; |
| import org.osgi.framework.wiring.BundleRequirement; |
| import org.osgi.framework.wiring.BundleRevision; |
| import org.osgi.framework.wiring.BundleWire; |
| import org.osgi.framework.wiring.BundleWiring; |
| |
| /** |
| * Utility class to return bundle description collections for a variety of |
| * dependency scenarios |
| * |
| * @noextend This class is not intended to be subclassed by clients. |
| * @noinstantiate This class is not intended to be instantiated by clients. |
| */ |
| public class DependencyManager { |
| |
| private DependencyManager() { // static use only |
| } |
| |
| public enum Options { |
| /** Specifies to include all optional dependencies into the closure. */ |
| INCLUDE_OPTIONAL_DEPENDENCIES, |
| |
| /** |
| * Specifies to include all fragments into the closure (must not be |
| * combined with {@link #INCLUDE_NON_TEST_FRAGMENTS}). |
| */ |
| INCLUDE_ALL_FRAGMENTS, |
| /** |
| * Specifies to include all non-test fragments into the closure (must |
| * not be combined with {@link #INCLUDE_ALL_FRAGMENTS}). |
| */ |
| INCLUDE_NON_TEST_FRAGMENTS; |
| } |
| |
| /** |
| * Returns a {@link Set} of bundle descriptions of the given |
| * {@link IPluginModelBase}s and all of their required dependencies |
| * (including optional) and fragments. |
| * <p> |
| * The set includes the descriptions of the given model bases as well as all |
| * transitively computed explicit, implicit (defined in the target-platform) |
| * and optional dependencies. So it is the self-contained closure for all |
| * required dependencies of the given set of plug-ins plus the implicit |
| * dependencies defined in the target platform. |
| * </p> |
| * |
| * @param plugins |
| * the collection of {@link IPluginModelBase}s to compute |
| * dependencies for |
| * @return a set of bundle descriptions |
| */ |
| public static Set<BundleDescription> getSelfAndDependencies(Collection<IPluginModelBase> plugins) { |
| Collection<NameVersionDescriptor> implicit = getImplicitDependencies(); |
| List<BundleDescription> bundles = mergeBundleDescriptions(plugins, implicit, TargetPlatformHelper.getState()); |
| return findRequirementsClosure(bundles, Options.INCLUDE_OPTIONAL_DEPENDENCIES, Options.INCLUDE_ALL_FRAGMENTS); |
| } |
| |
| /** |
| * Returns a {@link Set} of bundle descriptions for all required |
| * dependencies of the given objects from the given {@link State}. |
| * <p> |
| * The set includes the descriptions of all transitively computed explicit |
| * dependencies. The set does not include the descriptions of the given |
| * objects and only includes optional dependencies if requested. |
| * </p> |
| * |
| * @param plugins |
| * the group of {@link IPluginModelBase}s to compute dependencies |
| * for. |
| * @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 options |
| * the specified {@link Options} for computing the closure |
| * @return a set of bundle descriptions |
| */ |
| public static Set<BundleDescription> getDependencies(Collection<IPluginModelBase> plugins, |
| Collection<NameVersionDescriptor> implicit, State state, Options... options) { |
| List<BundleDescription> bundles = mergeBundleDescriptions(plugins, implicit, state); |
| Set<BundleDescription> closure = findRequirementsClosure(bundles, options); |
| plugins.forEach(p -> closure.remove(p.getBundleDescription())); |
| return closure; |
| } |
| |
| /** |
| * Returns a {@link Set} of bundle descriptions for all required |
| * dependencies of the given {@link IPluginModelBase}s. |
| * <p> |
| * The set includes the descriptions of the transitively computed explicit, |
| * implicit (defined in the target-platform) and optional (if requested) |
| * dependencies. The set does not include the descriptions of the given |
| * objects. |
| * </p> |
| * |
| * @param plugins |
| * selected the group of {@link IPluginModelBase}s to compute |
| * dependencies for. |
| * @param options |
| * the specified {@link Options} for computing the closure |
| * @return a set of bundle descriptions |
| */ |
| public static Set<BundleDescription> getDependencies(Collection<IPluginModelBase> plugins, Options... options) { |
| return getDependencies(plugins, getImplicitDependencies(), TargetPlatformHelper.getState(), options); |
| } |
| |
| /** |
| * Returns a {@link Set} of bundle descriptions of the given |
| * {@link IPluginModelBase}s and all of their required dependencies. |
| * <p> |
| * The set includes the descriptions of the given bundle descriptions as |
| * well as all transitively computed explicit and optional (if requested) |
| * dependencies. So it is the self-contained closure for all required |
| * dependencies of the given set of plug-ins. |
| * </p> |
| * |
| * @param bundles |
| * the group of {@link BundleDescription}s to compute |
| * dependencies for. |
| * @param options |
| * the specified {@link Options} for computing the closure |
| * @return a set of bundle descriptions |
| */ |
| public static Set<BundleDescription> findRequirementsClosure(Collection<BundleDescription> bundles, |
| Options... options) { |
| |
| Set<Options> optionSet = Set.of(options); |
| boolean includeOptional = optionSet.contains(Options.INCLUDE_OPTIONAL_DEPENDENCIES); |
| boolean includeAllFragments = optionSet.contains(Options.INCLUDE_ALL_FRAGMENTS); |
| boolean includeNonTestFragments = optionSet.contains(Options.INCLUDE_NON_TEST_FRAGMENTS); |
| if (includeAllFragments && includeNonTestFragments) { |
| throw new AssertionError("Cannot combine INCLUDE_ALL_FRAGMENTS and INCLUDE_NON_TEST_FRAGMENTS"); //$NON-NLS-1$ |
| } |
| |
| Set<BundleDescription> closure = new HashSet<>(bundles.size() * 4 / 3 + 1); |
| Queue<BundleDescription> pending = new ArrayDeque<>(); |
| |
| // initialize with given bundles |
| for (BundleDescription bundle : bundles) { |
| addNewRequiredBundle(bundle, closure, pending); |
| } |
| |
| // perform exhaustive iterative bfs for required wires |
| while (!pending.isEmpty()) { |
| BundleDescription bundle = pending.remove(); |
| |
| BundleWiring wiring = bundle.getWiring(); |
| if (wiring == null || !wiring.isInUse()) { |
| continue; |
| } |
| |
| List<BundleWire> requiredWires = wiring.getRequiredWires(null); |
| for (BundleWire wire : requiredWires) { |
| BundleRevision provider = getProvider(wire); |
| if (provider instanceof BundleDescription && (includeOptional || !isOptional(wire.getRequirement()))) { |
| BundleDescription requiredBundle = (BundleDescription) provider; |
| addNewRequiredBundle(requiredBundle, closure, pending); |
| } |
| } |
| |
| if (includeAllFragments || includeNonTestFragments) { |
| // A fragment's host is already required by a wire |
| for (BundleDescription fragment : bundle.getFragments()) { |
| if (includeAllFragments || !isTestWorkspaceProject(fragment)) { |
| addNewRequiredBundle(fragment, closure, pending); |
| } |
| } |
| } |
| } |
| return closure; |
| } |
| |
| private static void addNewRequiredBundle(BundleDescription bundle, Set<BundleDescription> requiredBundles, |
| Queue<BundleDescription> pending) { |
| if (bundle != null && bundle.isResolved() && !bundle.isRemovalPending() && requiredBundles.add(bundle)) { |
| pending.add(bundle); |
| } |
| } |
| |
| private static BundleRevision getProvider(BundleWire wire) { |
| // org.eclipse.osgi.internal.resolver.BundleDescriptionImpl.BundleWireImpl.getProvider() |
| // does not check for null |
| BundleWiring providerWiring = wire.getProviderWiring(); |
| return providerWiring != null ? providerWiring.getRevision() : null; |
| } |
| |
| private static boolean isOptional(BundleRequirement requirement) { |
| return Constants.RESOLUTION_OPTIONAL.equals(requirement.getDirectives().get(Constants.RESOLUTION_DIRECTIVE)); |
| } |
| |
| private static boolean isTestWorkspaceProject(BundleDescription f) { |
| // Be defensive when declaring a fragment as 'test'-fragment |
| IPluginModelBase pluginModel = PluginRegistry.findModel(f); |
| if (pluginModel != null) { |
| IResource resource = pluginModel.getUnderlyingResource(); |
| if (resource != null) { |
| return ClasspathComputer.hasTestOnlyClasspath(resource.getProject()); |
| } // test-fragments are usually not part of the target-platform |
| } |
| return false; |
| } |
| |
| /** |
| * Computes the set of implicit dependencies from the |
| * {@link PDEPreferencesManager}. |
| * |
| * @return a set of bundle ids |
| */ |
| private static Collection<NameVersionDescriptor> getImplicitDependencies() { |
| try { |
| ITargetPlatformService service = PDECore.getDefault().acquireService(ITargetPlatformService.class); |
| if (service != null) { |
| NameVersionDescriptor[] implicit = service.getWorkspaceTargetDefinition().getImplicitDependencies(); |
| if (implicit != null) { |
| return Arrays.asList(implicit); |
| } |
| } |
| } catch (CoreException e) { |
| PDECore.log(e); |
| } |
| return Collections.emptyList(); |
| } |
| |
| private static List<BundleDescription> mergeBundleDescriptions(Collection<IPluginModelBase> plugins, |
| Collection<NameVersionDescriptor> descriptors, State state) { |
| List<BundleDescription> bundles = new ArrayList<>(); |
| for (IPluginModelBase plugin : plugins) { |
| if (plugin != null) { |
| bundles.add(plugin.getBundleDescription()); |
| } |
| } |
| for (NameVersionDescriptor descriptor : descriptors) { |
| String versionStr = descriptor.getVersion(); |
| Version version = versionStr != null ? Version.parseVersion(versionStr) : null; |
| BundleDescription bundle = state.getBundle(descriptor.getId(), version); |
| bundles.add(bundle); |
| } |
| return bundles; |
| } |
| } |