[MNG-8256] FilteredProjectDependencyGraph fix for non-transitive case (#1724)

The `FilteredProjectDependencyGraph` class fix for non-transitive case and an UT. Also contains some improvement in classes, making them immutable as intended.

---

https://issues.apache.org/jira/browse/MNG-8256
This commit is contained in:
Tamas Cservenak 2024-09-12 22:50:55 +02:00 committed by GitHub
parent 4c059c401c
commit 45201347c4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 47 additions and 17 deletions

View File

@ -94,8 +94,10 @@ public class DefaultGraphBuilder implements GraphBuilder {
Result<ProjectDependencyGraph> result = null; Result<ProjectDependencyGraph> result = null;
if (session.getProjectDependencyGraph() != null || session.getProjects() != null) { if (session.getProjectDependencyGraph() != null || session.getProjects() != null) {
final ProjectDependencyGraph graph = ProjectDependencyGraph graph = new DefaultProjectDependencyGraph(session.getAllProjects());
new DefaultProjectDependencyGraph(session.getAllProjects(), session.getProjects()); if (session.getProjects() != null) {
graph = new FilteredProjectDependencyGraph(graph, session.getProjects());
}
result = Result.success(graph); result = Result.success(graph);
} }

View File

@ -24,6 +24,7 @@ import java.util.IdentityHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Collectors;
import org.apache.maven.execution.ProjectDependencyGraph; import org.apache.maven.execution.ProjectDependencyGraph;
import org.apache.maven.project.MavenProject; import org.apache.maven.project.MavenProject;
@ -35,11 +36,11 @@ import org.apache.maven.project.MavenProject;
*/ */
class FilteredProjectDependencyGraph implements ProjectDependencyGraph { class FilteredProjectDependencyGraph implements ProjectDependencyGraph {
private ProjectDependencyGraph projectDependencyGraph; private final ProjectDependencyGraph projectDependencyGraph;
private Map<MavenProject, ?> whiteList; private final Map<MavenProject, ?> whiteList;
private List<MavenProject> sortedProjects; private final List<MavenProject> sortedProjects;
/** /**
* Creates a new project dependency graph from the specified graph. * Creates a new project dependency graph from the specified graph.
@ -51,46 +52,58 @@ class FilteredProjectDependencyGraph implements ProjectDependencyGraph {
ProjectDependencyGraph projectDependencyGraph, Collection<? extends MavenProject> whiteList) { ProjectDependencyGraph projectDependencyGraph, Collection<? extends MavenProject> whiteList) {
this.projectDependencyGraph = this.projectDependencyGraph =
Objects.requireNonNull(projectDependencyGraph, "projectDependencyGraph cannot be null"); Objects.requireNonNull(projectDependencyGraph, "projectDependencyGraph cannot be null");
this.whiteList = new IdentityHashMap<>();
this.whiteList = new IdentityHashMap<MavenProject, Object>();
for (MavenProject project : whiteList) { for (MavenProject project : whiteList) {
this.whiteList.put(project, null); this.whiteList.put(project, null);
} }
this.sortedProjects = projectDependencyGraph.getSortedProjects().stream()
.filter(this.whiteList::containsKey)
.collect(Collectors.toList());
} }
/** /**
* @since 3.5.0 * @since 3.5.0
*/ */
@Override
public List<MavenProject> getAllProjects() { public List<MavenProject> getAllProjects() {
return this.projectDependencyGraph.getAllProjects(); return this.projectDependencyGraph.getAllProjects();
} }
@Override
public List<MavenProject> getSortedProjects() { public List<MavenProject> getSortedProjects() {
if (sortedProjects == null) {
sortedProjects = applyFilter(projectDependencyGraph.getSortedProjects());
}
return new ArrayList<>(sortedProjects); return new ArrayList<>(sortedProjects);
} }
@Override
public List<MavenProject> getDownstreamProjects(MavenProject project, boolean transitive) { public List<MavenProject> getDownstreamProjects(MavenProject project, boolean transitive) {
return applyFilter(projectDependencyGraph.getDownstreamProjects(project, transitive)); return applyFilter(projectDependencyGraph.getDownstreamProjects(project, transitive), transitive, false);
} }
@Override
public List<MavenProject> getUpstreamProjects(MavenProject project, boolean transitive) { public List<MavenProject> getUpstreamProjects(MavenProject project, boolean transitive) {
return applyFilter(projectDependencyGraph.getUpstreamProjects(project, transitive)); return applyFilter(projectDependencyGraph.getUpstreamProjects(project, transitive), transitive, true);
} }
private List<MavenProject> applyFilter(Collection<? extends MavenProject> projects) { /**
* Filter out whitelisted projects with a big twist:
* Assume we have all projects {@code a, b, c} while active are {@code a, c} and relation among all projects
* is {@code a -> b -> c}. This method handles well the case for transitive list. But, for non-transitive we need
* to "pull in" transitive dependencies of eliminated projects, as for case above, the properly filtered list would
* be {@code a -> c}.
* <p>
* Original code would falsely report {@code a} project as "without dependencies", basically would lose link due
* filtering. This causes build ordering issues in concurrent builders.
*/
private List<MavenProject> applyFilter(
Collection<? extends MavenProject> projects, boolean transitive, boolean upstream) {
List<MavenProject> filtered = new ArrayList<>(projects.size()); List<MavenProject> filtered = new ArrayList<>(projects.size());
for (MavenProject project : projects) { for (MavenProject project : projects) {
if (whiteList.containsKey(project)) { if (whiteList.containsKey(project)) {
filtered.add(project); filtered.add(project);
} else if (!transitive) {
filtered.addAll(upstream ? getUpstreamProjects(project, false) : getDownstreamProjects(project, false));
} }
} }
return filtered; return filtered;
} }

View File

@ -35,6 +35,10 @@ public class DefaultProjectDependencyGraphTest extends TestCase {
private final MavenProject aProject = createA(); private final MavenProject aProject = createA();
private final MavenProject bProject = createProject(Arrays.asList(toDependency(aProject)), "bProject");
private final MavenProject cProject = createProject(Arrays.asList(toDependency(bProject)), "cProject");
private final MavenProject depender1 = createProject(Arrays.asList(toDependency(aProject)), "depender1"); private final MavenProject depender1 = createProject(Arrays.asList(toDependency(aProject)), "depender1");
private final MavenProject depender2 = createProject(Arrays.asList(toDependency(aProject)), "depender2"); private final MavenProject depender2 = createProject(Arrays.asList(toDependency(aProject)), "depender2");
@ -46,6 +50,17 @@ public class DefaultProjectDependencyGraphTest extends TestCase {
private final MavenProject transitiveOnly = createProject(Arrays.asList(toDependency(depender3)), "depender5"); private final MavenProject transitiveOnly = createProject(Arrays.asList(toDependency(depender3)), "depender5");
public void testNonTransitiveFiltering() throws DuplicateProjectException, CycleDetectedException {
ProjectDependencyGraph graph = new FilteredProjectDependencyGraph(
new DefaultProjectDependencyGraph(Arrays.asList(aProject, bProject, cProject)),
Arrays.asList(aProject, cProject));
final List<MavenProject> sortedProjects = graph.getSortedProjects();
assertEquals(aProject, sortedProjects.get(0));
assertEquals(cProject, sortedProjects.get(1));
assertTrue(graph.getDownstreamProjects(aProject, false).contains(cProject));
}
public void testGetSortedProjects() throws DuplicateProjectException, CycleDetectedException { public void testGetSortedProjects() throws DuplicateProjectException, CycleDetectedException {
ProjectDependencyGraph graph = new DefaultProjectDependencyGraph(Arrays.asList(depender1, aProject)); ProjectDependencyGraph graph = new DefaultProjectDependencyGraph(Arrays.asList(depender1, aProject));
final List<MavenProject> sortedProjects = graph.getSortedProjects(); final List<MavenProject> sortedProjects = graph.getSortedProjects();