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/09/01 09:23:44 UTC

(maven) 01/01: Fix #12985: Upgrade toolchains-plugin when adding select-jdk-toolchain execution

This is an automated email from the ASF dual-hosted git repository.

gnodet pushed a commit to branch fix-apache-maven-12985-mvnup-pluginupgradestrate
in repository https://gitbox.apache.org/repos/asf/maven.git

commit 757341670ed04e2f9e40f775cfb00db875290e10
Author: Guillaume Nodet <gn...@gmail.com>
AuthorDate: Tue Sep 1 11:23:31 2026 +0200

    Fix #12985: Upgrade toolchains-plugin when adding select-jdk-toolchain execution
    
    When ToolchainPluginStrategy adds a select-jdk-toolchain execution,
    it now ensures the maven-toolchains-plugin version is >= 3.2.0 (the
    first version containing that goal). Previously, the plugin was added
    without a version, causing builds to fail when the project inherited
    an older version like 1.1 which only has 'help' and 'toolchain' goals.
    
    Changes:
    - Set plugin version to 3.2.0 when creating a new toolchains-plugin entry
    - Reuse existing toolchains-plugin entry instead of creating duplicates
    - Upgrade version of existing entries below 3.2.0 (in both build/plugins
      and build/pluginManagement/plugins)
    - Skip property-referenced versions to avoid unintended side effects
    
    Co-Authored-By: Claude Opus 4.6 <no...@anthropic.com>
---
 .../mvnup/goals/ToolchainPluginStrategy.java       |  87 ++++++++-
 .../mvnup/goals/ToolchainPluginStrategyTest.java   | 215 +++++++++++++++++++++
 .../MavenITMvnupToolchainPluginStrategyTest.java   |   3 +
 3 files changed, 303 insertions(+), 2 deletions(-)

diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/ToolchainPluginStrategy.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/ToolchainPluginStrategy.java
index 9db93a51a2..4cb0c19696 100644
--- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/ToolchainPluginStrategy.java
+++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/ToolchainPluginStrategy.java
@@ -24,11 +24,13 @@
 import java.util.Set;
 
 import eu.maveniverse.domtrip.Document;
+import eu.maveniverse.domtrip.Editor;
 import eu.maveniverse.domtrip.Element;
 import org.apache.maven.api.cli.mvnup.UpgradeOptions;
 import org.apache.maven.api.di.Named;
 import org.apache.maven.api.di.Priority;
 import org.apache.maven.api.di.Singleton;
+import org.apache.maven.artifact.versioning.ComparableVersion;
 import org.apache.maven.cling.invoker.mvnup.UpgradeContext;
 import org.apache.maven.impl.JdkSourceLevelSupport;
 
@@ -40,6 +42,7 @@
 import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.PLUGINS;
 import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.PLUGIN_MANAGEMENT;
 import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.PROPERTIES;
+import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.VERSION;
 
 /**
  * Strategy for adding the {@code maven-toolchains-plugin} with the {@code select-jdk-toolchain}
@@ -69,6 +72,13 @@ public class ToolchainPluginStrategy extends AbstractUpgradeStrategy {
     static final String TOOLCHAINS_PLUGIN_GROUP_ID = "org.apache.maven.plugins";
     static final String SELECT_JDK_TOOLCHAIN_GOAL = "select-jdk-toolchain";
 
+    /**
+     * Minimum toolchains-plugin version that contains the {@code select-jdk-toolchain} goal.
+     * Older versions (e.g. 1.1) only have {@code help} and {@code toolchain} goals, so adding
+     * a {@code select-jdk-toolchain} execution without upgrading would break the build.
+     */
+    static final String TOOLCHAINS_PLUGIN_MIN_VERSION = "3.2.0";
+
     private static final String MAVEN_COMPILER_RELEASE = "maven.compiler.release";
     private static final String MAVEN_COMPILER_SOURCE = "maven.compiler.source";
     private static final String MAVEN_COMPILER_PLUGIN = "maven-compiler-plugin";
@@ -322,6 +332,11 @@ private boolean hasSelectGoalInPluginSection(Element pluginsElement) {
      * to the POM's {@code <build><plugins>} section, configured with a version constraint
      * so the plugin selects a JDK that supports the project's source level.
      *
+     * <p>If the plugin already exists in the POM (e.g. with the older {@code toolchain} goal),
+     * the {@code select-jdk-toolchain} execution is appended to the existing entry and its
+     * version is upgraded to at least {@value #TOOLCHAINS_PLUGIN_MIN_VERSION} — the first
+     * version that contains the {@code select-jdk-toolchain} goal.
+     *
      * @param pomDocument the POM document to modify
      * @param maxJdkVersion the latest JDK major version that supports the source level
      */
@@ -330,8 +345,33 @@ void addToolchainsPlugin(Document pomDocument, int maxJdkVersion) {
         Element build = root.childElement(BUILD).orElseGet(() -> DomUtils.insertNewElement(BUILD, root));
         Element plugins = build.childElement(PLUGINS).orElseGet(() -> DomUtils.insertNewElement(PLUGINS, build));
 
-        Element plugin = DomUtils.createPlugin(plugins, TOOLCHAINS_PLUGIN_GROUP_ID, MAVEN_TOOLCHAINS_PLUGIN, null);
-        Element executions = DomUtils.insertNewElement("executions", plugin);
+        // Look for an existing toolchains-plugin entry (may have only the older 'toolchain' goal)
+        Element existingPlugin = findToolchainsPlugin(plugins);
+        final Element plugin;
+        if (existingPlugin != null) {
+            // Existing plugin — ensure version is >= 3.2.0
+            ensureMinimumVersion(pomDocument, existingPlugin);
+            plugin = existingPlugin;
+        } else {
+            // New plugin entry — set version to the minimum that has select-jdk-toolchain
+            plugin = DomUtils.createPlugin(
+                    plugins, TOOLCHAINS_PLUGIN_GROUP_ID, MAVEN_TOOLCHAINS_PLUGIN, TOOLCHAINS_PLUGIN_MIN_VERSION);
+        }
+
+        // Also upgrade version in pluginManagement if present there
+        Element pluginManagement = build.childElement(PLUGIN_MANAGEMENT).orElse(null);
+        if (pluginManagement != null) {
+            Element managedPlugins = pluginManagement.childElement(PLUGINS).orElse(null);
+            if (managedPlugins != null) {
+                Element managedPlugin = findToolchainsPlugin(managedPlugins);
+                if (managedPlugin != null) {
+                    ensureMinimumVersion(pomDocument, managedPlugin);
+                }
+            }
+        }
+
+        Element executions =
+                plugin.childElement("executions").orElseGet(() -> DomUtils.insertNewElement("executions", plugin));
         Element execution = DomUtils.insertNewElement("execution", executions);
         Element goals = DomUtils.insertNewElement("goals", execution);
         DomUtils.insertContentElement(goals, "goal", SELECT_JDK_TOOLCHAIN_GOAL);
@@ -339,6 +379,49 @@ void addToolchainsPlugin(Document pomDocument, int maxJdkVersion) {
         DomUtils.insertContentElement(configuration, "version", "(," + maxJdkVersion + "]");
     }
 
+    /**
+     * Finds an existing {@code maven-toolchains-plugin} element in a plugins section.
+     *
+     * @return the plugin element, or {@code null} if not found
+     */
+    private Element findToolchainsPlugin(Element pluginsElement) {
+        for (Element plugin : pluginsElement.childElements(PLUGIN).toList()) {
+            String artifactId = plugin.childTextTrimmed(ARTIFACT_ID);
+            String groupId = plugin.childTextTrimmed(GROUP_ID);
+            if (MAVEN_TOOLCHAINS_PLUGIN.equals(artifactId)
+                    && (groupId == null || groupId.isEmpty() || TOOLCHAINS_PLUGIN_GROUP_ID.equals(groupId))) {
+                return plugin;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Ensures the plugin element has a version >= {@value #TOOLCHAINS_PLUGIN_MIN_VERSION}.
+     * If the version is missing or below the minimum, it is set/upgraded.
+     */
+    void ensureMinimumVersion(Document pomDocument, Element plugin) {
+        Element versionElement = plugin.childElement(VERSION).orElse(null);
+        if (versionElement == null) {
+            // No version element — add one with the minimum version
+            DomUtils.insertContentElement(plugin, VERSION, TOOLCHAINS_PLUGIN_MIN_VERSION);
+        } else {
+            String currentVersion = versionElement.textContentTrimmed();
+            if (currentVersion != null && !currentVersion.startsWith("${")) {
+                if (isVersionBelow(currentVersion, TOOLCHAINS_PLUGIN_MIN_VERSION)) {
+                    new Editor(pomDocument).setTextContent(versionElement, TOOLCHAINS_PLUGIN_MIN_VERSION);
+                }
+            }
+        }
+    }
+
+    /**
+     * Checks if {@code currentVersion} is below {@code minVersion} using Maven version semantics.
+     */
+    static boolean isVersionBelow(String currentVersion, String minVersion) {
+        return new ComparableVersion(currentVersion).compareTo(new ComparableVersion(minVersion)) < 0;
+    }
+
     /**
      * Returns the major version of the running JDK.
      * Extracted as a method so tests can override it.
diff --git a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/ToolchainPluginStrategyTest.java b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/ToolchainPluginStrategyTest.java
index 7414d262a1..f7474f2c32 100644
--- a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/ToolchainPluginStrategyTest.java
+++ b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/ToolchainPluginStrategyTest.java
@@ -359,6 +359,8 @@ void addToEmptyPom() {
             assertTrue(strategy.hasToolchainsPluginWithSelectGoal(doc));
             String output = doc.toXml();
             assertTrue(output.contains("<version>(,11]</version>"), "Expected version constraint in output: " + output);
+            assertTrue(
+                    output.contains("<version>3.2.0</version>"), "Expected plugin version 3.2.0 in output: " + output);
         }
 
         @Test
@@ -386,6 +388,219 @@ void addToExistingBuild() {
             assertTrue(strategy.hasToolchainsPluginWithSelectGoal(doc));
             String output = doc.toXml();
             assertTrue(output.contains("<version>(,8]</version>"), "Expected version constraint in output: " + output);
+            assertTrue(
+                    output.contains("<version>3.2.0</version>"), "Expected plugin version 3.2.0 in output: " + output);
+        }
+
+        @Test
+        @DisplayName("should upgrade toolchains-plugin version 1.1 when adding select-jdk-toolchain execution")
+        void upgradeOldToolchainsPlugin() {
+            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>com.example</groupId>
+                        <artifactId>test</artifactId>
+                        <version>1.0</version>
+                        <build>
+                            <plugins>
+                                <plugin>
+                                    <groupId>org.apache.maven.plugins</groupId>
+                                    <artifactId>maven-toolchains-plugin</artifactId>
+                                    <version>1.1</version>
+                                    <executions>
+                                        <execution>
+                                            <goals>
+                                                <goal>toolchain</goal>
+                                            </goals>
+                                        </execution>
+                                    </executions>
+                                </plugin>
+                            </plugins>
+                        </build>
+                    </project>
+                    """;
+            Document doc = Document.of(pomXml);
+            strategy.addToolchainsPlugin(doc, 11);
+
+            assertTrue(strategy.hasToolchainsPluginWithSelectGoal(doc));
+            String output = doc.toXml();
+            // Version should be upgraded from 1.1 to 3.2.0
+            assertFalse(
+                    output.contains("<version>1.1</version>"), "Old version 1.1 should have been upgraded: " + output);
+            assertTrue(
+                    output.contains("<version>3.2.0</version>"),
+                    "Expected upgraded version 3.2.0 in output: " + output);
+            // Should NOT create a duplicate plugin entry
+            assertEquals(
+                    1,
+                    output.split("maven-toolchains-plugin").length - 1,
+                    "Should have exactly one toolchains-plugin entry: " + output);
+            // Old toolchain goal should still be present
+            assertTrue(
+                    output.contains("<goal>toolchain</goal>"), "Old toolchain goal should still be present: " + output);
+        }
+
+        @Test
+        @DisplayName("should not downgrade toolchains-plugin version when already >= 3.2.0")
+        void noDowngradeWhenSufficient() {
+            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>com.example</groupId>
+                        <artifactId>test</artifactId>
+                        <version>1.0</version>
+                        <build>
+                            <plugins>
+                                <plugin>
+                                    <groupId>org.apache.maven.plugins</groupId>
+                                    <artifactId>maven-toolchains-plugin</artifactId>
+                                    <version>3.3.0</version>
+                                </plugin>
+                            </plugins>
+                        </build>
+                    </project>
+                    """;
+            Document doc = Document.of(pomXml);
+            strategy.addToolchainsPlugin(doc, 11);
+
+            String output = doc.toXml();
+            // Version should remain 3.3.0, not downgraded to 3.2.0
+            assertTrue(
+                    output.contains("<version>3.3.0</version>"), "Version 3.3.0 should not be downgraded: " + output);
+        }
+
+        @Test
+        @DisplayName("should add version when existing toolchains-plugin has no version element")
+        void addVersionWhenMissing() {
+            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>com.example</groupId>
+                        <artifactId>test</artifactId>
+                        <version>1.0</version>
+                        <build>
+                            <plugins>
+                                <plugin>
+                                    <groupId>org.apache.maven.plugins</groupId>
+                                    <artifactId>maven-toolchains-plugin</artifactId>
+                                    <executions>
+                                        <execution>
+                                            <goals>
+                                                <goal>toolchain</goal>
+                                            </goals>
+                                        </execution>
+                                    </executions>
+                                </plugin>
+                            </plugins>
+                        </build>
+                    </project>
+                    """;
+            Document doc = Document.of(pomXml);
+            strategy.addToolchainsPlugin(doc, 11);
+
+            String output = doc.toXml();
+            assertTrue(output.contains("<version>3.2.0</version>"), "Expected version 3.2.0 to be added: " + output);
+        }
+
+        @Test
+        @DisplayName("should upgrade toolchains-plugin version in pluginManagement when adding execution")
+        void upgradeVersionInPluginManagement() {
+            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>com.example</groupId>
+                        <artifactId>test</artifactId>
+                        <version>1.0</version>
+                        <build>
+                            <pluginManagement>
+                                <plugins>
+                                    <plugin>
+                                        <groupId>org.apache.maven.plugins</groupId>
+                                        <artifactId>maven-toolchains-plugin</artifactId>
+                                        <version>1.1</version>
+                                    </plugin>
+                                </plugins>
+                            </pluginManagement>
+                        </build>
+                    </project>
+                    """;
+            Document doc = Document.of(pomXml);
+            strategy.addToolchainsPlugin(doc, 11);
+
+            assertTrue(strategy.hasToolchainsPluginWithSelectGoal(doc));
+            String output = doc.toXml();
+            // pluginManagement version should be upgraded
+            assertFalse(
+                    output.contains("<version>1.1</version>"),
+                    "Old version 1.1 in pluginManagement should have been upgraded: " + output);
+            assertTrue(output.contains("<version>3.2.0</version>"), "Expected upgraded version 3.2.0: " + output);
+        }
+
+        @Test
+        @DisplayName("should not touch property-referenced version in existing toolchains-plugin")
+        void skipPropertyVersion() {
+            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>com.example</groupId>
+                        <artifactId>test</artifactId>
+                        <version>1.0</version>
+                        <properties>
+                            <toolchains.version>1.1</toolchains.version>
+                        </properties>
+                        <build>
+                            <plugins>
+                                <plugin>
+                                    <groupId>org.apache.maven.plugins</groupId>
+                                    <artifactId>maven-toolchains-plugin</artifactId>
+                                    <version>${toolchains.version}</version>
+                                </plugin>
+                            </plugins>
+                        </build>
+                    </project>
+                    """;
+            Document doc = Document.of(pomXml);
+            strategy.addToolchainsPlugin(doc, 11);
+
+            String output = doc.toXml();
+            // Property reference should be left unchanged
+            assertTrue(
+                    output.contains("${toolchains.version}"),
+                    "Property-referenced version should be left unchanged: " + output);
+        }
+    }
+
+    @Nested
+    @DisplayName("Version comparison")
+    class VersionComparisonTests {
+
+        @Test
+        @DisplayName("version 1.1 should be below 3.2.0")
+        void v11BelowV320() {
+            assertTrue(ToolchainPluginStrategy.isVersionBelow("1.1", "3.2.0"));
+        }
+
+        @Test
+        @DisplayName("version 3.1.0 should be below 3.2.0")
+        void v310BelowV320() {
+            assertTrue(ToolchainPluginStrategy.isVersionBelow("3.1.0", "3.2.0"));
+        }
+
+        @Test
+        @DisplayName("version 3.2.0 should not be below 3.2.0")
+        void v320NotBelowV320() {
+            assertFalse(ToolchainPluginStrategy.isVersionBelow("3.2.0", "3.2.0"));
+        }
+
+        @Test
+        @DisplayName("version 3.3.0 should not be below 3.2.0")
+        void v330NotBelowV320() {
+            assertFalse(ToolchainPluginStrategy.isVersionBelow("3.3.0", "3.2.0"));
         }
     }
 
diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITMvnupToolchainPluginStrategyTest.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITMvnupToolchainPluginStrategyTest.java
index a2481bf740..1a49734c42 100644
--- a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITMvnupToolchainPluginStrategyTest.java
+++ b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITMvnupToolchainPluginStrategyTest.java
@@ -78,6 +78,9 @@ void testMvnupAddsToolchainsPluginForOldSourceLevel() throws Exception {
         assertTrue(
                 pomContent.contains("<version>(,8]</version>"),
                 "POM should contain version constraint (,8] for source 5 after mvnup apply");
+        assertTrue(
+                pomContent.contains("<version>3.2.0</version>"),
+                "POM should contain plugin version 3.2.0 (minimum for select-jdk-toolchain goal)");
 
         // Second run — should be idempotent (skip, already present)
         verifier = newVerifier(testDir);