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);
         }

Reply via email to