diff --git a/tycho-core/src/main/java/org/eclipse/tycho/p2tools/copiedfromp2/PermissiveSlicer.java b/tycho-core/src/main/java/org/eclipse/tycho/p2tools/copiedfromp2/PermissiveSlicer.java new file mode 100644 index 0000000000..efd12d0ead --- /dev/null +++ b/tycho-core/src/main/java/org/eclipse/tycho/p2tools/copiedfromp2/PermissiveSlicer.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * Copyright (c) 2009, 2017 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 + ******************************************************************************/ +package org.eclipse.tycho.p2tools.copiedfromp2; + +import java.util.Map; + +import org.eclipse.equinox.internal.p2.director.Slicer; +import org.eclipse.equinox.internal.p2.metadata.RequiredCapability; +import org.eclipse.equinox.p2.metadata.IInstallableUnit; +import org.eclipse.equinox.p2.metadata.IRequirement; +import org.eclipse.equinox.p2.metadata.expression.IMatchExpression; +import org.eclipse.equinox.p2.query.IQueryable; + +public class PermissiveSlicer extends Slicer { + private boolean includeOptionalDependencies; //Cause optional dependencies not be followed as part of the + private boolean everythingGreedy; + private boolean considerFilter; + private boolean considerOnlyStrictDependency; + private boolean evalFilterTo; + private boolean onlyFilteredRequirements; + + public PermissiveSlicer(IQueryable input, Map context, + boolean includeOptionalDependencies, boolean everythingGreedy, boolean evalFilterTo, + boolean considerOnlyStrictDependency, boolean onlyFilteredRequirements) { + super(input, context, true); + this.considerFilter = context != null && context.size() > 1; + this.includeOptionalDependencies = includeOptionalDependencies; + this.everythingGreedy = everythingGreedy; + this.evalFilterTo = evalFilterTo; + this.considerOnlyStrictDependency = considerOnlyStrictDependency; + this.onlyFilteredRequirements = onlyFilteredRequirements; + } + + @Override + protected boolean isApplicable(IInstallableUnit iu) { + if (considerFilter) { + return super.isApplicable(iu); + } + return iu.getFilter() == null || evalFilterTo; + } + + @Override + protected boolean isApplicable(IRequirement req) { + //Every filter in this method needs to continue except when the filter does not pass + if (!includeOptionalDependencies && req.getMin() == 0) { + return false; + } + if (considerOnlyStrictDependency && !RequiredCapability.isStrictVersionRequirement(req.getMatches())) { + return false; + } + //deal with filters + IMatchExpression filter = req.getFilter(); + if (filter == null) { + return !onlyFilteredRequirements; + } + return considerFilter ? filter.isMatch(selectionContext) : evalFilterTo; + } + + @Override + protected boolean isGreedy(IRequirement req) { + return everythingGreedy || super.isGreedy(req); + } +} diff --git a/tycho-core/src/main/java/org/eclipse/tycho/p2tools/copiedfromp2/Slicer.java b/tycho-core/src/main/java/org/eclipse/tycho/p2tools/copiedfromp2/Slicer.java new file mode 100644 index 0000000000..db43457c8a --- /dev/null +++ b/tycho-core/src/main/java/org/eclipse/tycho/p2tools/copiedfromp2/Slicer.java @@ -0,0 +1,239 @@ +/******************************************************************************* + * Copyright (c) 2007, 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 + * Sonatype, Inc. - ongoing development + * Christoph Läubrich - Issue #39 - Slicer should allow filtering of a requirement based on the IU currently processed + *******************************************************************************/ +package org.eclipse.tycho.p2tools.copiedfromp2; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.MultiStatus; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Status; +import org.eclipse.equinox.internal.p2.core.helpers.LogHelper; +import org.eclipse.equinox.internal.p2.core.helpers.Tracing; +import org.eclipse.equinox.internal.p2.director.Messages; +import org.eclipse.equinox.internal.p2.director.QueryableArray; +import org.eclipse.equinox.internal.p2.metadata.InstallableUnit; +import org.eclipse.equinox.internal.p2.metadata.InstallableUnitPatch; +import org.eclipse.equinox.p2.metadata.IInstallableUnit; +import org.eclipse.equinox.p2.metadata.IInstallableUnitFragment; +import org.eclipse.equinox.p2.metadata.IInstallableUnitPatch; +import org.eclipse.equinox.p2.metadata.IRequirement; +import org.eclipse.equinox.p2.metadata.IRequirementChange; +import org.eclipse.equinox.p2.metadata.Version; +import org.eclipse.equinox.p2.metadata.expression.IMatchExpression; +import org.eclipse.equinox.p2.query.IQueryResult; +import org.eclipse.equinox.p2.query.IQueryable; +import org.eclipse.equinox.p2.query.QueryUtil; +import org.eclipse.osgi.util.NLS; + +public class Slicer { + private static boolean DEBUG = false; + private final IQueryable possibilites; + private final boolean considerMetaRequirements; + protected final IInstallableUnit selectionContext; + /** The IUs that have been considered to be part of the problem */ + private final Map> slice = new HashMap<>(); + private final MultiStatus result = new MultiStatus(Slicer.class, 0, Messages.Planner_Problems_resolving_plan); + + private Queue toProcess; + private Set considered; // IUs to add to the slice + private final Set nonGreedyIUs = new HashSet<>(); // IUs that are brought in by non greedy dependencies + + public Slicer(IQueryable input, Map context, boolean considerMetaRequirements) { + this(input, InstallableUnit.contextIU(context), considerMetaRequirements); + } + + public Slicer(IQueryable possibilites, IInstallableUnit selectionContext, + boolean considerMetaRequirements) { + this.possibilites = possibilites; + this.selectionContext = selectionContext; + this.considerMetaRequirements = considerMetaRequirements; + } + + public IQueryable slice(Collection ius, IProgressMonitor monitor) { + monitor = IProgressMonitor.nullSafe(monitor); + try { + long start = 0; + if (DEBUG) { + start = System.currentTimeMillis(); + System.out.println("Start slicing: " + start); //$NON-NLS-1$ + } + validateInput(ius); + considered = new HashSet<>(ius); + toProcess = new LinkedList<>(considered); + while (!toProcess.isEmpty()) { + if (monitor.isCanceled()) { + result.merge(Status.CANCEL_STATUS); + throw new OperationCanceledException(); + } + processIU(toProcess.remove()); + } + computeNonGreedyIUs(); + if (DEBUG) { + long stop = System.currentTimeMillis(); + System.out.println("Slicing complete: " + (stop - start)); //$NON-NLS-1$ + } + } catch (IllegalStateException e) { + result.add(Status.error(e.getMessage(), e)); + } + if (Tracing.DEBUG && result.getSeverity() != IStatus.OK) { + LogHelper.log(result); + } + if (result.getSeverity() == IStatus.ERROR) { + return null; + } + return new QueryableArray(considered); + } + + private void computeNonGreedyIUs() { + IQueryable queryable = new QueryableArray(considered); + for (IInstallableUnit iu : queryable.query(QueryUtil.ALL_UNITS, new NullProgressMonitor())) { + iu = iu.unresolved(); + Collection reqs = getRequirements(iu); + for (IRequirement req : reqs) { + if (!isApplicable(iu, req)) { + continue; + } + if (!isGreedy(iu, req)) { + nonGreedyIUs.addAll( + queryable.query(QueryUtil.createMatchQuery(req.getMatches()), null).toUnmodifiableSet()); + } + } + } + } + + public MultiStatus getStatus() { + return result; + } + + // This is a shortcut to simplify the error reporting when the filter of the ius + // we are being asked to install does not pass + private void validateInput(Collection ius) { + for (IInstallableUnit iu : ius) { + if (!isApplicable(iu)) { + throw new IllegalStateException(NLS.bind(Messages.Explanation_missingRootFilter, iu)); + } + } + } + + // Check whether the requirement is applicable + + protected boolean isApplicable(IInstallableUnit unit, IRequirement req) { + return isApplicable(req); + } + + protected boolean isApplicable(IRequirement req) { + IMatchExpression filter = req.getFilter(); + return filter == null || filter.isMatch(selectionContext); + } + + protected boolean isApplicable(IInstallableUnit iu) { + IMatchExpression filter = iu.getFilter(); + return filter == null || filter.isMatch(selectionContext); + } + + protected void processIU(IInstallableUnit iu) { + iu = iu.unresolved(); + Map iuSlice = slice.computeIfAbsent(iu.getId(), i -> new HashMap<>()); + iuSlice.put(iu.getVersion(), iu); + if (!isApplicable(iu)) { + return; + } + Collection reqs = getRequirements(iu); + for (IRequirement req : reqs) { + if (isApplicable(iu, req) && isGreedy(iu, req)) { + expandRequirement(iu, req); + } + } + } + + protected boolean isGreedy(IInstallableUnit unit, IRequirement req) { + return isGreedy(req); + } + + protected boolean isGreedy(IRequirement req) { + return req.isGreedy(); + } + + private Collection getRequirements(IInstallableUnit iu) { + boolean isPatch = iu instanceof IInstallableUnitPatch; + boolean isFragment = iu instanceof IInstallableUnitFragment; + // Short-circuit for the case of an IInstallableUnit + if (!isFragment && !isPatch && iu.getMetaRequirements().isEmpty()) { + return iu.getRequirements(); + } + + List aggregatedRequirements = new ArrayList<>(iu.getRequirements().size() + + iu.getMetaRequirements().size() + (isFragment ? ((IInstallableUnitFragment) iu).getHost().size() : 0) + + (isPatch ? ((IInstallableUnitPatch) iu).getRequirementsChange().size() : 0)); + aggregatedRequirements.addAll(iu.getRequirements()); + + if (iu instanceof IInstallableUnitFragment iuFragment) { + aggregatedRequirements.addAll(iuFragment.getHost()); + } + if (iu instanceof InstallableUnitPatch patchIU) { + List changes = patchIU.getRequirementsChange(); + for (IRequirementChange change : changes) { + aggregatedRequirements.add(change.newValue()); + } + } + if (considerMetaRequirements) { + aggregatedRequirements.addAll(iu.getMetaRequirements()); + } + return aggregatedRequirements; + } + + private void expandRequirement(IInstallableUnit iu, IRequirement req) { + if (req.getMax() == 0) { + return; + } + IQueryResult matches = possibilites.query(QueryUtil.createMatchQuery(req.getMatches()), null); + int validMatches = 0; + for (IInstallableUnit match : matches) { + if (!isApplicable(match)) { + continue; + } + validMatches++; + Map iuSlice = slice.get(match.getId()); + if ((iuSlice == null || !iuSlice.containsKey(match.getVersion())) && considered.add(match)) { + toProcess.add(match); + } + } + if (validMatches == 0) { + if (req.getMin() == 0) { + if (DEBUG) { + System.out.println("No IU found to satisfy optional dependency of " + iu + " on req " + req); //$NON-NLS-1$//$NON-NLS-2$ + } + } else { + result.add(Status.warning(NLS.bind(Messages.Planner_Unsatisfied_dependency, iu, req))); + } + } + } + + Set getNonGreedyIUs() { + return nonGreedyIUs; + } +}