diff --git a/src/main/java/org/apache/maven/plugins/dependency/resolvers/ExcludeReactorProjectsGraphDependencyFilter.java b/src/main/java/org/apache/maven/plugins/dependency/resolvers/ExcludeReactorProjectsGraphDependencyFilter.java new file mode 100644 index 000000000..67740e75e --- /dev/null +++ b/src/main/java/org/apache/maven/plugins/dependency/resolvers/ExcludeReactorProjectsGraphDependencyFilter.java @@ -0,0 +1,66 @@ +/* + * 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.plugins.dependency.resolvers; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import org.apache.maven.artifact.ArtifactUtils; +import org.apache.maven.project.MavenProject; +import org.eclipse.aether.artifact.Artifact; +import org.eclipse.aether.graph.DependencyFilter; +import org.eclipse.aether.graph.DependencyNode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Aether {@link DependencyFilter} implementation that excludes artifacts found in the Reactor, applied to the whole + * resolved dependency graph so transitive nodes pointing at reactor modules are skipped during artifact resolution. + */ +public class ExcludeReactorProjectsGraphDependencyFilter implements DependencyFilter { + + private final Logger log = LoggerFactory.getLogger(ExcludeReactorProjectsGraphDependencyFilter.class); + private final Set reactorArtifactKeys; + + public ExcludeReactorProjectsGraphDependencyFilter(final List reactorProjects) { + this.reactorArtifactKeys = reactorProjects.stream() + .map(project -> ArtifactUtils.key(project.getArtifact())) + .collect(Collectors.toSet()); + } + + @Override + public boolean accept(DependencyNode node, List parents) { + if (node == null) { + return true; + } + Artifact artifact = node.getArtifact(); + if (artifact == null) { + return true; + } + String key = ArtifactUtils.key(artifact.getGroupId(), artifact.getArtifactId(), artifact.getVersion()); + if (reactorArtifactKeys.contains(key)) { + if (log.isDebugEnabled()) { + log.debug("Skipped transitive dependency {} because it is present in the reactor", key); + } + return false; + } + return true; + } +} diff --git a/src/main/java/org/apache/maven/plugins/dependency/resolvers/GoOfflineMojo.java b/src/main/java/org/apache/maven/plugins/dependency/resolvers/GoOfflineMojo.java index da0726d3d..4fc59e816 100644 --- a/src/main/java/org/apache/maven/plugins/dependency/resolvers/GoOfflineMojo.java +++ b/src/main/java/org/apache/maven/plugins/dependency/resolvers/GoOfflineMojo.java @@ -55,6 +55,7 @@ import org.apache.maven.shared.artifact.filter.collection.ScopeFilter; import org.apache.maven.shared.artifact.filter.collection.TypeFilter; import org.eclipse.aether.artifact.ArtifactTypeRegistry; +import org.eclipse.aether.graph.DependencyFilter; import org.eclipse.aether.resolution.ArtifactDescriptorException; import org.eclipse.aether.resolution.ArtifactResolutionException; import org.eclipse.aether.resolution.DependencyResolutionException; @@ -186,12 +187,18 @@ protected List resolveDependencyArtifacts( .collect(Collectors.toList())) .orElse(null); + DependencyFilter graphReactorFilter = null; + if (this.excludeReactor) { + graphReactorFilter = new ExcludeReactorProjectsGraphDependencyFilter(session.getProjects()); + } + return getResolverUtil() .resolveDependenciesForArtifact( RepositoryUtils.toArtifact(getProject().getArtifact()), dependableCoordinates, managedDependencies, - getProject().getRemoteProjectRepositories()); + getProject().getRemoteProjectRepositories(), + graphReactorFilter); } /** diff --git a/src/main/java/org/apache/maven/plugins/dependency/utils/ResolverUtil.java b/src/main/java/org/apache/maven/plugins/dependency/utils/ResolverUtil.java index 60ca42c97..0183dfa88 100644 --- a/src/main/java/org/apache/maven/plugins/dependency/utils/ResolverUtil.java +++ b/src/main/java/org/apache/maven/plugins/dependency/utils/ResolverUtil.java @@ -52,6 +52,7 @@ import org.eclipse.aether.collection.CollectResult; import org.eclipse.aether.collection.DependencyCollectionException; import org.eclipse.aether.graph.Dependency; +import org.eclipse.aether.graph.DependencyFilter; import org.eclipse.aether.repository.RemoteRepository; import org.eclipse.aether.repository.RepositoryPolicy; import org.eclipse.aether.resolution.ArtifactDescriptorException; @@ -64,6 +65,7 @@ import org.eclipse.aether.resolution.DependencyResolutionException; import org.eclipse.aether.resolution.DependencyResult; import org.eclipse.aether.util.graph.visitor.PreorderNodeListGenerator; +import org.jspecify.annotations.Nullable; /** * Helper class for using Resolver API. @@ -192,12 +194,14 @@ public List resolveDependencies( } /** - * Resolve transitive dependencies for artifact with managed dependencies. + * Resolve transitive dependencies for artifact with managed dependencies, applying the given graph-level filter + * to skip nodes (including transitive ones) that should not be resolved. * * @param rootArtifact a root artifact to resolve * @param dependencies a list of dependencies for artifact * @param managedDependencies a list of managed dependencies for artifact * @param remoteProjectRepositories remote repositories list + * @param dependencyFilter a filter applied to every node in the resolved graph, may be {@code null} * @return Resolved dependencies * @throws DependencyResolutionException if the dependency tree could not be built or any dependency artifact could * not be resolved @@ -206,14 +210,15 @@ public List resolveDependenciesForArtifact( Artifact rootArtifact, List dependencies, List managedDependencies, - List remoteProjectRepositories) + List remoteProjectRepositories, + @Nullable DependencyFilter dependencyFilter) throws DependencyResolutionException { MavenSession session = mavenSessionProvider.get(); CollectRequest collectRequest = new CollectRequest(dependencies, managedDependencies, remoteProjectRepositories); collectRequest.setRootArtifact(rootArtifact); - DependencyRequest request = new DependencyRequest(collectRequest, null); + DependencyRequest request = new DependencyRequest(collectRequest, dependencyFilter); DependencyResult result = repositorySystem.resolveDependencies(session.getRepositorySession(), request); return result.getArtifactResults().stream() .map(ArtifactResult::getArtifact) diff --git a/src/test/java/org/apache/maven/plugins/dependency/resolvers/ExcludeReactorProjectsGraphDependencyFilterTest.java b/src/test/java/org/apache/maven/plugins/dependency/resolvers/ExcludeReactorProjectsGraphDependencyFilterTest.java new file mode 100644 index 000000000..b686bf984 --- /dev/null +++ b/src/test/java/org/apache/maven/plugins/dependency/resolvers/ExcludeReactorProjectsGraphDependencyFilterTest.java @@ -0,0 +1,92 @@ +/* + * 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.plugins.dependency.resolvers; + +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.DefaultArtifact; +import org.apache.maven.project.MavenProject; +import org.eclipse.aether.graph.DefaultDependencyNode; +import org.eclipse.aether.graph.Dependency; +import org.eclipse.aether.graph.DependencyNode; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class ExcludeReactorProjectsGraphDependencyFilterTest { + + @Mock + private MavenProject project; + + @Test + void testRejectReactorTransitive() { + Artifact reactorArtifact = anArtifact(); + when(project.getArtifact()).thenReturn(reactorArtifact); + + ExcludeReactorProjectsGraphDependencyFilter filter = + new ExcludeReactorProjectsGraphDependencyFilter(singletonList(project)); + + DependencyNode node = + nodeFor(reactorArtifact.getGroupId(), reactorArtifact.getArtifactId(), reactorArtifact.getVersion()); + + assertFalse(filter.accept(node, emptyList())); + } + + @Test + void testAcceptNonReactorTransitive() { + Artifact reactorArtifact = anArtifact(); + when(project.getArtifact()).thenReturn(reactorArtifact); + + ExcludeReactorProjectsGraphDependencyFilter filter = + new ExcludeReactorProjectsGraphDependencyFilter(singletonList(project)); + + DependencyNode node = nodeFor("something-else", reactorArtifact.getArtifactId(), reactorArtifact.getVersion()); + + assertTrue(filter.accept(node, emptyList())); + } + + @Test + void testAcceptNullArtifact() { + Artifact reactorArtifact = anArtifact(); + when(project.getArtifact()).thenReturn(reactorArtifact); + + ExcludeReactorProjectsGraphDependencyFilter filter = + new ExcludeReactorProjectsGraphDependencyFilter(singletonList(project)); + + assertTrue(filter.accept(new DefaultDependencyNode((Dependency) null), emptyList())); + } + + private static DependencyNode nodeFor(String groupId, String artifactId, String version) { + org.eclipse.aether.artifact.Artifact aetherArtifact = + new org.eclipse.aether.artifact.DefaultArtifact(groupId, artifactId, "jar", version); + return new DefaultDependencyNode(new Dependency(aetherArtifact, null)); + } + + private Artifact anArtifact() { + return new DefaultArtifact( + "org.apache.maven.plugins", "maven-dependency-plugin-dummy", "1.0", null, "jar", "", null); + } +}