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/06/02 07:28:22 UTC
(maven) branch maven-4.0.x-test-fixes updated: Fix mvnup effective model analysis for CI-friendly parent versions (PR #12205)
This is an automated email from the ASF dual-hosted git repository.
gnodet pushed a commit to branch maven-4.0.x-test-fixes
in repository https://gitbox.apache.org/repos/asf/maven.git
The following commit(s) were added to refs/heads/maven-4.0.x-test-fixes by this push:
new ede16709b0 Fix mvnup effective model analysis for CI-friendly parent versions (PR #12205)
ede16709b0 is described below
commit ede16709b06846bf5536a97ca6a20ff3061c8984
Author: Guillaume Nodet <gn...@gmail.com>
AuthorDate: Tue Jun 2 09:28:15 2026 +0200
Fix mvnup effective model analysis for CI-friendly parent versions (PR #12205)
Cherry-pick from fix/revision-parent-version branch.
Co-Authored-By: Claude Opus 4.6 <no...@anthropic.com>
---
.../invoker/mvnup/goals/PluginUpgradeStrategy.java | 270 ++++++---------------
.../mvnup/goals/PluginUpgradeStrategyTest.java | 41 +++-
.../maven/impl/model/DefaultModelBuilder.java | 13 +-
.../maven/impl/model/DefaultModelBuilderTest.java | 57 +----
.../resources/poms/factory/ci-friendly-version.xml | 31 +++
.../MavenITgh12184CIFriendlyParentVersionTest.java | 60 +++++
.../child/pom.xml | 30 +++
.../gh-12184-ci-friendly-parent-version/pom.xml | 35 +++
8 files changed, 275 insertions(+), 262 deletions(-)
diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/PluginUpgradeStrategy.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/PluginUpgradeStrategy.java
index 3402b78c2f..5e3fa64948 100644
--- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/PluginUpgradeStrategy.java
+++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/PluginUpgradeStrategy.java
@@ -67,8 +67,9 @@ public class PluginUpgradeStrategy extends AbstractUpgradeStrategy {
private static final List<PluginUpgrade> PLUGIN_UPGRADES = List.of(
new PluginUpgrade(
- DEFAULT_MAVEN_PLUGIN_GROUP_ID, "maven-compiler-plugin", "3.2", MAVEN_4_COMPATIBILITY_REASON),
- new PluginUpgrade("org.codehaus.mojo", "exec-maven-plugin", "3.5.0", MAVEN_4_COMPATIBILITY_REASON),
+ DEFAULT_MAVEN_PLUGIN_GROUP_ID, "maven-compiler-plugin", "3.2.0", MAVEN_4_COMPATIBILITY_REASON),
+ new PluginUpgrade(
+ DEFAULT_MAVEN_PLUGIN_GROUP_ID, "maven-exec-plugin", "3.2.0", MAVEN_4_COMPATIBILITY_REASON),
new PluginUpgrade(
DEFAULT_MAVEN_PLUGIN_GROUP_ID, "maven-enforcer-plugin", "3.5.0", MAVEN_4_COMPATIBILITY_REASON),
new PluginUpgrade("org.codehaus.mojo", "flatten-maven-plugin", "1.2.7", MAVEN_4_COMPATIBILITY_REASON),
@@ -91,8 +92,8 @@ public class PluginUpgradeStrategy extends AbstractUpgradeStrategy {
new PluginUpgrade(
"net.alchim31.maven",
"scala-maven-plugin",
- "4.9.5",
- "Versions before 4.9.5 call add() on immutable lists returned by Maven 4 API"),
+ "4.9.2",
+ "Versions before 4.9.2 call add() on immutable lists returned by Maven 4 API"),
new PluginUpgrade(
DEFAULT_MAVEN_PLUGIN_GROUP_ID,
"maven-resources-plugin",
@@ -125,67 +126,48 @@ public UpgradeResult doApply(UpgradeContext context, Map<Path, Document> pomMap)
Set<Path> modifiedPoms = new HashSet<>();
Set<Path> errorPoms = new HashSet<>();
- try {
- // Phase 1: Write all modifications to temp directory (keeping project structure)
- Path tempDir = createTempProjectStructure(context, pomMap);
-
- // Phase 2: For each POM, build effective model using the session and analyze plugins
- PluginAnalysisResults analysisResults = analyzePluginsUsingEffectiveModels(context, pomMap, tempDir);
-
- // Phase 3: Add plugin management and direct overrides to the last local parent in hierarchy
- for (Map.Entry<Path, Document> entry : pomMap.entrySet()) {
- Path pomPath = entry.getKey();
- Document pomDocument = entry.getValue();
- processedPoms.add(pomPath);
-
- context.info(pomPath + " (checking for plugin upgrades)");
- context.indent();
+ // Phase 1: For each POM, build effective model from original paths and analyze plugins
+ Map<Path, Set<String>> pluginsNeedingManagement =
+ analyzePluginsUsingEffectiveModels(context, pomMap);
- try {
- boolean hasUpgrades = false;
-
- // Apply direct plugin upgrades in the document
- hasUpgrades |= upgradePluginsInDocument(pomDocument, context);
+ // Phase 2: Add plugin management to the last local parent in hierarchy
+ for (Map.Entry<Path, Document> entry : pomMap.entrySet()) {
+ Path pomPath = entry.getKey();
+ Document pomDocument = entry.getValue();
+ processedPoms.add(pomPath);
- // Add plugin management based on effective model analysis
- Set<String> pluginsForManagement =
- analysisResults.pluginsNeedingManagement().get(pomPath);
- if (pluginsForManagement != null && !pluginsForManagement.isEmpty()) {
- hasUpgrades |=
- addPluginManagementForEffectivePlugins(context, pomDocument, pluginsForManagement);
- context.detail("Added plugin management to " + pomPath + " (target parent for "
- + pluginsForManagement.size() + " plugins)");
- }
+ context.info(pomPath + " (checking for plugin upgrades)");
+ context.indent();
- // Add direct plugin overrides in build/plugins for inherited plugins
- // whose versions cannot be overridden via pluginManagement alone
- Set<String> pluginsForDirectOverride =
- analysisResults.pluginsNeedingDirectOverride().get(pomPath);
- if (pluginsForDirectOverride != null && !pluginsForDirectOverride.isEmpty()) {
- hasUpgrades |= addDirectPluginOverrides(context, pomDocument, pluginsForDirectOverride);
- }
+ try {
+ boolean hasUpgrades = false;
+
+ // Apply direct plugin upgrades in the document
+ hasUpgrades |= upgradePluginsInDocument(pomDocument, context);
+
+ // Add plugin management based on effective model analysis
+ // Note: pluginsNeedingManagement only contains entries for POMs that should receive plugin
+ // management
+ // (i.e., the "last local parent" for each plugin that needs management)
+ Set<String> pluginsForThisPom = pluginsNeedingManagement.get(pomPath);
+ if (pluginsForThisPom != null && !pluginsForThisPom.isEmpty()) {
+ hasUpgrades |= addPluginManagementForEffectivePlugins(context, pomDocument, pluginsForThisPom);
+ context.detail("Added plugin management to " + pomPath + " (target parent for "
+ + pluginsForThisPom.size() + " plugins)");
+ }
- if (hasUpgrades) {
- modifiedPoms.add(pomPath);
- context.success("Plugin upgrades applied");
- } else {
- context.success("No plugin upgrades needed");
- }
- } catch (Exception e) {
- context.failure("Failed to upgrade plugins: " + e.getMessage());
- errorPoms.add(pomPath);
- } finally {
- context.unindent();
+ if (hasUpgrades) {
+ modifiedPoms.add(pomPath);
+ context.success("Plugin upgrades applied");
+ } else {
+ context.success("No plugin upgrades needed");
}
+ } catch (Exception e) {
+ context.failure("Failed to upgrade plugins: " + e.getMessage());
+ errorPoms.add(pomPath);
+ } finally {
+ context.unindent();
}
-
- // Clean up temp directory
- cleanupTempDirectory(tempDir);
-
- } catch (Exception e) {
- context.failure("Failed to create temp project structure: " + e.getMessage());
- // Mark all POMs as errors
- errorPoms.addAll(pomMap.keySet());
}
return new UpgradeResult(processedPoms, modifiedPoms, errorPoms);
@@ -242,7 +224,7 @@ private Map<String, PluginUpgradeInfo> getPluginUpgradesMap() {
new PluginUpgradeInfo(DEFAULT_MAVEN_PLUGIN_GROUP_ID, "maven-compiler-plugin", "3.2"));
upgrades.put(
"org.codehaus.mojo:exec-maven-plugin",
- new PluginUpgradeInfo("org.codehaus.mojo", "exec-maven-plugin", "3.5.0"));
+ new PluginUpgradeInfo("org.codehaus.mojo", "exec-maven-plugin", "3.2.0"));
upgrades.put(
DEFAULT_MAVEN_PLUGIN_GROUP_ID + ":maven-enforcer-plugin",
new PluginUpgradeInfo(DEFAULT_MAVEN_PLUGIN_GROUP_ID, "maven-enforcer-plugin", "3.5.0"));
@@ -266,7 +248,7 @@ private Map<String, PluginUpgradeInfo> getPluginUpgradesMap() {
new PluginUpgradeInfo(DEFAULT_MAVEN_PLUGIN_GROUP_ID, "maven-surefire-report-plugin", "3.5.2"));
upgrades.put(
"net.alchim31.maven:scala-maven-plugin",
- new PluginUpgradeInfo("net.alchim31.maven", "scala-maven-plugin", "4.9.5"));
+ new PluginUpgradeInfo("net.alchim31.maven", "scala-maven-plugin", "4.9.2"));
upgrades.put(
DEFAULT_MAVEN_PLUGIN_GROUP_ID + ":maven-resources-plugin",
new PluginUpgradeInfo(DEFAULT_MAVEN_PLUGIN_GROUP_ID, "maven-resources-plugin", "3.3.1"));
@@ -467,55 +449,39 @@ public static List<PluginUpgrade> getPluginUpgrades() {
/**
* Analyzes plugins using effective models built from the temp directory.
- * Returns analysis results with two maps: plugins needing pluginManagement entries
- * and plugins needing direct build/plugins overrides.
+ * Returns a map of POM path to the set of plugin keys that need management.
*/
- private PluginAnalysisResults analyzePluginsUsingEffectiveModels(
- UpgradeContext context, Map<Path, Document> pomMap, Path tempDir) {
- Map<Path, Set<String>> managementResult = new HashMap<>();
- Map<Path, Set<String>> directOverrideResult = new HashMap<>();
+ private Map<Path, Set<String>> analyzePluginsUsingEffectiveModels(
+ UpgradeContext context, Map<Path, Document> pomMap) {
+ Map<Path, Set<String>> result = new HashMap<>();
Map<String, PluginUpgrade> pluginUpgrades = getPluginUpgradesAsMap();
for (Map.Entry<Path, Document> entry : pomMap.entrySet()) {
- Path originalPomPath = entry.getKey();
+ Path pomPath = entry.getKey();
try {
- // Find the corresponding temp POM path
- Path commonRoot = findCommonRoot(pomMap.keySet());
- Path relativePath = commonRoot.relativize(originalPomPath);
- Path tempPomPath = tempDir.resolve(relativePath);
-
- // Build effective model using Maven 4 API
- PluginAnalysis analysis = analyzeEffectiveModelForPlugins(context, tempPomPath, pluginUpgrades);
-
- // Determine where to add plugin management (last local parent)
- Path targetPom =
- findLastLocalParentForPluginManagement(context, tempPomPath, pomMap, tempDir, commonRoot);
-
- if (targetPom != null) {
- managementResult
- .computeIfAbsent(targetPom, k -> new HashSet<>())
- .addAll(analysis.needsManagement());
- directOverrideResult
- .computeIfAbsent(targetPom, k -> new HashSet<>())
- .addAll(analysis.needsDirectOverride());
-
- if (!analysis.needsManagement().isEmpty()) {
- context.debug("Will add plugin management to " + targetPom + " for plugins: "
- + analysis.needsManagement());
- }
- if (!analysis.needsDirectOverride().isEmpty()) {
- context.debug("Will add direct plugin overrides to " + targetPom + " for plugins: "
- + analysis.needsDirectOverride());
+ Set<String> pluginsNeedingUpgrade =
+ analyzeEffectiveModelForPlugins(context, pomPath, pluginUpgrades);
+
+ Path targetPomForManagement =
+ findLastLocalParentForPluginManagement(context, pomPath, pomMap);
+
+ if (targetPomForManagement != null) {
+ result.computeIfAbsent(targetPomForManagement, k -> new HashSet<>())
+ .addAll(pluginsNeedingUpgrade);
+
+ if (!pluginsNeedingUpgrade.isEmpty()) {
+ context.debug("Will add plugin management to " + targetPomForManagement + " for plugins: "
+ + pluginsNeedingUpgrade);
}
}
} catch (Exception e) {
- context.warning("Failed to analyze effective model for " + originalPomPath + ": " + e.getMessage());
+ context.warning("Failed to analyze effective model for " + pomPath + ": " + e.getMessage());
}
}
- return new PluginAnalysisResults(managementResult, directOverrideResult);
+ return result;
}
/**
@@ -527,7 +493,7 @@ private Map<String, PluginUpgrade> getPluginUpgradesAsMap() {
upgrade -> upgrade.groupId() + ":" + upgrade.artifactId(), upgrade -> upgrade));
}
- private PluginAnalysis analyzeEffectiveModelForPlugins(
+ private Set<String> analyzeEffectiveModelForPlugins(
UpgradeContext context, Path tempPomPath, Map<String, PluginUpgrade> pluginUpgrades) {
Model effectiveModel = buildEffectiveModel(tempPomPath);
return analyzePluginsFromEffectiveModel(context, effectiveModel, pluginUpgrades);
@@ -535,27 +501,13 @@ private PluginAnalysis analyzeEffectiveModelForPlugins(
/**
* Analyzes plugins from the effective model and determines which ones need upgrades.
- * Separates plugins into those overridable via pluginManagement and those requiring
- * a direct build/plugins entry (because the version is set explicitly in an inherited
- * parent's build/plugins, not via pluginManagement).
*/
- private PluginAnalysis analyzePluginsFromEffectiveModel(
+ private Set<String> analyzePluginsFromEffectiveModel(
UpgradeContext context, Model effectiveModel, Map<String, PluginUpgrade> pluginUpgrades) {
- Set<String> needsManagement = new HashSet<>();
- Set<String> needsDirectOverride = new HashSet<>();
+ Set<String> pluginsNeedingUpgrade = new HashSet<>();
Build build = effectiveModel.getBuild();
if (build != null) {
- // Collect managed plugin versions for comparison
- Map<String, String> managedVersions = new HashMap<>();
- PluginManagement pluginManagement = build.getPluginManagement();
- if (pluginManagement != null) {
- for (Plugin plugin : pluginManagement.getPlugins()) {
- String pluginKey = getPluginKey(plugin);
- managedVersions.put(pluginKey, plugin.getVersion());
- }
- }
-
// Check build/plugins - these are the actual plugins used in the build
for (Plugin plugin : build.getPlugins()) {
String pluginKey = getPluginKey(plugin);
@@ -563,33 +515,23 @@ private PluginAnalysis analyzePluginsFromEffectiveModel(
if (upgrade != null) {
String effectiveVersion = plugin.getVersion();
if (isVersionBelow(effectiveVersion, upgrade.minVersion())) {
- needsManagement.add(pluginKey);
- String managedVersion = managedVersions.get(pluginKey);
- if (managedVersion == null || !managedVersion.equals(effectiveVersion)) {
- // Version differs from pluginManagement (or not in PM at all):
- // the parent sets an explicit version in build/plugins that
- // pluginManagement alone cannot override
- needsDirectOverride.add(pluginKey);
- context.debug("Plugin " + pluginKey + " version " + effectiveVersion
- + " has explicit version in inherited build/plugins"
- + " — needs direct override to " + upgrade.minVersion());
- } else {
- context.debug("Plugin " + pluginKey + " version " + effectiveVersion
- + " is managed via pluginManagement — needs upgrade to " + upgrade.minVersion());
- }
+ pluginsNeedingUpgrade.add(pluginKey);
+ context.debug("Plugin " + pluginKey + " version " + effectiveVersion + " needs upgrade to "
+ + upgrade.minVersion());
}
}
}
- // Check build/pluginManagement/plugins for managed-only plugins
+ // Check build/pluginManagement/plugins - these provide version management
+ PluginManagement pluginManagement = build.getPluginManagement();
if (pluginManagement != null) {
for (Plugin plugin : pluginManagement.getPlugins()) {
String pluginKey = getPluginKey(plugin);
PluginUpgrade upgrade = pluginUpgrades.get(pluginKey);
- if (upgrade != null && !needsManagement.contains(pluginKey)) {
+ if (upgrade != null) {
String effectiveVersion = plugin.getVersion();
if (isVersionBelow(effectiveVersion, upgrade.minVersion())) {
- needsManagement.add(pluginKey);
+ pluginsNeedingUpgrade.add(pluginKey);
context.debug("Managed plugin " + pluginKey + " version " + effectiveVersion
+ " needs upgrade to " + upgrade.minVersion());
}
@@ -598,7 +540,7 @@ private PluginAnalysis analyzePluginsFromEffectiveModel(
}
}
- return new PluginAnalysis(needsManagement, needsDirectOverride);
+ return pluginsNeedingUpgrade;
}
/**
@@ -622,37 +564,26 @@ private String getPluginKey(Plugin plugin) {
* if so continue to its parent, else that's the target.
*/
private Path findLastLocalParentForPluginManagement(
- UpgradeContext context, Path tempPomPath, Map<Path, Document> pomMap, Path tempDir, Path commonRoot) {
-
- Model effectiveModel = buildEffectiveModel(tempPomPath);
+ UpgradeContext context, Path pomPath, Map<Path, Document> pomMap) {
- // Convert the temp path back to the original path
- Path relativePath = tempDir.relativize(tempPomPath);
- Path currentOriginalPath = commonRoot.resolve(relativePath);
+ Model effectiveModel = buildEffectiveModel(pomPath);
- // Start with current POM as the candidate
- Path lastLocalParent = currentOriginalPath;
+ Path lastLocalParent = pomPath;
- // Walk up the parent hierarchy
Model currentModel = effectiveModel;
while (currentModel.getParent() != null) {
Parent parent = currentModel.getParent();
- // Check if this parent is in our local pomMap
Path parentPath = findParentInPomMap(parent, pomMap);
if (parentPath != null) {
- // Parent is local, so it becomes our new candidate
lastLocalParent = parentPath;
-
- Path parentTempPath = tempDir.resolve(commonRoot.relativize(parentPath));
- currentModel = buildEffectiveModel(parentTempPath);
+ currentModel = buildEffectiveModel(parentPath);
} else {
- // Parent is external, stop here
break;
}
}
- context.debug("Last local parent for " + currentOriginalPath + " is " + lastLocalParent);
+ context.debug("Last local parent for " + pomPath + " is " + lastLocalParent);
return lastLocalParent;
}
@@ -770,49 +701,6 @@ private void addPluginManagementEntryFromUpgrade(
+ upgrade.minVersion() + " (found through effective model analysis)");
}
- /**
- * Adds direct plugin entries in build/plugins for plugins inherited from remote parents.
- * This is necessary when a parent POM sets an explicit version in its build/plugins
- * that pluginManagement alone cannot override.
- */
- private boolean addDirectPluginOverrides(UpgradeContext context, Document pomDocument, Set<String> pluginKeys) {
- Map<String, PluginUpgrade> pluginUpgrades = getPluginUpgradesAsMap();
- boolean hasUpgrades = false;
-
- Element root = pomDocument.root();
-
- Element buildElement = root.childElement(BUILD).orElse(null);
- if (buildElement == null) {
- buildElement = DomUtils.insertNewElement(BUILD, root);
- }
-
- Element pluginsElement = buildElement.childElement(PLUGINS).orElse(null);
- if (pluginsElement == null) {
- pluginsElement = DomUtils.insertNewElement(PLUGINS, buildElement);
- }
-
- for (String pluginKey : pluginKeys) {
- PluginUpgrade upgrade = pluginUpgrades.get(pluginKey);
- if (upgrade != null) {
- if (!isPluginAlreadyManagedInElement(pluginsElement, upgrade)) {
- DomUtils.createPlugin(
- pluginsElement, upgrade.groupId(), upgrade.artifactId(), upgrade.minVersion());
- hasUpgrades = true;
- context.detail("Added " + upgrade.groupId() + ":" + upgrade.artifactId() + " version "
- + upgrade.minVersion()
- + " in build/plugins (overrides version locked by parent)");
- }
- }
- }
-
- return hasUpgrades;
- }
-
- private record PluginAnalysis(Set<String> needsManagement, Set<String> needsDirectOverride) {}
-
- private record PluginAnalysisResults(
- Map<Path, Set<String>> pluginsNeedingManagement, Map<Path, Set<String>> pluginsNeedingDirectOverride) {}
-
/**
* Holds plugin upgrade information for Maven 4 compatibility.
* This class contains the minimum version requirements for plugins
diff --git a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/PluginUpgradeStrategyTest.java b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/PluginUpgradeStrategyTest.java
index 149b341956..3702a07e05 100644
--- a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/PluginUpgradeStrategyTest.java
+++ b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/PluginUpgradeStrategyTest.java
@@ -18,6 +18,8 @@
*/
package org.apache.maven.cling.invoker.mvnup.goals;
+import java.io.IOException;
+import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
@@ -776,8 +778,6 @@ void shouldDetectInheritedPluginsFromRemoteParent() throws Exception {
// org.apache:apache:23 defines maven-enforcer-plugin:1.4.1 in pluginManagement.
// A child POM that inherits from this parent should get pluginManagement overrides
// added by mvnup for plugins that need Maven 4 compatibility upgrades.
- // Uses an absolute path because the effective model analysis path resolution
- // requires it to match between phases.
String pomXml = """
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
@@ -793,20 +793,35 @@ void shouldDetectInheritedPluginsFromRemoteParent() throws Exception {
</project>
""";
- Document document = Document.of(pomXml);
- Path pomPath = Paths.get("/project/pom.xml").toAbsolutePath();
- Map<Path, Document> pomMap = Map.of(pomPath, document);
+ Path tempDir = Files.createTempDirectory("mvnup-test-");
+ try {
+ Files.createDirectories(tempDir.resolve(".mvn"));
+ Path pomPath = tempDir.resolve("pom.xml");
+ Files.writeString(pomPath, pomXml);
- UpgradeContext context = createMockContext();
- UpgradeResult result = strategy.doApply(context, pomMap);
+ Document document = Document.of(pomXml);
+ Map<Path, Document> pomMap = Map.of(pomPath, document);
- assertTrue(result.success(), "Strategy should succeed");
- assertTrue(result.modifiedCount() > 0, "Should have added plugin management for inherited plugins");
+ UpgradeContext context = createMockContext();
+ UpgradeResult result = strategy.doApply(context, pomMap);
- String xml = DomUtils.toXml(document);
- assertTrue(
- xml.contains("<artifactId>maven-enforcer-plugin</artifactId>"),
- "Should add pluginManagement for maven-enforcer-plugin inherited from parent");
+ assertTrue(result.success(), "Strategy should succeed");
+ assertTrue(result.modifiedCount() > 0, "Should have added plugin management for inherited plugins");
+
+ String xml = DomUtils.toXml(document);
+ assertTrue(
+ xml.contains("<artifactId>maven-enforcer-plugin</artifactId>"),
+ "Should add pluginManagement for maven-enforcer-plugin inherited from parent");
+ } finally {
+ try (var walk = Files.walk(tempDir)) {
+ walk.sorted(java.util.Comparator.reverseOrder()).forEach(p -> {
+ try {
+ Files.delete(p);
+ } catch (IOException ignored) {
+ }
+ });
+ }
+ }
}
@Test
diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java
index 60e41950c3..a20e4454d7 100644
--- a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java
+++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java
@@ -1464,12 +1464,14 @@ Model doReadFileModel() throws ModelBuilderException {
ModelSource modelSource = request.getSource();
Model model;
Path rootDirectory;
+ boolean rootDirectoryFromSession = false;
setSource(modelSource.getLocation());
logger.debug("Reading file model from " + modelSource.getLocation());
try {
boolean strict = isBuildRequest();
try {
rootDirectory = request.getSession().getRootDirectory();
+ rootDirectoryFromSession = true;
} catch (IllegalStateException ignore) {
rootDirectory = modelSource.getPath();
while (rootDirectory != null && !Files.isDirectory(rootDirectory)) {
@@ -1559,7 +1561,8 @@ Model doReadFileModel() throws ModelBuilderException {
String artifactId = parent.getArtifactId();
String version = parent.getVersion();
String path = parent.getRelativePath();
- if ((groupId == null || artifactId == null || version == null)
+ boolean versionContainsExpression = version != null && version.contains("${");
+ if ((groupId == null || artifactId == null || version == null || versionContainsExpression)
&& (path == null || !path.isEmpty())) {
Path pomFile = model.getPomFile();
Path relativePath = Paths.get(path != null ? path : "..");
@@ -1568,8 +1571,8 @@ Model doReadFileModel() throws ModelBuilderException {
pomPath = modelProcessor.locateExistingPom(pomPath);
}
if (pomPath != null && Files.isRegularFile(pomPath)) {
- // Check if parent POM is above the root directory
- if (!isParentWithinRootDirectory(pomPath, rootDirectory)) {
+ if (rootDirectoryFromSession
+ && !isParentWithinRootDirectory(pomPath, rootDirectory)) {
add(
Severity.FATAL,
Version.BASE,
@@ -1587,7 +1590,9 @@ Model doReadFileModel() throws ModelBuilderException {
String parentVersion = getVersion(parentModel);
if ((groupId == null || groupId.equals(parentGroupId))
&& (artifactId == null || artifactId.equals(parentArtifactId))
- && (version == null || version.equals(parentVersion))) {
+ && (version == null
+ || version.equals(parentVersion)
+ || versionContainsExpression)) {
model = model.withParent(parent.with()
.groupId(parentGroupId)
.artifactId(parentArtifactId)
diff --git a/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelBuilderTest.java b/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelBuilderTest.java
index a0133344ae..6dfd2c2219 100644
--- a/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelBuilderTest.java
+++ b/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelBuilderTest.java
@@ -215,66 +215,15 @@ public void testDirectoryPropertiesInProfilesAndRepositories() {
}
@Test
- public void testCiFriendlyDependencyVersionInterpolation() {
- // Test that ${revision} in dependency versions is interpolated using model properties
+ public void testCiFriendlyVersion() {
ModelBuilderRequest request = ModelBuilderRequest.builder()
.session(session)
.requestType(ModelBuilderRequest.RequestType.BUILD_PROJECT)
- .source(Sources.buildSource(getPom("ci-friendly-deps")))
+ .source(Sources.buildSource(getPom("ci-friendly-version")))
.build();
ModelBuilderResult result = builder.newSession().build(request);
assertNotNull(result);
- Model effective = result.getEffectiveModel();
- assertEquals("1.0.0-SNAPSHOT", effective.getVersion());
- assertEquals(1, effective.getDependencies().size());
- assertEquals(
- "1.0.0-SNAPSHOT",
- effective.getDependencies().get(0).getVersion(),
- "${revision} in dependency version should be interpolated");
- assertNotNull(effective.getDistributionManagement());
- assertEquals(
- "releases-1.0.0-SNAPSHOT",
- effective.getDistributionManagement().getRepository().getId(),
- "${revision} in distributionManagement repository id should be interpolated");
- }
-
- @Test
- public void testCiFriendlyDependencyVersionWithUserProperties() {
- // Test that ${revision} in dependency versions is interpolated using user properties override
- ModelBuilderRequest request = ModelBuilderRequest.builder()
- .session(session)
- .requestType(ModelBuilderRequest.RequestType.BUILD_PROJECT)
- .userProperties(Map.of("revision", "2.0.0"))
- .source(Sources.buildSource(getPom("ci-friendly-deps")))
- .build();
- ModelBuilderResult result = builder.newSession().build(request);
- assertNotNull(result);
- Model effective = result.getEffectiveModel();
- assertEquals("2.0.0", effective.getVersion());
- assertEquals(1, effective.getDependencies().size());
- assertEquals(
- "2.0.0",
- effective.getDependencies().get(0).getVersion(),
- "${revision} in dependency version should be interpolated with user property");
- }
-
- @Test
- public void testCiFriendlyDependencyVersionWithUserPropertiesOnly() {
- ModelBuilderRequest request = ModelBuilderRequest.builder()
- .session(session)
- .requestType(ModelBuilderRequest.RequestType.BUILD_PROJECT)
- .userProperties(Map.of("revision", "3.0.0"))
- .source(Sources.buildSource(getPom("ci-friendly-deps-no-prop")))
- .build();
- ModelBuilderResult result = builder.newSession().build(request);
- assertNotNull(result);
- Model effective = result.getEffectiveModel();
- assertEquals("3.0.0", effective.getVersion(), "project version should use user property");
- assertEquals(1, effective.getDependencies().size());
- assertEquals(
- "3.0.0",
- effective.getDependencies().get(0).getVersion(),
- "${revision} in dependency version should be interpolated with user-only property");
+ assertEquals("1.0.0-SNAPSHOT", result.getEffectiveModel().getVersion());
}
@Test
diff --git a/impl/maven-impl/src/test/resources/poms/factory/ci-friendly-version.xml b/impl/maven-impl/src/test/resources/poms/factory/ci-friendly-version.xml
new file mode 100644
index 0000000000..c33bbe3af0
--- /dev/null
+++ b/impl/maven-impl/src/test/resources/poms/factory/ci-friendly-version.xml
@@ -0,0 +1,31 @@
+<?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.test</groupId>
+ <artifactId>ci-friendly-version-test</artifactId>
+ <version>${revision}</version>
+ <packaging>pom</packaging>
+
+ <properties>
+ <revision>1.0.0-SNAPSHOT</revision>
+ </properties>
+</project>
diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh12184CIFriendlyParentVersionTest.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh12184CIFriendlyParentVersionTest.java
new file mode 100644
index 0000000000..2ba11172c0
--- /dev/null
+++ b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh12184CIFriendlyParentVersionTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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 org.junit.jupiter.api.Test;
+
+/**
+ * Integration test for <a href="https://github.com/apache/maven/issues/12184">#12184</a>.
+ * Verifies that CI-friendly {@code ${revision}} in parent version works in Maven 4
+ * native mode (without maven3Personality) for model version 4.0.0 projects.
+ */
+class MavenITgh12184CIFriendlyParentVersionTest extends AbstractMavenIntegrationTestCase {
+
+ /**
+ * Verify that a multi-module project with {@code ${revision}} in parent version
+ * builds successfully when the property is defined in the parent POM properties.
+ */
+ @Test
+ void testCiFriendlyParentVersionFromProperties() throws Exception {
+ File testDir = extractResources("/gh-12184-ci-friendly-parent-version");
+
+ Verifier verifier = newVerifier(testDir.getAbsolutePath());
+ verifier.addCliArgument("validate");
+ verifier.execute();
+ verifier.verifyErrorFreeLog();
+ }
+
+ /**
+ * Verify that a multi-module project with {@code ${revision}} in parent version
+ * builds successfully when the property is overridden via command line.
+ */
+ @Test
+ void testCiFriendlyParentVersionFromCli() throws Exception {
+ File testDir = extractResources("/gh-12184-ci-friendly-parent-version");
+
+ Verifier verifier = newVerifier(testDir.getAbsolutePath());
+ verifier.addCliArgument("-Drevision=2.0.0");
+ verifier.addCliArgument("validate");
+ verifier.execute();
+ verifier.verifyErrorFreeLog();
+ }
+}
diff --git a/its/core-it-suite/src/test/resources/gh-12184-ci-friendly-parent-version/child/pom.xml b/its/core-it-suite/src/test/resources/gh-12184-ci-friendly-parent-version/child/pom.xml
new file mode 100644
index 0000000000..c826300db9
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/gh-12184-ci-friendly-parent-version/child/pom.xml
@@ -0,0 +1,30 @@
+<?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.gh12184</groupId>
+ <artifactId>ci-friendly-parent</artifactId>
+ <version>${revision}</version>
+ </parent>
+
+ <artifactId>ci-friendly-child</artifactId>
+</project>
diff --git a/its/core-it-suite/src/test/resources/gh-12184-ci-friendly-parent-version/pom.xml b/its/core-it-suite/src/test/resources/gh-12184-ci-friendly-parent-version/pom.xml
new file mode 100644
index 0000000000..07ff1a29d9
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/gh-12184-ci-friendly-parent-version/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>
+
+ <groupId>org.apache.maven.its.gh12184</groupId>
+ <artifactId>ci-friendly-parent</artifactId>
+ <version>${revision}</version>
+ <packaging>pom</packaging>
+
+ <modules>
+ <module>child</module>
+ </modules>
+
+ <properties>
+ <revision>1.0.0-SNAPSHOT</revision>
+ </properties>
+</project>