Skip to content

Commit

Permalink
Enhance the incomplete soloution computation (#576)
Browse files Browse the repository at this point in the history
Fix #575 
- add a predicate to customize filtering
- Use loop instead of recursive call
- break on incomplete prerequisite
- Support NoExecutionEnvironmentResolutionHints
- Support HardRequirement's
- Also consider available units as not being a valid missing requirement
  • Loading branch information
laeubi authored Jan 28, 2022
1 parent 19b91ba commit e2cecb5
Show file tree
Hide file tree
Showing 4 changed files with 216 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,38 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.equinox.internal.p2.director.Explanation;
import org.eclipse.equinox.internal.p2.director.Explanation.HardRequirement;
import org.eclipse.equinox.internal.p2.director.Explanation.IUToInstall;
import org.eclipse.equinox.internal.p2.director.Explanation.MissingIU;
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;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.equinox.p2.metadata.IProvidedCapability;
import org.eclipse.equinox.p2.metadata.IRequirement;
import org.eclipse.equinox.p2.metadata.MetadataFactory;
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.metadata.expression.ExpressionUtil;
import org.eclipse.equinox.p2.metadata.expression.IExpression;
import org.eclipse.equinox.p2.metadata.expression.IMatchExpression;
import org.eclipse.equinox.p2.publisher.actions.JREAction;
import org.eclipse.equinox.p2.query.IQueryable;
import org.eclipse.tycho.core.shared.MavenLogger;
import org.eclipse.tycho.p2.target.ee.NoExecutionEnvironmentResolutionHints;
import org.eclipse.tycho.repository.p2base.metadata.QueryableCollection;
import org.eclipse.tycho.repository.util.StatusTool;

Expand Down Expand Up @@ -132,7 +144,7 @@ protected IInstallableUnit createUnitProviding(String name, Collection<IRequirem

InstallableUnitDescription result = new MetadataFactory.InstallableUnitDescription();
String time = Long.toString(System.currentTimeMillis());
result.setId(name + "-" + time);
result.setId(name + "-" + UUID.randomUUID());
result.setVersion(Version.createOSGi(0, 0, 0, time));
for (IRequirement requirement : requirements) {
if (requirement instanceof IRequiredCapability) {
Expand All @@ -148,11 +160,127 @@ protected IInstallableUnit createUnitProviding(String name, Collection<IRequirem
} catch (RuntimeException e) {
logger.debug("can't convert requirement " + requirement + " to capability: " + e.toString(), e);
}
} else if (requirement instanceof RequiredPropertiesMatch) {
try {
if (isEERequirement(requirement)) {
RequiredPropertiesMatch propertiesMatch = (RequiredPropertiesMatch) requirement;
IMatchExpression<IInstallableUnit> matches = propertiesMatch.getMatches();
Map<String, Object> properties = new HashMap<>();
Object p = matches.getParameters()[1];
if (p instanceof IExpression) {
IExpression expression = (IExpression) p;
IExpression operand = ExpressionUtil.getOperand(expression);
IExpression[] operands = ExpressionUtil.getOperands(operand);
for (IExpression eq : operands) {
IExpression lhs = ExpressionUtil.getLHS(eq);
IExpression rhs = ExpressionUtil.getRHS(eq);
Object value = ExpressionUtil.getValue(rhs);
String key = ExpressionUtil.getName(lhs);
if (IProvidedCapability.PROPERTY_VERSION.equals(key)) {
properties.put(key, Version.create(value.toString()));
} else {
properties.put(key, value.toString());
}
}
}
IProvidedCapability providedCapability = MetadataFactory.createProvidedCapability(
RequiredPropertiesMatch.extractNamespace(matches), properties);
result.addProvidedCapabilities(Collections.singleton(providedCapability));
}
} catch (RuntimeException e) {
logger.debug("can't convert requirement " + requirement + " to capability: " + e.toString(), e);
}
}
}
return MetadataFactory.createInstallableUnit(result);
}

/**
* Computes a list of current missing requirements. The list only contains requirements up to
* the point where it is known that this is a 'root' that means a requirement that prevents
* computation of a complete solution.
*
* @param explanation
* @return
*/
protected List<IRequirement> computeMissingRequirements(Set<Explanation> explanation) {
List<IRequirement> missingRequirements = new ArrayList<>();
//We collect here all units that are available but maybe incomplete due to an missing requirement.
//This is important as otherwise we could generate false missing requirements as they might just be chained
// Here is an example:
// a) Bundle require an EE or package what is missing
// b) Feature requires the Bundle
// c) Updatesite requires feature
// When resolving the Updatesite, it now seem to miss the Bundle *and* the Feature because the feature itself
// is incomplete but actually on only the EE or package is missing.
Collection<IInstallableUnit> availableIUs = new HashSet<>(data.getAvailableIUs());
for (Explanation exp : explanation) {
if (exp instanceof IUToInstall) {
IUToInstall iuToInstall = (IUToInstall) exp;
availableIUs.add(iuToInstall.iu);
} else if (exp instanceof MissingIU) {
MissingIU missingIU = (MissingIU) exp;
availableIUs.add(missingIU.iu);
if (isEERequirement(missingIU.req)) {
if (data.getEEResolutionHints() instanceof NoExecutionEnvironmentResolutionHints) {
//if NoEE is specified this is acceptable and should be recorded
missingRequirements.add(missingIU.req);
}
continue;
}
for (IInstallableUnit available : availableIUs) {
if (missingIU.req.isMatch(available)) {
if (logger.isExtendedDebugEnabled()) {
logger.debug("IU " + missingIU.iu + " requires an available or incomplete IU " + available
+ " ...");
}
return missingRequirements;
}
}
if (data.failOnMissingRequirements()) {
//we should not record those...
continue;
}
missingRequirements.add(missingIU.req);
} else if (exp instanceof HardRequirement) {
HardRequirement hardRequirement = (HardRequirement) exp;
availableIUs.add(hardRequirement.iu);
for (IInstallableUnit available : availableIUs) {
if (hardRequirement.req.isMatch(available)) {
if (logger.isExtendedDebugEnabled()) {
logger.debug("IU " + hardRequirement.iu + " has requirement on available or incomplete IU "
+ available + " ...");
}
return missingRequirements;
}
}
missingRequirements.add(hardRequirement.req);
} else {
if (logger.isExtendedDebugEnabled()) {
logger.debug("Ignoring Explanation of type " + exp.getClass()
+ " in computation of missing requirements: " + exp);
}
}
}
missingRequirements.forEach(data::addMissingRequirement);
return missingRequirements;
}

/**
* Check if this is an EE environment requirement
*
* @param requirement
* @return
*/
protected static boolean isEERequirement(IRequirement requirement) {
if (requirement instanceof RequiredPropertiesMatch) {
RequiredPropertiesMatch propertiesMatch = (RequiredPropertiesMatch) requirement;
String namespace = RequiredPropertiesMatch.extractNamespace(propertiesMatch.getMatches());
return JREAction.NAMESPACE_OSGI_EE.equals(namespace);
}
return false;
}

private static IRequirement createStrictRequirementTo(IInstallableUnit unit) {
VersionRange strictRange = new VersionRange(unit.getVersion(), true, unit.getVersion(), true);
int min = 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,15 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeoutException;
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.Explanation.MissingIU;
import org.eclipse.equinox.internal.p2.director.Projector;
import org.eclipse.equinox.internal.p2.director.QueryableArray;
import org.eclipse.equinox.internal.p2.director.SimplePlanner;
Expand All @@ -40,14 +41,29 @@

@SuppressWarnings("restriction")
public class ProjectorResolutionStrategy extends AbstractSlicerResolutionStrategy {
/**
* Internal property to control the maximum number of iterations performed to resolve an
* incomplete solution
*/
private static final int MAX_ITERATIONS = Integer
.getInteger("tycho.internal.ProjectorResolutionStrategy.maxIterations", 100);

public ProjectorResolutionStrategy(MavenLogger logger) {
super(logger);
}

@Override
protected Slicer newSlicer(IQueryable<IInstallableUnit> availableUnits, Map<String, String> properties) {
return new Slicer(availableUnits, properties, false);
Predicate<IInstallableUnit> acceptor = data.getIInstallableUnitAcceptor();
return new Slicer(availableUnits, properties, false) {
@Override
protected boolean isApplicable(IInstallableUnit iu) {
if (acceptor != null) {
return acceptor.test(iu);
}
return super.isApplicable(iu);
}
};
}

@Override
Expand All @@ -58,17 +74,8 @@ protected boolean isSlicerError(MultiStatus slicerStatus) {
@Override
public Collection<IInstallableUnit> resolve(Map<String, String> properties, IProgressMonitor monitor)
throws ResolverException {
List<IInstallableUnit> additionalUnits = new ArrayList<>();
Collection<IInstallableUnit> newState = resolveInternal(properties, additionalUnits, monitor);
newState.removeAll(additionalUnits); //remove the tycho generated IUs if any
return newState;
}

protected Collection<IInstallableUnit> resolveInternal(Map<String, String> properties,
List<IInstallableUnit> additionalUnits, IProgressMonitor monitor) throws ResolverException {
Map<String, String> newSelectionContext = SimplePlanner.createSelectionContext(properties);

IQueryable<IInstallableUnit> slice = slice(properties, additionalUnits, monitor);
List<IInstallableUnit> generatedUnits = new ArrayList<>();
Map<String, String> selectionContext = SimplePlanner.createSelectionContext(properties);

Set<IInstallableUnit> seedUnits = new LinkedHashSet<>(data.getRootIUs());
List<IRequirement> seedRequires = new ArrayList<>();
Expand All @@ -80,54 +87,61 @@ protected Collection<IInstallableUnit> resolveInternal(Map<String, String> prope
seedUnits.addAll(data.getEEResolutionHints().getMandatoryUnits());
seedRequires.addAll(data.getEEResolutionHints().getMandatoryRequires());

Projector projector = new Projector(slice, newSelectionContext, new HashSet<IInstallableUnit>(), false);
projector.encode(createUnitRequiring("tycho", seedUnits, seedRequires),
EMPTY_IU_ARRAY /* alreadyExistingRoots */,
new QueryableArray(EMPTY_IU_ARRAY) /* installedIUs */, seedUnits /* newRoots */, monitor);
IStatus s = projector.invokeSolver(monitor);
if (s.getSeverity() == IStatus.ERROR) {
Set<Explanation> explanation = projector.getExplanation(new NullProgressMonitor()); // suppress "Cannot complete the request. Generating details."
if (!data.failOnMissingRequirements()) {
List<IRequirement> missingRequirements = new ArrayList<>();
for (Explanation exp : explanation) {
if (exp instanceof MissingIU) {
MissingIU missingIU = (MissingIU) exp;
logger.debug("Recording missing requirement for IU " + missingIU.iu + ": " + missingIU.req);
data.addMissingRequirement(missingIU.req);
missingRequirements.add(missingIU.req);
} else {
int iteration = 0;
do {
Projector projector = new Projector(slice(properties, generatedUnits, monitor), selectionContext,
new HashSet<IInstallableUnit>(), false);
projector.encode(createUnitRequiring("tycho", seedUnits, seedRequires),
EMPTY_IU_ARRAY /* alreadyExistingRoots */,
new QueryableArray(EMPTY_IU_ARRAY) /* installedIUs */, seedUnits /* newRoots */, monitor);
IStatus s = projector.invokeSolver(monitor);
if (s.getSeverity() == IStatus.ERROR) {
Set<Explanation> explanation = projector.getExplanation(new NullProgressMonitor()); // suppress "Cannot complete the request. Generating details."
if (!data.failOnMissingRequirements()) {
List<IRequirement> missingRequirements = computeMissingRequirements(explanation);
if (missingRequirements.size() > 0) {
if (logger.isExtendedDebugEnabled()) {
logger.debug("Ignoring Explanation of type " + exp.getClass()
+ " in computation of missing requirements: " + exp);
logger.debug(
"At iteration " + iteration + " the following requirements are not yet satisfied:");
for (IRequirement requirement : missingRequirements) {
logger.debug("> " + requirement);
}
}
//only start a new resolve if we have collected additional requirements...
IInstallableUnit providing = createUnitProviding("tycho.unresolved.requirements",
missingRequirements);
int newCapabilities = providing.getProvidedCapabilities().size();
if (newCapabilities > 0) {
//... and we could provide additional capabilities
if (logger.isExtendedDebugEnabled()) {
logger.debug(newCapabilities
+ " new capabilities where created, starting next iteration...");
}
generatedUnits.add(providing);
iteration++;
continue;
}
}
}
if (missingRequirements.size() > 0) {
//only start a new resolve if we have collected additional requirements...
IInstallableUnit providing = createUnitProviding("tycho.unresolved.requirements",
missingRequirements);
if (providing.getProvidedCapabilities().size() > 0) {
//... and we could provide additional capabilities
additionalUnits.add(providing);
return resolveInternal(properties, additionalUnits, monitor);
}
}
// log all transitive requirements which cannot be satisfied; this doesn't print the dependency chain from the seed to the units with missing requirements, so this is less useful than the "explanation"
logger.debug(StatusTool.collectProblems(s));
explainProblems(explanation, MavenLogger::error);
throw new ResolverException(
explanation.stream().map(Object::toString).collect(Collectors.joining("\n")),
selectionContext.toString(), StatusTool.findException(s));
}
// log all transitive requirements which cannot be satisfied; this doesn't print the dependency chain from the seed to the units with missing requirements, so this is less useful than the "explanation"
logger.debug(StatusTool.collectProblems(s));
explainProblems(explanation, MavenLogger::error);
throw new ResolverException(explanation.stream().map(Object::toString).collect(Collectors.joining("\n")),
newSelectionContext.toString(), StatusTool.findException(s));
}
Collection<IInstallableUnit> newState = projector.extractSolution();
Collection<IInstallableUnit> newState = projector.extractSolution();

// remove fake IUs from resolved state
newState.removeAll(data.getEEResolutionHints().getTemporaryAdditions());
// remove fake IUs from resolved state
newState.removeAll(data.getEEResolutionHints().getTemporaryAdditions());
newState.removeAll(generatedUnits); //remove the tycho generated IUs if any

if (logger.isExtendedDebugEnabled()) {
logger.debug("Resolved IUs:\n" + ResolverDebugUtils.toDebugString(newState, false));
}
return newState;
if (logger.isExtendedDebugEnabled()) {
logger.debug("Resolved IUs:\n" + ResolverDebugUtils.toDebugString(newState, false));
}
return newState;
} while (iteration < MAX_ITERATIONS);
throw new ResolverException("Maximum iterations reached", new TimeoutException());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;

import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.equinox.p2.metadata.IRequirement;
Expand All @@ -41,4 +42,10 @@ public interface ResolutionData {
void addMissingRequirement(IRequirement requirement);

Collection<IRequirement> getMissingRequirements();

/**
*
* @return a predicate that us used to check if a given unit should be accepted by the slicer
*/
Predicate<IInstallableUnit> getIInstallableUnitAcceptor();
}
Loading

0 comments on commit e2cecb5

Please sign in to comment.