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/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..7a642c20a1 --- /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 + protected boolean isApplicable(IRequirement req) { + IMatchExpression filter = req.getFilter(); + return filter == null || filter.isMatch(selectionContext); + } + + protected boolean isApplicable(IInstallableUnit iu) { + IMatchExpression filter = iu.getFilter(); + return filter == null || filter.isMatch(selectionContext); + } + + 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); + } + } + + protected 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; + } + +} 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);