diff --git a/src/it/projects/dependency-sets/massembly-1008/pom.xml b/src/it/projects/dependency-sets/massembly-1008/pom.xml index 4cb15892..a7674575 100644 --- a/src/it/projects/dependency-sets/massembly-1008/pom.xml +++ b/src/it/projects/dependency-sets/massembly-1008/pom.xml @@ -23,6 +23,7 @@ under the License. org.apache.maven.plugin.assembly.test it-project-parent 1 + ../../../it-project-parent/pom.xml test diff --git a/src/it/projects/dependency-sets/massembly-1008/verify.groovy b/src/it/projects/dependency-sets/massembly-1008/verify.groovy index 55d7b281..5828daf7 100644 --- a/src/it/projects/dependency-sets/massembly-1008/verify.groovy +++ b/src/it/projects/dependency-sets/massembly-1008/verify.groovy @@ -19,18 +19,14 @@ import java.io.*; +// guava is absent from the runtime dependencySet: the nearer test-scoped guava 32.0.0-jre wins +// version mediation and is then excluded by scope, while guice's transitive guava 31.0.1-jre is +// omitted for the conflict. This is the runtime classpath Maven resolves. def expectedFilenames = [ "aopalliance-1.0.jar", - "checker-qual-3.12.0.jar", - "error_prone_annotations-2.7.1.jar", - "failureaccess-1.0.1.jar", - "guava-31.0.1-jre.jar", "guice-6.0.0.jar", - "j2objc-annotations-1.3.jar", "jakarta.inject-api-2.0.1.jar", - "javax.inject-1.jar", - "jsr305-3.0.2.jar", - "listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar" + "javax.inject-1.jar" ] File assemblyBasedir = new File( basedir, "target/massembly-1008-1-bin/" ) diff --git a/src/it/projects/dependency-sets/massembly-1026/pom.xml b/src/it/projects/dependency-sets/massembly-1026/pom.xml new file mode 100644 index 00000000..9b925873 --- /dev/null +++ b/src/it/projects/dependency-sets/massembly-1026/pom.xml @@ -0,0 +1,73 @@ + + + + 4.0.0 + + org.apache.maven.plugin.assembly.test + it-project-parent + 1 + ../../../it-project-parent/pom.xml + + + test + massembly-1026 + 1 + + https://issues.apache.org/jira/browse/MASSEMBLY-1026 + + + UTF-8 + + + + + com.fasterxml.jackson.datatype + jackson-datatype-guava + 2.7.3 + test + + + com.google.inject + guice + 6.0.0 + + + + + + maven-assembly-plugin + + + assembly + package + + single + + + + src/main/assembly/bin.xml + + + + + + + + diff --git a/src/it/projects/dependency-sets/massembly-1026/src/main/assembly/bin.xml b/src/it/projects/dependency-sets/massembly-1026/src/main/assembly/bin.xml new file mode 100644 index 00000000..230d3c93 --- /dev/null +++ b/src/it/projects/dependency-sets/massembly-1026/src/main/assembly/bin.xml @@ -0,0 +1,33 @@ + + + bin + + dir + + false + + + false + runtime + + false + + + diff --git a/src/it/projects/dependency-sets/massembly-1026/verify.groovy b/src/it/projects/dependency-sets/massembly-1026/verify.groovy new file mode 100644 index 00000000..acb93fdd --- /dev/null +++ b/src/it/projects/dependency-sets/massembly-1026/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. + */ + +import java.io.*; + +// guava 16.0 (from the test-scoped jackson-datatype-guava) wins version mediation over guice's +// guava 31.0.1-jre and stays on the runtime classpath via guice. +def expectedFilenames = [ + "aopalliance-1.0.jar", + "guava-16.0.jar", + "guice-6.0.0.jar", + "jakarta.inject-api-2.0.1.jar", + "javax.inject-1.jar" +] + +File assemblyBasedir = new File( basedir, "target/massembly-1026-1-bin/" ) + +assert assemblyBasedir.listFiles().length == expectedFilenames.size() + +for ( fileName in expectedFilenames ) +{ + File file = new File( assemblyBasedir, fileName ) + assert file.isFile() // exists and is file +} + +// the losing version must not be assembled +assert !new File( assemblyBasedir, "guava-31.0.1-jre.jar" ).exists() + +return true diff --git a/src/it/projects/dependency-sets/massembly-1031/pom.xml b/src/it/projects/dependency-sets/massembly-1031/pom.xml new file mode 100644 index 00000000..777fdcaa --- /dev/null +++ b/src/it/projects/dependency-sets/massembly-1031/pom.xml @@ -0,0 +1,73 @@ + + + + 4.0.0 + + org.apache.maven.plugin.assembly.test + it-project-parent + 1 + ../../../it-project-parent/pom.xml + + + test + massembly-1031 + 1 + + https://issues.apache.org/jira/browse/MASSEMBLY-1031 + + + UTF-8 + + + + + org.apache.commons + commons-text + 1.12.0 + + + org.apache.commons + commons-lang3 + 3.14.0 + provided + + + + + + maven-assembly-plugin + + + assembly + package + + single + + + + src/main/assembly/bin.xml + + + + + + + + diff --git a/src/it/projects/dependency-sets/massembly-1031/src/main/assembly/bin.xml b/src/it/projects/dependency-sets/massembly-1031/src/main/assembly/bin.xml new file mode 100644 index 00000000..0ad3ec58 --- /dev/null +++ b/src/it/projects/dependency-sets/massembly-1031/src/main/assembly/bin.xml @@ -0,0 +1,32 @@ + + + bin + + dir + + false + + + false + + false + + + diff --git a/src/it/projects/dependency-sets/massembly-1031/verify.groovy b/src/it/projects/dependency-sets/massembly-1031/verify.groovy new file mode 100644 index 00000000..9af6d9ab --- /dev/null +++ b/src/it/projects/dependency-sets/massembly-1031/verify.groovy @@ -0,0 +1,41 @@ +/* + * 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 java.io.*; + +// commons-lang3 is excluded: the direct provided declaration wins mediation over the transitive +// compile one pulled in by commons-text, so only commons-text remains on the runtime classpath. +def expectedFilenames = [ + "commons-text-1.12.0.jar" +] + +File assemblyBasedir = new File( basedir, "target/massembly-1031-1-bin/" ) + +assert assemblyBasedir.listFiles().length == expectedFilenames.size() + +for ( fileName in expectedFilenames ) +{ + File file = new File( assemblyBasedir, fileName ) + assert file.isFile() // exists and is file +} + +// commons-lang3 is provided and must be excluded +assert !new File( assemblyBasedir, "commons-lang3-3.14.0.jar" ).exists() + +return true diff --git a/src/main/java/org/apache/maven/plugins/assembly/artifact/DefaultDependencyResolver.java b/src/main/java/org/apache/maven/plugins/assembly/artifact/DefaultDependencyResolver.java index e9eef03b..a26d3628 100644 --- a/src/main/java/org/apache/maven/plugins/assembly/artifact/DefaultDependencyResolver.java +++ b/src/main/java/org/apache/maven/plugins/assembly/artifact/DefaultDependencyResolver.java @@ -52,7 +52,6 @@ import org.eclipse.aether.RepositorySystem; import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.collection.CollectRequest; -import org.eclipse.aether.graph.DefaultDependencyNode; import org.eclipse.aether.graph.Dependency; import org.eclipse.aether.graph.DependencyFilter; import org.eclipse.aether.graph.DependencyNode; @@ -213,13 +212,14 @@ private Set resolveTransitive( RepositorySystemSession repositorySession, String scope, MavenProject project) throws org.eclipse.aether.resolution.DependencyResolutionException { - // scope dependency filter - DependencyFilter scoopeDependencyFilter = DependencyFilterUtils.classpathFilter(scope); + DependencyFilter scopeDependencyFilter = DependencyFilterUtils.classpathFilter(scope); - // get project dependencies filtered by requested scope + // Collect every declared dependency and apply the scope filter only when resolving below. + // Filtering declared dependencies by scope first would drop ones that win mediation - a + // nearer version, or a direct provided dependency that also appears transitively - leaving + // a graph that disagrees with the resolved compile/runtime classpath. List dependencies = project.getDependencies().stream() .map(d -> RepositoryUtils.toDependency(d, repositorySession.getArtifactTypeRegistry())) - .filter(d -> scoopeDependencyFilter.accept(new DefaultDependencyNode(d), null)) .collect(Collectors.toList()); List managedDependencies = Optional.ofNullable(project.getDependencyManagement()) @@ -235,7 +235,7 @@ private Set resolveTransitive( collectRequest.setDependencies(dependencies); collectRequest.setRootArtifact(RepositoryUtils.toArtifact(project.getArtifact())); - DependencyRequest request = new DependencyRequest(collectRequest, scoopeDependencyFilter); + DependencyRequest request = new DependencyRequest(collectRequest, scopeDependencyFilter); DependencyResult dependencyResult = repositorySystem.resolveDependencies(repositorySession, request); diff --git a/src/test/java/org/apache/maven/plugins/assembly/artifact/DefaultDependencyResolverTest.java b/src/test/java/org/apache/maven/plugins/assembly/artifact/DefaultDependencyResolverTest.java index 8ed0e7d9..38d461b3 100644 --- a/src/test/java/org/apache/maven/plugins/assembly/artifact/DefaultDependencyResolverTest.java +++ b/src/test/java/org/apache/maven/plugins/assembly/artifact/DefaultDependencyResolverTest.java @@ -22,7 +22,9 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.DefaultArtifact; @@ -38,12 +40,14 @@ import org.apache.maven.project.MavenProject; import org.eclipse.aether.RepositorySystem; import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.artifact.ArtifactTypeRegistry; import org.eclipse.aether.graph.DefaultDependencyNode; import org.eclipse.aether.graph.Dependency; import org.eclipse.aether.resolution.DependencyRequest; import org.eclipse.aether.resolution.DependencyResult; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -110,6 +114,40 @@ void getDependencySetResolutionRequirementsTransitive() throws Exception { }); } + @Test + void transitiveResolutionCollectsDependenciesOfEveryScope() throws Exception { + // Declared dependencies must reach the CollectRequest regardless of scope, so that + // full-graph mediation runs before the scope filter selects leaves. + final DependencySet ds = new DependencySet(); + ds.setScope(Artifact.SCOPE_RUNTIME); + ds.setUseTransitiveDependencies(true); + + final MavenProject project = createMavenProject("main-group", "main-artifact", "1", null); + project.getModel().addDependency(newModelDependency("g.id", "compile-dep", "1", Artifact.SCOPE_COMPILE)); + project.getModel().addDependency(newModelDependency("g.id", "provided-dep", "1", Artifact.SCOPE_PROVIDED)); + project.getModel().addDependency(newModelDependency("g.id", "test-dep", "1", Artifact.SCOPE_TEST)); + + when(systemSession.getArtifactTypeRegistry()).thenReturn(mock(ArtifactTypeRegistry.class)); + + DependencyResult dependencyResult = new DependencyResult(new DependencyRequest()); + dependencyResult.setRoot(new DefaultDependencyNode((Dependency) null)); + + ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(DependencyRequest.class); + when(repositorySystem.resolveDependencies(eq(systemSession), requestCaptor.capture())) + .thenReturn(dependencyResult); + + resolver.updateDependencySetResolutionRequirements(systemSession, ds, new ResolutionManagementInfo(), project); + + List collected = + requestCaptor.getValue().getCollectRequest().getDependencies(); + Set collectedScopes = + collected.stream().map(Dependency::getScope).collect(Collectors.toSet()); + assertEquals(3, collected.size()); + assertEquals( + new HashSet<>(Arrays.asList(Artifact.SCOPE_COMPILE, Artifact.SCOPE_PROVIDED, Artifact.SCOPE_TEST)), + collectedScopes); + } + @Test void getDependencySetResolutionRequirementsNonTransitive() throws Exception { final DependencySet ds = new DependencySet(); @@ -220,6 +258,16 @@ private MavenProject createMavenProject( return project; } + private org.apache.maven.model.Dependency newModelDependency( + final String groupId, final String artifactId, final String version, final String scope) { + final org.apache.maven.model.Dependency dependency = new org.apache.maven.model.Dependency(); + dependency.setGroupId(groupId); + dependency.setArtifactId(artifactId); + dependency.setVersion(version); + dependency.setScope(scope); + return dependency; + } + private Artifact newArtifact(final String groupId, final String artifactId, final String version) { return new DefaultArtifact( groupId,