From 89ad1a3973614120dce48d510640eb728787c915 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Thu, 24 Sep 2026 16:47:41 +0000 Subject: [PATCH] [Backport maven-3.10.x] Fix #11397: treat reactor BOM imports as ProjectSorter edges so -am includes them - Register dependencyManagement BOM imports (scope=import, type=pom) as reactor graph edges in ProjectSorter - Ensures -am / -amd correctly includes imported BOM modules in the selected reactor subset - Fix Java 8 compatibility: replace List.of() with Arrays.asList() - Add test coverage for -amd direction (BOM reports consumer as dependent) and arbitrary property versions --- .../apache/maven/project/ProjectSorter.java | 32 +++++ ...ProjectSorterDependencyManagementTest.java | 113 ++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 maven-core/src/test/java/org/apache/maven/project/ProjectSorterDependencyManagementTest.java diff --git a/maven-core/src/main/java/org/apache/maven/project/ProjectSorter.java b/maven-core/src/main/java/org/apache/maven/project/ProjectSorter.java index 96be8ad26c52..f5fe1a4e56f4 100644 --- a/maven-core/src/main/java/org/apache/maven/project/ProjectSorter.java +++ b/maven-core/src/main/java/org/apache/maven/project/ProjectSorter.java @@ -122,6 +122,25 @@ public ProjectSorter(Collection projects) throws CycleDetectedExce false); } + org.apache.maven.model.Model originalModel = project.getOriginalModel(); + if (originalModel != null && originalModel.getDependencyManagement() != null) { + for (org.apache.maven.model.Dependency dependency : + originalModel.getDependencyManagement().getDependencies()) { + if ("import".equals(dependency.getScope()) && "pom".equals(dependency.getType())) { + addEdge( + projectMap, + vertexMap, + project, + projectVertex, + dependency.getGroupId(), + dependency.getArtifactId(), + resolveImportVersion(project, dependency.getVersion()), + false, + false); + } + } + } + Parent parent = project.getModel().getParent(); if (parent != null) { @@ -196,6 +215,19 @@ public ProjectSorter(Collection projects) throws CycleDetectedExce this.sortedProjects = Collections.unmodifiableList(sortedProjects); } + private static String resolveImportVersion(MavenProject project, String version) { + if ("${project.version}".equals(version) || "${pom.version}".equals(version)) { + return project.getVersion(); + } + if (version != null && version.startsWith("${") && version.endsWith("}")) { + String property = project.getProperties().getProperty(version.substring(2, version.length() - 1)); + if (property != null) { + return property; + } + } + return version; + } + @SuppressWarnings("checkstyle:parameternumber") private void addEdge( Map projectMap, diff --git a/maven-core/src/test/java/org/apache/maven/project/ProjectSorterDependencyManagementTest.java b/maven-core/src/test/java/org/apache/maven/project/ProjectSorterDependencyManagementTest.java new file mode 100644 index 000000000000..66bd3982053e --- /dev/null +++ b/maven-core/src/test/java/org/apache/maven/project/ProjectSorterDependencyManagementTest.java @@ -0,0 +1,113 @@ +/* + * 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.project; + +import java.util.Arrays; + +import org.apache.maven.model.Dependency; +import org.apache.maven.model.DependencyManagement; +import org.apache.maven.model.Model; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ProjectSorterDependencyManagementTest { + + /** + * Verifies that a BOM module imported via {@code } is treated as a + * reactor graph edge: the BOM must be sorted before its consumer (-am direction). + */ + @Test + void importedReactorBomPrecedesConsumer() throws Exception { + MavenProject bom = createProject("org.example", "test-bom", "1-SNAPSHOT"); + MavenProject consumer = createProject("org.example", "test-consumer", "1-SNAPSHOT"); + + addBomImport(consumer, "org.example", "test-bom", "${project.version}"); + + ProjectSorter sorter = new ProjectSorter(Arrays.asList(consumer, bom)); + + assertEquals(Arrays.asList(ProjectSorter.getId(bom)), sorter.getDependencies(ProjectSorter.getId(consumer))); + assertEquals(Arrays.asList(bom, consumer), sorter.getSortedProjects()); + } + + /** + * Verifies that a BOM version expressed as an arbitrary project property (not {@code ${project.version}}) + * is resolved correctly. The second branch of {@code resolveImportVersion} looks up the property from + * {@code project.getProperties()}; if it resolves to the BOM's concrete version the edge must be created. + */ + @Test + void importedReactorBomWithPropertyVersion() throws Exception { + MavenProject bom = createProject("org.example", "test-bom", "1.0"); + MavenProject consumer = createProject("org.example", "test-consumer", "1.0"); + + // version expressed as a project property — exercises the second branch of resolveImportVersion + consumer.getOriginalModel().getProperties().setProperty("bom.version", "1.0"); + addBomImport(consumer, "org.example", "test-bom", "${bom.version}"); + + ProjectSorter sorter = new ProjectSorter(Arrays.asList(consumer, bom)); + + assertEquals(Arrays.asList(ProjectSorter.getId(bom)), sorter.getDependencies(ProjectSorter.getId(consumer))); + assertEquals(Arrays.asList(bom, consumer), sorter.getSortedProjects()); + } + + /** + * Verifies the reverse direction (-amd): the consumer is a downstream dependent of the BOM, + * so the BOM knows about the consumer as one of its dependents. + */ + @Test + void importedReactorBomKnowsItsDependents() throws Exception { + MavenProject bom = createProject("org.example", "test-bom", "1-SNAPSHOT"); + MavenProject consumer = createProject("org.example", "test-consumer", "1-SNAPSHOT"); + + addBomImport(consumer, "org.example", "test-bom", "${project.version}"); + + ProjectSorter sorter = new ProjectSorter(Arrays.asList(consumer, bom)); + + assertTrue( + sorter.getDependents(ProjectSorter.getId(bom)).contains(ProjectSorter.getId(consumer)), + "BOM should list the importing consumer as a dependent"); + } + + // ------------------------------------------------------------------------- + + private static void addBomImport(MavenProject project, String groupId, String artifactId, String version) { + Dependency importedBom = new Dependency(); + importedBom.setGroupId(groupId); + importedBom.setArtifactId(artifactId); + importedBom.setVersion(version); + importedBom.setType("pom"); + importedBom.setScope("import"); + + DependencyManagement dependencyManagement = new DependencyManagement(); + dependencyManagement.addDependency(importedBom); + project.getOriginalModel().setDependencyManagement(dependencyManagement); + } + + private static MavenProject createProject(String groupId, String artifactId, String version) { + Model model = new Model(); + model.setModelVersion("4.0.0"); + model.setGroupId(groupId); + model.setArtifactId(artifactId); + model.setVersion(version); + MavenProject project = new MavenProject(model); + project.setOriginalModel(model); + return project; + } +}