From da6ff7dc58687184ea0e0fe396994b6e3ee6a288 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] 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; + } + +}