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,