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);