This is an automated email from the ASF dual-hosted git repository.
gnodet pushed a commit to branch maven-4.0.x
in repository https://gitbox.apache.org/repos/asf/maven.git
The following commit(s) were added to refs/heads/maven-4.0.x by this push:
new c6d4fe9a65 fix: mvnup: widen exact Maven version pins to allow Maven 4
(#12505) (#12508)
c6d4fe9a65 is described below
commit c6d4fe9a655a68e3ee7e46944ee8ae6d418d9ed2
Author: Guillaume Nodet <[email protected]>
AuthorDate: Mon Jul 20 10:33:37 2026 +0200
fix: mvnup: widen exact Maven version pins to allow Maven 4 (#12505)
(#12508)
Projects like Apache Flink pin their Maven version to an exact version
using ranges like [3.8.6,3.8.6] (both bounds equal, both inclusive).
The existing MAVEN4_EXCLUSIVE_UPPER_BOUND pattern only matched ranges
with an exclusive upper bound at 4.x (e.g. [3.8.8,4)), so exact pins
and ranges with an upper bound below 4 were silently skipped.
Add a new UPPER_BOUND_BELOW_MAVEN4 pattern that catches any version
range whose upper bound has a major version less than 4. This covers:
- Exact version pins: [3.8.6,3.8.6] -> [3.8.6,5)
- Sub-4 upper bounds: [3.8.0,3.9) -> [3.8.0,5)
- Unbounded lower ranges: (,3.9] -> (,5)
Co-authored-by: Claude Opus 4.6 <[email protected]>
---
.../mvnup/goals/EnforcerVersionRangeStrategy.java | 54 +++++++++++++++-
.../goals/EnforcerVersionRangeStrategyTest.java | 72 +++++++++++++++++++++-
2 files changed, 122 insertions(+), 4 deletions(-)
diff --git
a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/EnforcerVersionRangeStrategy.java
b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/EnforcerVersionRangeStrategy.java
index c3eefbf246..441fd4ed1f 100644
---
a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/EnforcerVersionRangeStrategy.java
+++
b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/EnforcerVersionRangeStrategy.java
@@ -87,6 +87,22 @@ public class EnforcerVersionRangeStrategy extends
AbstractUpgradeStrategy {
*/
static final Pattern MAVEN4_EXCLUSIVE_UPPER_BOUND =
Pattern.compile("^(\\[|\\()(.+?),\\s*(4(?:\\.\\d+)*)\\s*\\)$");
+ /**
+ * Pattern to match version ranges where the upper bound has a major
version below 4,
+ * which blocks Maven 4. This includes exact version pins like {@code
[3.8.6,3.8.6]}
+ * and ranges like {@code [3.8.0,3.9)}, {@code (,3.9]}.
+ * Captures:
+ * Group 1: opening bracket ([ or ()
+ * Group 2: lower bound (may be empty for unbounded lower ranges like
{@code (,3.9]})
+ * Group 3: upper bound (numeric version like 3.8.6, 3.9, 3)
+ * Group 4: closing bracket () or ])
+ *
+ * Examples matched: [3.8.6,3.8.6], [3.8.0,3.9), (,3.9], [3.0,3.0],
[3.9.0,3.9.0]
+ * Examples not matched: [3.8.8,), 3.8.8, [3.8.8,5), [3.8.8,4) (handled by
MAVEN4_EXCLUSIVE_UPPER_BOUND)
+ */
+ static final Pattern UPPER_BOUND_BELOW_MAVEN4 =
+
Pattern.compile("^(\\[|\\()(.*?),\\s*(\\d+(?:\\.\\d+)*)\\s*(\\)|\\])$");
+
@Override
public boolean isApplicable(UpgradeContext context) {
UpgradeOptions options = getOptions(context);
@@ -269,18 +285,54 @@ private boolean processRulesElement(Element
configuration, UpgradeContext contex
}
/**
- * Widens a Maven version range that has an exclusive upper bound at 4.x.
+ * Widens a Maven version range that blocks Maven 4.
+ *
+ * <p>Handles three cases:
+ * <ul>
+ * <li>Ranges with an exclusive upper bound at 4.x (e.g., {@code
[3.8.8,4)} → {@code [3.8.8,5)})</li>
+ * <li>Exact version pins (e.g., {@code [3.8.6,3.8.6]} → {@code
[3.8.6,5)})</li>
+ * <li>Ranges with an upper bound below 4 (e.g., {@code [3.8.0,3.9)} →
{@code [3.8.0,5)})</li>
+ * </ul>
*
* @param versionRange the version range string (e.g., "[3.8.8,4)")
* @return the widened range (e.g., "[3.8.8,5)"), or null if no widening
is needed
*/
static String widenVersionRange(String versionRange) {
+ // Check for ranges with exclusive upper bound at 4.x
Matcher matcher = MAVEN4_EXCLUSIVE_UPPER_BOUND.matcher(versionRange);
if (matcher.matches()) {
String openBracket = matcher.group(1);
String lowerBound = matcher.group(2);
return openBracket + lowerBound + ",5)";
}
+
+ // Check for ranges where the upper bound's major version is below 4,
+ // which block Maven 4. This catches exact version pins like
[3.8.6,3.8.6]
+ // and ranges like [3.8.0,3.9), (,3.9].
+ Matcher belowMatcher = UPPER_BOUND_BELOW_MAVEN4.matcher(versionRange);
+ if (belowMatcher.matches()) {
+ String openBracket = belowMatcher.group(1);
+ String lowerBound = belowMatcher.group(2);
+ String upperBound = belowMatcher.group(3);
+ if (getMajorVersion(upperBound) < 4) {
+ return openBracket + lowerBound + ",5)";
+ }
+ }
+
return null;
}
+
+ /**
+ * Extracts the major version number from a version string.
+ *
+ * @param version the version string (e.g., "3.8.6", "3", "4.0.0")
+ * @return the major version number, or {@link Integer#MAX_VALUE} if
unparseable
+ */
+ private static int getMajorVersion(String version) {
+ try {
+ return Integer.parseInt(version.split("\\.")[0]);
+ } catch (NumberFormatException e) {
+ return Integer.MAX_VALUE;
+ }
+ }
}
diff --git
a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/EnforcerVersionRangeStrategyTest.java
b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/EnforcerVersionRangeStrategyTest.java
index 7317bdac5a..03773acb57 100644
---
a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/EnforcerVersionRangeStrategyTest.java
+++
b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/EnforcerVersionRangeStrategyTest.java
@@ -153,9 +153,33 @@ void shouldNotWidenAlreadyWidened() {
}
@Test
- @DisplayName("should not widen [3.8.8,3.9) — upper bound is not at 4")
- void shouldNotWidenNon4UpperBound() {
-
assertNull(EnforcerVersionRangeStrategy.widenVersionRange("[3.8.8,3.9)"));
+ @DisplayName("should widen [3.8.8,3.9) to [3.8.8,5) — upper bound
below 4 blocks Maven 4")
+ void shouldWidenSub4UpperBound() {
+ assertEquals("[3.8.8,5)",
EnforcerVersionRangeStrategy.widenVersionRange("[3.8.8,3.9)"));
+ }
+
+ @Test
+ @DisplayName("should widen exact version pin [3.8.6,3.8.6] to
[3.8.6,5)")
+ void shouldWidenExactVersionPin() {
+ assertEquals("[3.8.6,5)",
EnforcerVersionRangeStrategy.widenVersionRange("[3.8.6,3.8.6]"));
+ }
+
+ @Test
+ @DisplayName("should widen exact version pin [3.0,3.0] to [3.0,5)")
+ void shouldWidenExactVersionPinShort() {
+ assertEquals("[3.0,5)",
EnforcerVersionRangeStrategy.widenVersionRange("[3.0,3.0]"));
+ }
+
+ @Test
+ @DisplayName("should widen exact version pin [3.9.0,3.9.0] to
[3.9.0,5)")
+ void shouldWidenExactVersionPinHighMinor() {
+ assertEquals("[3.9.0,5)",
EnforcerVersionRangeStrategy.widenVersionRange("[3.9.0,3.9.0]"));
+ }
+
+ @Test
+ @DisplayName("should not widen [4.0.0,4.0.0] — already allows Maven 4")
+ void shouldNotWidenExactPinAtMaven4() {
+
assertNull(EnforcerVersionRangeStrategy.widenVersionRange("[4.0.0,4.0.0]"));
}
}
@@ -367,6 +391,48 @@ void shouldNotModifyNonEnforcerPlugins() {
assertEquals(0, result.modifiedCount(), "No changes expected for
non-enforcer plugins");
}
+ @Test
+ @DisplayName("should widen exact version pin in requireMavenVersion
configuration")
+ void shouldWidenExactVersionPinInConfiguration() {
+ String pomXml = """
+ <?xml version="1.0" encoding="UTF-8"?>
+ <project xmlns="http://maven.apache.org/POM/4.0.0">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>test</groupId>
+ <artifactId>test</artifactId>
+ <version>1.0.0</version>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-enforcer-plugin</artifactId>
+ <configuration>
+ <rules>
+ <requireMavenVersion>
+ <version>[3.8.6,3.8.6]</version>
+ </requireMavenVersion>
+ </rules>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </project>
+ """;
+
+ Document document = Document.of(pomXml);
+ Map<Path, Document> pomMap = Map.of(Paths.get("pom.xml"),
document);
+
+ UpgradeContext context = createMockContext();
+ UpgradeResult result = strategy.doApply(context, pomMap);
+
+ assertTrue(result.success());
+ assertTrue(result.modifiedCount() > 0, "Exact version pin should
be widened");
+
+ String xml = DomUtils.toXml(document);
+ assertTrue(xml.contains("[3.8.6,5)"), "Version range should be
widened to [3.8.6,5)");
+ assertFalse(xml.contains("[3.8.6,3.8.6]"), "Original exact pin
should be replaced");
+ }
+
@Test
@DisplayName("should handle enforcer plugin without explicit groupId")
void shouldHandleEnforcerWithoutGroupId() {