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/07/31 05:48:38 UTC
(maven) branch fix/12640 created (now 92a791cda8)
This is an automated email from the ASF dual-hosted git repository.
gnodet pushed a change to branch fix/12640
in repository https://gitbox.apache.org/repos/asf/maven.git
at 92a791cda8 Fix #12640: BOM consumer POM must not include inherited dependency management
This branch includes the following new commits:
new 92a791cda8 Fix #12640: BOM consumer POM must not include inherited dependency management
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(maven) 01/01: Fix #12640: BOM consumer POM must not include inherited dependency management
Posted by gn...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
gnodet pushed a commit to branch fix/12640
in repository https://gitbox.apache.org/repos/asf/maven.git
commit 92a791cda87d1f70afb15e43c2af1ccd05c7766d
Author: Guillaume Nodet <gn...@gmail.com>
AuthorDate: Fri Jul 31 07:48:26 2026 +0200
Fix #12640: BOM consumer POM must not include inherited dependency management
BOM packaging was using getEffectiveModel() which includes all dependency
management entries inherited from the parent chain. This caused parent-declared
entries (and flattened BOM imports) to leak into the consumer BOM.
Use a hybrid approach: the raw model (no inheritance) determines WHICH entries
belong in the BOM, while the effective model provides resolved values. Import-
scoped entries are preserved as BOM references with versions interpolated from
the project properties, rather than being flattened.
Co-Authored-By: Claude Opus 4.6 <no...@anthropic.com>
---
.../impl/DefaultConsumerPomBuilder.java | 92 +++++++++++++-
.../it/MavenITgh12640BomInheritedDepMgmtTest.java | 132 +++++++++++++++++++++
.../gh-12640-bom-inherited-depmgmt/bom/pom.xml | 53 +++++++++
.../gh-12640-bom-inherited-depmgmt/mod-1/pom.xml | 35 ++++++
.../gh-12640-bom-inherited-depmgmt/mod-2/pom.xml | 35 ++++++
.../gh-12640-bom-inherited-depmgmt/pom.xml | 60 ++++++++++
6 files changed, 406 insertions(+), 1 deletion(-)
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 5aba713866..8a9e57e646 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
@@ -21,9 +21,11 @@
import javax.inject.Inject;
import javax.inject.Named;
+import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Properties;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -195,10 +197,98 @@ protected Model buildPom(RepositorySystemSession session, MavenProject project,
protected Model buildBom(RepositorySystemSession session, MavenProject project, ModelSource src)
throws ModelBuilderException {
ModelBuilderResult result = buildModel(session, project, src);
- Model model = result.getEffectiveModel();
+ Model rawModel = result.getRawModel();
+ Model effectiveModel = result.getEffectiveModel();
+ // The raw model has no inheritance — only entries declared in this POM.
+ // The effective model has fully interpolated values but includes inherited entries.
+ // Filter the effective model's dependency management to only include entries
+ // explicitly declared in this BOM, using the effective model for resolved values.
+ Model model = filterToOwnDependencyManagement(rawModel, effectiveModel, project);
return transformBom(model, project);
}
+ /**
+ * Filters the effective model's dependency management to include only entries
+ * that were explicitly declared in this BOM's raw model, not inherited from the
+ * parent chain. For non-import entries, the effective model's fully resolved entry
+ * is used. For import-scoped entries (which are consumed/flattened in the effective
+ * model), the raw entry is preserved as a BOM reference with its version interpolated
+ * from the project's properties.
+ *
+ * @param rawModel the raw model (no inheritance, no interpolation)
+ * @param effectiveModel the effective model (inheritance + interpolation)
+ * @param project the Maven project (provides resolved properties)
+ * @return the effective model with dependency management filtered to own entries
+ */
+ private Model filterToOwnDependencyManagement(Model rawModel, Model effectiveModel, MavenProject project) {
+ if (rawModel.getDependencyManagement() == null
+ || rawModel.getDependencyManagement().getDependencies().isEmpty()) {
+ // Nothing declared in this BOM — strip all inherited entries
+ return effectiveModel.withDependencyManagement(null);
+ }
+
+ List<Dependency> declaredDeps = rawModel.getDependencyManagement().getDependencies();
+
+ // Build lookup from the effective model's resolved dependency management
+ Map<String, Dependency> effectiveLookup = new LinkedHashMap<>();
+ if (effectiveModel.getDependencyManagement() != null) {
+ for (Dependency dep : effectiveModel.getDependencyManagement().getDependencies()) {
+ effectiveLookup.put(getDependencyKey(dep), dep);
+ }
+ }
+
+ // For each declared entry, resolve it against the effective model
+ List<Dependency> resolvedDeps = new ArrayList<>();
+ for (Dependency declared : declaredDeps) {
+ if ("import".equals(declared.getScope())) {
+ // BOM import entries are consumed (flattened) in the effective model,
+ // so they won't be in effectiveLookup. Preserve the import reference
+ // with its version resolved from project properties.
+ String resolvedVersion = interpolateVersion(declared.getVersion(), project);
+ resolvedDeps.add(declared.withVersion(resolvedVersion));
+ } else {
+ // Regular entry: use the effective model's fully resolved entry
+ String key = getDependencyKey(declared);
+ Dependency resolved = effectiveLookup.get(key);
+ resolvedDeps.add(resolved != null ? resolved : declared);
+ }
+ }
+
+ return effectiveModel.withDependencyManagement(
+ effectiveModel.getDependencyManagement() != null
+ ? effectiveModel.getDependencyManagement().withDependencies(resolvedDeps)
+ : org.apache.maven.api.model.DependencyManagement.newBuilder()
+ .dependencies(resolvedDeps)
+ .build());
+ }
+
+ /**
+ * Resolves property references ({@code ${...}}) in a version string using the
+ * Maven project's fully-resolved properties. Handles model properties, inherited
+ * properties, and CI-friendly properties ({@code ${revision}}, etc.).
+ */
+ private static String interpolateVersion(String version, MavenProject project) {
+ if (version == null || !version.contains("${")) {
+ return version;
+ }
+ String result = version;
+ Properties props = project.getProperties();
+ for (String name : props.stringPropertyNames()) {
+ String placeholder = "${" + name + "}";
+ if (result.contains(placeholder)) {
+ result = result.replace(placeholder, props.getProperty(name));
+ }
+ }
+ // Handle built-in project-coordinate properties
+ if (result.contains("${project.version}") && project.getVersion() != null) {
+ result = result.replace("${project.version}", project.getVersion());
+ }
+ if (result.contains("${project.groupId}") && project.getGroupId() != null) {
+ result = result.replace("${project.groupId}", project.getGroupId());
+ }
+ return result;
+ }
+
protected Model buildNonPom(RepositorySystemSession session, MavenProject project, ModelSource src)
throws ModelBuilderException {
Model model = buildEffectiveModel(session, project, src);
diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh12640BomInheritedDepMgmtTest.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh12640BomInheritedDepMgmtTest.java
new file mode 100644
index 0000000000..18487dc02d
--- /dev/null
+++ b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh12640BomInheritedDepMgmtTest.java
@@ -0,0 +1,132 @@
+/*
+ * 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.nio.file.Files;
+import java.nio.file.Path;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Verifies that the BOM consumer POM contains only the dependency management
+ * entries explicitly declared in the BOM module, not entries inherited from the
+ * parent POM.
+ * <p>
+ * Reproducer for <a href="https://github.com/apache/maven/issues/12640">#12640</a>:
+ * when the parent POM defines dependency management entries (or imports BOMs),
+ * those entries were leaking into the BOM's consumer POM because
+ * {@code DefaultConsumerPomBuilder.buildBom()} used the effective model which
+ * includes inherited dependency management.
+ *
+ * @since 4.0.0
+ */
+class MavenITgh12640BomInheritedDepMgmtTest extends AbstractMavenIntegrationTestCase {
+
+ /**
+ * Verify that the BOM consumer POM contains only entries declared in the BOM,
+ * not entries inherited from the parent's dependency management.
+ * <p>
+ * The parent defines {@code ext-lib-a} and {@code ext-lib-b} in its
+ * dependencyManagement. The BOM declares only {@code mod-1} and {@code mod-2}.
+ * The consumer POM must NOT contain {@code ext-lib-a} or {@code ext-lib-b}.
+ */
+ @Test
+ void testBomConsumerPomExcludesInheritedDepMgmt() throws Exception {
+ Path basedir = extractResources("/gh-12640-bom-inherited-depmgmt");
+
+ Verifier verifier = newVerifier(basedir);
+ verifier.deleteArtifacts("org.apache.maven.its.gh12640");
+ verifier.addCliArguments("install");
+ verifier.execute();
+ verifier.verifyErrorFreeLog();
+
+ // Read the consumer POM that was installed to the local repo
+ Path consumerPomPath =
+ verifier.getArtifactPath("org.apache.maven.its.gh12640", "bom", "1.0.0-SNAPSHOT", "pom");
+
+ assertTrue(Files.exists(consumerPomPath), "Consumer POM not found at " + consumerPomPath);
+
+ String content = Files.readString(consumerPomPath);
+
+ // 1. Packaging must be "pom" (not "bom")
+ assertTrue(content.contains("<packaging>pom</packaging>"), "Consumer POM packaging should be 'pom'");
+
+ // 2. Must contain the declared entries: mod-1 and mod-2
+ assertTrue(
+ content.contains("<artifactId>mod-1</artifactId>"),
+ "Consumer POM must contain mod-1.\nActual:\n" + content);
+ assertTrue(
+ content.contains("<artifactId>mod-2</artifactId>"),
+ "Consumer POM must contain mod-2.\nActual:\n" + content);
+
+ // 3. Must NOT contain inherited entries from the parent's dependency management
+ assertFalse(
+ content.contains("<artifactId>ext-lib-a</artifactId>"),
+ "Consumer POM must NOT contain parent's ext-lib-a.\nActual:\n" + content);
+ assertFalse(
+ content.contains("<artifactId>ext-lib-b</artifactId>"),
+ "Consumer POM must NOT contain parent's ext-lib-b.\nActual:\n" + content);
+
+ // 4. Must have resolved versions (not raw property references)
+ assertFalse(
+ content.contains("${"),
+ "Consumer POM must not contain unresolved property references.\nActual:\n" + content);
+
+ // 5. Versions must be inferred from the reactor (1.0.0-SNAPSHOT)
+ assertTrue(
+ content.contains("<version>1.0.0-SNAPSHOT</version>"),
+ "Consumer POM must contain resolved reactor version.\nActual:\n" + content);
+ }
+
+ /**
+ * Same test with flatten enabled — the behaviour should be the same for BOMs.
+ */
+ @Test
+ void testBomConsumerPomWithFlattenExcludesInheritedDepMgmt() throws Exception {
+ Path basedir = extractResources("/gh-12640-bom-inherited-depmgmt");
+
+ Verifier verifier = newVerifier(basedir);
+ verifier.deleteArtifacts("org.apache.maven.its.gh12640");
+ verifier.addCliArguments("install", "-Dmaven.consumer.pom.flatten=true");
+ verifier.execute();
+ verifier.verifyErrorFreeLog();
+
+ Path consumerPomPath =
+ verifier.getArtifactPath("org.apache.maven.its.gh12640", "bom", "1.0.0-SNAPSHOT", "pom");
+
+ assertTrue(Files.exists(consumerPomPath), "Consumer POM not found at " + consumerPomPath);
+
+ String content = Files.readString(consumerPomPath);
+
+ // Must contain declared entries
+ assertTrue(content.contains("<artifactId>mod-1</artifactId>"), "Must contain mod-1");
+ assertTrue(content.contains("<artifactId>mod-2</artifactId>"), "Must contain mod-2");
+
+ // Must NOT contain inherited entries
+ assertFalse(
+ content.contains("<artifactId>ext-lib-a</artifactId>"),
+ "Consumer POM must NOT contain parent's ext-lib-a.\nActual:\n" + content);
+ assertFalse(
+ content.contains("<artifactId>ext-lib-b</artifactId>"),
+ "Consumer POM must NOT contain parent's ext-lib-b.\nActual:\n" + content);
+ }
+}
diff --git a/its/core-it-suite/src/test/resources/gh-12640-bom-inherited-depmgmt/bom/pom.xml b/its/core-it-suite/src/test/resources/gh-12640-bom-inherited-depmgmt/bom/pom.xml
new file mode 100644
index 0000000000..d54b882cda
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/gh-12640-bom-inherited-depmgmt/bom/pom.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.maven.its.gh12640</groupId>
+ <artifactId>parent</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>bom</artifactId>
+ <packaging>bom</packaging>
+
+ <name>GH-12640 :: BOM</name>
+
+ <!--
+ This BOM declares ONLY mod-1 and mod-2 in its dependency management.
+ The parent POM defines ext-lib-a and ext-lib-b in ITS dependency management.
+ The consumer BOM must contain ONLY mod-1 and mod-2, not the parent's entries.
+ -->
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.maven.its.gh12640</groupId>
+ <artifactId>mod-1</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven.its.gh12640</groupId>
+ <artifactId>mod-2</artifactId>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+</project>
diff --git a/its/core-it-suite/src/test/resources/gh-12640-bom-inherited-depmgmt/mod-1/pom.xml b/its/core-it-suite/src/test/resources/gh-12640-bom-inherited-depmgmt/mod-1/pom.xml
new file mode 100644
index 0000000000..17ee3a466d
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/gh-12640-bom-inherited-depmgmt/mod-1/pom.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.maven.its.gh12640</groupId>
+ <artifactId>parent</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>mod-1</artifactId>
+ <packaging>jar</packaging>
+
+ <name>Module 1</name>
+</project>
diff --git a/its/core-it-suite/src/test/resources/gh-12640-bom-inherited-depmgmt/mod-2/pom.xml b/its/core-it-suite/src/test/resources/gh-12640-bom-inherited-depmgmt/mod-2/pom.xml
new file mode 100644
index 0000000000..5d747b540e
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/gh-12640-bom-inherited-depmgmt/mod-2/pom.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.maven.its.gh12640</groupId>
+ <artifactId>parent</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>mod-2</artifactId>
+ <packaging>jar</packaging>
+
+ <name>Module 2</name>
+</project>
diff --git a/its/core-it-suite/src/test/resources/gh-12640-bom-inherited-depmgmt/pom.xml b/its/core-it-suite/src/test/resources/gh-12640-bom-inherited-depmgmt/pom.xml
new file mode 100644
index 0000000000..e4f9746d03
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/gh-12640-bom-inherited-depmgmt/pom.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>org.apache.maven.its.gh12640</groupId>
+ <artifactId>parent</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ <packaging>pom</packaging>
+
+ <name>GH-12640: BOM inherited dependency management test</name>
+
+ <properties>
+ <ext.lib.version>2.0.0</ext.lib.version>
+ </properties>
+
+ <!--
+ These dependency management entries are declared in the PARENT, not in
+ the BOM module. They should NOT leak into the BOM's consumer POM.
+ -->
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.maven.its.gh12640</groupId>
+ <artifactId>ext-lib-a</artifactId>
+ <version>${ext.lib.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven.its.gh12640</groupId>
+ <artifactId>ext-lib-b</artifactId>
+ <version>${ext.lib.version}</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <modules>
+ <module>mod-1</module>
+ <module>mod-2</module>
+ <module>bom</module>
+ </modules>
+</project>