diff --git a/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java b/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java index bd44e9a6..410f4156 100644 --- a/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java +++ b/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java @@ -19,6 +19,7 @@ * under the License. */ +import java.util.LinkedHashSet; import java.util.Queue; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.factory.ArtifactFactory; @@ -1070,11 +1071,11 @@ private void createFlattenedDependenciesDirect( List projectDependen * The collected dependencies are stored in order, so that the leaf dependencies are prioritized in front of direct dependencies. * In addition, every non-leaf dependencies will exclude its own direct dependency, since all transitive dependencies * will be collected. - * + * * Transitive dependencies are all going to be collected and become a direct dependency. Maven should already resolve * versions properly because now the transitive dependencies are closer to the artifact. However, when this artifact is * being consumed, Maven Enforcer Convergence rule will fail because there may be multiple versions for the same transitive dependency. - * + * * Typically, exclusion can be done by using the wildcard. However, a known Maven issue prevents convergence enforcer from * working properly w/ wildcard exclusions. Thus, this will exclude each dependencies explicitly rather than using the wildcard. * @@ -1091,11 +1092,31 @@ private void createFlattenedDependenciesAll( List projectDependencie final Artifact projectArtifact = this.project.getArtifact(); - // The test-scope project dependencies will hinder transitive dependencies by marking them - // as 'omitted for duplicate.' - this.project.getDependencies().removeIf(dependency -> "test".equals(dependency.getScope())); + // The test-scope project dependencies may hinder transitive dependencies by marking them as 'omitted for duplicate' when + // building dependency tree. This is a problem when the transitive dependency is actually needed by another non-test dependency + // of the project. To avoid this interference of test-scope project dependencies, this plugin builds a dependency tree + // of the project without direct, test-scope dependencies. This is safe because these dependencies do not appear in + // library users' class path in any case. + final Set testScopeProjectDependencyKeys = new HashSet<>(); + for (Dependency projectDependency : projectDependencies) + { + if ("test".equals(projectDependency.getScope())) + { + testScopeProjectDependencyKeys.add(projectDependency.getManagementKey()); + } + } + // LinkedHashSet preserves the order. + final Set dependencyArtifactsWithoutTest = new LinkedHashSet<>(this.project.getDependencyArtifacts()); + dependencyArtifactsWithoutTest.removeIf(artifact -> { + // The same logic as org.apache.maven.model.Dependency.getManagementKey() + String managementKey = artifact.getGroupId() + ":" + artifact.getArtifactId() + ":" + artifact.getType() + + (artifact.getClassifier() != null ? ":" + artifact.getClassifier() : ""); + return testScopeProjectDependencyKeys.contains(managementKey); + }); + final MavenProject projectWithoutTestScopeDeps = this.project.clone(); + projectWithoutTestScopeDeps.setDependencyArtifacts(dependencyArtifactsWithoutTest); - final DependencyNode dependencyNode = this.dependencyTreeBuilder.buildDependencyTree(this.project, + final DependencyNode dependencyNode = this.dependencyTreeBuilder.buildDependencyTree(projectWithoutTestScopeDeps, this.localRepository, null); dependencyNode.accept(new DependencyNodeVisitor()