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 34d3b053df Fix incompatible extensions in mvnup for Maven 4 
compatibility (#12122)
34d3b053df is described below

commit 34d3b053df4be8289056ce6e658c50c86ec69471
Author: Guillaume Nodet <[email protected]>
AuthorDate: Wed May 20 16:31:26 2026 +0200

    Fix incompatible extensions in mvnup for Maven 4 compatibility (#12122)
    
    Add extension handling to mvnup that automatically fixes known
    Maven 4-incompatible extensions in .mvn/extensions.xml:
    
    - Replace os-maven-plugin (kr.motd.maven) with Maveniverse Nisse
      (eu.maveniverse.maven.nisse:extension:0.4.4) which works with
      both Maven 3 and 4. Also adds -Dnisse.compat.osDetector to
      .mvn/maven.config for drop-in compatibility.
    
    - Remove Develocity/Gradle Enterprise extensions (com.gradle)
      which depend on org.slf4j.impl.SimpleLogger not available in
      Maven 4.
    
    Co-authored-by: Claude Opus 4.6 <[email protected]>
---
 .../invoker/mvnup/goals/AbstractUpgradeGoal.java   | 104 ++++++++
 .../mvnup/goals/AbstractUpgradeGoalTest.java       | 261 +++++++++++++++++++++
 2 files changed, 365 insertions(+)

diff --git 
a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/AbstractUpgradeGoal.java
 
b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/AbstractUpgradeGoal.java
index 2f4916f41f..a427ce595b 100644
--- 
a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/AbstractUpgradeGoal.java
+++ 
b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/AbstractUpgradeGoal.java
@@ -22,10 +22,14 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 
 import eu.maveniverse.domtrip.Document;
 import eu.maveniverse.domtrip.DomTripException;
+import eu.maveniverse.domtrip.Element;
+import eu.maveniverse.domtrip.Parser;
 import eu.maveniverse.domtrip.maven.MavenPomElements;
 import org.apache.maven.api.cli.mvnup.UpgradeOptions;
 import org.apache.maven.api.di.Inject;
@@ -208,6 +212,9 @@ protected int doUpgrade(UpgradeContext context, String 
targetModel, Map<Path, Do
             // This is needed for both 4.0.0 and 4.1.0 to help Maven find the 
project root
             createMvnDirectoryIfNeeded(context);
 
+            // Fix incompatible extensions in .mvn/extensions.xml
+            fixIncompatibleExtensions(context);
+
             return result.errorPoms().isEmpty() ? 0 : 1;
         } catch (Exception e) {
             context.failure("Strategy execution failed: " + e.getMessage());
@@ -272,4 +279,101 @@ protected void createMvnDirectoryIfNeeded(UpgradeContext 
context) {
             context.failure("Failed to create .mvn directory: " + 
e.getMessage());
         }
     }
+
+    /**
+     * Fixes incompatible extensions in .mvn/extensions.xml for Maven 4 
compatibility.
+     *
+     * <ul>
+     *   <li><strong>os-maven-plugin</strong>: Replaced with Maveniverse Nisse 
extension
+     *       (compatible with both Maven 3 and 4). Also adds {@code 
-Dnisse.compat.osDetector}
+     *       to {@code .mvn/maven.config} for drop-in compatibility.</li>
+     *   <li><strong>Develocity/Gradle Enterprise extension</strong>: Removed 
because it depends
+     *       on {@code org.slf4j.impl.SimpleLogger} which is not available in 
Maven 4.</li>
+     * </ul>
+     */
+    protected void fixIncompatibleExtensions(UpgradeContext context) {
+        Path startingDirectory = 
context.options().directory().map(Paths::get).orElse(context.invokerRequest.cwd());
+        Path extensionsXml = 
startingDirectory.resolve(MVN_DIRECTORY).resolve("extensions.xml");
+
+        if (!Files.exists(extensionsXml)) {
+            return;
+        }
+
+        context.info("");
+        context.info("Checking .mvn/extensions.xml for Maven 4 incompatible 
extensions...");
+        context.indent();
+
+        try {
+            String content = Files.readString(extensionsXml);
+            Document doc = new Parser().parse(content);
+            Element root = doc.root();
+            boolean modified = false;
+            boolean needsNisseCompat = false;
+
+            List<Element> extensions = root.children("extension").toList();
+            List<Element> toRemove = new ArrayList<>();
+
+            for (Element ext : extensions) {
+                String groupId = ext.childTextTrimmed("groupId");
+                String artifactId = ext.childTextTrimmed("artifactId");
+
+                if ("kr.motd.maven".equals(groupId) && 
"os-maven-plugin".equals(artifactId)) {
+                    DomUtils.updateOrCreateChildElement(ext, "groupId", 
"eu.maveniverse.maven.nisse");
+                    DomUtils.updateOrCreateChildElement(ext, "artifactId", 
"extension");
+                    DomUtils.updateOrCreateChildElement(ext, "version", 
"0.4.4");
+                    context.detail(
+                            "Replaced kr.motd.maven:os-maven-plugin with 
eu.maveniverse.maven.nisse:extension:0.4.4");
+                    modified = true;
+                    needsNisseCompat = true;
+                } else if ("com.gradle".equals(groupId)
+                        && ("develocity-maven-extension".equals(artifactId)
+                                || 
"gradle-enterprise-maven-extension".equals(artifactId))) {
+                    toRemove.add(ext);
+                    context.detail("Removed incompatible extension: " + 
groupId + ":" + artifactId);
+                    modified = true;
+                }
+            }
+
+            for (Element ext : toRemove) {
+                DomUtils.removeElement(ext);
+            }
+
+            if (modified) {
+                if (shouldSaveModifications()) {
+                    String modifiedXml = DomUtils.toXml(doc);
+                    Files.writeString(extensionsXml, modifiedXml);
+                    context.success("Updated .mvn/extensions.xml");
+
+                    if (needsNisseCompat) {
+                        addNisseCompatFlag(startingDirectory, context);
+                    }
+                } else {
+                    context.action("Would update .mvn/extensions.xml");
+                    if (needsNisseCompat) {
+                        context.action("Would add -Dnisse.compat.osDetector to 
.mvn/maven.config");
+                    }
+                }
+            } else {
+                context.success("No incompatible extensions found");
+            }
+        } catch (Exception e) {
+            context.failure("Failed to process .mvn/extensions.xml: " + 
e.getMessage());
+        } finally {
+            context.unindent();
+        }
+    }
+
+    private void addNisseCompatFlag(Path startingDirectory, UpgradeContext 
context) {
+        Path mavenConfig = 
startingDirectory.resolve(MVN_DIRECTORY).resolve("maven.config");
+        try {
+            String configContent = Files.exists(mavenConfig) ? 
Files.readString(mavenConfig) : "";
+            if (!configContent.contains("-Dnisse.compat.osDetector")) {
+                String separator = configContent.isEmpty() || 
configContent.endsWith("\n") ? "" : "\n";
+                Files.writeString(mavenConfig, configContent + separator + 
"-Dnisse.compat.osDetector\n");
+                context.success("Added -Dnisse.compat.osDetector to 
.mvn/maven.config");
+            }
+        } catch (IOException e) {
+            context.failure("Failed to update .mvn/maven.config: " + 
e.getMessage());
+        }
+    }
 }
diff --git 
a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/AbstractUpgradeGoalTest.java
 
b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/AbstractUpgradeGoalTest.java
index 969b0cb43e..a3b7a86b2a 100644
--- 
a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/AbstractUpgradeGoalTest.java
+++ 
b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/AbstractUpgradeGoalTest.java
@@ -38,6 +38,7 @@
 import org.mockito.Mockito;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -272,6 +273,266 @@ void shouldHandleMvnDirectoryCreationFailureGracefully() 
throws Exception {
         }
     }
 
+    @Nested
+    @DisplayName("Incompatible Extension Fixes")
+    class IncompatibleExtensionFixTests {
+
+        @Test
+        @DisplayName("should replace os-maven-plugin with Maveniverse Nisse")
+        void shouldReplaceOsMavenPluginWithNisse() throws Exception {
+            Path projectDir = tempDir.resolve("project");
+            Path mvnDir = projectDir.resolve(".mvn");
+            Files.createDirectories(mvnDir);
+
+            String extensionsXml = """
+                    <?xml version="1.0" encoding="UTF-8"?>
+                    <extensions>
+                        <extension>
+                            <groupId>kr.motd.maven</groupId>
+                            <artifactId>os-maven-plugin</artifactId>
+                            <version>1.7.1</version>
+                        </extension>
+                    </extensions>
+                    """;
+            Files.writeString(mvnDir.resolve("extensions.xml"), extensionsXml);
+
+            UpgradeContext context = createMockContext(projectDir);
+            when(mockOrchestrator.executeStrategies(Mockito.any(), 
Mockito.any()))
+                    .thenReturn(UpgradeResult.empty());
+
+            upgradeGoal.testExecuteWithTargetModel(context, "4.0.0");
+
+            String result = Files.readString(mvnDir.resolve("extensions.xml"));
+            assertTrue(result.contains("eu.maveniverse.maven.nisse"), "Should 
contain Nisse groupId");
+            assertTrue(result.contains("<artifactId>extension</artifactId>"), 
"Should contain Nisse artifactId");
+            assertTrue(result.contains("0.4.4"), "Should contain Nisse 
version");
+            assertFalse(result.contains("kr.motd.maven"), "Should not contain 
os-maven-plugin groupId");
+            assertFalse(result.contains("os-maven-plugin"), "Should not 
contain os-maven-plugin artifactId");
+
+            // Should also create maven.config with Nisse compat flag
+            Path mavenConfig = mvnDir.resolve("maven.config");
+            assertTrue(Files.exists(mavenConfig), "maven.config should be 
created");
+            String configContent = Files.readString(mavenConfig);
+            assertTrue(
+                    configContent.contains("-Dnisse.compat.osDetector"),
+                    "maven.config should contain Nisse compat flag");
+        }
+
+        @Test
+        @DisplayName("should remove Develocity extension")
+        void shouldRemoveDevelocityExtension() throws Exception {
+            Path projectDir = tempDir.resolve("project");
+            Path mvnDir = projectDir.resolve(".mvn");
+            Files.createDirectories(mvnDir);
+
+            String extensionsXml = """
+                    <?xml version="1.0" encoding="UTF-8"?>
+                    <extensions>
+                        <extension>
+                            <groupId>com.gradle</groupId>
+                            <artifactId>develocity-maven-extension</artifactId>
+                            <version>1.21</version>
+                        </extension>
+                    </extensions>
+                    """;
+            Files.writeString(mvnDir.resolve("extensions.xml"), extensionsXml);
+
+            UpgradeContext context = createMockContext(projectDir);
+            when(mockOrchestrator.executeStrategies(Mockito.any(), 
Mockito.any()))
+                    .thenReturn(UpgradeResult.empty());
+
+            upgradeGoal.testExecuteWithTargetModel(context, "4.0.0");
+
+            String result = Files.readString(mvnDir.resolve("extensions.xml"));
+            assertFalse(result.contains("develocity-maven-extension"), "Should 
not contain Develocity extension");
+            assertFalse(result.contains("com.gradle"), "Should not contain 
com.gradle groupId");
+        }
+
+        @Test
+        @DisplayName("should remove Gradle Enterprise extension")
+        void shouldRemoveGradleEnterpriseExtension() throws Exception {
+            Path projectDir = tempDir.resolve("project");
+            Path mvnDir = projectDir.resolve(".mvn");
+            Files.createDirectories(mvnDir);
+
+            String extensionsXml = """
+                    <?xml version="1.0" encoding="UTF-8"?>
+                    <extensions>
+                        <extension>
+                            <groupId>com.gradle</groupId>
+                            
<artifactId>gradle-enterprise-maven-extension</artifactId>
+                            <version>1.18</version>
+                        </extension>
+                    </extensions>
+                    """;
+            Files.writeString(mvnDir.resolve("extensions.xml"), extensionsXml);
+
+            UpgradeContext context = createMockContext(projectDir);
+            when(mockOrchestrator.executeStrategies(Mockito.any(), 
Mockito.any()))
+                    .thenReturn(UpgradeResult.empty());
+
+            upgradeGoal.testExecuteWithTargetModel(context, "4.0.0");
+
+            String result = Files.readString(mvnDir.resolve("extensions.xml"));
+            assertFalse(
+                    result.contains("gradle-enterprise-maven-extension"),
+                    "Should not contain Gradle Enterprise extension");
+        }
+
+        @Test
+        @DisplayName("should handle both os-maven-plugin and Develocity 
together")
+        void shouldHandleBothOsMavenPluginAndDevelocity() throws Exception {
+            Path projectDir = tempDir.resolve("project");
+            Path mvnDir = projectDir.resolve(".mvn");
+            Files.createDirectories(mvnDir);
+
+            String extensionsXml = """
+                    <?xml version="1.0" encoding="UTF-8"?>
+                    <extensions>
+                        <extension>
+                            <groupId>kr.motd.maven</groupId>
+                            <artifactId>os-maven-plugin</artifactId>
+                            <version>1.7.1</version>
+                        </extension>
+                        <extension>
+                            <groupId>com.gradle</groupId>
+                            <artifactId>develocity-maven-extension</artifactId>
+                            <version>1.21</version>
+                        </extension>
+                        <extension>
+                            <groupId>org.apache.maven.extensions</groupId>
+                            
<artifactId>maven-build-cache-extension</artifactId>
+                            <version>1.0.0</version>
+                        </extension>
+                    </extensions>
+                    """;
+            Files.writeString(mvnDir.resolve("extensions.xml"), extensionsXml);
+
+            UpgradeContext context = createMockContext(projectDir);
+            when(mockOrchestrator.executeStrategies(Mockito.any(), 
Mockito.any()))
+                    .thenReturn(UpgradeResult.empty());
+
+            upgradeGoal.testExecuteWithTargetModel(context, "4.0.0");
+
+            String result = Files.readString(mvnDir.resolve("extensions.xml"));
+            assertTrue(result.contains("eu.maveniverse.maven.nisse"), "Should 
contain Nisse replacement");
+            assertFalse(result.contains("develocity-maven-extension"), "Should 
not contain Develocity");
+            assertTrue(result.contains("maven-build-cache-extension"), "Should 
preserve compatible extensions");
+        }
+
+        @Test
+        @DisplayName("should append to existing maven.config")
+        void shouldAppendToExistingMavenConfig() throws Exception {
+            Path projectDir = tempDir.resolve("project");
+            Path mvnDir = projectDir.resolve(".mvn");
+            Files.createDirectories(mvnDir);
+
+            String extensionsXml = """
+                    <?xml version="1.0" encoding="UTF-8"?>
+                    <extensions>
+                        <extension>
+                            <groupId>kr.motd.maven</groupId>
+                            <artifactId>os-maven-plugin</artifactId>
+                            <version>1.7.1</version>
+                        </extension>
+                    </extensions>
+                    """;
+            Files.writeString(mvnDir.resolve("extensions.xml"), extensionsXml);
+            Files.writeString(mvnDir.resolve("maven.config"), "-Xmx2g\n");
+
+            UpgradeContext context = createMockContext(projectDir);
+            when(mockOrchestrator.executeStrategies(Mockito.any(), 
Mockito.any()))
+                    .thenReturn(UpgradeResult.empty());
+
+            upgradeGoal.testExecuteWithTargetModel(context, "4.0.0");
+
+            String configContent = 
Files.readString(mvnDir.resolve("maven.config"));
+            assertTrue(configContent.contains("-Xmx2g"), "Should preserve 
existing config");
+            assertTrue(configContent.contains("-Dnisse.compat.osDetector"), 
"Should add Nisse compat flag");
+        }
+
+        @Test
+        @DisplayName("should not duplicate Nisse compat flag in maven.config")
+        void shouldNotDuplicateNisseCompatFlag() throws Exception {
+            Path projectDir = tempDir.resolve("project");
+            Path mvnDir = projectDir.resolve(".mvn");
+            Files.createDirectories(mvnDir);
+
+            String extensionsXml = """
+                    <?xml version="1.0" encoding="UTF-8"?>
+                    <extensions>
+                        <extension>
+                            <groupId>kr.motd.maven</groupId>
+                            <artifactId>os-maven-plugin</artifactId>
+                            <version>1.7.1</version>
+                        </extension>
+                    </extensions>
+                    """;
+            Files.writeString(mvnDir.resolve("extensions.xml"), extensionsXml);
+            Files.writeString(mvnDir.resolve("maven.config"), 
"-Dnisse.compat.osDetector\n");
+
+            UpgradeContext context = createMockContext(projectDir);
+            when(mockOrchestrator.executeStrategies(Mockito.any(), 
Mockito.any()))
+                    .thenReturn(UpgradeResult.empty());
+
+            upgradeGoal.testExecuteWithTargetModel(context, "4.0.0");
+
+            String configContent = 
Files.readString(mvnDir.resolve("maven.config"));
+            int count = configContent.split("-Dnisse.compat.osDetector", 
-1).length - 1;
+            assertEquals(1, count, "Should not duplicate Nisse compat flag");
+        }
+
+        @Test
+        @DisplayName("should be no-op when no extensions.xml exists")
+        void shouldBeNoOpWhenNoExtensionsXml() throws Exception {
+            Path projectDir = tempDir.resolve("project");
+            Files.createDirectories(projectDir);
+
+            UpgradeContext context = createMockContext(projectDir);
+            when(mockOrchestrator.executeStrategies(Mockito.any(), 
Mockito.any()))
+                    .thenReturn(UpgradeResult.empty());
+
+            int result = upgradeGoal.testExecuteWithTargetModel(context, 
"4.0.0");
+
+            assertEquals(0, result, "Should succeed with no extensions.xml");
+            assertFalse(
+                    Files.exists(projectDir.resolve(".mvn/maven.config")),
+                    "Should not create maven.config when no extensions needed 
fixing");
+        }
+
+        @Test
+        @DisplayName("should be no-op when no incompatible extensions found")
+        void shouldBeNoOpWhenNoIncompatibleExtensions() throws Exception {
+            Path projectDir = tempDir.resolve("project");
+            Path mvnDir = projectDir.resolve(".mvn");
+            Files.createDirectories(mvnDir);
+
+            String extensionsXml = """
+                    <?xml version="1.0" encoding="UTF-8"?>
+                    <extensions>
+                        <extension>
+                            <groupId>org.apache.maven.extensions</groupId>
+                            
<artifactId>maven-build-cache-extension</artifactId>
+                            <version>1.0.0</version>
+                        </extension>
+                    </extensions>
+                    """;
+            Files.writeString(mvnDir.resolve("extensions.xml"), extensionsXml);
+
+            UpgradeContext context = createMockContext(projectDir);
+            when(mockOrchestrator.executeStrategies(Mockito.any(), 
Mockito.any()))
+                    .thenReturn(UpgradeResult.empty());
+
+            upgradeGoal.testExecuteWithTargetModel(context, "4.0.0");
+
+            String result = Files.readString(mvnDir.resolve("extensions.xml"));
+            assertTrue(result.contains("maven-build-cache-extension"), "Should 
preserve compatible extensions");
+            assertFalse(
+                    Files.exists(mvnDir.resolve("maven.config")),
+                    "Should not create maven.config when no extensions needed 
fixing");
+        }
+    }
+
     /**
      * Testable subclass that exposes protected methods for testing.
      */

Reply via email to