From e3e3aac0f960c199a7145e454bf3c08e972c7fe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20L=C3=A4ubrich?= Date: Sun, 10 Dec 2023 17:17:23 +0100 Subject: [PATCH] Consider the max occurrence of a requirement Currently all matching units are considered even if there are only a lower number to use. This adds a filtering after the initial collection phase to limit the dependencies to the max of the requirement and also adds the infrastructure to support filtering based on the profile properties. relates to https://github.com/eclipse-tycho/tycho/issues/3197 (cherry picked from commit a3502816952ef3c99bab54d749128fb2a1c3f67d) --- .../tycho/p2maven/InstallableUnitSlicer.java | 42 ++++++------ .../MavenProjectDependencyProcessor.java | 64 +++++++++++++++---- .../org/eclipse/tycho/TargetEnvironment.java | 3 +- .../tycho/core/TychoProjectManager.java | 22 +++++++ 4 files changed, 98 insertions(+), 33 deletions(-) diff --git a/p2-maven-plugin/src/main/java/org/eclipse/tycho/p2maven/InstallableUnitSlicer.java b/p2-maven-plugin/src/main/java/org/eclipse/tycho/p2maven/InstallableUnitSlicer.java index 2c9779fd2f..e85c61f199 100644 --- a/p2-maven-plugin/src/main/java/org/eclipse/tycho/p2maven/InstallableUnitSlicer.java +++ b/p2-maven-plugin/src/main/java/org/eclipse/tycho/p2maven/InstallableUnitSlicer.java @@ -12,10 +12,12 @@ *******************************************************************************/ package org.eclipse.tycho.p2maven; +import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; -import java.util.LinkedHashSet; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import org.codehaus.plexus.component.annotations.Component; import org.codehaus.plexus.component.annotations.Requirement; @@ -27,7 +29,6 @@ import org.eclipse.equinox.internal.p2.metadata.InstallableUnit; 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.IQueryResult; import org.eclipse.equinox.p2.query.IQueryable; import org.eclipse.equinox.p2.query.QueryUtil; @@ -76,32 +77,35 @@ public IQueryResult computeDependencies(Collection computeDirectDependencies(Collection rootIus, + public Map> computeDirectDependencies( + Collection rootIus, IQueryable avaiableIUs) throws CoreException { - Collection result = new LinkedHashSet<>(); - List collect = rootIus.stream().flatMap(iu -> iu.getRequirements().stream()).filter(req -> { - for (IInstallableUnit unit : rootIus) { - if (unit.satisfies(req)) { - // self full filled requirement - return false; - } - } - return true; - }).toList(); + List collect = rootIus.stream().flatMap(iu -> iu.getRequirements().stream()) + .filter(req -> { + for (IInstallableUnit unit : rootIus) { + if (unit.satisfies(req)) { + // self full filled requirement + return false; + } + } + return true; + }).toList(); + Map> result = new LinkedHashMap<>(collect.size()); for (IInstallableUnit iu : avaiableIUs.query(QueryUtil.ALL_UNITS, new NullProgressMonitor()).toSet()) { for (IRequirement requirement : collect) { if (iu.satisfies(requirement)) { - result.add(iu); - // TODO remove the requirement from the set so we only collect exactly one - // provider for a requirement? - break; + result.computeIfAbsent(requirement, nil -> new ArrayList<>()).add(iu); } } } - return new CollectionResult<>(result); + return result; } private final class TychoSlicer extends PermissiveSlicer { diff --git a/p2-maven-plugin/src/main/java/org/eclipse/tycho/p2maven/MavenProjectDependencyProcessor.java b/p2-maven-plugin/src/main/java/org/eclipse/tycho/p2maven/MavenProjectDependencyProcessor.java index efa970ec09..f9ef50c124 100644 --- a/p2-maven-plugin/src/main/java/org/eclipse/tycho/p2maven/MavenProjectDependencyProcessor.java +++ b/p2-maven-plugin/src/main/java/org/eclipse/tycho/p2maven/MavenProjectDependencyProcessor.java @@ -44,6 +44,7 @@ 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.expression.IMatchExpression; import org.eclipse.equinox.p2.publisher.eclipse.BundlesAction; import org.eclipse.equinox.p2.query.CollectionResult; import org.eclipse.equinox.p2.query.IQueryable; @@ -57,7 +58,7 @@ public class MavenProjectDependencyProcessor { private static final ProjectDependencies EMPTY_DEPENDENCIES = new ProjectDependencies(Collections.emptyList(), - Collections.emptyList()); + Collections.emptyList(), Map.of(), Set.of()); private static final boolean DUMP_DATA = Boolean.getBoolean("tycho.p2.dump") || Boolean.getBoolean("tycho.p2.dump.dependencies"); @@ -72,13 +73,19 @@ public class MavenProjectDependencyProcessor { * Computes the {@link ProjectDependencyClosure} of the given collection of * projects. * - * @param projects the projects to include in the closure - * @param session the maven session for this request + * @param projects the projects to include in the closure + * @param session the maven session for this request + * @param profilePropertiesSupplier supplier of context IUs for a project that + * represent the the profile properties to + * consider during resolution, can be empty in + * which case a filter is always considered a + * match * @return the computed {@link ProjectDependencyClosure} * @throws CoreException if computation failed */ public ProjectDependencyClosure computeProjectDependencyClosure(Collection projects, - MavenSession session) throws CoreException { + MavenSession session) + throws CoreException { Objects.requireNonNull(session); Map> projectIUMap = generator.getInstallableUnits(projects, session); Collection availableIUs = projectIUMap.values().stream().flatMap(Collection::stream) @@ -144,7 +151,8 @@ private Map computeProjectDependencies(Collec Map result = new ConcurrentHashMap<>(); projects.parallelStream().unordered().takeWhile(nil -> errors.isEmpty()).forEach(project -> { try { - ProjectDependencies projectDependencies = computeProjectDependencies(projectIUMap.get(project), + ProjectDependencies projectDependencies = computeProjectDependencies( + Set.copyOf(projectIUMap.get(project)), avaiableIUs); result.put(project, projectDependencies); if (DUMP_DATA) { @@ -181,17 +189,28 @@ private Map computeProjectDependencies(Collec * @return the collection of dependent {@link InstallableUnit}s * @throws CoreException if computation failed */ - private ProjectDependencies computeProjectDependencies(Collection projectUnits, - IQueryable avaiableIUs) throws CoreException { + private ProjectDependencies computeProjectDependencies(Set projectUnits, + IQueryable avaiableIUs) + throws CoreException { if (projectUnits.isEmpty()) { return EMPTY_DEPENDENCIES; } - Set resolved = new LinkedHashSet<>( - slicer.computeDirectDependencies(projectUnits, avaiableIUs).toSet()); - resolved.removeAll(projectUnits); + Map> dependencies = slicer.computeDirectDependencies(projectUnits, + avaiableIUs); + // first collect the maximum desired number + Set resolved = new LinkedHashSet<>(); + for (Entry> entry : dependencies.entrySet()) { + IRequirement requirement = entry.getKey(); + Collection units = entry.getValue(); + List limit = units.stream().filter(unit -> !projectUnits.contains(unit)) + .limit(requirement.getMax()).toList(); + resolved.addAll(limit); + } // now we need to filter all fragments that we are a host! // for example SWT creates an explicit requirement to its fragments and we don't // want them included directly + // TODO reevaluate, this is where filters come into place we probabbly no longer + // need this part! Set projectFragments = new HashSet<>(); for (Iterator iterator = resolved.iterator(); iterator.hasNext();) { IInstallableUnit unit = iterator.next(); @@ -200,7 +219,7 @@ private ProjectDependencies computeProjectDependencies(Collection collection) { @@ -238,14 +257,27 @@ private static Stream getFragmentHostRequirement(IInstallableUnit }).filter(Objects::nonNull); } + private static boolean isMatch(IRequirement requirement, Collection contextIUs) { + IMatchExpression filter = requirement.getFilter(); + if (filter == null || contextIUs.isEmpty()) { + return true; + } + return contextIUs.stream().anyMatch(contextIU -> filter.isMatch(contextIU)); + } + public static final class ProjectDependencies { private final Collection dependencies; private final Collection fragments; + private Map> requirementsMap; + private Set projectUnits; - private ProjectDependencies(Collection dependencies, Collection fragments) { + ProjectDependencies(Collection dependencies, Collection fragments, + Map> requirementsMap, Set projectUnits) { this.dependencies = dependencies; this.fragments = fragments; + this.requirementsMap = requirementsMap; + this.projectUnits = projectUnits; } public Collection getDependencies() { @@ -256,6 +288,14 @@ public Collection getFragments() { return fragments; } + public Collection getDependencies(Collection contextIUs) { + return requirementsMap.entrySet().stream().filter(entry -> isMatch(entry.getKey(), contextIUs)) + .flatMap(entry -> entry.getValue().stream().filter(unit -> !projectUnits.contains(unit)) + .limit(entry.getKey().getMax())) + .distinct().toList(); + } + + } public static interface ProjectDependencyClosure { diff --git a/tycho-api/src/main/java/org/eclipse/tycho/TargetEnvironment.java b/tycho-api/src/main/java/org/eclipse/tycho/TargetEnvironment.java index 5e642f37b1..d5e2a81384 100644 --- a/tycho-api/src/main/java/org/eclipse/tycho/TargetEnvironment.java +++ b/tycho-api/src/main/java/org/eclipse/tycho/TargetEnvironment.java @@ -13,7 +13,6 @@ package org.eclipse.tycho; import java.util.ArrayList; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.Objects; @@ -88,7 +87,7 @@ public boolean isWindows() { * Returns the target environment as map. The keys are "osgi.ws", "osgi.os", and "osgi.arch". * This format is used by the p2 slicer to filter installable units by environments. * - * @return a new instance of {@link HashMap} with the target environment set + * @return a new instance of {@link LinkedHashMap} with the target environment set */ public Map toFilterProperties() { //for nicer debug output, use an ordered map here diff --git a/tycho-core/src/main/java/org/eclipse/tycho/core/TychoProjectManager.java b/tycho-core/src/main/java/org/eclipse/tycho/core/TychoProjectManager.java index 1627792620..e315e753a2 100644 --- a/tycho-core/src/main/java/org/eclipse/tycho/core/TychoProjectManager.java +++ b/tycho-core/src/main/java/org/eclipse/tycho/core/TychoProjectManager.java @@ -36,6 +36,8 @@ import org.codehaus.plexus.component.annotations.Component; import org.codehaus.plexus.component.annotations.Requirement; import org.codehaus.plexus.logging.Logger; +import org.eclipse.equinox.internal.p2.metadata.InstallableUnit; +import org.eclipse.equinox.p2.metadata.IInstallableUnit; import org.eclipse.tycho.ArtifactDescriptor; import org.eclipse.tycho.ArtifactKey; import org.eclipse.tycho.ClasspathEntry; @@ -43,6 +45,7 @@ import org.eclipse.tycho.ExecutionEnvironmentConfiguration; import org.eclipse.tycho.ReactorProject; import org.eclipse.tycho.ResolvedArtifactKey; +import org.eclipse.tycho.TargetEnvironment; import org.eclipse.tycho.TargetPlatform; import org.eclipse.tycho.TargetPlatformService; import org.eclipse.tycho.TychoConstants; @@ -143,6 +146,25 @@ public void readExecutionEnvironmentConfiguration(ReactorProject project, Execut } } + public List getContextIUs(MavenProject project) { + TargetPlatformConfiguration configuration = getTargetPlatformConfiguration(project); + return configuration.getEnvironments().stream().map(env -> getProfileProperties(env, configuration)) + .map(InstallableUnit::contextIU).toList(); + } + + public Map getProfileProperties(MavenProject project, TargetEnvironment environment) { + TargetPlatformConfiguration configuration = getTargetPlatformConfiguration(project); + return getProfileProperties(environment, configuration); + } + + private Map getProfileProperties(TargetEnvironment environment, + TargetPlatformConfiguration configuration) { + Map properties = environment.toFilterProperties(); + properties.put("org.eclipse.update.install.features", "true"); + properties.putAll(configuration.getProfileProperties()); + return properties; + } + public TargetPlatformConfiguration getTargetPlatformConfiguration(MavenProject project) { ReactorProject reactorProject = DefaultReactorProject.adapt(project); return reactorProject.computeContextValue(CTX_TARGET_PLATFORM_CONFIGURATION,