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() {

Reply via email to