Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -729,33 +729,53 @@ private void applyAfterLinks(MojoDescriptor mojoDescriptor, MavenProject project
* all upstream projects are returned. Otherwise, only projects that the given
* project depends on with a matching scope are included.
* <p>
* Matching is exact on the dependency's declared scope string (e.g. "compile",
* "provided", "test"). Maven's default dependency scope is "compile" (when no
* scope is declared), so a null scope in the model is treated as "compile" for
* matching purposes. Note that this does <em>not</em> perform path-scope
* resolution — for example, filtering by "compile" will not include
* "provided"-scoped dependencies even though they contribute to
* {@code PathScope.MAIN_COMPILE}. This keeps the filter simple and predictable;
* broader scope-aware filtering can be added in a follow-up if needed.
* The scope parameter is a lifecycle dependency scope (as declared in
* {@link org.apache.maven.api.DependencyScope}). It is expanded to match all
* artifact scopes that contribute to that dependency scope for build ordering:
* <ul>
* <li>{@code "compile"} matches compile, provided, system, and null-scoped
* (Maven default) dependencies</li>
* <li>{@code "runtime"} matches compile, runtime, and null-scoped dependencies</li>
* <li>{@code "test"} matches all scopes</li>
* <li>{@code "test-only"} matches only test-scoped dependencies</li>
* </ul>
* This ensures that reactor dependencies contributing to a given classpath are
* properly ordered in the build plan (e.g. a provided-scope reactor dependency
* is built before the consumer's compile phase).
*
* @param project the project whose dependencies to check
* @param upstreamProjects the list of upstream reactor projects
* @param scope the dependency scope to filter by, or null/empty for all
* @param scope the lifecycle dependency scope to filter by, or null/empty for all
* @return the filtered list of upstream projects
*/
static List<MavenProject> filterByScope(
MavenProject project, List<MavenProject> upstreamProjects, String scope) {
if (scope == null || scope.isEmpty()) {
return upstreamProjects;
}
Set<String> matchingScopes = expandScope(scope);
return upstreamProjects.stream()
.filter(dep -> project.getDependencies().stream()
.anyMatch(d -> dep.getGroupId().equals(d.getGroupId())
&& dep.getArtifactId().equals(d.getArtifactId())
&& scope.equals(d.getScope() != null ? d.getScope() : "compile")))
&& matchingScopes.contains(d.getScope() != null ? d.getScope() : "compile")))
.collect(Collectors.toList());
}

/**
* Expands a lifecycle dependency scope to the set of artifact scopes that
* contribute to it for build ordering purposes.
*/
private static Set<String> expandScope(String scope) {
return switch (scope) {
case "compile" -> Set.of("compile", "provided", "system");
case "runtime" -> Set.of("compile", "runtime");
case "test" -> Set.of("compile", "provided", "system", "runtime", "test");
case "test-only" -> Set.of("test");
default -> Set.of(scope);
};
}

protected BuildPlan computeForkPlan(BuildStep step, MojoExecution execution, BuildPlan buildPlan) {
MojoDescriptor mojoDescriptor = execution.getMojoDescriptor();
PluginDescriptor pluginDescriptor = mojoDescriptor.getPluginDescriptor();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,40 +240,54 @@ void testFilterByScopeNullReturnsAll() {
}

/**
* Tests that {@code filterByScope} filters upstream projects by exact scope match,
* treating null-scoped dependencies as "compile" (Maven default).
* Tests that {@code filterByScope} expands scopes for build ordering.
* "compile" includes compile, provided, system, and null-scoped (Maven default) dependencies
* so that provided/system-scope reactor dependencies are properly ordered.
*/
@Test
void testFilterByScopeMatchesExact() {
MavenProject compileDep = createProjectWithId("g", "compile-dep");
MavenProject providedDep = createProjectWithId("g", "provided-dep");
MavenProject systemDep = createProjectWithId("g", "system-dep");
MavenProject runtimeDep = createProjectWithId("g", "runtime-dep");
MavenProject testDep = createProjectWithId("g", "test-dep");
MavenProject nullScopeDep = createProjectWithId("g", "null-scope-dep");
List<MavenProject> upstream = List.of(compileDep, providedDep, testDep, nullScopeDep);
List<MavenProject> upstream = List.of(compileDep, providedDep, systemDep, runtimeDep, testDep, nullScopeDep);

MavenProject consumer = new MavenProject();
consumer.getDependencies().add(createDependency("g", "compile-dep", "compile"));
consumer.getDependencies().add(createDependency("g", "provided-dep", "provided"));
consumer.getDependencies().add(createDependency("g", "system-dep", "system"));
consumer.getDependencies().add(createDependency("g", "runtime-dep", "runtime"));
consumer.getDependencies().add(createDependency("g", "test-dep", "test"));
consumer.getDependencies().add(createDependency("g", "null-scope-dep", null));

// "compile" matches explicit compile + null-scoped (Maven default is compile)
// "compile" scope expands to compile + provided + system + null-scoped (Maven default)
List<MavenProject> compileFiltered =
BuildPlanExecutor.BuildContext.filterByScope(consumer, upstream, "compile");
assertEquals(2, compileFiltered.size());
assertEquals(4, compileFiltered.size());
assertTrue(compileFiltered.contains(compileDep));
assertTrue(compileFiltered.contains(providedDep));
assertTrue(compileFiltered.contains(systemDep));
assertTrue(compileFiltered.contains(nullScopeDep));

// "provided" matches only provided-scoped
List<MavenProject> providedFiltered =
BuildPlanExecutor.BuildContext.filterByScope(consumer, upstream, "provided");
assertEquals(1, providedFiltered.size());
assertTrue(providedFiltered.contains(providedDep));
// "runtime" scope expands to compile + runtime + null-scoped
List<MavenProject> runtimeFiltered =
BuildPlanExecutor.BuildContext.filterByScope(consumer, upstream, "runtime");
assertEquals(3, runtimeFiltered.size());
assertTrue(runtimeFiltered.contains(compileDep));
assertTrue(runtimeFiltered.contains(runtimeDep));
assertTrue(runtimeFiltered.contains(nullScopeDep));

// "test" matches only test-scoped
// "test" scope expands to all scopes
List<MavenProject> testFiltered = BuildPlanExecutor.BuildContext.filterByScope(consumer, upstream, "test");
assertEquals(1, testFiltered.size());
assertTrue(testFiltered.contains(testDep));
assertEquals(6, testFiltered.size());

// "test-only" scope matches only test-scoped dependencies
List<MavenProject> testOnlyFiltered =
BuildPlanExecutor.BuildContext.filterByScope(consumer, upstream, "test-only");
assertEquals(1, testOnlyFiltered.size());
assertTrue(testOnlyFiltered.contains(testDep));
}

/**
Expand Down
Loading