This is an automated email from the ASF dual-hosted git repository.
jamesnetherton pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-quarkus.git
The following commit(s) were added to refs/heads/main by this push:
new 8119d8fd981 Incremental build: only include source consumers of an
aggregator when their sources changed
8119d8fd981 is described below
commit 8119d8fd981f7abef63b1cdec62acefa4f7c999a
Author: James Netherton <[email protected]>
AuthorDate: Fri Nov 13 11:27:39 2026 +0000
Incremental build: only include source consumers of an aggregator when
their sources changed
An affected aggregator pom such as integration-tests or
integration-test-groups/xml
previously pulled in every module consuming a copy-tests or group-tests
source
directory beneath it, so editing its module list scheduled unrelated tests.
For example, #9486 scheduled langchain4j-agent-ql4j and every grouped test
module
because it removed a module from integration-tests/pom.xml.
Use the Scalpel report changedFiles to only match an aggregator above a
source
directory when one of the changed files lies inside that directory, as when
a
module beneath it is added or removed. Reports without changedFiles keep the
previous behavior.
Co-authored-by: Claude Opus 5.5 <[email protected]>
---
.../camel/quarkus/maven/IncrementalBuildMojo.java | 47 +++++++++++++++----
.../quarkus/maven/IncrementalBuildMojoTest.java | 52 ++++++++++++++++++++++
2 files changed, 90 insertions(+), 9 deletions(-)
diff --git
a/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/IncrementalBuildMojo.java
b/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/IncrementalBuildMojo.java
index 90720bbe668..22db0422664 100644
---
a/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/IncrementalBuildMojo.java
+++
b/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/IncrementalBuildMojo.java
@@ -211,7 +211,8 @@ public class IncrementalBuildMojo extends AbstractMojo {
Map<String, Object> raw =
jsonMapper.readValue(scalpelReportJson.toFile(), JSON_TYPE_REF);
ScalpelReport report = expandGeneratedSourceConsumers(
Boolean.TRUE.equals(raw.get("fullBuildTriggered")),
- (List<Map<String, Object>>) raw.get("affectedModules"));
+ (List<Map<String, Object>>) raw.get("affectedModules"),
+ (List<String>) raw.get("changedFiles"));
// Matches the properties written by
tooling/scripts/generate-test-containers-config-properties.groovy
List<String> changedProperties = (List<String>)
raw.get("changedProperties");
@@ -235,12 +236,14 @@ public class IncrementalBuildMojo extends AbstractMojo {
* Likewise {@code integration-tests-jvm/xml-grouped} groups the modules
under
* {@code integration-test-groups/xml/jvm}.
* <p>
- * Consumers are matched both for changes inside a declared source
directory and for changes at or above it, so
- * that a change to an aggregator pom such as {@code
integration-test-groups/xml} pulls in every consumer beneath
- * it.
+ * Consumers are matched for affected modules inside a declared source
directory. An affected aggregator above it,
+ * such as {@code integration-tests} or {@code
integration-test-groups/xml}, only matches when one of
+ * {@code changedFiles} lies inside the source directory, as when a module
beneath it is added or removed. Other
+ * aggregator changes, like listing a module outside the source directory,
do not alter the consumed sources.
+ * Without {@code changedFiles}, every affected aggregator matches.
*/
private ScalpelReport expandGeneratedSourceConsumers(boolean
fullBuildTriggered,
- List<Map<String, Object>> affectedModules) throws IOException {
+ List<Map<String, Object>> affectedModules, List<String>
changedFiles) throws IOException {
if (affectedModules == null || affectedModules.isEmpty()) {
return new ScalpelReport(fullBuildTriggered, affectedModules,
Set.of());
@@ -254,9 +257,17 @@ public class IncrementalBuildMojo extends AbstractMojo {
}
}
+ Set<String> normalizedChangedFiles = null;
+ if (changedFiles != null) {
+ normalizedChangedFiles = new LinkedHashSet<>();
+ for (String changedFile : changedFiles) {
+ normalizedChangedFiles.add(normalizePath(changedFile));
+ }
+ }
+
// Paths whose consumers are known precisely, so that heuristics
elsewhere can be skipped for them
Set<String> resolvedPaths = new LinkedHashSet<>();
- Set<String> consumers = findTransitiveConsumers(affectedPaths,
resolvedPaths);
+ Set<String> consumers = findTransitiveConsumers(affectedPaths,
resolvedPaths, normalizedChangedFiles);
if (consumers.isEmpty()) {
return new ScalpelReport(fullBuildTriggered, affectedModules,
resolvedPaths);
}
@@ -276,8 +287,12 @@ public class IncrementalBuildMojo extends AbstractMojo {
* Returns the paths of all modules transitively consuming the sources of
any of {@code paths}, excluding
* {@code paths} themselves. Input paths that matched a declared source
directory are added to
* {@code resolvedPaths} when it is non-null.
+ * <p>
+ * When {@code changedFiles} is non-null, a path above a source directory
only matches its consumers if one of
+ * {@code changedFiles} lies inside that source directory.
*/
- private Set<String> findTransitiveConsumers(Set<String> paths, Set<String>
resolvedPaths) throws IOException {
+ private Set<String> findTransitiveConsumers(Set<String> paths, Set<String>
resolvedPaths, Set<String> changedFiles)
+ throws IOException {
Map<String, Set<String>> sourceToConsumers =
generatedSourceConsumers();
if (sourceToConsumers.isEmpty()) {
return Set.of();
@@ -299,9 +314,14 @@ public class IncrementalBuildMojo extends AbstractMojo {
continue;
}
+ // Resolved even when skipped below, so that the grouped
module name heuristic does not re-add
+ // the consumers of an aggregator whose change leaves
their sources untouched
if (resolvedPaths != null) {
resolvedPaths.add(path);
}
+ if (aboveSource && changedFiles != null &&
!containsPathInside(changedFiles, sourcePath)) {
+ continue;
+ }
for (String consumer : entry.getValue()) {
if (seen.add(consumer)) {
getLog().info("Including " + consumer + " which
consumes sources of " + sourcePath);
@@ -317,6 +337,15 @@ public class IncrementalBuildMojo extends AbstractMojo {
return consumers;
}
+ private static boolean containsPathInside(Set<String> paths, String dir) {
+ for (String path : paths) {
+ if (path.startsWith(dir + "/")) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Lazily computed and cached, since the underlying source tree scan is
shared by Scalpel report expansion and
* container property detection.
@@ -627,7 +656,7 @@ public class IncrementalBuildMojo extends AbstractMojo {
paths.add(normalizePath(jvmTestsPrefix.trim()) + "/" + moduleName);
}
- addTestModules(findTransitiveConsumers(paths, null), result);
+ addTestModules(findTransitiveConsumers(paths, null, null), result);
}
/**
@@ -639,7 +668,7 @@ public class IncrementalBuildMojo extends AbstractMojo {
private void addGroupingModules(String groupName, Path moduleDir,
ContainerAffectedModules result)
throws IOException {
String path =
normalizePath(projectRootDir.relativize(moduleDir).toString());
- Set<String> consumers = findTransitiveConsumers(Set.of(path), null);
+ Set<String> consumers = findTransitiveConsumers(Set.of(path), null,
null);
if (consumers.isEmpty()) {
result.nativeModules.add(groupName + "-grouped");
} else {
diff --git
a/tooling/maven-plugin/src/test/java/org/apache/camel/quarkus/maven/IncrementalBuildMojoTest.java
b/tooling/maven-plugin/src/test/java/org/apache/camel/quarkus/maven/IncrementalBuildMojoTest.java
index f56e1886c6b..a518d7d7828 100644
---
a/tooling/maven-plugin/src/test/java/org/apache/camel/quarkus/maven/IncrementalBuildMojoTest.java
+++
b/tooling/maven-plugin/src/test/java/org/apache/camel/quarkus/maven/IncrementalBuildMojoTest.java
@@ -368,6 +368,50 @@ class IncrementalBuildMojoTest {
assertEquals(Map.of("runTests", true, "modules", "xml-grouped"),
output.get("integrationTestsJvm"));
}
+ @Test
+ void groupTestsAggregatorChangeOutsideSourceDirsIncludesNoConsumers()
throws Exception {
+
Files.createDirectories(projectRoot.resolve("integration-test-groups/xml/jvm"));
+
Files.createDirectories(projectRoot.resolve("integration-test-groups/xml/native"));
+ writeConsumerPom("integration-tests-jvm/xml-grouped", "group",
"integration-test-groups/xml/jvm");
+ writeConsumerPom("integration-tests/xml-grouped", "group",
"integration-test-groups/xml/native");
+ writeReport(false, List.of(module("integration-test-groups/xml",
"DIRECT")), null,
+ List.of("integration-test-groups/xml/pom.xml"));
+
+ Map<String, Object> output = execute();
+
+ assertEquals(true, output.get("incrementalBuild"));
+ assertEquals(List.of(), output.get("affectedModules"));
+ assertEquals(Map.of("runTests", false, "modules", ""),
output.get("integrationTestsJvm"));
+ }
+
+ @Test
+ void
groupTestsAggregatorChangeIncludesConsumersOfSourceDirWithChangedFiles() throws
Exception {
+
Files.createDirectories(projectRoot.resolve("integration-test-groups/xml/jvm"));
+
Files.createDirectories(projectRoot.resolve("integration-test-groups/xml/native"));
+ writeConsumerPom("integration-tests-jvm/xml-grouped", "group",
"integration-test-groups/xml/jvm");
+ writeConsumerPom("integration-tests/xml-grouped", "group",
"integration-test-groups/xml/native");
+ writeReport(false, List.of(module("integration-test-groups/xml",
"DIRECT")), null,
+ List.of("integration-test-groups/xml/pom.xml",
+ "integration-test-groups/xml/jvm/removed/pom.xml"));
+
+ Map<String, Object> output = execute();
+
+ assertEquals(List.of(), output.get("affectedModules"));
+ assertEquals(Map.of("runTests", true, "modules", "xml-grouped"),
output.get("integrationTestsJvm"));
+ }
+
+ @Test
+ void copyTestsConsumersNotIncludedForUnrelatedAggregatorChange() throws
Exception {
+ Files.createDirectories(projectRoot.resolve("integration-tests/foo"));
+ writeConsumerPom("integration-tests/foo-copy", "copy",
"integration-tests/foo");
+ writeReport(false, List.of(
+ module("integration-tests", "DIRECT"),
+ module("integration-tests/bar", "DIRECT")), null,
+ List.of("integration-tests/pom.xml",
"integration-tests/bar/pom.xml"));
+
+ assertEquals(List.of("bar"), execute().get("affectedModules"));
+ }
+
@Test
void groupedModuleNameHeuristicUsedWithoutDeclaredSourceDir() throws
Exception {
writeReport(false, List.of(module("integration-test-groups/foo/bar",
"DIRECT")));
@@ -513,9 +557,17 @@ class IncrementalBuildMojoTest {
private void writeReport(boolean fullBuildTriggered, List<Map<String,
Object>> affectedModules,
List<String> changedProperties) throws IOException {
+ writeReport(fullBuildTriggered, affectedModules, changedProperties,
null);
+ }
+
+ private void writeReport(boolean fullBuildTriggered, List<Map<String,
Object>> affectedModules,
+ List<String> changedProperties, List<String> changedFiles) throws
IOException {
Map<String, Object> report = new LinkedHashMap<>();
report.put("fullBuildTriggered", fullBuildTriggered);
report.put("affectedModules", affectedModules);
+ if (changedFiles != null) {
+ report.put("changedFiles", changedFiles);
+ }
if (changedProperties != null) {
report.put("changedProperties", changedProperties);
}