diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java index c27eb4dc0820..0946c76b36d8 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java @@ -1169,9 +1169,6 @@ private Model readEffectiveModel() throws ModelBuilderException { Model model = inheritanceAssembler.assembleModelInheritance(inputModel, parentModel, request, this); - // model normalization - model = modelNormalizer.mergeDuplicates(model, request, this); - // profile activation profileActivationContext.setModel(model); @@ -1186,6 +1183,9 @@ private Model readEffectiveModel() throws ModelBuilderException { Model resultModel = model; resultModel = interpolateModel(resultModel, request, this); + // model normalization + resultModel = modelNormalizer.mergeDuplicates(resultModel, request, this); + // url normalization resultModel = modelUrlNormalizer.normalize(resultModel, request); diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh2532DuplicateDependencyEffectiveModelTest.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh2532DuplicateDependencyEffectiveModelTest.java new file mode 100644 index 000000000000..8c54722aa50b --- /dev/null +++ b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh2532DuplicateDependencyEffectiveModelTest.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.maven.it; + +import java.io.File; + +import org.junit.jupiter.api.Test; + +/** + * This is a test set for GH-2532. + *
+ * The issue occurs when a project has duplicate dependencies in the effective model due to + * property placeholders in dependency coordinates. Before the fix, deduplication was performed + * before interpolation, causing dependencies like {@code scalatest_${scala.binary.version}} and + * {@code scalatest_2.13} to be seen as different dependencies. After interpolation, they become + * the same dependency, leading to a "duplicate dependency" error during the build. + *
+ * The fix moves the deduplication step to after interpolation, ensuring that dependencies with + * property placeholders are properly deduplicated after their values are resolved. + */ +class MavenITgh2532DuplicateDependencyEffectiveModelTest extends AbstractMavenIntegrationTestCase { + + MavenITgh2532DuplicateDependencyEffectiveModelTest() { + super("[4.0.0-rc-3,)"); + } + + /** + * Tests that a project with dependencies using property placeholders in artifact coordinates + * can be built successfully without "duplicate dependency" errors when the same dependency + * appears in multiple places in the effective model. + *
+ * This test reproduces the scenario where: + *
+ * The fix moves deduplication to after interpolation, ensuring proper deduplication.
+ */
+ @Test
+ void testDuplicateDependencyWithPropertyPlaceholders() throws Exception {
+ File testDir = extractResources("/gh-2532-duplicate-dependency-effective-model");
+
+ Verifier verifier = new Verifier(testDir.getAbsolutePath());
+ verifier.setLogFileName("testDuplicateDependencyWithPropertyPlaceholders.txt");
+ verifier.addCliArgument("package");
+ verifier.execute();
+
+ verifier.verifyErrorFreeLog();
+ }
+}
diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/TestSuiteOrdering.java b/its/core-it-suite/src/test/java/org/apache/maven/it/TestSuiteOrdering.java
index 91a50cf03b58..001d66b5a9ee 100644
--- a/its/core-it-suite/src/test/java/org/apache/maven/it/TestSuiteOrdering.java
+++ b/its/core-it-suite/src/test/java/org/apache/maven/it/TestSuiteOrdering.java
@@ -101,6 +101,7 @@ public TestSuiteOrdering() {
* the tests are to finishing. Newer tests are also more likely to fail, so this is
* a fail fast technique as well.
*/
+ suite.addTestSuite(MavenITgh2532DuplicateDependencyEffectiveModelTest.class);
suite.addTestSuite(MavenITmng8736ConcurrentFileActivationTest.class);
suite.addTestSuite(MavenITmng8744CIFriendlyTest.class);
suite.addTestSuite(MavenITmng8572DITypeHandlerTest.class);
diff --git a/its/core-it-suite/src/test/resources/gh-2532-duplicate-dependency-effective-model/module-a/pom.xml b/its/core-it-suite/src/test/resources/gh-2532-duplicate-dependency-effective-model/module-a/pom.xml
new file mode 100644
index 000000000000..7a4a3826133e
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/gh-2532-duplicate-dependency-effective-model/module-a/pom.xml
@@ -0,0 +1,24 @@
+
+