Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -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>
Loading
Loading