You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by gn...@apache.org on 2026/08/31 02:33:38 UTC
(maven) 01/05: [MNG-8287] Dependencies should always use consumer pom
This is an automated email from the ASF dual-hosted git repository.
gnodet pushed a commit to branch backport/12744-to-maven-4.0.x
in repository https://gitbox.apache.org/repos/asf/maven.git
commit 5c6b05c4027d7202c29644695f17ccdde6ef6bf1
Author: Hitesh <hi...@saividya.ac.in>
AuthorDate: Thu Aug 13 20:11:07 2026 +0530
[MNG-8287] Dependencies should always use consumer pom
When generating a consumer POM, profiles activated by packaging that match the current project's packaging should have their content inlined into the consumer POM and the activation should be removed. This ensures consistent dependency resolution across different tools.
---
.../impl/DefaultConsumerPomBuilder.java | 110 +++++++++++++++++++++
.../impl/ConsumerPomBuilderTest.java | 56 ++++++++++-
2 files changed, 163 insertions(+), 3 deletions(-)
diff --git a/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomBuilder.java b/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomBuilder.java
index 4f4062c2de..77facf964c 100644
--- a/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomBuilder.java
+++ b/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomBuilder.java
@@ -25,6 +25,7 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Properties;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -35,7 +36,9 @@
import org.apache.maven.api.PathScope;
import org.apache.maven.api.SessionData;
import org.apache.maven.api.feature.Features;
+import org.apache.maven.api.model.Activation;
import org.apache.maven.api.model.Dependency;
+import org.apache.maven.api.model.DependencyManagement;
import org.apache.maven.api.model.DistributionManagement;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.model.ModelBase;
@@ -405,6 +408,10 @@ private ModelBuilderResult buildModel(RepositorySystemSession session, MavenProj
static Model transformNonPom(Model model, MavenProject project) {
boolean preserveModelVersion = model.isPreserveModelVersion();
+ String packaging = model.getPackaging();
+
+ // Inline packaging-activated profiles into the model
+ model = inlinePackagingActivatedProfiles(model, packaging);
Model.Builder builder = prune(
Model.newBuilder(model, true)
@@ -437,6 +444,10 @@ static Model transformNonPom(Model model, MavenProject project) {
private static Model transformBom(Model model, MavenProject project) {
boolean preserveModelVersion = model.isPreserveModelVersion();
+ String packaging = model.getPackaging();
+
+ // Inline packaging-activated profiles into the model
+ model = inlinePackagingActivatedProfiles(model, packaging);
Model.Builder builder = prune(
Model.newBuilder(model, true)
@@ -496,6 +507,105 @@ private static void warnNotDowngraded(MavenProject project) {
+ "attribute on the <project> element of your POM.");
}
+ /**
+ * Inlines packaging-activated profiles into the model.
+ * <p>
+ * When a profile is activated by packaging and the packaging matches the project's packaging,
+ * the profile's content (dependencies, dependency management, repositories) is merged into
+ * the main model and the profile is removed. This ensures consistent behavior across all
+ * tools consuming the POM, since packaging activation is a 4.1.0+ feature not available
+ * in Maven 3 or other tools like Gradle.
+ * <p>
+ * If the profile has other activation conditions besides packaging, only the packaging
+ * part is stripped from the activation.
+ *
+ * @param model the model to process
+ * @param packaging the project's packaging type
+ * @return the model with packaging-activated profiles inlined
+ */
+ static Model inlinePackagingActivatedProfiles(Model model, String packaging) {
+ List<Profile> remainingProfiles = new ArrayList<>();
+ List<Dependency> additionalDeps = new ArrayList<>();
+ List<Dependency> additionalManagedDeps = new ArrayList<>();
+ List<Repository> additionalRepos = new ArrayList<>();
+
+ for (Profile profile : model.getProfiles()) {
+ Activation activation = profile.getActivation();
+ if (activation != null && activation.getPackaging() != null) {
+ if (Objects.equals(activation.getPackaging(), packaging)) {
+ // Packaging matches — inline profile content into the model
+ additionalDeps.addAll(profile.getDependencies());
+ if (profile.getDependencyManagement() != null) {
+ additionalManagedDeps.addAll(
+ profile.getDependencyManagement().getDependencies());
+ }
+ additionalRepos.addAll(profile.getRepositories());
+
+ // Check if the profile has other activation conditions besides packaging
+ Activation strippedActivation = stripPackagingActivation(activation);
+ if (strippedActivation != null) {
+ // Keep the profile but remove the packaging activation part
+ remainingProfiles.add(profile.withActivation(strippedActivation));
+ }
+ // else: profile only had packaging activation, fully inlined, drop it
+ } else {
+ // Packaging does not match — keep the profile as-is
+ remainingProfiles.add(profile);
+ }
+ } else {
+ // No packaging activation — keep the profile as-is
+ remainingProfiles.add(profile);
+ }
+ }
+
+ // Merge additional dependencies into the model
+ if (!additionalDeps.isEmpty()) {
+ List<Dependency> allDeps = new ArrayList<>(model.getDependencies());
+ allDeps.addAll(additionalDeps);
+ model = model.withDependencies(allDeps);
+ }
+
+ // Merge additional managed dependencies into the model
+ if (!additionalManagedDeps.isEmpty()) {
+ DependencyManagement dm = model.getDependencyManagement();
+ List<Dependency> allManagedDeps = dm != null
+ ? new ArrayList<>(dm.getDependencies())
+ : new ArrayList<>();
+ allManagedDeps.addAll(additionalManagedDeps);
+ model = model.withDependencyManagement(
+ (dm != null ? dm : DependencyManagement.newInstance()).withDependencies(allManagedDeps));
+ }
+
+ // Merge additional repositories into the model
+ if (!additionalRepos.isEmpty()) {
+ List<Repository> allRepos = new ArrayList<>(model.getRepositories());
+ allRepos.addAll(additionalRepos);
+ model = model.withRepositories(allRepos);
+ }
+
+ return model.withProfiles(remainingProfiles);
+ }
+
+ /**
+ * Strips the packaging activation from an activation, returning the remaining activation
+ * or {@code null} if packaging was the only activation condition.
+ */
+ private static Activation stripPackagingActivation(Activation activation) {
+ Activation stripped = Activation.newBuilder(activation, true)
+ .packaging(null)
+ .build();
+ // Check if the remaining activation has any other conditions
+ if (!stripped.isActiveByDefault()
+ && stripped.getJdk() == null
+ && stripped.getOs() == null
+ && stripped.getProperty() == null
+ && stripped.getFile() == null
+ && stripped.getCondition() == null) {
+ return null;
+ }
+ return stripped;
+ }
+
private static List<Profile> prune(List<Profile> profiles) {
return profiles.stream()
.map(p -> {
diff --git a/impl/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilderTest.java b/impl/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilderTest.java
index a601a09947..82646d81ba 100644
--- a/impl/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilderTest.java
+++ b/impl/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilderTest.java
@@ -31,7 +31,11 @@
import org.apache.maven.api.PathScope;
import org.apache.maven.api.Session;
import org.apache.maven.api.SessionData;
+import org.apache.maven.api.model.Activation;
+import org.apache.maven.api.model.Dependency;
+import org.apache.maven.api.model.DependencyManagement;
import org.apache.maven.api.model.Model;
+import org.apache.maven.api.model.Profile;
import org.apache.maven.api.model.Scm;
import org.apache.maven.api.services.DependencyResolver;
import org.apache.maven.api.services.DependencyResolverResult;
@@ -55,6 +59,7 @@
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
@@ -334,7 +339,7 @@ void testConsumerPomIncludesActiveProfileProperties() throws Exception {
userProps.containsKey("dep.version"), "User properties should contain properties from active profiles");
assertTrue(
"1.0.0".equals(userProps.get("dep.version")),
- "Profile property 'dep.version' should have value '1.0.0'");
+ "Profile property \'dep.version\' should have value \'1.0.0\'");
}
/**
@@ -382,7 +387,52 @@ void testConsumerPomSessionPropertiesOverrideProfileProperties() throws Exceptio
Map<String, String> userProps = consumerRequest.getUserProperties();
assertTrue(
"2.0.0".equals(userProps.get("dep.version")),
- "Session user property should override profile property; expected '2.0.0' but got '"
- + userProps.get("dep.version") + "'");
+ "Session user property should override profile property; expected \'2.0.0\' but got \'"
+ + userProps.get("dep.version") + "\'");
+ }
+
+ @Test
+ void testInlinePackagingActivatedProfiles() {
+ Dependency dep1 = Dependency.newBuilder().groupId("g").artifactId("a1").version("1").build();
+ Dependency dep2 = Dependency.newBuilder().groupId("g").artifactId("a2").version("2").build();
+
+ Profile profileMatching = Profile.newBuilder()
+ .id("matching")
+ .activation(Activation.newBuilder().packaging("jar").build())
+ .dependencies(List.of(dep1))
+ .build();
+
+ Profile profileNotMatching = Profile.newBuilder()
+ .id("not-matching")
+ .activation(Activation.newBuilder().packaging("war").build())
+ .dependencies(List.of(dep2))
+ .build();
+
+ Profile profileMixed = Profile.newBuilder()
+ .id("mixed")
+ .activation(Activation.newBuilder().packaging("jar").jdk("11").build())
+ .dependencies(List.of(dep2))
+ .build();
+
+ Model model = Model.newBuilder()
+ .packaging("jar")
+ .profiles(List.of(profileMatching, profileNotMatching, profileMixed))
+ .build();
+
+ Model transformed = DefaultConsumerPomBuilder.inlinePackagingActivatedProfiles(model, "jar");
+
+ // matching profile should be removed and its deps added to model
+ // not-matching profile should be kept intact
+ // mixed profile should be kept but packaging activation stripped
+ assertEquals(2, transformed.getProfiles().size());
+ assertEquals("not-matching", transformed.getProfiles().get(0).getId());
+ assertEquals("war", transformed.getProfiles().get(0).getActivation().getPackaging());
+
+ assertEquals("mixed", transformed.getProfiles().get(1).getId());
+ assertNull(transformed.getProfiles().get(1).getActivation().getPackaging());
+ assertEquals("11", transformed.getProfiles().get(1).getActivation().getJdk());
+
+ assertEquals(1, transformed.getDependencies().size());
+ assertEquals("a1", transformed.getDependencies().get(0).getArtifactId());
}
}