From e6ba68b4f6ea4d7c383db4c9bfe5b12660492e2a Mon Sep 17 00:00:00 2001 From: Tomo Suzuki Date: Tue, 9 Aug 2022 15:43:51 -0400 Subject: [PATCH] Exclude direct test-scope dependencies when building dependency graph Fixes #185 --- .../pom.xml | 35 +++++++++++++++ .../verify.groovy | 45 +++++++++++++++++++ .../codehaus/mojo/flatten/FlattenMojo.java | 44 ++++++++++++++++-- 3 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 src/it/projects/flatten-dependency-all-both-test-and-transitive/pom.xml create mode 100644 src/it/projects/flatten-dependency-all-both-test-and-transitive/verify.groovy diff --git a/src/it/projects/flatten-dependency-all-both-test-and-transitive/pom.xml b/src/it/projects/flatten-dependency-all-both-test-and-transitive/pom.xml new file mode 100644 index 00000000..8ca9f3a6 --- /dev/null +++ b/src/it/projects/flatten-dependency-all-both-test-and-transitive/pom.xml @@ -0,0 +1,35 @@ + + 4.0.0 + org.codehaus.mojo.flatten.its + flatten-dependency-all-both-test-and-transitive + 0.0.1-SNAPSHOT + + + verify + + + org.codehaus.mojo + flatten-maven-plugin + + oss + all + + + + + + + + org.codehaus.mojo.flatten.its + core + + 3.2.1 + + + org.codehaus.mojo.flatten.its + dep + 3.2.1 + test + + + \ No newline at end of file diff --git a/src/it/projects/flatten-dependency-all-both-test-and-transitive/verify.groovy b/src/it/projects/flatten-dependency-all-both-test-and-transitive/verify.groovy new file mode 100644 index 00000000..8a1426c4 --- /dev/null +++ b/src/it/projects/flatten-dependency-all-both-test-and-transitive/verify.groovy @@ -0,0 +1,45 @@ +/* + * 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. + */ +File originalPom = new File( basedir, 'pom.xml' ) +assert originalPom.exists() + +def originalProject = new XmlSlurper().parse( originalPom ) +assert 2 == originalProject.dependencies.dependency.size() +assert "dep" == originalProject.dependencies.dependency[1].artifactId.text() +assert "3.2.1" == originalProject.dependencies.dependency[1].version.text() +assert "test" == originalProject.dependencies.dependency[1].scope.text() + +File flattenedPom = new File( basedir, '.flattened-pom.xml' ) +assert flattenedPom.exists() + +def flattenedProject = new XmlSlurper().parse( flattenedPom ) + +// core and dep should be there. It's because while the test-scope dep (the +// direct dependency), core declares dep as compile-scope (default) dependency. +assert 2 == flattenedProject.dependencies.dependency.size() + +assert "core" == flattenedProject.dependencies.dependency[0].artifactId.text() +assert "3.2.1" == flattenedProject.dependencies.dependency[0].version.text() +assert "compile" == flattenedProject.dependencies.dependency[0].scope.text() + +// The flattened pom.xml should declare the dep under core as compile scope. +// It's ok to ignore the one in the test-scope dependency. +assert "dep" == flattenedProject.dependencies.dependency[1].artifactId.text() +assert "3.2.1" == flattenedProject.dependencies.dependency[1].version.text() +assert "compile" == flattenedProject.dependencies.dependency[1].scope.text() diff --git a/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java b/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java index a4bb4625..09457e3b 100644 --- a/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java +++ b/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java @@ -87,6 +87,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Properties; @@ -1095,11 +1096,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. * @@ -1117,7 +1118,7 @@ private void createFlattenedDependenciesAll( List projectDependencie final Artifact projectArtifact = this.project.getArtifact(); ProjectBuildingRequest buildingRequest = new DefaultProjectBuildingRequest( session.getProjectBuildingRequest() ); - buildingRequest.setProject( project ); + buildingRequest.setProject( cloneProjectWithoutTestDependencies( project ) ); final DependencyNode dependencyNode = this.dependencyGraphBuilder.buildDependencyGraph( buildingRequest, null); @@ -1206,6 +1207,43 @@ private void createFlattenedDependenciesAll( List projectDependencie } } + /** + * Returns a cloned project that does not have direct test-scope dependencies. + * + * Test-scope project dependencies may hinder transitive dependencies by marking them as 'omitted for duplicate' when + * building dependency tree. This was a problem when the transitive dependency is actually needed by another non-test dependency + * of the project (See https://github.com/mojohaus/flatten-maven-plugin/issues/185). To avoid this interference of + * test-scope project dependencies, this plugin builds a dependency tree of the project without direct, test-scope dependencies. + * + * Removal of test scope dependencies is safe because these dependencies do not appear in library users' class path in + * any case. + * + * @param project is the original project to clone. + * @return a cloned project without direct test-scope dependencies. + */ + private static MavenProject cloneProjectWithoutTestDependencies( MavenProject project ) + { + final Set testScopeProjectDependencyKeys = new HashSet<>(); + for ( Dependency projectDependency : project.getDependencies() ) + { + if ( "test".equals( projectDependency.getScope() ) ) + { + testScopeProjectDependencyKeys.add( projectDependency.getManagementKey() ); + } + } + // LinkedHashSet preserves the order. + final Set dependencyArtifactsWithoutTest = new LinkedHashSet<>( 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 = project.clone(); + projectWithoutTestScopeDeps.setDependencyArtifacts( dependencyArtifactsWithoutTest ); + return projectWithoutTestScopeDeps; + } + /** * Collects the resolved {@link Dependency dependencies} from the given effectiveModel. *