From 9945c45b0b2420b673ea0b83575c999f527ea8f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20L=C3=A4ubrich?= Date: Sun, 21 Jan 2024 16:28:38 +0100 Subject: [PATCH 1/2] Add Projector classes from P2 --- .../copiedfromp2/InfiniteProgress.java | 66 + .../copiedfromp2/OptimizationFunction.java | 164 +++ .../tycho/p2tools/copiedfromp2/Projector.java | 1151 +++++++++++++++++ .../UserDefinedOptimizationFunction.java | 233 ++++ 4 files changed, 1614 insertions(+) create mode 100644 tycho-core/src/main/java/org/eclipse/tycho/p2tools/copiedfromp2/InfiniteProgress.java create mode 100644 tycho-core/src/main/java/org/eclipse/tycho/p2tools/copiedfromp2/OptimizationFunction.java create mode 100644 tycho-core/src/main/java/org/eclipse/tycho/p2tools/copiedfromp2/Projector.java create mode 100644 tycho-core/src/main/java/org/eclipse/tycho/p2tools/copiedfromp2/UserDefinedOptimizationFunction.java diff --git a/tycho-core/src/main/java/org/eclipse/tycho/p2tools/copiedfromp2/InfiniteProgress.java b/tycho-core/src/main/java/org/eclipse/tycho/p2tools/copiedfromp2/InfiniteProgress.java new file mode 100644 index 0000000000..b9d1350376 --- /dev/null +++ b/tycho-core/src/main/java/org/eclipse/tycho/p2tools/copiedfromp2/InfiniteProgress.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright (c) 2006, 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 org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.ProgressMonitorWrapper; + +/** + * This class provides a simulation of progress. This is useful for situations where computing the + * amount of work to do in advance is too costly. The monitor will accept any number of calls to + * {@link #worked(int)}, and will scale the actual reported work appropriately so that the progress + * never quite completes. + */ +class InfiniteProgress extends ProgressMonitorWrapper { + /* + * Fields for progress monitoring algorithm. Initially, give progress for every 4 resources, + * double this value at halfway point, then reset halfway point to be half of remaining work. + * (this gives an infinite series that converges at total work after an infinite number of + * resources). + */ + private int totalWork; + private int currentIncrement = 4; + private int halfWay; + private int nextProgress = currentIncrement; + private int worked = 0; + + protected InfiniteProgress(IProgressMonitor monitor) { + super(monitor); + } + + @Override + public void beginTask(String name, int work) { + super.beginTask(name, work); + this.totalWork = work; + this.halfWay = totalWork / 2; + } + + @Override + public void worked(int work) { + if (--nextProgress <= 0) { + //we have exhausted the current increment, so report progress + super.worked(1); + worked++; + if (worked >= halfWay) { + //we have passed the current halfway point, so double the + //increment and reset the halfway point. + currentIncrement *= 2; + halfWay += (totalWork - halfWay) / 2; + } + //reset the progress counter to another full increment + nextProgress = currentIncrement; + } + } + +} diff --git a/tycho-core/src/main/java/org/eclipse/tycho/p2tools/copiedfromp2/OptimizationFunction.java b/tycho-core/src/main/java/org/eclipse/tycho/p2tools/copiedfromp2/OptimizationFunction.java new file mode 100644 index 0000000000..848160ccf8 --- /dev/null +++ b/tycho-core/src/main/java/org/eclipse/tycho/p2tools/copiedfromp2/OptimizationFunction.java @@ -0,0 +1,164 @@ +/******************************************************************************* + * Copyright (c) 2013, 2018 Rapicorp Inc. 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: + * Rapicorp, Inc. - initial API and implementation + ******************************************************************************/ +package org.eclipse.tycho.p2tools.copiedfromp2; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.equinox.p2.metadata.IInstallableUnit; +import org.eclipse.equinox.p2.metadata.IInstallableUnitPatch; +import org.eclipse.equinox.p2.metadata.IRequirement; +import org.eclipse.equinox.p2.metadata.Version; +import org.eclipse.equinox.p2.query.Collector; +import org.eclipse.equinox.p2.query.IQueryResult; +import org.eclipse.equinox.p2.query.IQueryable; +import org.eclipse.equinox.p2.query.QueryUtil; +import org.eclipse.tycho.p2tools.copiedfromp2.Projector.AbstractVariable; +import org.sat4j.pb.tools.WeightedObject; + +public class OptimizationFunction { + + private IQueryable picker; + private IInstallableUnit selectionContext; + protected Map> slice; //The IUs that have been considered to be part of the problem + private IQueryable lastState; + private List optionalRequirementVariable; + + public OptimizationFunction(IQueryable lastState, List abstractVariables, + List optionalRequirementVariable, IQueryable picker, + IInstallableUnit selectionContext, Map> slice) { + this.lastState = lastState; + this.optionalRequirementVariable = optionalRequirementVariable; + this.picker = picker; + this.selectionContext = selectionContext; + this.slice = slice; + } + + //Create an optimization function favoring the highest version of each IU + public List> createOptimizationFunction(IInstallableUnit metaIu, + Collection newRoots) { + int numberOfInstalledIUs = sizeOf(lastState); + List> weightedObjects = new ArrayList<>(); + + Set transitiveClosure; //The transitive closure of the IUs we are adding (this also means updating) + if (newRoots.isEmpty()) { + transitiveClosure = Collections.emptySet(); + } else { + IQueryable queryable = new Slicer(picker, selectionContext, false).slice(newRoots, null); + if (queryable == null) { + transitiveClosure = Collections.emptySet(); + } else { + transitiveClosure = queryable.query(QueryUtil.ALL_UNITS, new NullProgressMonitor()).toSet(); + } + } + + Set>> s = slice.entrySet(); + final BigInteger POWER = BigInteger.valueOf(numberOfInstalledIUs > 0 ? numberOfInstalledIUs + 1 : 2); + + BigInteger maxWeight = POWER; + for (Entry> entry : s) { + List conflictingEntries = new ArrayList<>(entry.getValue().values()); + if (conflictingEntries.size() == 1) { + //Only one IU exists with the namespace. + IInstallableUnit iu = conflictingEntries.get(0); + if (iu != metaIu) { + weightedObjects.add(WeightedObject.newWO(iu, POWER)); + } + continue; + } + + // Set the weight such that things that are already installed are not updated + conflictingEntries.sort(Collections.reverseOrder()); + BigInteger weight = POWER; + // have we already found a version that is already installed? + boolean foundInstalled = false; + // have we already found a version that is in the new roots? + boolean foundRoot = false; + for (IInstallableUnit iu : conflictingEntries) { + if (!foundRoot && isInstalled(iu) && !transitiveClosure.contains(iu)) { + foundInstalled = true; + weightedObjects.add(WeightedObject.newWO(iu, BigInteger.ONE)); + } else if (!foundInstalled && !foundRoot && isRoot(iu, newRoots)) { + foundRoot = true; + weightedObjects.add(WeightedObject.newWO(iu, BigInteger.ONE)); + } else { + weightedObjects.add(WeightedObject.newWO(iu, weight)); + } + weight = weight.multiply(POWER); + } + if (weight.compareTo(maxWeight) > 0) + maxWeight = weight; + } + + // no need to add one here, since maxWeight is strictly greater than the + // maximal weight used so far. + maxWeight = maxWeight.multiply(POWER).multiply(BigInteger.valueOf(s.size())); + + // Add the optional variables + BigInteger optionalVarWeight = maxWeight.negate(); + for (AbstractVariable var : optionalRequirementVariable) { + weightedObjects.add(WeightedObject.newWO(var, optionalVarWeight)); + } + + maxWeight = maxWeight.multiply(POWER).add(BigInteger.ONE); + + //Now we deal the optional IUs, + long countOptional = 1; + List requestedPatches = new ArrayList<>(); + for (IRequirement req : metaIu.getRequirements()) { + if (req.getMin() > 0 || !req.isGreedy()) + continue; + for (IInstallableUnit match : picker.query(QueryUtil.createMatchQuery(req.getMatches()), null)) { + if (match instanceof IInstallableUnitPatch) { + requestedPatches.add(match); + countOptional = countOptional + 1; + } + } + } + + // and we make sure that patches are always favored + BigInteger patchWeight = maxWeight.multiply(POWER).multiply(BigInteger.valueOf(countOptional)).negate(); + for (IInstallableUnit iu : requestedPatches) { + weightedObjects.add(WeightedObject.newWO(iu, patchWeight)); + } + return weightedObjects; + } + + protected boolean isInstalled(IInstallableUnit iu) { + return !lastState.query(QueryUtil.createIUQuery(iu), null).isEmpty(); + } + + private boolean isRoot(IInstallableUnit iu, Collection newRoots) { + return newRoots.contains(iu); + } + + /** + * Efficiently compute the size of a queryable + */ + private int sizeOf(IQueryable installedIUs) { + IQueryResult qr = installedIUs.query(QueryUtil.createIUAnyQuery(), null); + if (qr instanceof Collector) + return ((Collector) qr).size(); + return qr.toUnmodifiableSet().size(); + } + +} diff --git a/tycho-core/src/main/java/org/eclipse/tycho/p2tools/copiedfromp2/Projector.java b/tycho-core/src/main/java/org/eclipse/tycho/p2tools/copiedfromp2/Projector.java new file mode 100644 index 0000000000..f87d606149 --- /dev/null +++ b/tycho-core/src/main/java/org/eclipse/tycho/p2tools/copiedfromp2/Projector.java @@ -0,0 +1,1151 @@ +/******************************************************************************* + * 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 + * Daniel Le Berre - Fix in the encoding and the optimization function + * Alban Browaeys - Optimized string concatenation in bug 251357 + * Jed Anderson - switch from opb files to API calls to DependencyHelper in bug 200380 + * Sonatype, Inc. - ongoing development + * Rapicorp, Inc. - split the optimization function + * Red Hat, Inc. - support for remediation page + * Christoph Läubrich - access activator static singelton in a safe way + ******************************************************************************/ +package org.eclipse.tycho.p2tools.copiedfromp2; + +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.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +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.OperationCanceledException; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.equinox.internal.p2.core.helpers.Tracing; +import org.eclipse.equinox.internal.p2.director.ApplicablePatchQuery; +import org.eclipse.equinox.internal.p2.director.DirectorActivator; +import org.eclipse.equinox.internal.p2.director.Explanation; +import org.eclipse.equinox.internal.p2.director.Explanation.NotInstallableRoot; +import org.eclipse.equinox.internal.p2.director.Messages; +import org.eclipse.equinox.internal.p2.director.QueryableArray; +import org.eclipse.equinox.internal.p2.metadata.IRequiredCapability; +import org.eclipse.equinox.internal.p2.metadata.InstallableUnit; +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; +import org.sat4j.minisat.restarts.LubyRestarts; +import org.sat4j.pb.IPBSolver; +import org.sat4j.pb.SolverFactory; +import org.sat4j.pb.UserFriendlyPBStringSolver; +import org.sat4j.pb.core.PBSolverResolution; +import org.sat4j.pb.tools.DependencyHelper; +import org.sat4j.pb.tools.LexicoHelper; +import org.sat4j.pb.tools.SteppedTimeoutLexicoHelper; +import org.sat4j.pb.tools.WeightedObject; +import org.sat4j.specs.ContradictionException; +import org.sat4j.specs.IVec; +import org.sat4j.specs.TimeoutException; + +/** + * This class is the interface between SAT4J and the planner. It produces a boolean satisfiability + * problem, invokes the solver, and converts the solver result back into information understandable + * by the planner. + */ +public class Projector { + /** + * The name of a Java system property specifying the timeout to set in the SAT solver. Note this + * value is not a time, but rather a conflict count. Essentially the solver will give up on a + * particular solution path when this number of conflicts is reached. + */ + private static final String PROP_PROJECTOR_TIMEOUT = "eclipse.p2.projector.timeout"; //$NON-NLS-1$ + /** + * The default SAT solver timeout (in number of conflicts). See bug 372529 for discussion. + */ + private static final int DEFAULT_SOLVER_TIMEOUT = 10000; + private static final int UNSATISFIABLE = 1; + static boolean DEBUG = Tracing.DEBUG_PLANNER_PROJECTOR; + private static boolean DEBUG_ENCODING = Tracing.DEBUG_PLANNER_PROJECTOR_ENCODING; + private IQueryable picker; + private QueryableArray patches; + + private List allOptionalAbstractRequirements; + private List abstractVariables; + + private Map> slice; //The IUs that have been considered to be part of the problem + + private IInstallableUnit selectionContext; + + DependencyHelper dependencyHelper; + private Collection solution; + private Collection assumptions; + + private MultiStatus result; + + private Collection alreadyInstalledIUs; + private IQueryable lastState; + + private boolean considerMetaRequirements; + private IInstallableUnit entryPoint; + private Map> fragments = new HashMap<>(); + + //Non greedy things + private Set nonGreedyIUs; //All the IUs that would satisfy non greedy dependencies + private Map nonGreedyVariables = new HashMap<>(); + private Map> nonGreedyProvider = new HashMap<>(); //Keeps track of all the "object" that provide an IU that is non greedly requested + + private boolean emptyBecauseFiltered; + private boolean userDefinedFunction; + + static class AbstractVariable { + // private String name; + + public AbstractVariable(String name) { + // this.name = name; + } + + public AbstractVariable() { + // TODO Auto-generated constructor stub + } + + @Override + public String toString() { + return "AbstractVariable: " + hashCode(); //$NON-NLS-1$ + // return name == null ? "AbstractVariable: " + hashCode() : name; //$NON-NLS-1$ + } + } + + /** + * Job for computing SAT failure explanation in the background. + */ + class ExplanationJob extends Job { + private Set explanation; + + public ExplanationJob() { + super(Messages.Planner_NoSolution); + //explanations cannot be canceled directly, so don't show it to the user + setSystem(true); + } + + @Override + public boolean belongsTo(Object family) { + return family == ExplanationJob.this; + } + + @Override + protected void canceling() { + super.canceling(); + dependencyHelper.stopExplanation(); + } + + public Set getExplanationResult() { + return explanation; + } + + @Override + protected IStatus run(IProgressMonitor monitor) { + long start = 0; + if (DEBUG) { + start = System.currentTimeMillis(); + Tracing.debug("Determining cause of failure: " + start); //$NON-NLS-1$ + } + try { + explanation = dependencyHelper.why(); + if (DEBUG) { + long stop = System.currentTimeMillis(); + Tracing.debug("Explanation found: " + (stop - start)); //$NON-NLS-1$ + Tracing.debug("Explanation:"); //$NON-NLS-1$ + for (Explanation ex : explanation) { + Tracing.debug(ex.toString()); + } + } + } catch (TimeoutException e) { + if (DEBUG) + Tracing.debug("Timeout while computing explanations"); //$NON-NLS-1$ + } finally { + //must never have a null result, because caller is waiting on result to be non-null + if (explanation == null) + explanation = Collections.emptySet(); + } + synchronized (this) { + ExplanationJob.this.notify(); + } + return Status.OK_STATUS; + } + + } + + public Projector(IQueryable q, Map context, Set nonGreedyIUs, + boolean considerMetaRequirements) { + picker = q; + slice = new HashMap<>(); + selectionContext = InstallableUnit.contextIU(context); + abstractVariables = new ArrayList<>(); + allOptionalAbstractRequirements = new ArrayList<>(); + result = new MultiStatus(DirectorActivator.PI_DIRECTOR, IStatus.OK, Messages.Planner_Problems_resolving_plan, + null); + assumptions = new ArrayList<>(); + this.nonGreedyIUs = nonGreedyIUs; + this.considerMetaRequirements = considerMetaRequirements; + } + + @SuppressWarnings("unchecked") + public void encode(IInstallableUnit entryPointIU, IInstallableUnit[] alreadyExistingRoots, + IQueryable installedIUs, Collection newRoots, + IProgressMonitor monitor) { + alreadyInstalledIUs = Arrays.asList(alreadyExistingRoots); + lastState = installedIUs; + this.entryPoint = entryPointIU; + try { + long start = 0; + if (DEBUG) { + start = System.currentTimeMillis(); + Tracing.debug("Start projection: " + start); //$NON-NLS-1$ + } + IPBSolver solver; + if (DEBUG_ENCODING) { + solver = new UserFriendlyPBStringSolver<>(); + } else { + if (userDefinedFunction) { + PBSolverResolution mysolver = SolverFactory.newCompetPBResLongWLMixedConstraintsObjectiveExpSimp(); + mysolver.setSimplifier(mysolver.SIMPLE_SIMPLIFICATION); + mysolver.setRestartStrategy(new LubyRestarts(512)); + solver = mysolver; + } else { + solver = SolverFactory.newEclipseP2(); + } + } + int timeout = DEFAULT_SOLVER_TIMEOUT; + String timeoutString = null; + try { + // allow the user to specify a longer timeout. + // only set the value if it is a positive integer larger than the default. + // see https://bugs.eclipse.org/336967 + timeoutString = DirectorActivator.context.map(ctx -> ctx.getProperty(PROP_PROJECTOR_TIMEOUT)) + .orElse(null); + if (timeoutString != null) + timeout = Math.max(timeout, Integer.parseInt(timeoutString)); + } catch (Exception e) { + // intentionally catch all errors (npe, number format, etc) + // print out to syserr and fall through + System.err.println("Ignoring user-specified 'eclipse.p2.projector.timeout' value of: " + timeoutString); //$NON-NLS-1$ + e.printStackTrace(); + } + if (userDefinedFunction) + solver.setTimeoutOnConflicts(timeout / 4); + else + solver.setTimeoutOnConflicts(timeout); + + IQueryResult queryResult = picker.query(QueryUtil.createIUAnyQuery(), null); + if (DEBUG_ENCODING) { + dependencyHelper = new LexicoHelper<>(solver, false); + ((UserFriendlyPBStringSolver) solver).setMapping(dependencyHelper.getMappingToDomain()); + } else { + if (userDefinedFunction) + dependencyHelper = new SteppedTimeoutLexicoHelper<>(solver); + else + dependencyHelper = new DependencyHelper<>(solver); + } + List iusToOrder = new ArrayList<>(queryResult.toSet()); + iusToOrder.sort(null); + for (IInstallableUnit iu : iusToOrder) { + if (monitor.isCanceled()) { + result.merge(Status.CANCEL_STATUS); + throw new OperationCanceledException(); + } + if (iu != entryPointIU) { + processIU(iu, false); + } + } + createMustHave(entryPointIU, alreadyExistingRoots); + + createConstraintsForSingleton(); + + createConstraintsForNonGreedy(); + + createOptimizationFunction(entryPointIU, newRoots); + if (DEBUG) { + long stop = System.currentTimeMillis(); + Tracing.debug("Projection complete: " + (stop - start)); //$NON-NLS-1$ + } + if (DEBUG_ENCODING) { + System.out.println(solver.toString()); + } + } catch (IllegalStateException e) { + result.add(Status.error(e.getMessage(), e)); + } catch (ContradictionException e) { + result.add(Status.error(Messages.Planner_Unsatisfiable_problem)); + } + } + + private void createConstraintsForNonGreedy() throws ContradictionException { + for (IInstallableUnit iu : nonGreedyIUs) { + AbstractVariable var = getNonGreedyVariable(iu); + List providers = nonGreedyProvider.get(var); + if (providers == null || providers.size() == 0) { + dependencyHelper.setFalse(var, new Explanation.MissingGreedyIU(iu)); + } else { + createImplication(var, providers, Explanation.OPTIONAL_REQUIREMENT);//FIXME + } + } + + } + + private void createOptimizationFunction(IInstallableUnit entryPointIU, Collection newRoots) { + if (!userDefinedFunction) { + createStandardOptimizationFunction(entryPointIU, newRoots); + } else { + createUserDefinedOptimizationFunction(entryPointIU, newRoots); + } + } + + //Create an optimization function favoring the highest version of each IU + private void createStandardOptimizationFunction(IInstallableUnit entryPointIU, + Collection newRoots) { + List> weights = new OptimizationFunction(lastState, abstractVariables, + allOptionalAbstractRequirements, picker, selectionContext, slice) + .createOptimizationFunction(entryPointIU, newRoots); + createObjectiveFunction(weights); + } + + private void createUserDefinedOptimizationFunction(IInstallableUnit entryPointIU, + Collection newRoots) { + List> weights = new UserDefinedOptimizationFunction(lastState, + abstractVariables, allOptionalAbstractRequirements, picker, selectionContext, slice, dependencyHelper, + alreadyInstalledIUs).createOptimizationFunction(entryPointIU, newRoots); + createObjectiveFunction(weights); + } + + private void createObjectiveFunction(List> weightedObjects) { + if (weightedObjects == null) + return; + if (DEBUG) { + StringBuilder b = new StringBuilder(); + for (WeightedObject object : weightedObjects) { + if (b.length() > 0) + b.append(", "); //$NON-NLS-1$ + b.append(object.getWeight()); + b.append(' '); + b.append(object.thing); + } + Tracing.debug("objective function: " + b); //$NON-NLS-1$ + } + @SuppressWarnings("unchecked") + WeightedObject[] array = (WeightedObject[]) weightedObjects + .toArray(new WeightedObject[weightedObjects.size()]); + dependencyHelper.setObjectiveFunction(array); + } + + private void createMustHave(IInstallableUnit iu, IInstallableUnit[] alreadyExistingRoots) + throws ContradictionException { + processIU(iu, true); + if (DEBUG) { + Tracing.debug(iu + "=1"); //$NON-NLS-1$ + } + // dependencyHelper.setTrue(variable, new Explanation.IUToInstall(iu)); + assumptions.add(iu); + } + + private void createNegation(IInstallableUnit iu, IRequirement req) throws ContradictionException { + if (DEBUG) { + Tracing.debug(iu + "=0"); //$NON-NLS-1$ + } + dependencyHelper.setFalse(iu, new Explanation.MissingIU(iu, req, iu == this.entryPoint)); + } + + // Check whether the requirement is applicable + private boolean isApplicable(IRequirement req) { + IMatchExpression filter = req.getFilter(); + return filter == null || filter.isMatch(selectionContext); + } + + private boolean isApplicable(IInstallableUnit iu) { + IMatchExpression filter = iu.getFilter(); + return filter == null || filter.isMatch(selectionContext); + } + + private void expandNegatedRequirement(IRequirement req, IInstallableUnit iu, + List optionalAbstractRequirements, boolean isRootIu) throws ContradictionException { + if (!isApplicable(req)) + return; + List matches = getApplicableMatches(req); + if (matches.isEmpty()) { + return; + } + Explanation explanation; + if (isRootIu) { + IInstallableUnit reqIu = matches.get(0); + if (alreadyInstalledIUs.contains(reqIu)) { + explanation = new Explanation.IUInstalled(reqIu); + } else { + explanation = new Explanation.IUToInstall(reqIu); + } + } else { + explanation = new Explanation.HardRequirement(iu, req); + } + createNegationImplication(iu, matches, explanation); + } + + private void determinePotentialHostsForFragment(IInstallableUnit iu) { + // determine matching hosts only for fragments + if (!(iu instanceof IInstallableUnitFragment)) + return; + + IInstallableUnitFragment fragment = (IInstallableUnitFragment) iu; + // for each host requirement, find matches and remember them + for (IRequirement req : fragment.getHost()) { + List matches = getApplicableMatches(req); + rememberHostMatches((IInstallableUnitFragment) iu, matches); + } + } + + private void expandRequirement(IRequirement req, IInstallableUnit iu, + List optionalAbstractRequirements, boolean isRootIu) throws ContradictionException { + if (req.getMax() == 0) { + expandNegatedRequirement(req, iu, optionalAbstractRequirements, isRootIu); + return; + } + if (!isApplicable(req)) + return; + List matches = getApplicableMatches(req); + determinePotentialHostsForFragment(iu); + if (req.getMin() > 0) { + if (matches.isEmpty()) { + if (iu == entryPoint && emptyBecauseFiltered) { + dependencyHelper.setFalse(iu, new NotInstallableRoot(req)); + } else { + missingRequirement(iu, req); + } + } else { + if (req.isGreedy()) { + IInstallableUnit reqIu = matches.get(0); + Explanation explanation; + if (isRootIu) { + if (alreadyInstalledIUs.contains(reqIu)) { + explanation = new Explanation.IUInstalled(reqIu); + } else { + explanation = new Explanation.IUToInstall(reqIu); + } + } else { + explanation = new Explanation.HardRequirement(iu, req); + } + createImplication(iu, matches, explanation); + IInstallableUnit current; + for (Iterator it = matches.iterator(); it.hasNext();) { + current = it.next(); + if (nonGreedyIUs.contains(current)) { + addNonGreedyProvider(getNonGreedyVariable(current), iu); + } + } + } else { + List newConstraint = new ArrayList<>(matches.size()); + for (IInstallableUnit current : matches) { + newConstraint.add(getNonGreedyVariable(current)); + } + createImplication(new Object[] { iu }, newConstraint, new Explanation.HardRequirement(iu, req)); // FIXME + } + } + } else { + if (!matches.isEmpty()) { + AbstractVariable abs; + if (req.isGreedy()) { + abs = getAbstractVariable(req); + createImplication(new Object[] { abs, iu }, matches, Explanation.OPTIONAL_REQUIREMENT); + for (IInstallableUnit current : matches) { + if (nonGreedyIUs.contains(current)) { + addNonGreedyProvider(getNonGreedyVariable(current), abs); + } + } + optionalAbstractRequirements.add(abs); + } else { + abs = getAbstractVariable(req, false); + List newConstraint = new ArrayList<>(); + for (IInstallableUnit current : matches) { + newConstraint.add(getNonGreedyVariable(current)); + } + createImplication(new Object[] { abs, iu }, newConstraint, Explanation.OPTIONAL_REQUIREMENT); + } + } + } + } + + private void addNonGreedyProvider(AbstractVariable nonGreedyVariable, Object o) { + List providers = nonGreedyProvider.get(nonGreedyVariable); + if (providers == null) { + providers = new ArrayList<>(); + nonGreedyProvider.put(nonGreedyVariable, providers); + } + providers.add(o); + } + + private void expandRequirements(Collection reqs, IInstallableUnit iu, boolean isRootIu) + throws ContradictionException { + if (reqs.isEmpty()) + return; + for (IRequirement req : reqs) { + expandRequirement(req, iu, allOptionalAbstractRequirements, isRootIu); + } + } + + public void processIU(IInstallableUnit iu, boolean isRootIU) throws ContradictionException { + iu = iu.unresolved(); + Map iuSlice = slice.get(iu.getId()); + if (iuSlice == null) { + iuSlice = new HashMap<>(); + slice.put(iu.getId(), iuSlice); + } + iuSlice.put(iu.getVersion(), iu); + if (!isApplicable(iu)) { + createNegation(iu, null); + return; + } + + IQueryResult applicablePatches = getApplicablePatches(iu); + expandLifeCycle(iu, isRootIU); + //No patches apply, normal code path + if (applicablePatches.isEmpty()) { + expandRequirements(getRequiredCapabilities(iu), iu, isRootIU); + } else { + //Patches are applicable to the IU + expandRequirementsWithPatches(iu, applicablePatches, allOptionalAbstractRequirements, isRootIU); + } + } + + private Collection getRequiredCapabilities(IInstallableUnit iu) { + boolean isFragment = iu instanceof IInstallableUnitFragment; + //Short-circuit for the case of an IInstallableUnit + if ((!isFragment) && iu.getMetaRequirements().size() == 0) + return iu.getRequirements(); + + ArrayList aggregatedRequirements = new ArrayList<>( + iu.getRequirements().size() + iu.getMetaRequirements().size() + + (isFragment ? ((IInstallableUnitFragment) iu).getHost().size() : 0)); + aggregatedRequirements.addAll(iu.getRequirements()); + + if (iu instanceof IInstallableUnitFragment) { + aggregatedRequirements.addAll(((IInstallableUnitFragment) iu).getHost()); + } + + if (considerMetaRequirements) + aggregatedRequirements.addAll(iu.getMetaRequirements()); + return aggregatedRequirements; + } + + static final class Pending { + List matches; + Explanation explanation; + Object left; + } + + private void expandRequirementsWithPatches(IInstallableUnit iu, IQueryResult applicablePatches, + List optionalAbstractRequirements, boolean isRootIu) throws ContradictionException { + //Unmodified dependencies + Collection iuRequirements = getRequiredCapabilities(iu); + Map> unchangedRequirements = new HashMap<>(iuRequirements.size()); + Map nonPatchedRequirements = new HashMap<>(iuRequirements.size()); + for (IInstallableUnit iup : applicablePatches) { + IInstallableUnitPatch patch = (IInstallableUnitPatch) iup; + IRequirement[][] reqs = mergeRequirements(iu, patch); + if (reqs.length == 0) + return; + + // Optional requirements are encoded via: + // ABS -> (match1(req) or match2(req) or ... or matchN(req)) + // noop(IU)-> ~ABS + // IU -> (noop(IU) or ABS) + // Therefore we only need one optional requirement statement per IU + for (IRequirement[] requirement : reqs) { + //The requirement is unchanged + if (requirement[0] == requirement[1]) { + if (requirement[0].getMax() == 0) { + expandNegatedRequirement(requirement[0], iu, optionalAbstractRequirements, isRootIu); + return; + } + if (!isApplicable(requirement[0])) { + continue; + } + List patchesAppliedElseWhere = unchangedRequirements.get(requirement[0]); + if (patchesAppliedElseWhere == null) { + patchesAppliedElseWhere = new ArrayList<>(); + unchangedRequirements.put(requirement[0], patchesAppliedElseWhere); + } + patchesAppliedElseWhere.add(patch); + continue; + } + //Generate dependency when the patch is applied + //P1 -> (A -> D) equiv. (P1 & A) -> D + if (isApplicable(requirement[1])) { + IRequirement req = requirement[1]; + List matches = getApplicableMatches(req); + determinePotentialHostsForFragment(iu); + if (req.getMin() > 0) { + if (matches.isEmpty()) { + missingRequirement(patch, req); + } else { + if (req.isGreedy()) { + IInstallableUnit reqIu = matches.get(0); + Explanation explanation; + if (isRootIu) { + if (alreadyInstalledIUs.contains(reqIu)) { + explanation = new Explanation.IUInstalled(reqIu); + } else { + explanation = new Explanation.IUToInstall(reqIu); + } + } else { + explanation = new Explanation.PatchedHardRequirement(iu, req, patch); + } + createImplication(new Object[] { patch, iu }, matches, explanation); + for (IInstallableUnit current : matches) { + if (nonGreedyIUs.contains(current)) { + addNonGreedyProvider(getNonGreedyVariable(current), iu); + } + } + } else { + List newConstraint = new ArrayList<>(); + for (IInstallableUnit current : matches) { + newConstraint.add(getNonGreedyVariable(current)); + } + createImplication(new Object[] { iu }, newConstraint, + new Explanation.HardRequirement(iu, req)); // FIXME + } + } + } else { + if (!matches.isEmpty()) { + AbstractVariable abs; + if (req.isGreedy()) { + abs = getAbstractVariable(req); + createImplication(new Object[] { patch, abs, iu }, matches, + Explanation.OPTIONAL_REQUIREMENT); + for (IInstallableUnit current : matches) { + if (nonGreedyIUs.contains(current)) { + addNonGreedyProvider(getNonGreedyVariable(current), abs); + } + } + optionalAbstractRequirements.add(abs); + } else { + abs = getAbstractVariable(req, false); + List newConstraint = new ArrayList<>(matches.size()); + for (IInstallableUnit current : matches) { + newConstraint.add(getNonGreedyVariable(current)); + } + createImplication(new Object[] { patch, abs, iu }, newConstraint, + Explanation.OPTIONAL_REQUIREMENT); + } + } + } + } + //Generate dependency when the patch is not applied + //-P1 -> (A -> B) ( equiv. A -> (P1 or B) ) + if (isApplicable(requirement[0])) { + IRequirement req = requirement[0]; + // Fix: if multiple patches apply to the same IU-req, we need to make sure we list each + // patch as an optional match + Pending pending = nonPatchedRequirements.get(req); + if (pending != null) { + pending.matches.add(patch); + continue; + } + pending = new Pending(); + pending.left = iu; + List matches = getApplicableMatches(req); + determinePotentialHostsForFragment(iu); + if (req.getMin() > 0) { + if (matches.isEmpty()) { + matches.add(patch); + pending.explanation = new Explanation.HardRequirement(iu, req); + pending.matches = matches; + } else { + // manage non greedy IUs + List nonGreedys = new ArrayList<>(); + for (IInstallableUnit current : matches) { + if (nonGreedyIUs.contains(current)) { + nonGreedys.add(getNonGreedyVariable(current)); + } + } + matches.add(patch); + if (req.isGreedy()) { + IInstallableUnit reqIu = matches.get(0);///(IInstallableUnit) picker.query(new CapabilityQuery(req), new Collector(), null).iterator().next(); + Explanation explanation; + if (isRootIu) { + if (alreadyInstalledIUs.contains(reqIu)) { + explanation = new Explanation.IUInstalled(reqIu); + } else { + explanation = new Explanation.IUToInstall(reqIu); + } + } else { + explanation = new Explanation.HardRequirement(iu, req); + } + + // Fix: make sure we collect all patches that will impact this IU-req, not just one + pending.explanation = explanation; + pending.matches = matches; + for (IInstallableUnit current : matches) { + if (nonGreedyIUs.contains(current)) { + addNonGreedyProvider(getNonGreedyVariable(current), iu); + } + } + } else { + List newConstraint = new ArrayList<>(matches.size()); + for (IInstallableUnit current : matches) { + newConstraint.add(getNonGreedyVariable(current)); + } + pending.explanation = new Explanation.HardRequirement(iu, req); + pending.matches = newConstraint; + } + nonPatchedRequirements.put(req, pending); + + } + } else { + if (!matches.isEmpty()) { + AbstractVariable abs; + matches.add(patch); + pending = new Pending(); + pending.explanation = Explanation.OPTIONAL_REQUIREMENT; + + if (req.isGreedy()) { + abs = getAbstractVariable(req); + // Fix: make sure we collect all patches that will impact this IU-req, not just one + pending.left = new Object[] { abs, iu }; + pending.matches = matches; + for (IInstallableUnit current : matches) { + if (nonGreedyIUs.contains(current)) { + addNonGreedyProvider(getNonGreedyVariable(current), abs); + } + } + } else { + abs = getAbstractVariable(req, false); + List newConstraint = new ArrayList<>(matches.size()); + for (IInstallableUnit current : matches) { + newConstraint.add(getNonGreedyVariable(current)); + } + newConstraint.add(patch); + pending.left = new Object[] { abs, iu }; + pending.matches = newConstraint; + } + nonPatchedRequirements.put(req, pending); + optionalAbstractRequirements.add(abs); + } + } + } + } + } + + // Fix: now create the pending non-patch requirements based on the full set of patches + for (Pending pending : nonPatchedRequirements.values()) { + createImplication(pending.left, pending.matches, pending.explanation); + } + + for (Entry> entry : unchangedRequirements.entrySet()) { + List patchesApplied = entry.getValue(); + Iterator allPatches = applicablePatches.iterator(); + List requiredPatches = new ArrayList<>(); + while (allPatches.hasNext()) { + IInstallableUnitPatch patch = (IInstallableUnitPatch) allPatches.next(); + if (!patchesApplied.contains(patch)) + requiredPatches.add(patch); + } + IRequirement req = entry.getKey(); + List matches = getApplicableMatches(req); + determinePotentialHostsForFragment(iu); + if (req.getMin() > 0) { + if (matches.isEmpty()) { + if (requiredPatches.isEmpty()) { + missingRequirement(iu, req); + } else { + createImplication(iu, requiredPatches, new Explanation.HardRequirement(iu, req)); + } + } else { + // manage non greedy IUs + List nonGreedys = new ArrayList<>(matches.size()); + for (IInstallableUnit current : matches) { + if (nonGreedyIUs.contains(current)) { + nonGreedys.add(getNonGreedyVariable(current)); + } + } + if (!requiredPatches.isEmpty()) + matches.addAll(requiredPatches); + if (req.isGreedy()) { + IInstallableUnit reqIu = matches.get(0); + Explanation explanation; + if (isRootIu) { + if (alreadyInstalledIUs.contains(reqIu)) { + explanation = new Explanation.IUInstalled(reqIu); + } else { + explanation = new Explanation.IUToInstall(reqIu); + } + } else { + explanation = new Explanation.HardRequirement(iu, req); + } + createImplication(iu, matches, explanation); + for (IInstallableUnit current : matches) { + if (nonGreedyIUs.contains(current)) { + addNonGreedyProvider(getNonGreedyVariable(current), iu); + } + } + } else { + List newConstraint = new ArrayList<>(matches.size()); + for (IInstallableUnit current : matches) { + newConstraint.add(getNonGreedyVariable(current)); + } + createImplication(new Object[] { iu }, newConstraint, new Explanation.HardRequirement(iu, req)); // FIXME + } + } + } else { + if (!matches.isEmpty()) { + if (!requiredPatches.isEmpty()) + matches.addAll(requiredPatches); + AbstractVariable abs; + if (req.isGreedy()) { + abs = getAbstractVariable(req); + createImplication(new Object[] { abs, iu }, matches, Explanation.OPTIONAL_REQUIREMENT); + for (IInstallableUnit current : matches) { + if (nonGreedyIUs.contains(current)) { + addNonGreedyProvider(getNonGreedyVariable(current), iu); + } + } + } else { + abs = getAbstractVariable(req, false); + List newConstraint = new ArrayList<>(matches.size()); + for (IInstallableUnit current : matches) { + newConstraint.add(getNonGreedyVariable(current)); + } + createImplication(new Object[] { abs, iu }, newConstraint, + new Explanation.HardRequirement(iu, req)); // FIXME + } + optionalAbstractRequirements.add(abs); + } + } + } + } + + private void expandLifeCycle(IInstallableUnit iu, boolean isRootIu) throws ContradictionException { + if (!(iu instanceof IInstallableUnitPatch)) + return; + IInstallableUnitPatch patch = (IInstallableUnitPatch) iu; + IRequirement req = patch.getLifeCycle(); + if (req == null) + return; + expandRequirement(req, iu, Collections.emptyList(), isRootIu); + } + + private void missingRequirement(IInstallableUnit iu, IRequirement req) throws ContradictionException { + result.add(Status.warning(NLS.bind(Messages.Planner_Unsatisfied_dependency, iu, req))); + createNegation(iu, req); + } + + /** + * @return a list of mandatory requirements if any, an empty list if req.isOptional(). + */ + private List getApplicableMatches(IRequirement req) { + List target = new ArrayList<>(); + IQueryResult matches = picker.query(QueryUtil.createMatchQuery(req.getMatches()), null); + for (IInstallableUnit match : matches) { + if (isApplicable(match)) { + target.add(match); + } + } + emptyBecauseFiltered = !matches.isEmpty() && target.isEmpty(); + return target; + } + + //Return a new array of requirements representing the application of the patch + private IRequirement[][] mergeRequirements(IInstallableUnit iu, IInstallableUnitPatch patch) { + if (patch == null) + return null; + List changes = patch.getRequirementsChange(); + Collection iuRequirements = iu.getRequirements(); + IRequirement[] originalRequirements = iuRequirements.toArray(new IRequirement[iuRequirements.size()]); + List rrr = new ArrayList<>(); + boolean found = false; + for (IRequirementChange change : changes) { + for (int j = 0; j < originalRequirements.length; j++) { + if (originalRequirements[j] != null && safeMatch(originalRequirements, change, j)) { + found = true; + if (change.newValue() != null) + rrr.add(new IRequirement[] { originalRequirements[j], change.newValue() }); + else + // case where a requirement is removed + rrr.add(new IRequirement[] { originalRequirements[j], null }); + originalRequirements[j] = null; + } + // break; + } + if (!found && change.applyOn() == null && change.newValue() != null) //Case where a new requirement is added + rrr.add(new IRequirement[] { null, change.newValue() }); + } + //Add all the unmodified requirements to the result + for (IRequirement originalRequirement : originalRequirements) { + if (originalRequirement != null) + rrr.add(new IRequirement[] { originalRequirement, originalRequirement }); + } + return rrr.toArray(new IRequirement[rrr.size()][]); + } + + private boolean safeMatch(IRequirement[] originalRequirements, IRequirementChange change, int j) { + try { + return change.matches((IRequiredCapability) originalRequirements[j]); + } catch (ClassCastException e) { + return false; + } + } + + //This will create as many implication as there is element in the right argument + private void createNegationImplication(Object left, List right, Explanation name) throws ContradictionException { + if (DEBUG) { + Tracing.debug(name + ": " + left + "->" + right); //$NON-NLS-1$ //$NON-NLS-2$ + } + for (Object r : right) + dependencyHelper.implication(new Object[] { left }).impliesNot(r).named(name); + } + + private void createImplication(Object left, List right, Explanation name) throws ContradictionException { + if (DEBUG) { + Tracing.debug(name + ": " + left + "->" + right); //$NON-NLS-1$ //$NON-NLS-2$ + } + dependencyHelper.implication(new Object[] { left }).implies(right.toArray()).named(name); + } + + private void createImplication(Object[] left, List right, Explanation name) throws ContradictionException { + if (DEBUG) { + Tracing.debug(name + ": " + Arrays.asList(left) + "->" + right); //$NON-NLS-1$ //$NON-NLS-2$ + } + dependencyHelper.implication(left).implies(right.toArray()).named(name); + } + + //Return IUPatches that are applicable for the given iu + private IQueryResult getApplicablePatches(IInstallableUnit iu) { + if (patches == null) + patches = new QueryableArray(picker.query(QueryUtil.createIUPatchQuery(), null).toUnmodifiableSet()); + + return patches.query(new ApplicablePatchQuery(iu), null); + } + + //Create constraints to deal with singleton + //When there is a mix of singleton and non singleton, several constraints are generated + private void createConstraintsForSingleton() throws ContradictionException { + Set>> s = slice.entrySet(); + for (Entry> entry : s) { + Map conflictingEntries = entry.getValue(); + if (conflictingEntries.size() < 2) + continue; + + Collection conflictingVersions = conflictingEntries.values(); + List singletons = new ArrayList<>(); + List nonSingletons = new ArrayList<>(); + for (IInstallableUnit iu : conflictingVersions) { + if (iu.isSingleton()) { + singletons.add(iu); + } else { + nonSingletons.add(iu); + } + } + if (singletons.isEmpty()) + continue; + + IInstallableUnit[] singletonArray; + if (nonSingletons.isEmpty()) { + singletonArray = singletons.toArray(new IInstallableUnit[singletons.size()]); + createAtMostOne(singletonArray); + } else { + singletonArray = singletons.toArray(new IInstallableUnit[singletons.size() + 1]); + for (IInstallableUnit nonSingleton : nonSingletons) { + singletonArray[singletonArray.length - 1] = nonSingleton; + createAtMostOne(singletonArray); + } + } + } + } + + private void createAtMostOne(IInstallableUnit[] ius) throws ContradictionException { + if (DEBUG) { + StringBuilder b = new StringBuilder(); + for (IInstallableUnit iu : ius) { + b.append(iu.toString()); + } + Tracing.debug("At most 1 of " + b); //$NON-NLS-1$ + } + dependencyHelper.atMost(1, (Object[]) ius).named(new Explanation.Singleton(ius)); + } + + private AbstractVariable getAbstractVariable(IRequirement req) { + return getAbstractVariable(req, true); + } + + private AbstractVariable getAbstractVariable(IRequirement req, boolean appearInOptFunction) { + AbstractVariable abstractVariable = DEBUG_ENCODING ? new AbstractVariable("Abs_" + req.toString()) //$NON-NLS-1$ + : new AbstractVariable(); + if (appearInOptFunction) { + abstractVariables.add(abstractVariable); + } + return abstractVariable; + } + + private AbstractVariable getNonGreedyVariable(IInstallableUnit iu) { + AbstractVariable v = nonGreedyVariables.get(iu); + if (v == null) { + v = DEBUG_ENCODING ? new AbstractVariable("NG_" + iu.toString()) : new AbstractVariable(); //$NON-NLS-1$ + nonGreedyVariables.put(iu, v); + } + return v; + } + + public IStatus invokeSolver(IProgressMonitor monitor) { + if (result.getSeverity() == IStatus.ERROR) + return result; + // CNF filename is given on the command line + long start = System.currentTimeMillis(); + if (DEBUG) + Tracing.debug("Invoking solver: " + start); //$NON-NLS-1$ + try { + if (monitor.isCanceled()) + return Status.CANCEL_STATUS; + if (dependencyHelper.hasASolution(assumptions)) { + if (DEBUG) { + Tracing.debug("Satisfiable !"); //$NON-NLS-1$ + } + backToIU(); + long stop = System.currentTimeMillis(); + if (DEBUG) + Tracing.debug("Solver solution found in: " + (stop - start) + " ms."); //$NON-NLS-1$ //$NON-NLS-2$ + } else { + long stop = System.currentTimeMillis(); + if (DEBUG) { + Tracing.debug("Unsatisfiable !"); //$NON-NLS-1$ + Tracing.debug("Solver solution NOT found: " + (stop - start)); //$NON-NLS-1$ + } + result = new MultiStatus(DirectorActivator.PI_DIRECTOR, UNSATISFIABLE, result.getChildren(), + Messages.Planner_Unsatisfiable_problem, null); + result.merge(new Status(IStatus.ERROR, DirectorActivator.PI_DIRECTOR, UNSATISFIABLE, + Messages.Planner_Unsatisfiable_problem, null)); + } + } catch (TimeoutException e) { + result.merge(Status.error(Messages.Planner_Timeout)); + } catch (Exception e) { + result.merge(Status.error(Messages.Planner_Unexpected_problem, e)); + } + if (DEBUG) + System.out.println(); + return result; + } + + private void backToIU() { + solution = new ArrayList<>(); + IVec sat4jSolution = dependencyHelper.getSolution(); + for (Iterator iter = sat4jSolution.iterator(); iter.hasNext();) { + Object var = iter.next(); + if (var instanceof IInstallableUnit) { + IInstallableUnit iu = (IInstallableUnit) var; + if (iu == entryPoint) + continue; + solution.add(iu); + } + } + } + + private void printSolution(Collection state) { + ArrayList l = new ArrayList<>(state); + l.sort(null); + Tracing.debug("Solution:"); //$NON-NLS-1$ + Tracing.debug("Numbers of IUs selected: " + l.size()); //$NON-NLS-1$ + for (IInstallableUnit s : l) { + Tracing.debug(s.toString()); + } + } + + public Collection extractSolution() { + if (DEBUG) + printSolution(solution); + return solution; + } + + public Set getExplanation(IProgressMonitor monitor) { + ExplanationJob job = new ExplanationJob(); + job.schedule(); + monitor.setTaskName(Messages.Planner_NoSolution); + IProgressMonitor pm = new InfiniteProgress(monitor); + pm.beginTask(Messages.Planner_NoSolution, 1000); + try { + synchronized (job) { + while (job.getExplanationResult() == null && job.getState() != Job.NONE) { + if (monitor.isCanceled()) { + job.cancel(); + throw new OperationCanceledException(); + } + pm.worked(1); + try { + job.wait(100); + } catch (InterruptedException e) { + if (DEBUG) + Tracing.debug("Interrupted while computing explanations"); //$NON-NLS-1$ + } + } + } + } finally { + monitor.done(); + } + return job.getExplanationResult(); + } + + public Map> getFragmentAssociation() { + Map> resolvedFragments = new HashMap<>(fragments.size()); + for (Entry> fragment : fragments.entrySet()) { + if (!dependencyHelper.getBooleanValueFor(fragment.getKey())) + continue; + Set potentialHosts = fragment.getValue(); + List resolvedHost = new ArrayList<>(potentialHosts.size()); + for (IInstallableUnit host : potentialHosts) { + if (dependencyHelper.getBooleanValueFor(host)) + resolvedHost.add(host); + } + if (resolvedHost.size() != 0) + resolvedFragments.put(fragment.getKey(), resolvedHost); + } + return resolvedFragments; + } + + private void rememberHostMatches(IInstallableUnitFragment fragment, List matches) { + Set existingMatches = fragments.get(fragment); + if (existingMatches == null) { + existingMatches = new HashSet<>(); + fragments.put(fragment, existingMatches); + existingMatches.addAll(matches); + } + existingMatches.retainAll(matches); + } + + public void setUserDefined(boolean containsKey) { + userDefinedFunction = containsKey; + } + + public void close() { + if (dependencyHelper != null) { + dependencyHelper.reset(); + dependencyHelper = null; + } + } +} diff --git a/tycho-core/src/main/java/org/eclipse/tycho/p2tools/copiedfromp2/UserDefinedOptimizationFunction.java b/tycho-core/src/main/java/org/eclipse/tycho/p2tools/copiedfromp2/UserDefinedOptimizationFunction.java new file mode 100644 index 0000000000..a4c2faee89 --- /dev/null +++ b/tycho-core/src/main/java/org/eclipse/tycho/p2tools/copiedfromp2/UserDefinedOptimizationFunction.java @@ -0,0 +1,233 @@ +/******************************************************************************* + * Copyright (c) 2009, 2020 Daniel Le Berre 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: + * Daniel Le Berre - initial API and implementation + * Red Hat, Inc. - support for remediation page + ******************************************************************************/ +package org.eclipse.tycho.p2tools.copiedfromp2; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.eclipse.equinox.internal.p2.director.Explanation; +import org.eclipse.equinox.p2.metadata.IInstallableUnit; +import org.eclipse.equinox.p2.metadata.IRequirement; +import org.eclipse.equinox.p2.metadata.Version; +import org.eclipse.equinox.p2.query.IQuery; +import org.eclipse.equinox.p2.query.IQueryResult; +import org.eclipse.equinox.p2.query.IQueryable; +import org.eclipse.equinox.p2.query.QueryUtil; +import org.eclipse.tycho.p2tools.copiedfromp2.Projector.AbstractVariable; +import org.sat4j.pb.tools.DependencyHelper; +import org.sat4j.pb.tools.SteppedTimeoutLexicoHelper; +import org.sat4j.pb.tools.WeightedObject; +import org.sat4j.specs.ContradictionException; + +public class UserDefinedOptimizationFunction extends OptimizationFunction { + private Collection alreadyExistingRoots; + private SteppedTimeoutLexicoHelper dependencyHelper; + private IQueryable picker; + + public UserDefinedOptimizationFunction(IQueryable lastState, + List abstractVariables, List optionalVariables, + IQueryable picker, IInstallableUnit selectionContext, + Map> slice, DependencyHelper dependencyHelper, + Collection alreadyInstalledIUs) { + super(lastState, abstractVariables, optionalVariables, picker, selectionContext, slice); + this.picker = picker; + this.slice = slice; + this.dependencyHelper = (SteppedTimeoutLexicoHelper) dependencyHelper; + this.alreadyExistingRoots = alreadyInstalledIUs; + } + + @Override + public List> createOptimizationFunction(IInstallableUnit metaIu, + Collection newRoots) { + List> weightedObjects = new ArrayList<>(); + List objects = new ArrayList<>(); + BigInteger weight = BigInteger.valueOf(slice.size() + 1); + String[] criterias = new String[] { "+new", "-notuptodate", "-changed", "-removed" }; //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$//$NON-NLS-4$ + BigInteger currentWeight = weight.pow(criterias.length - 1); + boolean maximizes; + Object thing; + for (String criteria : criterias) { + if (criteria.endsWith("new")) { //$NON-NLS-1$ + weightedObjects.clear(); + newRoots(weightedObjects, criteria.startsWith("+") ? currentWeight.negate() : currentWeight, metaIu); //$NON-NLS-1$ + currentWeight = currentWeight.divide(weight); + } else if (criteria.endsWith("removed")) { //$NON-NLS-1$ + weightedObjects.clear(); + removedRoots(weightedObjects, criteria.startsWith("+") ? currentWeight.negate() : currentWeight, //$NON-NLS-1$ + metaIu); + currentWeight = currentWeight.divide(weight); + } else if (criteria.endsWith("notuptodate")) { //$NON-NLS-1$ + weightedObjects.clear(); + notuptodate(weightedObjects, criteria.startsWith("+") ? currentWeight.negate() : currentWeight, metaIu); //$NON-NLS-1$ + currentWeight = currentWeight.divide(weight); + } else if (criteria.endsWith("changed")) { //$NON-NLS-1$ + weightedObjects.clear(); + changedRoots(weightedObjects, criteria.startsWith("+") ? currentWeight.negate() : currentWeight, //$NON-NLS-1$ + metaIu); + currentWeight = currentWeight.divide(weight); + } + objects.clear(); + maximizes = criteria.startsWith("+"); //$NON-NLS-1$ + for (WeightedObject weightedObject : weightedObjects) { + thing = weightedObject.thing; + if (maximizes) { + thing = dependencyHelper.not(thing); + } + objects.add(thing); + } + dependencyHelper.addCriterion(objects); + } + weightedObjects.clear(); + return null; + } + + protected void changedRoots(List> weightedObjects, BigInteger weight, + IInstallableUnit entryPointIU) { + Collection requirements = entryPointIU.getRequirements(); + for (IRequirement req : requirements) { + IQuery query = QueryUtil.createMatchQuery(req.getMatches()); + IQueryResult matches = picker.query(query, null); + Object[] changed = new Object[matches.toUnmodifiableSet().size()]; + int i = 0; + for (IInstallableUnit match : matches) { + changed[i++] = isInstalledAsRoot(match) ? dependencyHelper.not(match) : match; + } + try { + Projector.AbstractVariable abs = new Projector.AbstractVariable("CHANGED"); //$NON-NLS-1$ + dependencyHelper.or(FakeExplanation.getInstance(), abs, changed); + weightedObjects.add(WeightedObject.newWO(abs, weight)); + } catch (ContradictionException e) { + // TODO Auto-generated catch block TODO + e.printStackTrace(); + } + } + } + + protected void newRoots(List> weightedObjects, BigInteger weight, IInstallableUnit entryPointIU) { + Collection requirements = entryPointIU.getRequirements(); + for (IRequirement req : requirements) { + IQuery query = QueryUtil.createMatchQuery(req.getMatches()); + IQueryResult matches = picker.query(query, null); + boolean oneInstalled = false; + for (IInstallableUnit match : matches) { + oneInstalled = oneInstalled || isInstalledAsRoot(match); + } + if (!oneInstalled) { + try { + Projector.AbstractVariable abs = new Projector.AbstractVariable("NEW"); //$NON-NLS-1$ + dependencyHelper.or(FakeExplanation.getInstance(), abs, + (Object[]) matches.toArray(IInstallableUnit.class)); + weightedObjects.add(WeightedObject.newWO(abs, weight)); + } catch (ContradictionException e) { + // should not happen + e.printStackTrace(); + } + } + } + } + + protected void removedRoots(List> weightedObjects, BigInteger weight, + IInstallableUnit entryPointIU) { + Collection requirements = entryPointIU.getRequirements(); + for (IRequirement req : requirements) { + IQuery query = QueryUtil.createMatchQuery(req.getMatches()); + IQueryResult matches = picker.query(query, null); + boolean installed = false; + Object[] literals = new Object[matches.toUnmodifiableSet().size()]; + int i = 0; + for (IInstallableUnit match : matches) { + installed = installed || isInstalledAsRoot(match); + literals[i++] = dependencyHelper.not(match); + } + if (installed) { + try { + Projector.AbstractVariable abs = new Projector.AbstractVariable("REMOVED"); //$NON-NLS-1$ + dependencyHelper.and(FakeExplanation.getInstance(), abs, literals); + weightedObjects.add(WeightedObject.newWO(abs, weight)); + } catch (ContradictionException e) { + // should not happen TODO + e.printStackTrace(); + } + } + } + } + + protected void notuptodate(List> weightedObjects, BigInteger weight, + IInstallableUnit entryPointIU) { + Collection requirements = entryPointIU.getRequirements(); + for (IRequirement req : requirements) { + IQuery query = QueryUtil.createMatchQuery(req.getMatches()); + IQueryResult matches = picker.query(query, null); + List toSort = new ArrayList<>(matches.toUnmodifiableSet()); + toSort.sort(Collections.reverseOrder()); + if (toSort.isEmpty()) + continue; + + Projector.AbstractVariable abs = new Projector.AbstractVariable(); + Object notlatest = dependencyHelper.not(toSort.get(0)); + try { + // notuptodate <=> not iuvn and (iuv1 or iuv2 or ... iuvn-1) + dependencyHelper.implication(new Object[] { abs }).implies(notlatest) + .named(FakeExplanation.getInstance()); + Object[] clause = new Object[toSort.size()]; + toSort.toArray(clause); + clause[0] = dependencyHelper.not(abs); + dependencyHelper.clause(FakeExplanation.getInstance(), clause); + for (int i = 1; i < toSort.size(); i++) { + dependencyHelper.implication(new Object[] { notlatest, toSort.get(i) }).implies(abs) + .named(FakeExplanation.getInstance()); + } + } catch (ContradictionException e) { + // should never happen + e.printStackTrace(); + } + + weightedObjects.add(WeightedObject.newWO(abs, weight)); + } + } + + private static class FakeExplanation extends Explanation { + private static Explanation singleton = new FakeExplanation(); + + public static Explanation getInstance() { + return singleton; + } + + @Override + protected int orderValue() { + return Explanation.OTHER_REASON; + } + + @Override + public int shortAnswer() { + return 0; + } + + } + + private boolean isInstalledAsRoot(IInstallableUnit isInstalled) { + for (IInstallableUnit installed : alreadyExistingRoots) { + if (isInstalled.equals(installed)) + return true; + } + return false; + } + +} From fc737fd651821ae440ef8cb21d87ed904500a423 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20L=C3=A4ubrich?= Date: Sat, 20 Jan 2024 19:42:17 +0100 Subject: [PATCH 2/2] Fail on missing plugin product requirement Currently Tycho does not fail if a requirement of a product can't be found in the resolve phase but only if the product is materialized. Even then it fails with an obscure error that claims the products bundle is not found where actually the dependency can't be found. --- .../actions/ProductDependenciesAction.java | 2 +- .../TargetPlatformConfigurationMojo.java | 43 +++++++- .../maven/TychoProjectExecutionListener.java | 2 +- .../publisher/ProductDependenciesAction.java | 103 ------------------ .../AbstractSlicerResolutionStrategy.java | 2 +- .../tycho/p2resolver/P2GeneratorImpl.java | 2 +- .../tycho/p2resolver/P2ResolverImpl.java | 2 +- .../ProjectorResolutionStrategy.java | 35 +++++- .../tycho/p2resolver/ResolutionData.java | 12 ++ .../p2resolver/SlicerResolutionStrategy.java | 4 +- .../tycho/p2tools/copiedfromp2/Projector.java | 6 +- .../tycho/test/util/InstallableUnitUtil.java | 9 +- .../feature/build.properties | 11 ++ .../feature/feature.xml | 30 +++++ .../feature/pom.xml | 50 +++++++++ .../missing-requirements-feature/pom.xml | 29 +++++ .../product/feature.product | 10 ++ .../product/pom.xml | 49 +++++++++ .../plugin.product | 8 ++ .../missing-requirements-plugins/pom.xml | 49 +++++++++ .../plugin.product | 14 +++ .../product-with-native-fragments/pom.xml | 94 ++++++++++++++++ .../tycho/test/product/ProductBuildTest.java | 36 ++++++ 23 files changed, 478 insertions(+), 124 deletions(-) delete mode 100644 tycho-core/src/main/java/org/eclipse/tycho/p2/publisher/ProductDependenciesAction.java create mode 100644 tycho-its/projects/tycho-p2-director-plugin/missing-requirements-feature/feature/build.properties create mode 100644 tycho-its/projects/tycho-p2-director-plugin/missing-requirements-feature/feature/feature.xml create mode 100644 tycho-its/projects/tycho-p2-director-plugin/missing-requirements-feature/feature/pom.xml create mode 100644 tycho-its/projects/tycho-p2-director-plugin/missing-requirements-feature/pom.xml create mode 100644 tycho-its/projects/tycho-p2-director-plugin/missing-requirements-feature/product/feature.product create mode 100644 tycho-its/projects/tycho-p2-director-plugin/missing-requirements-feature/product/pom.xml create mode 100644 tycho-its/projects/tycho-p2-director-plugin/missing-requirements-plugins/plugin.product create mode 100644 tycho-its/projects/tycho-p2-director-plugin/missing-requirements-plugins/pom.xml create mode 100644 tycho-its/projects/tycho-p2-director-plugin/product-with-native-fragments/plugin.product create mode 100644 tycho-its/projects/tycho-p2-director-plugin/product-with-native-fragments/pom.xml diff --git a/p2-maven-plugin/src/main/java/org/eclipse/tycho/p2maven/actions/ProductDependenciesAction.java b/p2-maven-plugin/src/main/java/org/eclipse/tycho/p2maven/actions/ProductDependenciesAction.java index 3058dbc170..dbdf4e85eb 100644 --- a/p2-maven-plugin/src/main/java/org/eclipse/tycho/p2maven/actions/ProductDependenciesAction.java +++ b/p2-maven-plugin/src/main/java/org/eclipse/tycho/p2maven/actions/ProductDependenciesAction.java @@ -61,7 +61,7 @@ protected Set getRequiredCapabilities() { } if (type == ProductContentType.BUNDLES || type == ProductContentType.MIXED) { for (FeatureEntry plugin : ((ProductFile) product).getProductEntries()) { - addRequiredCapability(required, plugin.getId(), Version.parseVersion(plugin.getVersion()), null, true); + addRequiredCapability(required, plugin.getId(), Version.parseVersion(plugin.getVersion()), null, false); } } diff --git a/target-platform-configuration/src/main/java/org/eclipse/tycho/target/TargetPlatformConfigurationMojo.java b/target-platform-configuration/src/main/java/org/eclipse/tycho/target/TargetPlatformConfigurationMojo.java index 8f062b3d30..5b8a39a0f7 100644 --- a/target-platform-configuration/src/main/java/org/eclipse/tycho/target/TargetPlatformConfigurationMojo.java +++ b/target-platform-configuration/src/main/java/org/eclipse/tycho/target/TargetPlatformConfigurationMojo.java @@ -37,7 +37,45 @@ public class TargetPlatformConfigurationMojo extends AbstractMojo { /** + *

* Target environments (os/ws/arch) to consider. + *

+ * Example: + * + *
+     *<environments>
+    *  <environment>
+    *    <os>linux</os>
+    *    <ws>gtk</ws>
+    *    <arch>x86_64</arch>
+    *  </environment>
+    *  <environment>
+    *    <os>linux</os>
+    *    <ws>gtk</ws>
+    *    <arch>ppc64le</arch>
+    *  </environment>
+    *   <environment>
+    *    <os>linux</os>
+    *    <ws>gtk</ws>
+    *    <arch>aarch64</arch>
+    *  </environment>
+    *  <environment>
+    *    <os>win32</os>
+    *    <ws>win32</ws>
+    *    <arch>x86_64</arch>
+    *  </environment>
+    *  <environment>
+    *    <os>macosx</os>
+    *    <ws>cocoa</ws>
+    *    <arch>x86_64</arch>
+    *  </environment>
+    *  <environment>
+    *    <os>macosx</os>
+    *    <ws>cocoa</ws>
+    *    <arch>aarch64</arch>
+    *  </environment>
+    *</environments>
+     * 
*/ @Parameter(name = DefaultTargetPlatformConfigurationReader.ENVIRONMENTS) private TargetEnvironment[] environments; @@ -208,8 +246,9 @@ public class TargetPlatformConfigurationMojo extends AbstractMojo { private IncludeSourceMode targetDefinionIncludeSource; /** - * Configures if referenced repositories should be included when fetching repositories. - * The default is ignore. To specify to use referenced repositories, pass include. + * Configures if referenced repositories should be included when fetching repositories. The + * default is ignore. To specify to use referenced repositories, pass + * include. */ @Parameter(name = DefaultTargetPlatformConfigurationReader.REFERENCED_REPOSITORY_MODE) private ReferencedRepositoryMode referencedRepositoryMode; diff --git a/tycho-core/src/main/java/org/eclipse/tycho/core/maven/TychoProjectExecutionListener.java b/tycho-core/src/main/java/org/eclipse/tycho/core/maven/TychoProjectExecutionListener.java index bc0b33fbd2..48a3c39aed 100644 --- a/tycho-core/src/main/java/org/eclipse/tycho/core/maven/TychoProjectExecutionListener.java +++ b/tycho-core/src/main/java/org/eclipse/tycho/core/maven/TychoProjectExecutionListener.java @@ -120,7 +120,7 @@ public void beforeProjectLifecycleExecution(ProjectExecutionEvent event) throws + " with context " + resolverException.getSelectionContext() + System.lineSeparator() + resolverException.explanations() .map(exp -> " " + exp.toString()).collect(Collectors.joining("\n")), - null, mavenProject); + null, mavenProject, resolverException); } } TychoProject tychoProject = projectManager.getTychoProject(mavenProject).orElse(null); diff --git a/tycho-core/src/main/java/org/eclipse/tycho/p2/publisher/ProductDependenciesAction.java b/tycho-core/src/main/java/org/eclipse/tycho/p2/publisher/ProductDependenciesAction.java deleted file mode 100644 index 13ff9d43eb..0000000000 --- a/tycho-core/src/main/java/org/eclipse/tycho/p2/publisher/ProductDependenciesAction.java +++ /dev/null @@ -1,103 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2008, 2014 Sonatype Inc. 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: - * Sonatype Inc. - initial API and implementation - *******************************************************************************/ -package org.eclipse.tycho.p2.publisher; - -import java.io.File; -import java.util.LinkedHashSet; -import java.util.Set; - -import org.eclipse.core.runtime.IPath; -import org.eclipse.core.runtime.Path; -import org.eclipse.equinox.internal.p2.publisher.eclipse.IProductDescriptor; -import org.eclipse.equinox.internal.p2.publisher.eclipse.ProductContentType; -import org.eclipse.equinox.internal.p2.publisher.eclipse.ProductFile; -import org.eclipse.equinox.p2.metadata.IRequirement; -import org.eclipse.equinox.p2.metadata.IVersionedId; -import org.eclipse.equinox.p2.metadata.MetadataFactory.InstallableUnitDescription; -import org.eclipse.equinox.p2.metadata.Version; -import org.eclipse.equinox.p2.publisher.AdviceFileAdvice; -import org.eclipse.equinox.p2.publisher.IPublisherInfo; -import org.eclipse.equinox.p2.publisher.eclipse.FeatureEntry; -import org.eclipse.tycho.p2maven.actions.AbstractDependenciesAction; - -public class ProductDependenciesAction extends AbstractDependenciesAction { - private final IProductDescriptor product; - - public ProductDependenciesAction(IProductDescriptor product) { - this.product = product; - } - - @Override - protected Version getVersion() { - return Version.create(product.getVersion()); - } - - @Override - protected String getId() { - return product.getId(); - } - - @Override - protected Set getRequiredCapabilities() { - Set required = new LinkedHashSet<>(); - - ProductContentType type = product.getProductContentType(); - if (type == ProductContentType.FEATURES || type == ProductContentType.MIXED) { - for (IVersionedId feature : product.getFeatures()) { - String id = feature.getId() + FEATURE_GROUP_IU_SUFFIX; - Version version = feature.getVersion(); - - addRequiredCapability(required, id, version, null, false); - } - } - if (type == ProductContentType.BUNDLES || type == ProductContentType.MIXED) { - for (FeatureEntry plugin : ((ProductFile) product).getProductEntries()) { - addRequiredCapability(required, plugin.getId(), Version.parseVersion(plugin.getVersion()), null, true); - } - } - - if (product.includeLaunchers()) { - addRequiredCapability(required, "org.eclipse.equinox.executable.feature.group", null, null, false); - } - return required; - } - - @Override - protected void addPublisherAdvice(IPublisherInfo publisherInfo) { - // see org.eclipse.equinox.p2.publisher.eclipse.ProductAction.createAdviceFileAdvice() - - File productFileLocation = product.getLocation(); - if (productFileLocation == null) { - return; - } - - String id = product.getId(); - Version parseVersion = Version.parseVersion(product.getVersion()); - IPath basePath = new Path(productFileLocation.getParent()); - - // must match org.eclipse.tycho.plugins.p2.publisher.PublishProductMojo.getSourceP2InfFile(File) - final String productFileName = productFileLocation.getName(); - final String p2infFilename = productFileName.substring(0, productFileName.length() - ".product".length()) - + ".p2.inf"; - - AdviceFileAdvice advice = new AdviceFileAdvice(id, parseVersion, basePath, new Path(p2infFilename)); - if (advice.containsAdvice()) { - publisherInfo.addAdvice(advice); - } - } - - @Override - protected void addProperties(InstallableUnitDescription iud) { - iud.setProperty(InstallableUnitDescription.PROP_TYPE_PRODUCT, Boolean.toString(true)); - } -} diff --git a/tycho-core/src/main/java/org/eclipse/tycho/p2resolver/AbstractSlicerResolutionStrategy.java b/tycho-core/src/main/java/org/eclipse/tycho/p2resolver/AbstractSlicerResolutionStrategy.java index 230b437b8b..f2d64c9a95 100644 --- a/tycho-core/src/main/java/org/eclipse/tycho/p2resolver/AbstractSlicerResolutionStrategy.java +++ b/tycho-core/src/main/java/org/eclipse/tycho/p2resolver/AbstractSlicerResolutionStrategy.java @@ -28,7 +28,6 @@ import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.MultiStatus; import org.eclipse.equinox.internal.p2.director.QueryableArray; -import org.eclipse.equinox.internal.p2.director.Slicer; import org.eclipse.equinox.internal.p2.metadata.IRequiredCapability; import org.eclipse.equinox.internal.p2.metadata.RequiredCapability; import org.eclipse.equinox.internal.p2.metadata.RequiredPropertiesMatch; @@ -48,6 +47,7 @@ import org.eclipse.tycho.core.shared.MavenLogger; import org.eclipse.tycho.core.shared.StatusTool; import org.eclipse.tycho.p2.resolver.ResolverException; +import org.eclipse.tycho.p2tools.copiedfromp2.Slicer; abstract class AbstractSlicerResolutionStrategy extends AbstractResolutionStrategy { diff --git a/tycho-core/src/main/java/org/eclipse/tycho/p2resolver/P2GeneratorImpl.java b/tycho-core/src/main/java/org/eclipse/tycho/p2resolver/P2GeneratorImpl.java index 87d919d8ca..4f834050b5 100644 --- a/tycho-core/src/main/java/org/eclipse/tycho/p2resolver/P2GeneratorImpl.java +++ b/tycho-core/src/main/java/org/eclipse/tycho/p2resolver/P2GeneratorImpl.java @@ -79,12 +79,12 @@ import org.eclipse.tycho.p2.publisher.DownloadStatsAdvice; import org.eclipse.tycho.p2.publisher.FeatureDependenciesAction; import org.eclipse.tycho.p2.publisher.P2Artifact; -import org.eclipse.tycho.p2.publisher.ProductDependenciesAction; import org.eclipse.tycho.p2.publisher.TransientArtifactRepository; import org.eclipse.tycho.p2.publisher.rootfiles.FeatureRootAdvice; import org.eclipse.tycho.p2.repository.ArtifactsIO; import org.eclipse.tycho.p2.repository.MetadataIO; import org.eclipse.tycho.p2maven.actions.CategoryDependenciesAction; +import org.eclipse.tycho.p2maven.actions.ProductDependenciesAction; import org.eclipse.tycho.p2maven.actions.ProductFile2; import org.osgi.framework.BundleException; diff --git a/tycho-core/src/main/java/org/eclipse/tycho/p2resolver/P2ResolverImpl.java b/tycho-core/src/main/java/org/eclipse/tycho/p2resolver/P2ResolverImpl.java index fa0715103a..e02bc5fbd3 100644 --- a/tycho-core/src/main/java/org/eclipse/tycho/p2resolver/P2ResolverImpl.java +++ b/tycho-core/src/main/java/org/eclipse/tycho/p2resolver/P2ResolverImpl.java @@ -35,7 +35,6 @@ import org.apache.felix.resolver.util.CopyOnWriteSet; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.equinox.internal.p2.director.QueryableArray; -import org.eclipse.equinox.internal.p2.director.Slicer; import org.eclipse.equinox.p2.metadata.IArtifactKey; import org.eclipse.equinox.p2.metadata.IInstallableUnit; import org.eclipse.equinox.p2.metadata.IProvidedCapability; @@ -76,6 +75,7 @@ import org.eclipse.tycho.p2.resolver.ResolverException; import org.eclipse.tycho.p2.target.facade.TargetPlatformConfigurationStub; import org.eclipse.tycho.p2.target.facade.TargetPlatformFactory; +import org.eclipse.tycho.p2tools.copiedfromp2.Slicer; import org.eclipse.tycho.targetplatform.P2TargetPlatform; public class P2ResolverImpl implements P2Resolver { diff --git a/tycho-core/src/main/java/org/eclipse/tycho/p2resolver/ProjectorResolutionStrategy.java b/tycho-core/src/main/java/org/eclipse/tycho/p2resolver/ProjectorResolutionStrategy.java index 6adf97db66..8bc48e5be9 100644 --- a/tycho-core/src/main/java/org/eclipse/tycho/p2resolver/ProjectorResolutionStrategy.java +++ b/tycho-core/src/main/java/org/eclipse/tycho/p2resolver/ProjectorResolutionStrategy.java @@ -21,23 +21,25 @@ import java.util.Map; import java.util.Set; import java.util.function.Predicate; -import java.util.stream.Collectors; 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.equinox.internal.p2.director.Explanation; -import org.eclipse.equinox.internal.p2.director.Projector; import org.eclipse.equinox.internal.p2.director.QueryableArray; import org.eclipse.equinox.internal.p2.director.SimplePlanner; -import org.eclipse.equinox.internal.p2.director.Slicer; import org.eclipse.equinox.p2.metadata.IInstallableUnit; import org.eclipse.equinox.p2.metadata.IRequirement; +import org.eclipse.equinox.p2.query.IQuery; +import org.eclipse.equinox.p2.query.IQueryResult; import org.eclipse.equinox.p2.query.IQueryable; +import org.eclipse.equinox.p2.query.QueryUtil; import org.eclipse.tycho.core.shared.MavenLogger; import org.eclipse.tycho.core.shared.StatusTool; import org.eclipse.tycho.p2.resolver.ResolverException; +import org.eclipse.tycho.p2tools.copiedfromp2.Projector; +import org.eclipse.tycho.p2tools.copiedfromp2.Slicer; public class ProjectorResolutionStrategy extends AbstractSlicerResolutionStrategy { @@ -79,9 +81,32 @@ public Collection resolve(Map properties, IPro // force profile UIs to be used during resolution seedUnits.addAll(data.getEEResolutionHints().getMandatoryUnits()); seedRequires.addAll(data.getEEResolutionHints().getMandatoryRequires()); - Projector projector = new Projector(slice(properties, generatedUnits, monitor), selectionContext, - new HashSet<>(), false); + new HashSet<>(), false) { + IQueryable units; + + @Override + protected Collection getRequiredCapabilities(IInstallableUnit iu) { + Collection requiredCapabilities = super.getRequiredCapabilities(iu); + if (QueryUtil.isProduct(iu)) { + if (units == null) { + units = data.units(); + } + return requiredCapabilities.stream().filter(requirement -> { + IQuery query = QueryUtil.createMatchQuery(requirement.getMatches()); + IQueryResult result = units.query(query, null); + if (result.isEmpty()) { + //this must fail the resolve so we need to include the requirement + return true; + } + //If none of the results are applicable this means they are filtered and need not to be considered + //this happens in plugin based products that include native fragments from different platforms + return result.stream().anyMatch(matchIu -> isApplicable(matchIu)); + }).toList(); + } + return requiredCapabilities; + } + }; projector.encode(createUnitRequiring("tycho", seedUnits, seedRequires), EMPTY_IU_ARRAY /* alreadyExistingRoots */, new QueryableArray(List.of()) /* installedIUs */, seedUnits /* newRoots */, monitor); diff --git a/tycho-core/src/main/java/org/eclipse/tycho/p2resolver/ResolutionData.java b/tycho-core/src/main/java/org/eclipse/tycho/p2resolver/ResolutionData.java index dfb308b19a..c08738713d 100644 --- a/tycho-core/src/main/java/org/eclipse/tycho/p2resolver/ResolutionData.java +++ b/tycho-core/src/main/java/org/eclipse/tycho/p2resolver/ResolutionData.java @@ -20,8 +20,10 @@ import org.eclipse.equinox.p2.metadata.IInstallableUnit; import org.eclipse.equinox.p2.metadata.IRequirement; +import org.eclipse.equinox.p2.query.CollectionResult; import org.eclipse.equinox.p2.query.IQueryable; import org.eclipse.tycho.ExecutionEnvironmentResolutionHints; +import org.eclipse.tycho.p2maven.ListQueryable; public interface ResolutionData { @@ -42,4 +44,14 @@ public interface ResolutionData { Predicate getIInstallableUnitAcceptor(); IQueryable getAdditionalUnitStore(); + + default IQueryable units() { + ListQueryable listQueryable = new ListQueryable<>(); + listQueryable.add(new CollectionResult<>(getAvailableIUs())); + IQueryable unitStore = getAdditionalUnitStore(); + if (unitStore != null) { + listQueryable.add(unitStore); + } + return listQueryable; + } } diff --git a/tycho-core/src/main/java/org/eclipse/tycho/p2resolver/SlicerResolutionStrategy.java b/tycho-core/src/main/java/org/eclipse/tycho/p2resolver/SlicerResolutionStrategy.java index bf8f85e9d8..b96f8b0bf9 100644 --- a/tycho-core/src/main/java/org/eclipse/tycho/p2resolver/SlicerResolutionStrategy.java +++ b/tycho-core/src/main/java/org/eclipse/tycho/p2resolver/SlicerResolutionStrategy.java @@ -22,14 +22,14 @@ import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.MultiStatus; -import org.eclipse.equinox.internal.p2.director.PermissiveSlicer; -import org.eclipse.equinox.internal.p2.director.Slicer; import org.eclipse.equinox.p2.metadata.IInstallableUnit; import org.eclipse.equinox.p2.query.IQueryable; import org.eclipse.equinox.p2.query.QueryUtil; import org.eclipse.tycho.TargetEnvironment; import org.eclipse.tycho.core.shared.MavenLogger; import org.eclipse.tycho.p2.resolver.ResolverException; +import org.eclipse.tycho.p2tools.copiedfromp2.PermissiveSlicer; +import org.eclipse.tycho.p2tools.copiedfromp2.Slicer; public class SlicerResolutionStrategy extends AbstractSlicerResolutionStrategy { diff --git a/tycho-core/src/main/java/org/eclipse/tycho/p2tools/copiedfromp2/Projector.java b/tycho-core/src/main/java/org/eclipse/tycho/p2tools/copiedfromp2/Projector.java index f87d606149..7a642c20a1 100644 --- a/tycho-core/src/main/java/org/eclipse/tycho/p2tools/copiedfromp2/Projector.java +++ b/tycho-core/src/main/java/org/eclipse/tycho/p2tools/copiedfromp2/Projector.java @@ -379,12 +379,12 @@ private void createNegation(IInstallableUnit iu, IRequirement req) throws Contra } // Check whether the requirement is applicable - private boolean isApplicable(IRequirement req) { + protected boolean isApplicable(IRequirement req) { IMatchExpression filter = req.getFilter(); return filter == null || filter.isMatch(selectionContext); } - private boolean isApplicable(IInstallableUnit iu) { + protected boolean isApplicable(IInstallableUnit iu) { IMatchExpression filter = iu.getFilter(); return filter == null || filter.isMatch(selectionContext); } @@ -536,7 +536,7 @@ public void processIU(IInstallableUnit iu, boolean isRootIU) throws Contradictio } } - private Collection getRequiredCapabilities(IInstallableUnit iu) { + protected Collection getRequiredCapabilities(IInstallableUnit iu) { boolean isFragment = iu instanceof IInstallableUnitFragment; //Short-circuit for the case of an IInstallableUnit if ((!isFragment) && iu.getMetaRequirements().size() == 0) diff --git a/tycho-core/src/test/java/org/eclipse/tycho/test/util/InstallableUnitUtil.java b/tycho-core/src/test/java/org/eclipse/tycho/test/util/InstallableUnitUtil.java index c20baadc87..9236e3837b 100644 --- a/tycho-core/src/test/java/org/eclipse/tycho/test/util/InstallableUnitUtil.java +++ b/tycho-core/src/test/java/org/eclipse/tycho/test/util/InstallableUnitUtil.java @@ -26,13 +26,14 @@ import org.eclipse.equinox.p2.metadata.MetadataFactory.InstallableUnitDescription; import org.eclipse.equinox.p2.metadata.Version; import org.eclipse.equinox.p2.metadata.VersionRange; +import org.eclipse.equinox.p2.publisher.eclipse.BundlesAction; public class InstallableUnitUtil { - static final String IU_CAPABILITY_NS = "org.eclipse.equinox.p2.iu"; // see IInstallableUnit.NAMESPACE_IU_ID; - static final String BUNDLE_CAPABILITY_NS = "osgi.bundle"; // see BundlesAction.CAPABILITY_NS_OSGI_BUNDLE - static final String PRODUCT_TYPE_PROPERTY = "org.eclipse.equinox.p2.type.product"; // see InstallableUnitDescription.PROP_TYPE_PRODUCT; - static final String FEATURE_TYPE_PROPERTY = "org.eclipse.equinox.p2.type.group"; // see InstallableUnitDescription.PROP_TYPE_GROUP; + static final String IU_CAPABILITY_NS = IInstallableUnit.NAMESPACE_IU_ID; + static final String BUNDLE_CAPABILITY_NS = BundlesAction.CAPABILITY_NS_OSGI_BUNDLE; + static final String PRODUCT_TYPE_PROPERTY = InstallableUnitDescription.PROP_TYPE_PRODUCT; + static final String FEATURE_TYPE_PROPERTY = InstallableUnitDescription.PROP_TYPE_GROUP; public static String DEFAULT_VERSION = "0.0.20"; diff --git a/tycho-its/projects/tycho-p2-director-plugin/missing-requirements-feature/feature/build.properties b/tycho-its/projects/tycho-p2-director-plugin/missing-requirements-feature/feature/build.properties new file mode 100644 index 0000000000..0b6dc2b511 --- /dev/null +++ b/tycho-its/projects/tycho-p2-director-plugin/missing-requirements-feature/feature/build.properties @@ -0,0 +1,11 @@ +############################################################################### +# Copyright (c) 2010, 2011 SAP AG and others. +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License v1.0 +# which accompanies this distribution, and is available at +# https://www.eclipse.org/legal/epl-v10.html +# +# Contributors: +# SAP AG - initial API and implementation +############################################################################### +bin.includes = feature.xml diff --git a/tycho-its/projects/tycho-p2-director-plugin/missing-requirements-feature/feature/feature.xml b/tycho-its/projects/tycho-p2-director-plugin/missing-requirements-feature/feature/feature.xml new file mode 100644 index 0000000000..c124ac532d --- /dev/null +++ b/tycho-its/projects/tycho-p2-director-plugin/missing-requirements-feature/feature/feature.xml @@ -0,0 +1,30 @@ + + + + + A description of an example feature + + + + [Enter Copyright Description here.] + + + + [Enter License Description here.] + + + + + + + + + + diff --git a/tycho-its/projects/tycho-p2-director-plugin/missing-requirements-feature/feature/pom.xml b/tycho-its/projects/tycho-p2-director-plugin/missing-requirements-feature/feature/pom.xml new file mode 100644 index 0000000000..defb37f1b3 --- /dev/null +++ b/tycho-its/projects/tycho-p2-director-plugin/missing-requirements-feature/feature/pom.xml @@ -0,0 +1,50 @@ + + + 4.0.0 + + + tycho-its + missing.parent + 1.0.0-SNAPSHOT + + + org.osgi.service.jaxrs.feature + eclipse-feature + Example Feature + + + + org.osgi + org.osgi.service.jaxrs + 1.0.1 + provided + + + org.apache.aries.spec + org.apache.aries.javax.jax.rs-api + 1.0.4 + provided + + + javax.xml.bind + jaxb-api + 2.3.1 + provided + + + jakarta.activation + jakarta.activation-api + 1.2.2 + provided + + + org.eclipse.platform + org.eclipse.osgi + 3.18.600 + provided + + + + diff --git a/tycho-its/projects/tycho-p2-director-plugin/missing-requirements-feature/pom.xml b/tycho-its/projects/tycho-p2-director-plugin/missing-requirements-feature/pom.xml new file mode 100644 index 0000000000..e6378baee9 --- /dev/null +++ b/tycho-its/projects/tycho-p2-director-plugin/missing-requirements-feature/pom.xml @@ -0,0 +1,29 @@ + + 4.0.0 + + tycho-its + missing.parent + 1.0.0-SNAPSHOT + pom + + + UTF-8 + + + + feature + product + + + + + + org.eclipse.tycho + tycho-maven-plugin + ${tycho-version} + true + + + + diff --git a/tycho-its/projects/tycho-p2-director-plugin/missing-requirements-feature/product/feature.product b/tycho-its/projects/tycho-p2-director-plugin/missing-requirements-feature/product/feature.product new file mode 100644 index 0000000000..238246b4e9 --- /dev/null +++ b/tycho-its/projects/tycho-p2-director-plugin/missing-requirements-feature/product/feature.product @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/tycho-its/projects/tycho-p2-director-plugin/missing-requirements-feature/product/pom.xml b/tycho-its/projects/tycho-p2-director-plugin/missing-requirements-feature/product/pom.xml new file mode 100644 index 0000000000..d29c849d38 --- /dev/null +++ b/tycho-its/projects/tycho-p2-director-plugin/missing-requirements-feature/product/pom.xml @@ -0,0 +1,49 @@ + + + + + 4.0.0 + + tycho-its + missing.parent + 1.0.0-SNAPSHOT + + feature.product + eclipse-repository + + + + + org.eclipse.tycho + tycho-maven-plugin + ${tycho-version} + true + + + org.eclipse.tycho + tycho-p2-director-plugin + ${tycho-version} + + + materialize-products + + materialize-products + + + + + + + + + + org.osgi + org.osgi.service.jaxrs + 1.0.1 + + + + \ No newline at end of file diff --git a/tycho-its/projects/tycho-p2-director-plugin/missing-requirements-plugins/plugin.product b/tycho-its/projects/tycho-p2-director-plugin/missing-requirements-plugins/plugin.product new file mode 100644 index 0000000000..b0c1ef4d9b --- /dev/null +++ b/tycho-its/projects/tycho-p2-director-plugin/missing-requirements-plugins/plugin.product @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/tycho-its/projects/tycho-p2-director-plugin/missing-requirements-plugins/pom.xml b/tycho-its/projects/tycho-p2-director-plugin/missing-requirements-plugins/pom.xml new file mode 100644 index 0000000000..9c35ce9b0e --- /dev/null +++ b/tycho-its/projects/tycho-p2-director-plugin/missing-requirements-plugins/pom.xml @@ -0,0 +1,49 @@ + + + + 4.0.0 + tycho-its + plugin.product + 1.0.0-SNAPSHOT + eclipse-repository + + + UTF-8 + + + + + + org.eclipse.tycho + tycho-maven-plugin + ${tycho-version} + true + + + org.eclipse.tycho + tycho-p2-director-plugin + ${tycho-version} + + + materialize-products + + materialize-products + + + + + + + + + + org.osgi + org.osgi.service.jaxrs + 1.0.1 + + + + \ No newline at end of file diff --git a/tycho-its/projects/tycho-p2-director-plugin/product-with-native-fragments/plugin.product b/tycho-its/projects/tycho-p2-director-plugin/product-with-native-fragments/plugin.product new file mode 100644 index 0000000000..0bf4f2ece4 --- /dev/null +++ b/tycho-its/projects/tycho-p2-director-plugin/product-with-native-fragments/plugin.product @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/tycho-its/projects/tycho-p2-director-plugin/product-with-native-fragments/pom.xml b/tycho-its/projects/tycho-p2-director-plugin/product-with-native-fragments/pom.xml new file mode 100644 index 0000000000..2f166e6e25 --- /dev/null +++ b/tycho-its/projects/tycho-p2-director-plugin/product-with-native-fragments/pom.xml @@ -0,0 +1,94 @@ + + + + 4.0.0 + tycho-its + plugin.product + 1.0.0-SNAPSHOT + eclipse-repository + + + UTF-8 + + + + + + org.eclipse.tycho + tycho-maven-plugin + ${tycho-version} + true + + + org.eclipse.tycho + tycho-p2-director-plugin + ${tycho-version} + + + materialize-products + + materialize-products + + + + + + org.eclipse.tycho + target-platform-configuration + ${tycho-version} + + + + linux + gtk + x86_64 + + + win32 + win32 + x86_64 + + + macosx + cocoa + x86_64 + + + + + + + + + + org.eclipse.platform + org.eclipse.swt + 3.124.200 + + + * + * + + + + + org.eclipse.platform + org.eclipse.swt.gtk.linux.x86_64 + 3.124.200 + + + org.eclipse.platform + org.eclipse.swt.win32.win32.x86_64 + 3.124.200 + + + org.eclipse.platform + org.eclipse.swt.cocoa.macosx.x86_64 + 3.124.200 + + + + \ No newline at end of file diff --git a/tycho-its/src/test/java/org/eclipse/tycho/test/product/ProductBuildTest.java b/tycho-its/src/test/java/org/eclipse/tycho/test/product/ProductBuildTest.java index aad80f804c..fb58c40393 100644 --- a/tycho-its/src/test/java/org/eclipse/tycho/test/product/ProductBuildTest.java +++ b/tycho-its/src/test/java/org/eclipse/tycho/test/product/ProductBuildTest.java @@ -13,6 +13,7 @@ import static org.hamcrest.Matchers.hasItem; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertThrows; import java.io.File; import java.io.IOException; @@ -24,6 +25,7 @@ import java.util.Optional; import java.util.stream.Collectors; +import org.apache.maven.it.VerificationException; import org.apache.maven.it.Verifier; import org.eclipse.tycho.TargetEnvironment; import org.eclipse.tycho.TychoConstants; @@ -39,6 +41,40 @@ public class ProductBuildTest extends AbstractTychoIntegrationTest { private static final List REQUIRED_PGP_PROPERTIES = List.of(TychoConstants.PROP_PGP_SIGNATURES, TychoConstants.PROP_PGP_KEYS); + @Test + public void testPluginProjectFailsOnMissingDependencies() throws Exception { + Verifier verifier = getVerifier("tycho-p2-director-plugin/missing-requirements-plugins", false, true); + assertThrows(VerificationException.class, () -> verifier.executeGoals(Arrays.asList("clean", "package"))); + verifier.verifyTextInLog( + "Cannot resolve dependencies of project tycho-its:plugin.product:eclipse-repository:1.0.0-SNAPSHOT"); + } + + @Test + public void testFeatureProjectFailsOnMissingDependencies() throws Exception { + Verifier verifier = getVerifier("tycho-p2-director-plugin/missing-requirements-feature", false, true); + assertThrows(VerificationException.class, () -> verifier.executeGoals(Arrays.asList("clean", "package"))); + verifier.verifyTextInLog( + "Cannot resolve dependencies of project tycho-its:feature.product:eclipse-repository:1.0.0-SNAPSHOT"); + } + + @Test + public void testPluginProductWithDifferentNativesCanBuild() throws Exception { + Verifier verifier = getVerifier("tycho-p2-director-plugin/product-with-native-fragments", false, true); + verifier.executeGoals(Arrays.asList("clean", "package")); + // FIXME verifier.verifyErrorFreeLog(); + // See https://github.com/eclipse-platform/eclipse.platform.swt/issues/992 + verifyTextNotInLog(verifier, "BUILD FAILURE"); + File basedir = new File(verifier.getBasedir()); + assertFileExists(basedir, + "target/products/plugin.product/linux/gtk/x86_64/plugins/org.eclipse.swt.gtk.linux.x86_64_*.jar"); + assertFileExists(basedir, + "target/products/plugin.product/linux/gtk/x86_64/plugins/org.eclipse.swt.gtk.linux.x86_64_*.jar"); + assertFileExists(basedir, + "target/products/plugin.product/win32/win32/x86_64/plugins/org.eclipse.swt.win32.win32.x86_64_*.jar"); + assertFileExists(basedir, + "target/products/plugin.product/macosx/cocoa/x86_64/Eclipse.app/Contents/Eclipse/plugins/org.eclipse.swt.cocoa.macosx.x86_64_*.jar"); + } + @Test public void testMavenDepedencyInTarget() throws Exception { Verifier verifier = getVerifier("product.mavenLocation", false);