From 5588879fbbc1f7b06ac75c6acedda4d46604a2d9 Mon Sep 17 00:00:00 2001
From: ptreilhes
Date: Wed, 24 Sep 2025 03:10:09 +0200
Subject: [PATCH 01/16] Fix for #408 ensure nested exclusion aren't overrided
---
.../codehaus/mojo/flatten/FlattenMojo.java | 23 ---
.../FlattenMojoNestedExclusionTest.java | 136 ++++++++++++++++++
src/test/resources/nested-exclusion/pom.xml | 34 +++++
.../flatten-maven-plugin/a/0.0.1/a-0.0.1.pom | 22 +++
.../flatten-maven-plugin/b/0.0.1/b-0.0.1.pom | 16 +++
.../flatten-maven-plugin/c/0.0.1/c-0.0.1.pom | 7 +
6 files changed, 215 insertions(+), 23 deletions(-)
create mode 100644 src/test/java/org/codehaus/mojo/flatten/FlattenMojoNestedExclusionTest.java
create mode 100644 src/test/resources/nested-exclusion/pom.xml
create mode 100644 src/test/resources/nested-exclusion/repository/flatten-maven-plugin/a/0.0.1/a-0.0.1.pom
create mode 100644 src/test/resources/nested-exclusion/repository/flatten-maven-plugin/b/0.0.1/b-0.0.1.pom
create mode 100644 src/test/resources/nested-exclusion/repository/flatten-maven-plugin/c/0.0.1/c-0.0.1.pom
diff --git a/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java b/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java
index 66724b45..237cfee4 100644
--- a/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java
+++ b/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java
@@ -1,24 +1,5 @@
package org.codehaus.mojo.flatten;
-/*
- * 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.
- */
-
import javax.inject.Inject;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
@@ -1123,10 +1104,6 @@ private void createFlattenedDependenciesAll(
dependency, session.getRepositorySession().getArtifactTypeRegistry()));
}
- for (Artifact artifact : project.getArtifacts()) {
- collectRequest.addDependency(RepositoryUtils.toDependency(artifact, null));
- }
-
for (Dependency dependency : managedDependencies) {
collectRequest.addManagedDependency(RepositoryUtils.toDependency(
dependency, session.getRepositorySession().getArtifactTypeRegistry()));
diff --git a/src/test/java/org/codehaus/mojo/flatten/FlattenMojoNestedExclusionTest.java b/src/test/java/org/codehaus/mojo/flatten/FlattenMojoNestedExclusionTest.java
new file mode 100644
index 00000000..1f6440c9
--- /dev/null
+++ b/src/test/java/org/codehaus/mojo/flatten/FlattenMojoNestedExclusionTest.java
@@ -0,0 +1,136 @@
+package org.codehaus.mojo.flatten;
+
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.apache.maven.execution.DefaultMavenExecutionRequest;
+import org.apache.maven.execution.DefaultMavenExecutionResult;
+import org.apache.maven.execution.MavenExecutionRequest;
+import org.apache.maven.execution.MavenExecutionResult;
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.plugin.testing.MojoRule;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.project.ProjectBuilder;
+import org.apache.maven.project.ProjectBuildingException;
+import org.apache.maven.project.ProjectBuildingRequest;
+import org.apache.maven.project.ProjectBuildingResult;
+import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
+import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
+import org.eclipse.aether.DefaultRepositorySystemSession;
+import org.eclipse.aether.internal.impl.SimpleLocalRepositoryManagerFactory;
+import org.eclipse.aether.repository.LocalRepository;
+import org.eclipse.aether.repository.LocalRepositoryManager;
+import org.eclipse.aether.repository.NoLocalRepositoryManagerException;
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+
+/**
+ * Test-Case for {@link FlattenMojo}.
+ *
+ * @author treilhes
+ */
+public class FlattenMojoNestedExclusionTest {
+
+ private static final String PATH = "src/test/resources/nested-exclusion/";
+ private static final String POM = PATH + "pom.xml";
+ private static final String FLATTENED_POM = PATH + ".flattened-pom.xml";
+
+ private static final String REPO_PATH = "src/test/resources/nested-exclusion/repository/";
+
+ @Rule
+ public MojoRule rule = new MojoRule();
+
+ /**
+ * Test method to check that nested exclusion are correctly honored.
+ * Three levels of dependencies are defined for this test:
+ *
+ *
A depends on B with B excluding C
+ *
B depends on C
+ *
+ *
+ * The tested pom depends on A only. It is then expected that C is not present in the flattened pom.
+ * Only A and B should be present.
+ *
+ * 1.3.0 of the plugin was handling this case correctly but since 1.4.0 it is not the case anymore.
+ * @see Issue #408
+ *
+ * @since 1.7.2+
+ * @throws Exception if something goes wrong.
+ */
+ @Test
+ public void testNestedExclusionAreEnforced() throws Exception {
+
+ MavenSession session = newMavenSession(REPO_PATH);
+ MavenProject project = loadResolvedProject(session, new File(POM));
+ FlattenMojo flattenMojo = (FlattenMojo) rule.lookupConfiguredMojo(project, "flatten");
+ rule.setVariableValueToObject(flattenMojo, "session", session);
+
+ flattenMojo.execute();
+
+ MavenProject flattenedProject = loadResolvedProject(session, new File(FLATTENED_POM));
+
+ flattenedProject.getDependencies().stream()
+ .filter(dep -> dep.getArtifactId().equals("c"))
+ .findAny()
+ .ifPresent(dep -> fail(
+ "As B dependency in A is excluding C, no C dependency should be present in flattened POM."));
+ }
+
+ /**
+ * Load a maven project with resolved dependencies (same as standard maven execution).
+ * By default dependencies are not resolved by MojoRule and project.getArtifacts is empty
+ * @param session
+ * @param pomFile
+ * @return
+ * @throws ComponentLookupException
+ * @throws ProjectBuildingException
+ */
+ private MavenProject loadResolvedProject(MavenSession session, File pomFile)
+ throws ComponentLookupException, ProjectBuildingException {
+ ProjectBuilder projectBuilder = rule.lookup(ProjectBuilder.class);
+
+ ProjectBuildingRequest buildingRequest = session.getProjectBuildingRequest();
+ buildingRequest.setResolveDependencies(true);
+
+ ProjectBuildingResult result = projectBuilder.build(pomFile, buildingRequest);
+ return result.getProject();
+ }
+
+ /**
+ * Create a new maven session with a local repository at the given path.
+ * @param repoPath
+ * @return
+ * @throws NoLocalRepositoryManagerException
+ */
+ protected MavenSession newMavenSession(String repoPath) throws NoLocalRepositoryManagerException {
+ MavenExecutionRequest request = new DefaultMavenExecutionRequest();
+ MavenExecutionResult result = new DefaultMavenExecutionResult();
+
+ MavenSession session =
+ new MavenSession(rule.getContainer(), MavenRepositorySystemUtils.newSession(), request, result);
+ LocalRepository testRepo = new LocalRepository(repoPath);
+ LocalRepositoryManager lrm =
+ new SimpleLocalRepositoryManagerFactory().newInstance(session.getRepositorySession(), testRepo);
+
+ ((DefaultRepositorySystemSession) session.getRepositorySession()).setLocalRepositoryManager(lrm);
+ return session;
+ }
+
+ /**
+ * After test method. Removes flattened-pom.xml file which is created during test.
+ *
+ * @throws IOException if can't remove file.
+ */
+ @After
+ public void removeFlattenedPom() throws IOException {
+ File flattenedPom = new File(FLATTENED_POM);
+ if (flattenedPom.exists()) {
+ if (!flattenedPom.delete()) {
+ throw new IOException("Can't delete " + flattenedPom);
+ }
+ }
+ }
+}
diff --git a/src/test/resources/nested-exclusion/pom.xml b/src/test/resources/nested-exclusion/pom.xml
new file mode 100644
index 00000000..d6e2d1b1
--- /dev/null
+++ b/src/test/resources/nested-exclusion/pom.xml
@@ -0,0 +1,34 @@
+
+ 4.0.0
+ org.codehaus.mojo.flatten.its
+ nested-exclusion
+ ${revision}
+
+
+ 1.2.3.4
+
+
+
+
+ flatten-maven-plugin
+ a
+ 0.0.1
+
+
+
+
+ verify
+
+
+ org.codehaus.mojo
+ flatten-maven-plugin
+
+ bom
+ all
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/resources/nested-exclusion/repository/flatten-maven-plugin/a/0.0.1/a-0.0.1.pom b/src/test/resources/nested-exclusion/repository/flatten-maven-plugin/a/0.0.1/a-0.0.1.pom
new file mode 100644
index 00000000..fdce45b4
--- /dev/null
+++ b/src/test/resources/nested-exclusion/repository/flatten-maven-plugin/a/0.0.1/a-0.0.1.pom
@@ -0,0 +1,22 @@
+
+ 4.0.0
+ flatten-maven-plugin
+ a
+ 0.0.1
+
+
+
+ flatten-maven-plugin
+ b
+ 0.0.1
+
+
+ flatten-maven-plugin
+ c
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/resources/nested-exclusion/repository/flatten-maven-plugin/b/0.0.1/b-0.0.1.pom b/src/test/resources/nested-exclusion/repository/flatten-maven-plugin/b/0.0.1/b-0.0.1.pom
new file mode 100644
index 00000000..5f6e1606
--- /dev/null
+++ b/src/test/resources/nested-exclusion/repository/flatten-maven-plugin/b/0.0.1/b-0.0.1.pom
@@ -0,0 +1,16 @@
+
+ 4.0.0
+ flatten-maven-plugin
+ b
+ 0.0.1
+
+
+
+ flatten-maven-plugin
+ c
+ 0.0.1
+
+
+
\ No newline at end of file
diff --git a/src/test/resources/nested-exclusion/repository/flatten-maven-plugin/c/0.0.1/c-0.0.1.pom b/src/test/resources/nested-exclusion/repository/flatten-maven-plugin/c/0.0.1/c-0.0.1.pom
new file mode 100644
index 00000000..d78221ed
--- /dev/null
+++ b/src/test/resources/nested-exclusion/repository/flatten-maven-plugin/c/0.0.1/c-0.0.1.pom
@@ -0,0 +1,7 @@
+
+ 4.0.0
+ flatten-maven-plugin
+ c
+ 0.0.1
+
+
\ No newline at end of file
From 0cd0ddf05d3a8becccdec21c005abbb57b6b861e Mon Sep 17 00:00:00 2001
From: ptreilhes
Date: Wed, 24 Sep 2025 03:40:12 +0200
Subject: [PATCH 02/16] Add mistakenly removed header
---
.../codehaus/mojo/flatten/FlattenMojo.java | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java b/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java
index 237cfee4..6fc2a3b1 100644
--- a/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java
+++ b/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java
@@ -1,5 +1,24 @@
package org.codehaus.mojo.flatten;
+/*
+ * 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.
+ */
+
import javax.inject.Inject;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
From d4c9ff40d38907c05dcedfaedee5e494d5ab0d85 Mon Sep 17 00:00:00 2001
From: ptreilhes
Date: Wed, 24 Sep 2025 08:07:29 +0200
Subject: [PATCH 03/16] appeasing spotless with static imports a the bottom
---
.../codehaus/mojo/flatten/FlattenMojoNestedExclusionTest.java | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/test/java/org/codehaus/mojo/flatten/FlattenMojoNestedExclusionTest.java b/src/test/java/org/codehaus/mojo/flatten/FlattenMojoNestedExclusionTest.java
index 1f6440c9..d14ac210 100644
--- a/src/test/java/org/codehaus/mojo/flatten/FlattenMojoNestedExclusionTest.java
+++ b/src/test/java/org/codehaus/mojo/flatten/FlattenMojoNestedExclusionTest.java
@@ -1,7 +1,5 @@
package org.codehaus.mojo.flatten;
-import static org.junit.Assert.fail;
-
import java.io.File;
import java.io.IOException;
@@ -27,6 +25,8 @@
import org.junit.Rule;
import org.junit.Test;
+import static org.junit.Assert.fail;
+
/**
* Test-Case for {@link FlattenMojo}.
*
From 412751d2597bbd797d83142c44661a3db9627eee Mon Sep 17 00:00:00 2001
From: ptreilhes
Date: Mon, 6 Oct 2025 03:08:14 +0200
Subject: [PATCH 04/16] restored providing project dependencies to collect
request
but only direct ones
---
.../codehaus/mojo/flatten/FlattenMojo.java | 5 +++++
.../FlattenMojoNestedExclusionTest.java | 20 +++++++++----------
2 files changed, 14 insertions(+), 11 deletions(-)
diff --git a/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java b/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java
index 6fc2a3b1..1670be38 100644
--- a/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java
+++ b/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java
@@ -1123,6 +1123,11 @@ private void createFlattenedDependenciesAll(
dependency, session.getRepositorySession().getArtifactTypeRegistry()));
}
+ for (Dependency dependency : project.getDependencies()) {
+ collectRequest.addDependency(RepositoryUtils.toDependency(
+ dependency, session.getRepositorySession().getArtifactTypeRegistry()));
+ }
+
for (Dependency dependency : managedDependencies) {
collectRequest.addManagedDependency(RepositoryUtils.toDependency(
dependency, session.getRepositorySession().getArtifactTypeRegistry()));
diff --git a/src/test/java/org/codehaus/mojo/flatten/FlattenMojoNestedExclusionTest.java b/src/test/java/org/codehaus/mojo/flatten/FlattenMojoNestedExclusionTest.java
index d14ac210..39bc6945 100644
--- a/src/test/java/org/codehaus/mojo/flatten/FlattenMojoNestedExclusionTest.java
+++ b/src/test/java/org/codehaus/mojo/flatten/FlattenMojoNestedExclusionTest.java
@@ -29,8 +29,6 @@
/**
* Test-Case for {@link FlattenMojo}.
- *
- * @author treilhes
*/
public class FlattenMojoNestedExclusionTest {
@@ -47,7 +45,7 @@ public class FlattenMojoNestedExclusionTest {
* Test method to check that nested exclusion are correctly honored.
* Three levels of dependencies are defined for this test:
*
- *
A depends on B with B excluding C
+ *
A depends on B with A excluding C trough B
*
B depends on C
*
*
@@ -82,11 +80,11 @@ public void testNestedExclusionAreEnforced() throws Exception {
/**
* Load a maven project with resolved dependencies (same as standard maven execution).
* By default dependencies are not resolved by MojoRule and project.getArtifacts is empty
- * @param session
- * @param pomFile
- * @return
- * @throws ComponentLookupException
- * @throws ProjectBuildingException
+ * @param session the Maven session to use for building the project
+ * @param pomFile the POM file to load and resolve dependencies for
+ * @return the resolved MavenProject instance with dependencies
+ * @throws ComponentLookupException if the ProjectBuilder component cannot be found
+ * @throws ProjectBuildingException if an error occurs while building the project
*/
private MavenProject loadResolvedProject(MavenSession session, File pomFile)
throws ComponentLookupException, ProjectBuildingException {
@@ -101,9 +99,9 @@ private MavenProject loadResolvedProject(MavenSession session, File pomFile)
/**
* Create a new maven session with a local repository at the given path.
- * @param repoPath
- * @return
- * @throws NoLocalRepositoryManagerException
+ * @param repoPath the path to the local repository to use for the session
+ * @return a new MavenSession instance configured with the specified local repository
+ * @throws NoLocalRepositoryManagerException if the local repository manager cannot be created
*/
protected MavenSession newMavenSession(String repoPath) throws NoLocalRepositoryManagerException {
MavenExecutionRequest request = new DefaultMavenExecutionRequest();
From d3b0527ec843276376665b7ae10b17fba1ce9a8c Mon Sep 17 00:00:00 2001
From: ptreilhes
Date: Tue, 28 Oct 2025 02:46:00 +0100
Subject: [PATCH 05/16] ensure conflict are resolved successfully by discarding
looser deps
---
.../codehaus/mojo/flatten/FlattenMojo.java | 6 +
.../FlattenMojoConflictWinnerTest.java | 143 ++++++++++++++++++
src/test/resources/conflict-winner/pom.xml | 39 +++++
.../flatten-maven-plugin/a/0.0.1/a-0.0.1.pom | 16 ++
.../flatten-maven-plugin/b/0.0.1/b-0.0.1.pom | 9 ++
.../flatten-maven-plugin/b/0.0.2/b-0.0.2.pom | 9 ++
6 files changed, 222 insertions(+)
create mode 100644 src/test/java/org/codehaus/mojo/flatten/FlattenMojoConflictWinnerTest.java
create mode 100644 src/test/resources/conflict-winner/pom.xml
create mode 100644 src/test/resources/conflict-winner/repository/flatten-maven-plugin/a/0.0.1/a-0.0.1.pom
create mode 100644 src/test/resources/conflict-winner/repository/flatten-maven-plugin/b/0.0.1/b-0.0.1.pom
create mode 100644 src/test/resources/conflict-winner/repository/flatten-maven-plugin/b/0.0.2/b-0.0.2.pom
diff --git a/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java b/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java
index 1670be38..93b22f16 100644
--- a/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java
+++ b/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java
@@ -1151,6 +1151,12 @@ public boolean visitEnter(DependencyNode node) {
if (root == node) {
return true;
}
+
+ if (node.getData().containsKey(ConflictResolver.NODE_DATA_WINNER)) {
+ // if this node has a conflict winner in data, it means this node lost in conflict
+ return false; // skip lost conflicts
+ }
+
if (JavaScopes.PROVIDED.equals(node.getDependency().getScope())) {
String dependencyKey = getKey(node.getDependency());
if (!directDependencyKeys.contains(dependencyKey)) {
diff --git a/src/test/java/org/codehaus/mojo/flatten/FlattenMojoConflictWinnerTest.java b/src/test/java/org/codehaus/mojo/flatten/FlattenMojoConflictWinnerTest.java
new file mode 100644
index 00000000..9a8d6fb9
--- /dev/null
+++ b/src/test/java/org/codehaus/mojo/flatten/FlattenMojoConflictWinnerTest.java
@@ -0,0 +1,143 @@
+package org.codehaus.mojo.flatten;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.apache.maven.execution.DefaultMavenExecutionRequest;
+import org.apache.maven.execution.DefaultMavenExecutionResult;
+import org.apache.maven.execution.MavenExecutionRequest;
+import org.apache.maven.execution.MavenExecutionResult;
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.plugin.testing.MojoRule;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.project.ProjectBuilder;
+import org.apache.maven.project.ProjectBuildingException;
+import org.apache.maven.project.ProjectBuildingRequest;
+import org.apache.maven.project.ProjectBuildingResult;
+import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
+import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
+import org.eclipse.aether.DefaultRepositorySystemSession;
+import org.eclipse.aether.internal.impl.SimpleLocalRepositoryManagerFactory;
+import org.eclipse.aether.repository.LocalRepository;
+import org.eclipse.aether.repository.LocalRepositoryManager;
+import org.eclipse.aether.repository.NoLocalRepositoryManagerException;
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+
+import static org.junit.Assert.fail;
+
+/**
+ * Test-Case for {@link FlattenMojo}.
+ */
+public class FlattenMojoConflictWinnerTest {
+
+ private static final String PATH = "src/test/resources/conflict-winner/";
+ private static final String POM = PATH + "pom.xml";
+ private static final String FLATTENED_POM = PATH + ".flattened-pom.xml";
+
+ private static final String REPO_PATH = "src/test/resources/conflict-winner/repository/";
+
+ @Rule
+ public MojoRule rule = new MojoRule();
+
+ /**
+ * Test method to check that version conflict are correctly honored. Two levels
+ * of dependencies are defined for this test:
+ *
+ *
A depends on B 0.0.1
+ *
B 0.0.2
+ *
+ *
+ * The tested pom depends on A and B. It is then expected that B use version
+ * 0.0.2 in the flattened pom because B 0.0.2 is closer to the root.
+ *
+ * 1.7.3 of the plugin was handling this case correctly but since solving #408
+ * it is not the case anymore. By removing resolved transitive dependencies from
+ * the collect request parameters, the conflict resolution is not applied anymore
+ * as resolved transitive dependencies does not appear first anymore. This test ensure
+ * that the conflicting dependencies are correctly filtered from collect
+ * results and the winner version is kept.
+ *
+ * @see Issue
+ * #408
+ *
+ * @since 1.7.3+
+ * @throws Exception if something goes wrong.
+ */
+ @Test
+ public void testNestedExclusionAreEnforced() throws Exception {
+
+ MavenSession session = newMavenSession(REPO_PATH);
+ MavenProject project = loadResolvedProject(session, new File(POM));
+ FlattenMojo flattenMojo = (FlattenMojo) rule.lookupConfiguredMojo(project, "flatten");
+ rule.setVariableValueToObject(flattenMojo, "session", session);
+
+ flattenMojo.execute();
+
+ MavenProject flattenedProject = loadResolvedProject(session, new File(FLATTENED_POM));
+
+ flattenedProject.getDependencies().stream()
+ .filter(dep -> dep.getArtifactId().equals("b"))
+ .filter(dep -> dep.getVersion().equals("0.0.1"))
+ .findAny()
+ .ifPresent(dep -> fail(
+ "B dependency version 0.0.2 must win the conflicting version match in flattened POM."));
+ }
+
+ /**
+ * Load a maven project with resolved dependencies (same as standard maven execution).
+ * By default dependencies are not resolved by MojoRule and project.getArtifacts is empty
+ * @param session the Maven session to use for building the project
+ * @param pomFile the POM file to load and resolve dependencies for
+ * @return the resolved MavenProject instance with dependencies
+ * @throws ComponentLookupException if the ProjectBuilder component cannot be found
+ * @throws ProjectBuildingException if an error occurs while building the project
+ */
+ private MavenProject loadResolvedProject(MavenSession session, File pomFile)
+ throws ComponentLookupException, ProjectBuildingException {
+ ProjectBuilder projectBuilder = rule.lookup(ProjectBuilder.class);
+
+ ProjectBuildingRequest buildingRequest = session.getProjectBuildingRequest();
+ buildingRequest.setResolveDependencies(true);
+
+ ProjectBuildingResult result = projectBuilder.build(pomFile, buildingRequest);
+ return result.getProject();
+ }
+
+ /**
+ * Create a new maven session with a local repository at the given path.
+ * @param repoPath the path to the local repository to use for the session
+ * @return a new MavenSession instance configured with the specified local repository
+ * @throws NoLocalRepositoryManagerException if the local repository manager cannot be created
+ */
+ protected MavenSession newMavenSession(String repoPath) throws NoLocalRepositoryManagerException {
+ MavenExecutionRequest request = new DefaultMavenExecutionRequest();
+ MavenExecutionResult result = new DefaultMavenExecutionResult();
+
+ MavenSession session =
+ new MavenSession(rule.getContainer(), MavenRepositorySystemUtils.newSession(), request, result);
+ LocalRepository testRepo = new LocalRepository(repoPath);
+ LocalRepositoryManager lrm =
+ new SimpleLocalRepositoryManagerFactory().newInstance(session.getRepositorySession(), testRepo);
+
+ ((DefaultRepositorySystemSession) session.getRepositorySession()).setLocalRepositoryManager(lrm);
+ return session;
+ }
+
+ /**
+ * After test method. Removes flattened-pom.xml file which is created during test.
+ *
+ * @throws IOException if can't remove file.
+ */
+ @After
+ public void removeFlattenedPom() throws IOException {
+ File flattenedPom = new File(FLATTENED_POM);
+ if (flattenedPom.exists()) {
+ if (!flattenedPom.delete()) {
+ throw new IOException("Can't delete " + flattenedPom);
+ }
+ }
+ }
+}
diff --git a/src/test/resources/conflict-winner/pom.xml b/src/test/resources/conflict-winner/pom.xml
new file mode 100644
index 00000000..f4265c10
--- /dev/null
+++ b/src/test/resources/conflict-winner/pom.xml
@@ -0,0 +1,39 @@
+
+ 4.0.0
+ org.codehaus.mojo.flatten.its
+ conflict-winner
+ ${revision}
+
+
+ 1.2.3.4
+
+
+
+
+ flatten-maven-plugin
+ a
+ 0.0.1
+
+
+ flatten-maven-plugin
+ b
+ 0.0.2
+
+
+
+
+ verify
+
+
+ org.codehaus.mojo
+ flatten-maven-plugin
+
+ bom
+ all
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/resources/conflict-winner/repository/flatten-maven-plugin/a/0.0.1/a-0.0.1.pom b/src/test/resources/conflict-winner/repository/flatten-maven-plugin/a/0.0.1/a-0.0.1.pom
new file mode 100644
index 00000000..0a8f418a
--- /dev/null
+++ b/src/test/resources/conflict-winner/repository/flatten-maven-plugin/a/0.0.1/a-0.0.1.pom
@@ -0,0 +1,16 @@
+
+ 4.0.0
+ flatten-maven-plugin
+ a
+ 0.0.1
+
+
+
+ flatten-maven-plugin
+ b
+ 0.0.1
+
+
+
\ No newline at end of file
diff --git a/src/test/resources/conflict-winner/repository/flatten-maven-plugin/b/0.0.1/b-0.0.1.pom b/src/test/resources/conflict-winner/repository/flatten-maven-plugin/b/0.0.1/b-0.0.1.pom
new file mode 100644
index 00000000..39c88f8e
--- /dev/null
+++ b/src/test/resources/conflict-winner/repository/flatten-maven-plugin/b/0.0.1/b-0.0.1.pom
@@ -0,0 +1,9 @@
+
+ 4.0.0
+ flatten-maven-plugin
+ b
+ 0.0.1
+
+
\ No newline at end of file
diff --git a/src/test/resources/conflict-winner/repository/flatten-maven-plugin/b/0.0.2/b-0.0.2.pom b/src/test/resources/conflict-winner/repository/flatten-maven-plugin/b/0.0.2/b-0.0.2.pom
new file mode 100644
index 00000000..e70943aa
--- /dev/null
+++ b/src/test/resources/conflict-winner/repository/flatten-maven-plugin/b/0.0.2/b-0.0.2.pom
@@ -0,0 +1,9 @@
+
+ 4.0.0
+ flatten-maven-plugin
+ b
+ 0.0.2
+
+
\ No newline at end of file
From 90643f18dc260fe58089c91423a4719216227e25 Mon Sep 17 00:00:00 2001
From: ptreilhes
Date: Tue, 28 Oct 2025 02:59:37 +0100
Subject: [PATCH 06/16] fix spotless issue
---
.../mojo/flatten/FlattenMojoConflictWinnerTest.java | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/test/java/org/codehaus/mojo/flatten/FlattenMojoConflictWinnerTest.java b/src/test/java/org/codehaus/mojo/flatten/FlattenMojoConflictWinnerTest.java
index 9a8d6fb9..e473303a 100644
--- a/src/test/java/org/codehaus/mojo/flatten/FlattenMojoConflictWinnerTest.java
+++ b/src/test/java/org/codehaus/mojo/flatten/FlattenMojoConflictWinnerTest.java
@@ -58,7 +58,7 @@ public class FlattenMojoConflictWinnerTest {
* as resolved transitive dependencies does not appear first anymore. This test ensure
* that the conflicting dependencies are correctly filtered from collect
* results and the winner version is kept.
- *
+ *
* @see Issue
* #408
@@ -82,8 +82,8 @@ public void testNestedExclusionAreEnforced() throws Exception {
.filter(dep -> dep.getArtifactId().equals("b"))
.filter(dep -> dep.getVersion().equals("0.0.1"))
.findAny()
- .ifPresent(dep -> fail(
- "B dependency version 0.0.2 must win the conflicting version match in flattened POM."));
+ .ifPresent(dep ->
+ fail("B dependency version 0.0.2 must win the conflicting version match in flattened POM."));
}
/**
From 5b9a7e2dacdf5a8f84be94f74470ab52eda12c02 Mon Sep 17 00:00:00 2001
From: ptreilhes
Date: Tue, 28 Oct 2025 13:59:28 +0100
Subject: [PATCH 07/16] test dependencies musn't override application
dependencies
---
.../codehaus/mojo/flatten/FlattenMojo.java | 92 ++++++++++++++-----
1 file changed, 68 insertions(+), 24 deletions(-)
diff --git a/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java b/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java
index 93b22f16..4c12464b 100644
--- a/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java
+++ b/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java
@@ -1,24 +1,5 @@
package org.codehaus.mojo.flatten;
-/*
- * 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.
- */
-
import javax.inject.Inject;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
@@ -34,6 +15,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
@@ -84,20 +66,24 @@
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
import org.eclipse.aether.DefaultRepositorySystemSession;
+import org.eclipse.aether.RepositoryException;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RequestTrace;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.collection.CollectRequest;
import org.eclipse.aether.collection.CollectResult;
-import org.eclipse.aether.collection.DependencyCollectionException;
import org.eclipse.aether.graph.DependencyNode;
import org.eclipse.aether.graph.DependencyVisitor;
-import org.eclipse.aether.resolution.ArtifactDescriptorException;
+import org.eclipse.aether.internal.impl.collect.DefaultDependencyGraphTransformationContext;
import org.eclipse.aether.resolution.ArtifactDescriptorRequest;
import org.eclipse.aether.resolution.ArtifactDescriptorResult;
import org.eclipse.aether.util.artifact.JavaScopes;
import org.eclipse.aether.util.graph.manager.DependencyManagerUtils;
import org.eclipse.aether.util.graph.transformer.ConflictResolver;
+import org.eclipse.aether.util.graph.transformer.JavaScopeDeriver;
+import org.eclipse.aether.util.graph.transformer.JavaScopeSelector;
+import org.eclipse.aether.util.graph.transformer.NearestVersionSelector;
+import org.eclipse.aether.util.graph.transformer.SimpleOptionalitySelector;
import org.xml.sax.Attributes;
import org.xml.sax.ext.DefaultHandler2;
@@ -1103,14 +1089,13 @@ private void createFlattenedDependenciesDirect(
*
* @param projectDependencies is the effective POM {@link Model}'s current dependencies
* @param flattenedDependencies is the {@link List} where to add the collected {@link Dependency dependencies}.
- * @throws DependencyCollectionException
- * @throws ArtifactDescriptorException
+ * @throws RepositoryException throws on conflict
*/
private void createFlattenedDependenciesAll(
List projectDependencies,
List managedDependencies,
List flattenedDependencies)
- throws ArtifactDescriptorException, DependencyCollectionException {
+ throws RepositoryException {
final Queue dependencyNodeLinkedList = new LinkedList<>();
final Set processedDependencies = new HashSet<>();
final Artifact projectArtifact = this.project.getArtifact();
@@ -1140,6 +1125,10 @@ private void createFlattenedDependenciesAll(
CollectResult collectResult = repositorySystem.collectDependencies(derived, collectRequest);
final DependencyNode root = collectResult.getRoot();
+
+ removeTestDependencies(root);
+ resolveConflicts(derived, root);
+
final Set directDependencyKeys = Stream.concat(
projectDependencies.stream().map(this::getKey),
project.getArtifacts().stream().map(this::getKey))
@@ -1330,6 +1319,61 @@ public boolean isUpdatePomFile() {
}
}
+ /**
+ * Recursively removes all test-scoped dependencies from the given dependency node.
+ *
+ * @param node the root dependency node
+ */
+ private void removeTestDependencies(DependencyNode node) {
+ Iterator it = node.getChildren().iterator();
+ while (it.hasNext()) {
+ DependencyNode child = it.next();
+ org.eclipse.aether.graph.Dependency dep = child.getDependency();
+ if (dep != null && "test".equals(dep.getScope())) {
+ it.remove();
+ } else {
+ removeTestDependencies(child);
+ }
+ }
+ }
+
+ /**
+ * Recursively clears previous conflict resolution data from the dependency tree.
+ *
+ * @param node the root dependency node
+ */
+ private void clearPreviousConflictResolution(DependencyNode node) {
+ Iterator it = node.getChildren().iterator();
+ while (it.hasNext()) {
+ DependencyNode child = it.next();
+ child.getData().clear();
+ clearPreviousConflictResolution(child);
+ }
+ }
+
+ /**
+ * Resolves version conflicts in the dependency tree rooted at the given node.
+ *
+ * @param derived the repository system session
+ * @param root the root dependency node
+ * @throws RepositoryException if an error occurs during conflict resolution
+ */
+ private void resolveConflicts(DefaultRepositorySystemSession derived, final DependencyNode root)
+ throws RepositoryException {
+ clearPreviousConflictResolution(root);
+
+ DefaultDependencyGraphTransformationContext transformationContext =
+ new DefaultDependencyGraphTransformationContext(derived);
+
+ ConflictResolver conflictResolver = new ConflictResolver(
+ new NearestVersionSelector(),
+ new JavaScopeSelector(),
+ new SimpleOptionalitySelector(),
+ new JavaScopeDeriver());
+
+ conflictResolver.transformGraph(root, transformationContext);
+ }
+
private class ModelsFactory {
private final File pomFile;
private Model effectivePom;
From bb202f683f161117351221b24868d4edef415d18 Mon Sep 17 00:00:00 2001
From: ptreilhes
Date: Tue, 28 Oct 2025 14:56:31 +0100
Subject: [PATCH 08/16] re-add again this facetious header that keep
disapearing
---
.../codehaus/mojo/flatten/FlattenMojo.java | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java b/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java
index 4c12464b..a78c1ec2 100644
--- a/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java
+++ b/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java
@@ -1,5 +1,24 @@
package org.codehaus.mojo.flatten;
+/*
+ * 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.
+ */
+
import javax.inject.Inject;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
From 11337cf9643a879236a0fa28abc0dbbf090ea9c4 Mon Sep 17 00:00:00 2001
From: ptreilhes
Date: Fri, 31 Oct 2025 18:47:08 +0100
Subject: [PATCH 09/16] Ensure compatibility with maven 3.6.3
---
.../codehaus/mojo/flatten/FlattenMojo.java | 59 +++++++++++++++----
.../FlattenMojoConflictWinnerTest.java | 21 ++++---
2 files changed, 60 insertions(+), 20 deletions(-)
diff --git a/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java b/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java
index a78c1ec2..b22e93cb 100644
--- a/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java
+++ b/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java
@@ -33,12 +33,14 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Properties;
import java.util.Queue;
import java.util.Set;
@@ -87,22 +89,20 @@
import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.RepositoryException;
import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.RequestTrace;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.collection.CollectRequest;
import org.eclipse.aether.collection.CollectResult;
+import org.eclipse.aether.collection.DependencyGraphTransformationContext;
+import org.eclipse.aether.collection.DependencyGraphTransformer;
import org.eclipse.aether.graph.DependencyNode;
import org.eclipse.aether.graph.DependencyVisitor;
-import org.eclipse.aether.internal.impl.collect.DefaultDependencyGraphTransformationContext;
import org.eclipse.aether.resolution.ArtifactDescriptorRequest;
import org.eclipse.aether.resolution.ArtifactDescriptorResult;
import org.eclipse.aether.util.artifact.JavaScopes;
import org.eclipse.aether.util.graph.manager.DependencyManagerUtils;
import org.eclipse.aether.util.graph.transformer.ConflictResolver;
-import org.eclipse.aether.util.graph.transformer.JavaScopeDeriver;
-import org.eclipse.aether.util.graph.transformer.JavaScopeSelector;
-import org.eclipse.aether.util.graph.transformer.NearestVersionSelector;
-import org.eclipse.aether.util.graph.transformer.SimpleOptionalitySelector;
import org.xml.sax.Attributes;
import org.xml.sax.ext.DefaultHandler2;
@@ -1377,20 +1377,15 @@ private void clearPreviousConflictResolution(DependencyNode node) {
* @param root the root dependency node
* @throws RepositoryException if an error occurs during conflict resolution
*/
- private void resolveConflicts(DefaultRepositorySystemSession derived, final DependencyNode root)
+ private void resolveConflicts(RepositorySystemSession derived, final DependencyNode root)
throws RepositoryException {
clearPreviousConflictResolution(root);
DefaultDependencyGraphTransformationContext transformationContext =
new DefaultDependencyGraphTransformationContext(derived);
- ConflictResolver conflictResolver = new ConflictResolver(
- new NearestVersionSelector(),
- new JavaScopeSelector(),
- new SimpleOptionalitySelector(),
- new JavaScopeDeriver());
-
- conflictResolver.transformGraph(root, transformationContext);
+ DependencyGraphTransformer transformer = derived.getDependencyGraphTransformer();
+ transformer.transformGraph(root, transformationContext);
}
private class ModelsFactory {
@@ -1538,4 +1533,42 @@ public void startElement(String uri, String localName, String qName, Attributes
this.rootTagSeen = true;
}
}
+
+ /**
+ * Default implementation of {@link DependencyGraphTransformationContext}.
+ * As maven libraries do not expose an implementation, we need to provide our own.
+ */
+ class DefaultDependencyGraphTransformationContext implements DependencyGraphTransformationContext {
+
+ private final RepositorySystemSession session;
+
+ private final Map