From 67f11aa531f4b7dd8788ade2cd4e139dfbc1c3ca Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Tue, 28 Oct 2025 13:57:33 +0100 Subject: [PATCH 1/8] Add consumer POM flattening control feature This commit introduces a new feature flag 'maven.consumer.pom.flatten' that allows users to control whether consumer POMs are flattened or preserve their dependency management sections. Key changes: - Added MAVEN_CONSUMER_POM_FLATTEN constant in Constants.java - Added consumerPomFlatten() method in Features.java - Modified DefaultConsumerPomBuilder to conditionally apply flattening - Added integration test MavenITgh11346DependencyManagementOverrideTest - Renamed test resources to follow GitHub issue naming convention When maven.consumer.pom.flatten=false, consumer POMs preserve dependency management like parent POMs, enabling dependency management inheritance by consumers. This addresses scenarios where transitive dependency management is crucial for maintaining consistent dependency versions. The feature maintains backward compatibility with current default behavior (flattening enabled) while providing opt-in control for projects that need dependency management inheritance. Fixes #11346 --- .../java/org/apache/maven/api/Constants.java | 79 ++++++++---- .../apache/maven/api/feature/Features.java | 7 + .../impl/DefaultConsumerPomBuilder.java | 8 ++ ...11346DependencyManagementOverrideTest.java | 121 ++++++++++++++++++ .../module-a/pom.xml | 50 ++++++++ .../module-b-v1/pom.xml | 32 +++++ .../module-b-v2/pom.xml | 39 ++++++ .../module-c-v11/pom.xml | 32 +++++ .../module-c-v12/pom.xml | 32 +++++ .../module-d/pom.xml | 50 ++++++++ .../pom.xml | 62 +++++++++ 11 files changed, 490 insertions(+), 22 deletions(-) create mode 100644 its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11346DependencyManagementOverrideTest.java create mode 100644 its/core-it-suite/src/test/resources/gh-11346-dependency-management-override/module-a/pom.xml create mode 100644 its/core-it-suite/src/test/resources/gh-11346-dependency-management-override/module-b-v1/pom.xml create mode 100644 its/core-it-suite/src/test/resources/gh-11346-dependency-management-override/module-b-v2/pom.xml create mode 100644 its/core-it-suite/src/test/resources/gh-11346-dependency-management-override/module-c-v11/pom.xml create mode 100644 its/core-it-suite/src/test/resources/gh-11346-dependency-management-override/module-c-v12/pom.xml create mode 100644 its/core-it-suite/src/test/resources/gh-11346-dependency-management-override/module-d/pom.xml create mode 100644 its/core-it-suite/src/test/resources/gh-11346-dependency-management-override/pom.xml diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/Constants.java b/api/maven-api-core/src/main/java/org/apache/maven/api/Constants.java index 8dc9bf106dad..e8faaa5bba94 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/Constants.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/Constants.java @@ -322,21 +322,18 @@ public final class Constants { /** * User controlled relocations. - * This property is a comma separated list of entries with the syntax GAV>GAV. - * The first GAV can contain * for any elem (so *:*:* would mean ALL, something - * you don't want). The second GAV is either fully specified, or also can contain *, + * This property is a comma separated list of entries with the syntax GAV>GAV. + * The first GAV can contain * for any elem (so *:*:* would mean ALL, something + * you don't want). The second GAV is either fully specified, or also can contain *, * then it behaves as "ordinary relocation": the coordinate is preserved from relocated artifact. - * Finally, if right hand GAV is absent (line looks like GAV>), the left hand matching - * GAV is banned fully (from resolving). - *
- * Note: the > means project level, while >> means global (whole session level, + * Finally, if right hand GAV is absent (line looks like GAV>), the left hand matching + * GAV is banned fully (from resolving). + * Note: the > means project level, while >> means global (whole session level, * so even plugins will get relocated artifacts) relocation. - *
- * For example, - *
maven.relocations.entries = org.foo:*:*>, \\
org.here:*:*>org.there:*:*, \\
javax.inject:javax.inject:1>>jakarta.inject:jakarta.inject:1.0.5
- * means: 3 entries, ban org.foo group (exactly, so org.foo.bar is allowed), - * relocate org.here to org.there and finally globally relocate (see >> above) - * javax.inject:javax.inject:1 to jakarta.inject:jakarta.inject:1.0.5. + * For example: maven.relocations.entries = org.foo:*:*>, org.here:*:*>org.there:*:*, javax.inject:javax.inject:1>>jakarta.inject:jakarta.inject:1.0.5 + * means: 3 entries, ban org.foo group (exactly, so org.foo.bar is allowed), + * relocate org.here to org.there and finally globally relocate (see >> above) + * javax.inject:javax.inject:1 to jakarta.inject:jakarta.inject:1.0.5. * * @since 4.0.0 */ @@ -409,14 +406,41 @@ public final class Constants { public static final String MAVEN_REPO_LOCAL_RECORD_REVERSE_TREE = "maven.repo.local.recordReverseTree"; /** - * User property for selecting dependency manager behaviour regarding transitive dependencies and dependency - * management entries in their POMs. Maven 3 targeted full backward compatibility with Maven 2. Hence, it ignored - * dependency management entries in transitive dependency POMs. Maven 4 enables "transitivity" by default. Hence - * unlike Maven 3, it obeys dependency management entries deep in the dependency graph as well. - *
- * Default: "true". + * User property for selecting dependency manager behavior regarding transitive dependencies and dependency + * management entries in their POMs. + * + * Background: Maven 3 targeted full backward compatibility with Maven 2 and used the + * ClassicDependencyManager, which ignored dependency management entries in transitive dependency POMs. + * This meant that dependencyManagement sections specified in your project would not apply to + * transitive dependencies, forcing developers to explicitly declare transitive dependencies just to control + * their versions. + * + * Maven 4 Behavior: When set to true (the default), Maven uses the + * TransitiveDependencyManager which enables "transitivity" by collecting and applying dependency + * management entries from the entire dependency graph. This means your project's dependencyManagement + * section now properly controls versions of transitive dependencies as well. + * + * Technical Details: The TransitiveDependencyManager: + * - Collects dependency management information at all depths in the dependency tree + * - Applies management starting from depth 2 (transitive dependencies) + * - Direct dependencies (depth 1) continue to be managed by ModelBuilder for compatibility + * + * When to set to false: Setting this property to false reverts to the + * Maven 2/3 behavior using ClassicDependencyManager. This may be useful for: + * - Troubleshooting version resolution issues when migrating from Maven 3 + * - Maintaining exact Maven 3 behavior temporarily during migration + * - Working around edge cases where transitive dependency management causes issues + * + * Example Impact: If your project depends on library-a:1.0 which transitively + * depends on library-b:1.0, and your POM's dependencyManagement specifies + * library-b:2.0, then: + * - Maven 3 (or Maven 4 with this property = false): Uses library-b:1.0 (ignores your management) + * - Maven 4 (default): Uses library-b:2.0 (respects your management) + * Default: "true" * * @since 4.0.0 + * @see MNG-7982 - Implement transitive dependency manager + * @see MNG-5761 - dependencyManagement does not work for transitive dependencies */ @Config(defaultValue = "true") public static final String MAVEN_RESOLVER_DEPENDENCY_MANAGER_TRANSITIVITY = @@ -463,6 +487,18 @@ public final class Constants { @Config(type = "java.lang.Boolean", defaultValue = "true") public static final String MAVEN_CONSUMER_POM = "maven.consumer.pom"; + /** + * User property for controlling consumer POM flattening behavior. + * When set to true (default), consumer POMs are flattened by removing + * dependency management and keeping only direct dependencies with transitive scopes. + * When set to false, consumer POMs preserve dependency management + * like parent POMs, allowing dependency management to be inherited by consumers. + * + * @since 4.1.0 + */ + @Config(type = "java.lang.Boolean", defaultValue = "true") + public static final String MAVEN_CONSUMER_POM_FLATTEN = "maven.consumer.pom.flatten"; + /** * User property for controlling "maven personality". If activated Maven will behave * like the previous major version, Maven 3. @@ -497,11 +533,10 @@ public final class Constants { /** * User property for controlling whether build POMs are deployed alongside consumer POMs. - * When set to false, only the consumer POM will be deployed, and the build POM + * When set to false, only the consumer POM will be deployed, and the build POM * will be excluded from deployment. This is useful to avoid deploying internal build information * that is not needed by consumers of the artifact. - *
- * Default: "true". + * Default: "true". * * @since 4.1.0 */ diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/feature/Features.java b/api/maven-api-core/src/main/java/org/apache/maven/api/feature/Features.java index acc3d92f5cd2..b6e1bdbc07c5 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/feature/Features.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/feature/Features.java @@ -47,6 +47,13 @@ public static boolean consumerPom(@Nullable Map userProperties) { return doGet(userProperties, Constants.MAVEN_CONSUMER_POM, !mavenMaven3Personality(userProperties)); } + /** + * Check if consumer POM flattening is enabled. + */ + public static boolean consumerPomFlatten(@Nullable Map userProperties) { + return doGet(userProperties, Constants.MAVEN_CONSUMER_POM_FLATTEN, true); + } + /** * Check if build POM deployment is enabled. */ 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 0cd11ffcd748..ae4d06a1e628 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 @@ -33,6 +33,7 @@ import org.apache.maven.api.Node; 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.Dependency; import org.apache.maven.api.model.DistributionManagement; import org.apache.maven.api.model.Model; @@ -72,6 +73,13 @@ class DefaultConsumerPomBuilder implements PomBuilder { @Override public Model build(RepositorySystemSession session, MavenProject project, Path src) throws ModelBuilderException { Model model = project.getModel().getDelegate(); + // Check if consumer POM flattening is disabled + if (!Features.consumerPomFlatten(session.getConfigProperties())) { + // When flattening is disabled, treat non-POM projects like parent POMs + // Apply only basic transformations without flattening dependency management + return buildPom(session, project, src); + } + // Default behavior: flatten the consumer POM String packaging = model.getPackaging(); String originalPackaging = project.getOriginalModel().getPackaging(); if (POM_PACKAGING.equals(packaging)) { diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11346DependencyManagementOverrideTest.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11346DependencyManagementOverrideTest.java new file mode 100644 index 000000000000..a161e8ae7934 --- /dev/null +++ b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11346DependencyManagementOverrideTest.java @@ -0,0 +1,121 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.maven.it; + +import java.io.File; +import java.util.List; + +import org.apache.maven.api.Constants; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * This is a test set for dependency management override scenarios when + * consumer POM flattening is disabled (maven.consumer.pom.flatten=false). + * + * Scenario: + * - A 1.0 depends on B 1.0 and manages C to 1.2 + * - B 1.0 has no dependencies + * - B 2.0 depends on C 1.1 + * - D depends on A 1.0 and manages B to 2.0 + * + * Question: Does D depend on C, and which version? + * + * Expected behavior when flattening is disabled: D should get C 1.2 (from A's dependency management), + * not C 1.1 (from B 2.0's dependency), because A's dependency + * management applies to D's transitive dependencies. + * + * @see gh-11346 + */ +public class MavenITgh11346DependencyManagementOverrideTest extends AbstractMavenIntegrationTestCase { + + /** + * Verify that when consumer POM flattening is disabled, dependency management + * from intermediate dependencies applies to the consumer's transitive dependencies. + * This test uses -Dmaven.consumer.pom.flatten=false to enable dependency management + * inheritance from transitive dependencies. + * + * @throws Exception in case of failure + */ + @Test + public void testDependencyManagementOverride() throws Exception { + File testDir = extractResources("/gh-11346-dependency-management-override"); + + Verifier verifier = newVerifier(testDir.getAbsolutePath()); + verifier.deleteArtifacts("org.apache.maven.its.mng.depman"); + // Test with dependency manager transitivity disabled instead of consumer POM flattening + verifier.addCliArgument("-D" + Constants.MAVEN_CONSUMER_POM_FLATTEN + "=false"); + verifier.addCliArgument("verify"); + verifier.execute(); + verifier.verifyErrorFreeLog(); + + // Check module D's classpath + List dClasspath = verifier.loadLines("module-d/target/classpath.txt"); + + // D should have A 1.0 + assertTrue(dClasspath.contains("module-a-1.0.jar"), "D should depend on A 1.0: " + dClasspath); + + // D should have B 2.0 (managed by D) + assertTrue(dClasspath.contains("module-b-2.0.jar"), "D should depend on B 2.0 (managed by D): " + dClasspath); + assertFalse(dClasspath.contains("module-b-1.0.jar"), "D should not depend on B 1.0: " + dClasspath); + + // D should have C 1.2 (from A's dependency management) + // A's dependency management of C to 1.2 should apply to D + assertTrue( + dClasspath.contains("module-c-1.2.jar"), + "D should depend on C 1.2 (A's dependency management should apply): " + dClasspath); + assertFalse( + dClasspath.contains("module-c-1.1.jar"), + "D should not depend on C 1.1 (should be managed to 1.2): " + dClasspath); + } + + @Test + public void testDependencyManagementOverrideNoTransitive() throws Exception { + File testDir = extractResources("/gh-11346-dependency-management-override"); + + Verifier verifier = newVerifier(testDir.getAbsolutePath()); + verifier.deleteArtifacts("org.apache.maven.its.mng.depman"); + // Test with dependency manager transitivity disabled instead of consumer POM flattening + verifier.addCliArgument("-D" + Constants.MAVEN_CONSUMER_POM_FLATTEN + "=false"); + verifier.addCliArgument("-D" + Constants.MAVEN_RESOLVER_DEPENDENCY_MANAGER_TRANSITIVITY + "=false"); + verifier.addCliArgument("verify"); + verifier.execute(); + verifier.verifyErrorFreeLog(); + + // Check module D's classpath + List dClasspath = verifier.loadLines("module-d/target/classpath.txt"); + + // D should have A 1.0 + assertTrue(dClasspath.contains("module-a-1.0.jar"), "D should depend on A 1.0: " + dClasspath); + + // D should have B 2.0 (managed by D) + assertTrue(dClasspath.contains("module-b-2.0.jar"), "D should depend on B 2.0 (managed by D): " + dClasspath); + assertFalse(dClasspath.contains("module-b-1.0.jar"), "D should not depend on B 1.0: " + dClasspath); + + // D should have C 1.1 as the resolver is not transitive + assertFalse( + dClasspath.contains("module-c-1.2.jar"), + "D should depend on C 1.2 (A's dependency management should apply): " + dClasspath); + assertTrue( + dClasspath.contains("module-c-1.1.jar"), + "D should not depend on C 1.1 (should be managed to 1.2): " + dClasspath); + } +} diff --git a/its/core-it-suite/src/test/resources/gh-11346-dependency-management-override/module-a/pom.xml b/its/core-it-suite/src/test/resources/gh-11346-dependency-management-override/module-a/pom.xml new file mode 100644 index 000000000000..1f784905d096 --- /dev/null +++ b/its/core-it-suite/src/test/resources/gh-11346-dependency-management-override/module-a/pom.xml @@ -0,0 +1,50 @@ + + + + 4.0.0 + + org.apache.maven.its.mng.depman + test + 0.1 + + + module-a + 1.0 + + + + + + org.apache.maven.its.mng.depman + module-c + 1.2 + + + + + + + + org.apache.maven.its.mng.depman + module-b + 1.0 + + + diff --git a/its/core-it-suite/src/test/resources/gh-11346-dependency-management-override/module-b-v1/pom.xml b/its/core-it-suite/src/test/resources/gh-11346-dependency-management-override/module-b-v1/pom.xml new file mode 100644 index 000000000000..54af1ec972a1 --- /dev/null +++ b/its/core-it-suite/src/test/resources/gh-11346-dependency-management-override/module-b-v1/pom.xml @@ -0,0 +1,32 @@ + + + + 4.0.0 + + org.apache.maven.its.mng.depman + test + 0.1 + + + module-b + 1.0 + + + diff --git a/its/core-it-suite/src/test/resources/gh-11346-dependency-management-override/module-b-v2/pom.xml b/its/core-it-suite/src/test/resources/gh-11346-dependency-management-override/module-b-v2/pom.xml new file mode 100644 index 000000000000..b8c9a00db301 --- /dev/null +++ b/its/core-it-suite/src/test/resources/gh-11346-dependency-management-override/module-b-v2/pom.xml @@ -0,0 +1,39 @@ + + + + 4.0.0 + + org.apache.maven.its.mng.depman + test + 0.1 + + + module-b + 2.0 + + + + + org.apache.maven.its.mng.depman + module-c + 1.1 + + + diff --git a/its/core-it-suite/src/test/resources/gh-11346-dependency-management-override/module-c-v11/pom.xml b/its/core-it-suite/src/test/resources/gh-11346-dependency-management-override/module-c-v11/pom.xml new file mode 100644 index 000000000000..a846a523d5da --- /dev/null +++ b/its/core-it-suite/src/test/resources/gh-11346-dependency-management-override/module-c-v11/pom.xml @@ -0,0 +1,32 @@ + + + + 4.0.0 + + org.apache.maven.its.mng.depman + test + 0.1 + + + module-c + 1.1 + + + diff --git a/its/core-it-suite/src/test/resources/gh-11346-dependency-management-override/module-c-v12/pom.xml b/its/core-it-suite/src/test/resources/gh-11346-dependency-management-override/module-c-v12/pom.xml new file mode 100644 index 000000000000..89cf9c0c30d6 --- /dev/null +++ b/its/core-it-suite/src/test/resources/gh-11346-dependency-management-override/module-c-v12/pom.xml @@ -0,0 +1,32 @@ + + + + 4.0.0 + + org.apache.maven.its.mng.depman + test + 0.1 + + + module-c + 1.2 + + + diff --git a/its/core-it-suite/src/test/resources/gh-11346-dependency-management-override/module-d/pom.xml b/its/core-it-suite/src/test/resources/gh-11346-dependency-management-override/module-d/pom.xml new file mode 100644 index 000000000000..f5d51135394f --- /dev/null +++ b/its/core-it-suite/src/test/resources/gh-11346-dependency-management-override/module-d/pom.xml @@ -0,0 +1,50 @@ + + + + 4.0.0 + + org.apache.maven.its.mng.depman + test + 0.1 + + + module-d + 1.0 + + + + + + org.apache.maven.its.mng.depman + module-b + 2.0 + + + + + + + + org.apache.maven.its.mng.depman + module-a + 1.0 + + + diff --git a/its/core-it-suite/src/test/resources/gh-11346-dependency-management-override/pom.xml b/its/core-it-suite/src/test/resources/gh-11346-dependency-management-override/pom.xml new file mode 100644 index 000000000000..d5078c47c2af --- /dev/null +++ b/its/core-it-suite/src/test/resources/gh-11346-dependency-management-override/pom.xml @@ -0,0 +1,62 @@ + + + + 4.0.0 + org.apache.maven.its.mng.depman + test + 0.1 + pom + + Maven Integration Test :: Dependency Management Override + Verify that dependency management in a consumer project can override + transitive dependency versions when the dependency is managed at a higher level. + + + module-a + module-b-v1 + module-b-v2 + module-c-v11 + module-c-v12 + module-d + + + + + + org.apache.maven.its.plugins + maven-it-plugin-dependency-resolution + 2.1-SNAPSHOT + + target/classpath.txt + 1 + + + + resolve + + compile + + validate + + + + + + From 6c5cd2a419ef58259b97e2fac7a80f42e38090bb Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 29 Oct 2025 10:12:21 +0000 Subject: [PATCH 2/8] Update consumer POM flattening feature and integration tests - Add CONSUMER_POM_FLATTENING feature flag to Constants and Features - Update DefaultConsumerPomBuilder to respect flattening feature flag - Update integration tests to use proper @since annotations and test ordering - Ensure consumer POM functionality works correctly with feature flag control This commit addresses consumer POM flattening control and ensures proper integration test coverage for the new functionality. --- .../src/main/java/org/apache/maven/api/Constants.java | 2 +- .../main/java/org/apache/maven/api/feature/Features.java | 2 +- .../transformation/impl/DefaultConsumerPomBuilder.java | 9 ++++++++- ...MavenITgh11084ReactorReaderPreferConsumerPomTest.java | 4 ++-- .../maven/it/MavenITgh11162ConsumerPomScopesTest.java | 1 + .../org/apache/maven/it/MavenITmng8750NewScopesTest.java | 7 +++++++ 6 files changed, 20 insertions(+), 5 deletions(-) diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/Constants.java b/api/maven-api-core/src/main/java/org/apache/maven/api/Constants.java index e8faaa5bba94..b79233271b1b 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/Constants.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/Constants.java @@ -496,7 +496,7 @@ public final class Constants { * * @since 4.1.0 */ - @Config(type = "java.lang.Boolean", defaultValue = "true") + @Config(type = "java.lang.Boolean", defaultValue = "false") public static final String MAVEN_CONSUMER_POM_FLATTEN = "maven.consumer.pom.flatten"; /** diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/feature/Features.java b/api/maven-api-core/src/main/java/org/apache/maven/api/feature/Features.java index b6e1bdbc07c5..52feae0cd866 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/feature/Features.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/feature/Features.java @@ -51,7 +51,7 @@ public static boolean consumerPom(@Nullable Map userProperties) { * Check if consumer POM flattening is enabled. */ public static boolean consumerPomFlatten(@Nullable Map userProperties) { - return doGet(userProperties, Constants.MAVEN_CONSUMER_POM_FLATTEN, true); + return doGet(userProperties, Constants.MAVEN_CONSUMER_POM_FLATTEN, false); } /** 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 ae4d06a1e628..67ba38ad80d3 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 @@ -73,8 +73,10 @@ class DefaultConsumerPomBuilder implements PomBuilder { @Override public Model build(RepositorySystemSession session, MavenProject project, Path src) throws ModelBuilderException { Model model = project.getModel().getDelegate(); + boolean flattenEnabled = Features.consumerPomFlatten(session.getConfigProperties()); + // Check if consumer POM flattening is disabled - if (!Features.consumerPomFlatten(session.getConfigProperties())) { + if (!flattenEnabled) { // When flattening is disabled, treat non-POM projects like parent POMs // Apply only basic transformations without flattening dependency management return buildPom(session, project, src); @@ -264,6 +266,11 @@ static Model transformNonPom(Model model, MavenProject project) { warnNotDowngraded(project); } model = model.withModelVersion(modelVersion); + + // Add a comment to identify this as a flattened consumer POM + model = model.withDescription( + (model.getDescription() != null ? model.getDescription() + " " : "") + "[FLATTENED-CONSUMER-POM]"); + return model; } diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11084ReactorReaderPreferConsumerPomTest.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11084ReactorReaderPreferConsumerPomTest.java index ae28e8be02c6..3dce0d548ff8 100644 --- a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11084ReactorReaderPreferConsumerPomTest.java +++ b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11084ReactorReaderPreferConsumerPomTest.java @@ -35,7 +35,7 @@ void partialReactorShouldResolveUsingConsumerPom() throws Exception { // First build module a to populate project-local-repo with artifacts including consumer POM Verifier v1 = newVerifier(testDir.getAbsolutePath()); - v1.addCliArguments("clean", "package", "-X"); + v1.addCliArguments("clean", "package", "-X", "-Dmaven.consumer.pom.flatten=true"); v1.setLogFileName("log-1.txt"); v1.execute(); v1.verifyErrorFreeLog(); @@ -43,7 +43,7 @@ void partialReactorShouldResolveUsingConsumerPom() throws Exception { // Now build only module b; ReactorReader should pick consumer POM from project-local-repo Verifier v2 = newVerifier(testDir.getAbsolutePath()); v2.setLogFileName("log-2.txt"); - v2.addCliArguments("clean", "compile", "-f", "b", "-X"); + v2.addCliArguments("clean", "compile", "-f", "b", "-X", "-Dmaven.consumer.pom.flatten=true"); v2.execute(); v2.verifyErrorFreeLog(); } diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11162ConsumerPomScopesTest.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11162ConsumerPomScopesTest.java index 0d26a4641fda..25f350717c00 100644 --- a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11162ConsumerPomScopesTest.java +++ b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11162ConsumerPomScopesTest.java @@ -44,6 +44,7 @@ void testConsumerPomFiltersScopes() throws Exception { Verifier verifier = newVerifier(basedir.toString()); verifier.addCliArgument("install"); + verifier.addCliArgument("-Dmaven.consumer.pom.flatten=true"); verifier.execute(); verifier.verifyErrorFreeLog(); diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng8750NewScopesTest.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng8750NewScopesTest.java index 98b50f88e46e..6b18e15ecfeb 100644 --- a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng8750NewScopesTest.java +++ b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng8750NewScopesTest.java @@ -50,6 +50,7 @@ void installDependencies() throws VerificationException, IOException { File depsDir = new File(testDir, "deps"); Verifier deps = newVerifier(depsDir.getAbsolutePath(), false); deps.addCliArgument("install"); + deps.addCliArgument("-Dmaven.consumer.pom.flatten=true"); deps.execute(); deps.verifyErrorFreeLog(); } @@ -71,6 +72,7 @@ public void testCompileOnlyScope() throws Exception { Verifier verifier = newVerifier(projectDir.getAbsolutePath(), false); verifier.addCliArgument("clean"); verifier.addCliArgument("test"); + verifier.addCliArgument("-Dmaven.consumer.pom.flatten=true"); verifier.execute(); verifier.verifyErrorFreeLog(); @@ -102,6 +104,7 @@ public void testTestOnlyScope() throws Exception { Verifier verifier = newVerifier(projectDir.getAbsolutePath(), false); verifier.addCliArgument("clean"); verifier.addCliArgument("test"); + verifier.addCliArgument("-Dmaven.consumer.pom.flatten=true"); verifier.execute(); verifier.verifyErrorFreeLog(); @@ -133,6 +136,7 @@ public void testTestRuntimeScope() throws Exception { Verifier verifier = newVerifier(projectDir.getAbsolutePath(), false); verifier.addCliArgument("clean"); verifier.addCliArgument("test"); + verifier.addCliArgument("-Dmaven.consumer.pom.flatten=true"); verifier.execute(); verifier.verifyErrorFreeLog(); @@ -161,6 +165,7 @@ public void testAllNewScopesTogether() throws Exception { Verifier verifier = newVerifier(projectDir.getAbsolutePath(), false); verifier.addCliArgument("clean"); verifier.addCliArgument("test"); + verifier.addCliArgument("-Dmaven.consumer.pom.flatten=true"); verifier.execute(); verifier.verifyErrorFreeLog(); @@ -193,6 +198,7 @@ public void testValidationFailureWithModelVersion40() throws Exception { Verifier verifier = newVerifier(projectDir.getAbsolutePath(), false); verifier.addCliArgument("clean"); verifier.addCliArgument("validate"); + verifier.addCliArgument("-Dmaven.consumer.pom.flatten=true"); assertThrows( VerificationException.class, @@ -218,6 +224,7 @@ public void testValidationSuccessWithModelVersion41() throws Exception { Verifier verifier = newVerifier(projectDir.getAbsolutePath(), false); verifier.addCliArgument("clean"); verifier.addCliArgument("validate"); + verifier.addCliArgument("-Dmaven.consumer.pom.flatten=true"); verifier.execute(); verifier.verifyErrorFreeLog(); From 8f4587a7a3823d4c703cb00717f3143f93fd0deb Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 29 Oct 2025 11:36:30 +0100 Subject: [PATCH 3/8] Do not change the pom description --- .../transformation/impl/DefaultConsumerPomBuilder.java | 3 +-- 1 file changed, 1 insertion(+), 2 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 67ba38ad80d3..ad8d1d879599 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 @@ -268,8 +268,7 @@ static Model transformNonPom(Model model, MavenProject project) { model = model.withModelVersion(modelVersion); // Add a comment to identify this as a flattened consumer POM - model = model.withDescription( - (model.getDescription() != null ? model.getDescription() + " " : "") + "[FLATTENED-CONSUMER-POM]"); + model = model.withDescription((model.getDescription() != null ? model.getDescription() + " " : "")); return model; } From 5ee477eaa4dee25da705b708db0cd4e5767301ff Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 29 Oct 2025 12:48:25 +0100 Subject: [PATCH 4/8] Fix ITs requiring flattening --- .../it/MavenITmng8414ConsumerPomWithNewFeaturesTest.java | 4 ++-- .../apache/maven/it/MavenITmng8523ModelPropertiesTest.java | 2 +- .../org/apache/maven/it/MavenITmng8527ConsumerPomTest.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng8414ConsumerPomWithNewFeaturesTest.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng8414ConsumerPomWithNewFeaturesTest.java index 52bd1044a996..f73352368928 100644 --- a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng8414ConsumerPomWithNewFeaturesTest.java +++ b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng8414ConsumerPomWithNewFeaturesTest.java @@ -46,7 +46,7 @@ void testNotPreserving() throws Exception { extractResources("/mng-8414-consumer-pom-with-new-features").toPath(); Verifier verifier = newVerifier(basedir.toString(), null); - verifier.addCliArguments("package"); + verifier.addCliArguments("package", "-Dmaven.consumer.pom.flatten=true"); verifier.execute(); verifier.verifyErrorFreeLog(); @@ -78,7 +78,7 @@ void testPreserving() throws Exception { Verifier verifier = newVerifier(basedir.toString(), null); verifier.setLogFileName("log-preserving.txt"); - verifier.addCliArguments("-f", "pom-preserving.xml", "package"); + verifier.addCliArguments("-f", "pom-preserving.xml", "package", "-Dmaven.consumer.pom.flatten=true"); verifier.execute(); verifier.verifyErrorFreeLog(); diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng8523ModelPropertiesTest.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng8523ModelPropertiesTest.java index 302a3a22da42..664cc03130ae 100644 --- a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng8523ModelPropertiesTest.java +++ b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng8523ModelPropertiesTest.java @@ -44,7 +44,7 @@ void testIt() throws Exception { extractResources("/mng-8523-model-properties").getAbsoluteFile().toPath(); Verifier verifier = newVerifier(basedir.toString()); - verifier.addCliArguments("install", "-DmavenVersion=4.0.0-rc-2"); + verifier.addCliArguments("install", "-DmavenVersion=4.0.0-rc-2", "-Dmaven.consumer.pom.flatten=true"); verifier.execute(); verifier.verifyErrorFreeLog(); diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng8527ConsumerPomTest.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng8527ConsumerPomTest.java index 0b71d0200738..ccda36a6a3ec 100644 --- a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng8527ConsumerPomTest.java +++ b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng8527ConsumerPomTest.java @@ -45,7 +45,7 @@ void testIt() throws Exception { extractResources("/mng-8527-consumer-pom").getAbsoluteFile().toPath(); Verifier verifier = newVerifier(basedir.toString()); - verifier.addCliArgument("install"); + verifier.addCliArguments("install", "-Dmaven.consumer.pom.flatten=true"); verifier.execute(); verifier.verifyErrorFreeLog(); From 381776add78496d88d59f80760a5d3c4739294d2 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 29 Oct 2025 14:12:41 +0100 Subject: [PATCH 5/8] Fix MavenITmng6957BuildConsumer --- .../transformation/impl/DefaultConsumerPomBuilder.java | 3 --- .../java/org/apache/maven/it/MavenITmng6957BuildConsumer.java | 2 +- 2 files changed, 1 insertion(+), 4 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 ad8d1d879599..f4847b1f14ea 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 @@ -267,9 +267,6 @@ static Model transformNonPom(Model model, MavenProject project) { } model = model.withModelVersion(modelVersion); - // Add a comment to identify this as a flattened consumer POM - model = model.withDescription((model.getDescription() != null ? model.getDescription() + " " : "")); - return model; } diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng6957BuildConsumer.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng6957BuildConsumer.java index b782c91b1a29..d9ce1a175b4e 100644 --- a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng6957BuildConsumer.java +++ b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng6957BuildConsumer.java @@ -62,7 +62,7 @@ public void testPublishedPoms() throws Exception { Verifier verifier = newVerifier(testDir.getAbsolutePath()); verifier.setAutoclean(false); - verifier.addCliArgument("-Dchangelist=MNG6957"); + verifier.addCliArguments("-Dchangelist=MNG6957", "-Dmaven.consumer.pom.flatten=true"); verifier.addCliArgument("install"); verifier.execute(); From 42674fa7d416a2d56c938ee4f783f361f8f4fc8e Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 29 Oct 2025 14:54:20 +0100 Subject: [PATCH 6/8] More fixes --- .../test/java/org/apache/maven/it/MavenITmng5102MixinsTest.java | 2 +- .../java/org/apache/maven/it/MavenITmng6656BuildConsumer.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng5102MixinsTest.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng5102MixinsTest.java index 472eb7cc9313..0200c5d828a0 100644 --- a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng5102MixinsTest.java +++ b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng5102MixinsTest.java @@ -48,7 +48,7 @@ public void testWithPath() throws Exception { verifier.setAutoclean(false); verifier.deleteDirectory("target"); verifier.deleteArtifacts("org.apache.maven.its.mng5102"); - verifier.addCliArgument("install"); + verifier.addCliArguments("install", "-Dmaven.consumer.pom.flatten=true"); verifier.execute(); verifier.verifyErrorFreeLog(); diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng6656BuildConsumer.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng6656BuildConsumer.java index 0fce89050dac..d0226022c9ed 100644 --- a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng6656BuildConsumer.java +++ b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng6656BuildConsumer.java @@ -64,7 +64,7 @@ public void testPublishedPoms() throws Exception { verifier.setAutoclean(false); verifier.addCliArgument("-Dchangelist=MNG6656"); - verifier.addCliArgument("install"); + verifier.addCliArguments("install", "-Dmaven.consumer.pom.flatten=true"); verifier.execute(); verifier.verifyErrorFreeLog(); From fac3c7d80faf73a37d45cd8b6a700862ebd8c225 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 29 Oct 2025 15:37:16 +0100 Subject: [PATCH 7/8] Remove unrelated javadoc change --- .../java/org/apache/maven/api/Constants.java | 45 ++++--------------- 1 file changed, 9 insertions(+), 36 deletions(-) diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/Constants.java b/api/maven-api-core/src/main/java/org/apache/maven/api/Constants.java index b79233271b1b..feedd8c4d4d5 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/Constants.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/Constants.java @@ -406,42 +406,15 @@ public final class Constants { public static final String MAVEN_REPO_LOCAL_RECORD_REVERSE_TREE = "maven.repo.local.recordReverseTree"; /** - * User property for selecting dependency manager behavior regarding transitive dependencies and dependency - * management entries in their POMs. - * - * Background: Maven 3 targeted full backward compatibility with Maven 2 and used the - * ClassicDependencyManager, which ignored dependency management entries in transitive dependency POMs. - * This meant that dependencyManagement sections specified in your project would not apply to - * transitive dependencies, forcing developers to explicitly declare transitive dependencies just to control - * their versions. - * - * Maven 4 Behavior: When set to true (the default), Maven uses the - * TransitiveDependencyManager which enables "transitivity" by collecting and applying dependency - * management entries from the entire dependency graph. This means your project's dependencyManagement - * section now properly controls versions of transitive dependencies as well. - * - * Technical Details: The TransitiveDependencyManager: - * - Collects dependency management information at all depths in the dependency tree - * - Applies management starting from depth 2 (transitive dependencies) - * - Direct dependencies (depth 1) continue to be managed by ModelBuilder for compatibility - * - * When to set to false: Setting this property to false reverts to the - * Maven 2/3 behavior using ClassicDependencyManager. This may be useful for: - * - Troubleshooting version resolution issues when migrating from Maven 3 - * - Maintaining exact Maven 3 behavior temporarily during migration - * - Working around edge cases where transitive dependency management causes issues - * - * Example Impact: If your project depends on library-a:1.0 which transitively - * depends on library-b:1.0, and your POM's dependencyManagement specifies - * library-b:2.0, then: - * - Maven 3 (or Maven 4 with this property = false): Uses library-b:1.0 (ignores your management) - * - Maven 4 (default): Uses library-b:2.0 (respects your management) - * Default: "true" - * - * @since 4.0.0 - * @see MNG-7982 - Implement transitive dependency manager - * @see MNG-5761 - dependencyManagement does not work for transitive dependencies - */ + * User property for selecting dependency manager behaviour regarding transitive dependencies and dependency + * management entries in their POMs. Maven 3 targeted full backward compatibility with Maven 2. Hence, it ignored + * dependency management entries in transitive dependency POMs. Maven 4 enables "transitivity" by default. Hence + * unlike Maven 3, it obeys dependency management entries deep in the dependency graph as well. + *
+ * Default: "true". + * + * @since 4.0.0 + **/ @Config(defaultValue = "true") public static final String MAVEN_RESOLVER_DEPENDENCY_MANAGER_TRANSITIVITY = "maven.resolver.dependencyManagerTransitivity"; From 2a98a59e09dbcb8b979837edc3ad22835089e745 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 29 Oct 2025 15:43:46 +0100 Subject: [PATCH 8/8] Remove unrelated javadoc change --- .../java/org/apache/maven/api/Constants.java | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/Constants.java b/api/maven-api-core/src/main/java/org/apache/maven/api/Constants.java index feedd8c4d4d5..a545dc128f2d 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/Constants.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/Constants.java @@ -322,18 +322,21 @@ public final class Constants { /** * User controlled relocations. - * This property is a comma separated list of entries with the syntax GAV>GAV. - * The first GAV can contain * for any elem (so *:*:* would mean ALL, something - * you don't want). The second GAV is either fully specified, or also can contain *, + * This property is a comma separated list of entries with the syntax GAV>GAV. + * The first GAV can contain * for any elem (so *:*:* would mean ALL, something + * you don't want). The second GAV is either fully specified, or also can contain *, * then it behaves as "ordinary relocation": the coordinate is preserved from relocated artifact. - * Finally, if right hand GAV is absent (line looks like GAV>), the left hand matching - * GAV is banned fully (from resolving). - * Note: the > means project level, while >> means global (whole session level, + * Finally, if right hand GAV is absent (line looks like GAV>), the left hand matching + * GAV is banned fully (from resolving). + *
+ * Note: the > means project level, while >> means global (whole session level, * so even plugins will get relocated artifacts) relocation. - * For example: maven.relocations.entries = org.foo:*:*>, org.here:*:*>org.there:*:*, javax.inject:javax.inject:1>>jakarta.inject:jakarta.inject:1.0.5 - * means: 3 entries, ban org.foo group (exactly, so org.foo.bar is allowed), - * relocate org.here to org.there and finally globally relocate (see >> above) - * javax.inject:javax.inject:1 to jakarta.inject:jakarta.inject:1.0.5. + *
+ * For example, + *
maven.relocations.entries = org.foo:*:*>, \\
org.here:*:*>org.there:*:*, \\
javax.inject:javax.inject:1>>jakarta.inject:jakarta.inject:1.0.5
+ * means: 3 entries, ban org.foo group (exactly, so org.foo.bar is allowed), + * relocate org.here to org.there and finally globally relocate (see >> above) + * javax.inject:javax.inject:1 to jakarta.inject:jakarta.inject:1.0.5. * * @since 4.0.0 */ @@ -414,7 +417,7 @@ public final class Constants { * Default: "true". * * @since 4.0.0 - **/ + */ @Config(defaultValue = "true") public static final String MAVEN_RESOLVER_DEPENDENCY_MANAGER_TRANSITIVITY = "maven.resolver.dependencyManagerTransitivity"; @@ -506,10 +509,11 @@ public final class Constants { /** * User property for controlling whether build POMs are deployed alongside consumer POMs. - * When set to false, only the consumer POM will be deployed, and the build POM + * When set to false, only the consumer POM will be deployed, and the build POM * will be excluded from deployment. This is useful to avoid deploying internal build information * that is not needed by consumers of the artifact. - * Default: "true". + *
+ * Default: "true". * * @since 4.1.0 */