From a9c4f26065e0c6d7af4c035fb4f33633065b8913 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 29 Jul 2026 15:58:09 +0200 Subject: [PATCH 1/2] Use resource filtering for mng-12534 IT plugin mavenVersion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the plugin POM to src/test/resources-filtered/ (which already has true in the core-it-suite build) and replace the hardcoded version with ${maven-version}, defined in the ITs parent POM. Maven resource filtering resolves the token at build time, so the version always matches the Maven build under test — including during the release process, where the release plugin bumps the version. Co-Authored-By: Claude Opus 4.6 --- .../mng-12534-after-annotation/plugin/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename its/core-it-suite/src/test/{resources => resources-filtered}/mng-12534-after-annotation/plugin/pom.xml (96%) diff --git a/its/core-it-suite/src/test/resources/mng-12534-after-annotation/plugin/pom.xml b/its/core-it-suite/src/test/resources-filtered/mng-12534-after-annotation/plugin/pom.xml similarity index 96% rename from its/core-it-suite/src/test/resources/mng-12534-after-annotation/plugin/pom.xml rename to its/core-it-suite/src/test/resources-filtered/mng-12534-after-annotation/plugin/pom.xml index 7363c59c1979..e268977329ba 100644 --- a/its/core-it-suite/src/test/resources/mng-12534-after-annotation/plugin/pom.xml +++ b/its/core-it-suite/src/test/resources-filtered/mng-12534-after-annotation/plugin/pom.xml @@ -11,7 +11,7 @@ 17 - 4.1.0-SNAPSHOT + ${maven-version} From d214fa3115b2eb0741e873ffe8168c2c7d3c8fa7 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 29 Jul 2026 15:58:22 +0200 Subject: [PATCH 2/2] mvnup: skip dedup inside plugin elements Co-Authored-By: Claude Opus 4.6 --- .../mvnup/goals/DuplicateElementStrategy.java | 29 +- .../goals/DuplicateElementStrategyTest.java | 290 ++++++++++++++++++ 2 files changed, 316 insertions(+), 3 deletions(-) diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/DuplicateElementStrategy.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/DuplicateElementStrategy.java index d85c745f18cc..f66c3c959b1c 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/DuplicateElementStrategy.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/DuplicateElementStrategy.java @@ -43,13 +43,20 @@ * each POM element's children and removes duplicates, keeping only the last * occurrence of each element name. * - *

This strategy only targets "scalar" elements — elements that should appear - * at most once within their parent according to the Maven POM schema. Elements - * inside list containers (e.g., {@code } inside {@code }, + *

This strategy only targets "scalar" elements at well-known POM schema + * positions — elements that should appear at most once within their parent + * according to the Maven POM schema. Elements inside list containers + * (e.g., {@code } inside {@code }, * {@code } inside {@code }) are not affected by this strategy * since duplicate dependencies and plugins are handled by * {@link DeduplicateDependenciesStrategy}. * + *

Plugin {@code } elements are skipped entirely, because they + * contain free-form, plugin-specific XML whose schema is not known to this tool. + * Treating same-named children as duplicates in configuration sections (e.g., + * multiple {@code } entries inside {@code }) would silently + * remove valid list entries and break builds. + * * @see #12530 */ @Named @@ -134,6 +141,14 @@ protected UpgradeResult doApply(UpgradeContext context, Map pomM return new UpgradeResult(processedPoms, modifiedPoms, errorPoms); } + /** + * Parent element names whose contents are free-form, plugin-specific XML. + * Deduplication is skipped entirely for these elements and their descendants, + * because same-named children (e.g., multiple {@code } in + * {@code }) are list entries, not schema-level duplicates. + */ + static final Set FREEFORM_ELEMENTS = Set.of("configuration"); + /** * Recursively scans an element's children for duplicates and removes them. * Uses last-wins semantics (consistent with Maven 3's behavior). @@ -145,6 +160,14 @@ protected UpgradeResult doApply(UpgradeContext context, Map pomM private boolean removeDuplicateElements(Element element, UpgradeContext context) { boolean removed = false; + // Skip free-form plugin configuration elements — their XML schema is + // plugin-specific and unknown to this tool. Treating same-named children + // as duplicates here would silently remove valid list entries (e.g., + // elements inside ) and break builds. + if (FREEFORM_ELEMENTS.contains(element.name())) { + return false; + } + // Skip list container elements — their children naturally repeat if (LIST_CONTAINER_ELEMENTS.contains(element.name())) { // Still recurse into each child to check for duplicates within them diff --git a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/DuplicateElementStrategyTest.java b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/DuplicateElementStrategyTest.java index 602cc0626958..4ae2d795d93a 100644 --- a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/DuplicateElementStrategyTest.java +++ b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/DuplicateElementStrategyTest.java @@ -457,6 +457,296 @@ void shouldNotRemoveModulesInList() { } } + @Nested + @DisplayName("Plugin Configuration Skipping") + class PluginConfigurationSkippingTests { + + @Test + @DisplayName("should not remove multiple arg elements inside compilerArgs in configuration") + void shouldNotRemoveCompilerArgsInConfiguration() { + String pomXml = """ + + + 4.0.0 + test + test + 1.0.0 + + + + org.apache.maven.plugins + maven-compiler-plugin + + + -Xlint:all + -Xlint:-processing + -Xmaxwarns + 5 + + + + + + + """; + + Document document = Document.of(pomXml); + Map pomMap = Map.of(Paths.get("pom.xml"), document); + + UpgradeContext context = createMockContext(); + UpgradeResult result = strategy.doApply(context, pomMap); + + assertTrue(result.success(), "Strategy should succeed"); + assertEquals(0, result.modifiedCount(), "Should not have modified any POM"); + + String xml = DomUtils.toXml(document); + assertTrue(xml.contains("-Xlint:all"), "Should keep -Xlint:all arg"); + assertTrue(xml.contains("-Xlint:-processing"), "Should keep -Xlint:-processing arg"); + assertTrue(xml.contains("-Xmaxwarns"), "Should keep -Xmaxwarns arg"); + assertTrue(xml.contains("5"), "Should keep 5 arg"); + } + + @Test + @DisplayName("should not remove elements inside execution configuration") + void shouldNotRemoveElementsInsideExecutionConfiguration() { + String pomXml = """ + + + 4.0.0 + test + test + 1.0.0 + + + + org.codehaus.mojo + exec-maven-plugin + + + + + -classpath + ${project.build.outputDirectory} + com.example.Main + + + + + + + + + """; + + Document document = Document.of(pomXml); + Map pomMap = Map.of(Paths.get("pom.xml"), document); + + UpgradeContext context = createMockContext(); + UpgradeResult result = strategy.doApply(context, pomMap); + + assertTrue(result.success(), "Strategy should succeed"); + assertEquals(0, result.modifiedCount(), "Should not have modified any POM"); + + String xml = DomUtils.toXml(document); + // Count argument occurrences + int count = xml.split("", -1).length - 1; + assertEquals(3, count, "Should still have 3 argument elements"); + } + + @Test + @DisplayName("should not remove checkstyle module elements inside configuration") + void shouldNotRemoveCheckstyleModulesInConfiguration() { + String pomXml = """ + + + 4.0.0 + test + test + 1.0.0 + + + + maven-checkstyle-plugin + + + + + + + + + + + + + + + + + + """; + + Document document = Document.of(pomXml); + Map pomMap = Map.of(Paths.get("pom.xml"), document); + + UpgradeContext context = createMockContext(); + UpgradeResult result = strategy.doApply(context, pomMap); + + assertTrue(result.success(), "Strategy should succeed"); + assertEquals(0, result.modifiedCount(), "Should not have modified any POM"); + + String xml = DomUtils.toXml(document); + // Count module occurrences (Checker, FileTabCharacter, TreeWalker, AvoidStarImport, NeedBraces = 5) + int moduleCount = xml.split(" + + 4.0.0 + test + old-name + new-name + 1.0.0 + + + + maven-compiler-plugin + + + -Xlint:all + -Xmaxwarns + 5 + + + + + + + """; + + Document document = Document.of(pomXml); + Map pomMap = Map.of(Paths.get("pom.xml"), document); + + UpgradeContext context = createMockContext(); + UpgradeResult result = strategy.doApply(context, pomMap); + + assertTrue(result.success(), "Strategy should succeed"); + assertEquals(1, result.modifiedCount(), "Should have modified 1 POM"); + + String xml = DomUtils.toXml(document); + // POM-level duplicate should be removed + assertTrue(xml.contains("new-name"), "Should keep last artifactId"); + assertFalse(xml.contains("old-name"), "Should remove first artifactId"); + // Configuration args should be preserved + assertTrue(xml.contains("-Xlint:all"), "Should keep -Xlint:all arg"); + assertTrue(xml.contains("-Xmaxwarns"), "Should keep -Xmaxwarns arg"); + assertTrue(xml.contains("5"), "Should keep 5 arg"); + } + + @Test + @DisplayName("should not remove exclude elements inside inputExcludes in configuration") + void shouldNotRemoveExcludesInConfiguration() { + String pomXml = """ + + + 4.0.0 + test + test + 1.0.0 + + + + apache-rat-plugin + + + .github/** + src/main/antlr4/Abnf.g4 + + + + + + + """; + + Document document = Document.of(pomXml); + Map pomMap = Map.of(Paths.get("pom.xml"), document); + + UpgradeContext context = createMockContext(); + UpgradeResult result = strategy.doApply(context, pomMap); + + assertTrue(result.success(), "Strategy should succeed"); + assertEquals(0, result.modifiedCount(), "Should not have modified any POM"); + + String xml = DomUtils.toXml(document); + assertTrue(xml.contains(".github/**"), "Should keep first exclude"); + assertTrue(xml.contains("src/main/antlr4/Abnf.g4"), "Should keep second exclude"); + } + + @Test + @DisplayName("should not remove pluginExecution elements inside lifecycleMappingMetadata in configuration") + void shouldNotRemovePluginExecutionsInConfiguration() { + String pomXml = """ + + + 4.0.0 + test + test + 1.0.0 + + + + + org.eclipse.m2e + lifecycle-mapping + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + + + org.codehaus.mojo + exec-maven-plugin + + + + + + + + + + + """; + + Document document = Document.of(pomXml); + Map pomMap = Map.of(Paths.get("pom.xml"), document); + + UpgradeContext context = createMockContext(); + UpgradeResult result = strategy.doApply(context, pomMap); + + assertTrue(result.success(), "Strategy should succeed"); + assertEquals(0, result.modifiedCount(), "Should not have modified any POM"); + + String xml = DomUtils.toXml(document); + int count = xml.split("", -1).length - 1; + assertEquals(2, count, "Should still have 2 pluginExecution elements"); + } + } + @Nested @DisplayName("Strategy Description") class StrategyDescriptionTests {