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 c628ce2192 Upgrade to Scalpel 0.4.3
c628ce2192 is described below

commit c628ce2192855e214eb3e88bd6b6cfcd6e29be1a
Author: James Netherton <[email protected]>
AuthorDate: Wed Oct 7 08:00:05 2026 +0100

    Upgrade to Scalpel 0.4.3
    
    * Version changes in imported BOMs now only affect the modules using them, 
instead of triggering a full build
    * Give the Scalpel analysis step a larger heap
    * Run functional tests for affected modules only via -pl, falling back to 
trim mode for full builds and extensions-core changes
    * Add a functional test step for extensions-support
    * Fix catalog scope detection
    * Detect changed container image properties from the Scalpel report 
changedProperties instead of grepping the git diff of pom.xml. This also 
catches properties whose value changes indirectly, such as fhir.container.image 
via fhir.container.image.base
    * Remove unused incremental-build mojo actions, leaving the analysis that 
CI uses as the only behavior
    
    Co-authored-by: Claude Opus 5.5 <[email protected]>
---
 .github/workflows/ci-build.yaml                    | 103 ++++++-----
 .mvn/extensions.xml                                |   2 +-
 .../camel/quarkus/maven/IncrementalBuildMojo.java  | 195 +++++++++++----------
 3 files changed, 155 insertions(+), 145 deletions(-)

diff --git a/.github/workflows/ci-build.yaml b/.github/workflows/ci-build.yaml
index 1d8ae20539..c8aa7f04bb 100644
--- a/.github/workflows/ci-build.yaml
+++ b/.github/workflows/ci-build.yaml
@@ -105,8 +105,8 @@ env:
   TESTCONTAINERS_RYUK_DISABLED: true
   CHECKOUT_REF: ${{ github.event_name == 'pull_request' && 
github.event.pull_request.user.login == 'dependabot[bot]' && github.head_ref || 
'' }}
   # Scalpel configuration (defaults for all jobs; initial-mvn-install 
overrides SCALPEL_FULL_BUILD_TRIGGERS).
-  # Broad extensions-core/** trigger here is intentional: 
functional-extension-tests uses trim mode,
-  # which actively excludes modules from the build, so any extensions-core 
change should run full tests.
+  # Broad extensions-core/** trigger here is intentional: 
functional-extension-tests falls back to trim mode
+  # when an extensions-core change is detected, so any extensions-core change 
should run full tests.
   SCALPEL_FULL_BUILD_TRIGGERS: 
"-Dscalpel.fullBuildTriggers=poms/build-parent/**,poms/build-parent-it/**,extensions-core/**,.mvn/**,.github/workflows/ci-build.yaml"
   SCALPEL_EXCLUDE_PATHS: 
"-Dscalpel.excludePaths=**.adoc,**.md,docs/**,Jenkinsfile*,LICENSE.txt,NOTICE.txt,KEYS,camel-quarkus-sbom/**,.github/*.sh"
   CQ_MAVEN_SKIP_CHECKS: "-Dformatter.skip -Dimpsort.skip -Denforcer.skip 
-Dcamel-quarkus.update-extension-doc-page.skip"
@@ -196,12 +196,14 @@ jobs:
       cache-key: ${{ steps.maven-cache.outputs.cache-key }}
       # Incremental build job outputs. Keep sorted alphabetically.
       examples-matrix: ${{ steps.set-examples-matrix.outputs.examples-matrix }}
+      functional-test-modules: ${{ 
steps.process-incremental.outputs.functional-test-modules || '{}' }}
       integration-tests-jvm-modules: ${{ 
steps.process-incremental.outputs.integration-tests-jvm-modules || '' }}
       integration-tests-matrix: ${{ 
steps.process-incremental.outputs.use-incremental == 'true' && 
steps.process-incremental.outputs.integration-tests-matrix || 
steps.set-native-matrix-full.outputs.matrix || '{"include":[]}' }}
       integration-tests-modules: ${{ 
steps.incremental-build.outputs.integration-tests-modules }}
       run-catalog-tests: ${{ 
steps.process-incremental.outputs.run-catalog-tests }}
       run-examples: ${{ steps.process-incremental.outputs.run-examples }}
       run-extensions-core-tests: ${{ 
steps.process-incremental.outputs.run-extensions-core-tests }}
+      run-extensions-support-tests: ${{ 
steps.process-incremental.outputs.run-extensions-support-tests }}
       run-extensions-tests: ${{ 
steps.process-incremental.outputs.run-extensions-tests }}
       run-integration-tests-jvm: ${{ 
steps.process-incremental.outputs.run-integration-tests-jvm }}
       run-test-framework-tests: ${{ 
steps.process-incremental.outputs.run-test-framework-tests }}
@@ -263,6 +265,9 @@ jobs:
         id: scalpel-analysis
         if: steps.detect-incremental.outputs.incremental-build == 'true'
         continue-on-error: true
+        env:
+          # Comparing the effective models of the whole reactor needs a much 
larger heap than the build itself
+          MAVEN_OPTS: -Xmx10g
         run: |
           ./mvnw validate -Dquickly \
             ${SCALPEL_ENABLED} \
@@ -281,31 +286,6 @@ jobs:
       - name: Warn on Scalpel failure
         if: steps.scalpel-analysis.outcome == 'failure'
         run: echo "::warning::Scalpel analysis failed, falling back to full 
build"
-      - name: Detect Changed Container Properties
-        id: detect-container-props
-        if: steps.detect-incremental.outputs.incremental-build == 'true' && 
steps.scalpel-analysis.outcome != 'failure'
-        env:
-          EVENT_NAME: ${{ github.event_name }}
-          PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
-          PUSH_BEFORE_SHA: ${{ github.event.before }}
-        run: |
-          # NOTE: We do this manual checking since the container image 
properties are not linked to any other pom.xml in the project.
-          # They are queried dynamically via Java code. This logic enables 
pom.xml to be removed from Scalpel full build triggers.
-
-          CHANGED_CONTAINER_PROPS=""
-          if [ "${EVENT_NAME}" == "pull_request" ]; then
-            BASE_SHA="${PR_BASE_SHA}"
-          else
-            BASE_SHA="${PUSH_BEFORE_SHA}"
-          fi
-          if [ -n "$BASE_SHA" ] && git diff "$BASE_SHA" -- pom.xml | grep -qE 
'[+-].*\.container\.image>'; then
-            CHANGED_CONTAINER_PROPS=$(git diff "$BASE_SHA" -- pom.xml \
-              | grep -E '^\+.*\.container\.image>' \
-              | sed -E 's/.*<([^<\/]+\.container\.image)>.*/\1/' \
-              | paste -sd,)
-            echo "Changed container properties: $CHANGED_CONTAINER_PROPS"
-          fi
-          echo "props=$CHANGED_CONTAINER_PROPS" >> $GITHUB_OUTPUT
       - name: Incremental Build Analysis
         id: incremental-build
         if: steps.detect-incremental.outputs.incremental-build == 'true' && 
steps.scalpel-analysis.outcome != 'failure'
@@ -317,18 +297,14 @@ jobs:
           PROJECT_VERSION=$(./mvnw help:evaluate -Dexpression=project.version 
-q -DforceStdout -N)
           echo "Using camel-quarkus-maven-plugin version: $PROJECT_VERSION"
 
-          CONTAINER_PROPS="${{ steps.detect-container-props.outputs.props }}"
-
           ./mvnw 
org.apache.camel.quarkus:camel-quarkus-maven-plugin:${PROJECT_VERSION}:incremental-build
 \
-            -Dcq.action=analyze \
             -Dcq.useIncrementalBuild=true \
             -Dcq.extensionDirs="extensions/,extensions-jvm/,extensions-core/" \
             
-Dcq.integrationTestDirs="integration-tests/,integration-tests-jvm/" \
             -Dcq.nativeTestsPrefix="integration-tests/" \
             -Dcq.jvmTestsPrefix="integration-tests-jvm/" \
             -Dcq.integrationTestGroupsPrefix="integration-test-groups/" \
-            
-Dcq.functionalScopeDirs="extensions-core/:runExtensionsCoreTests,extensions/:runExtensionsTests,test-framework/:runTestFrameworkTests,tooling/:runToolingTests,catalog/:runCatalogTests"
 \
-            
${CONTAINER_PROPS:+-Dcq.changedContainerProperties="$CONTAINER_PROPS"} \
+            
-Dcq.functionalScopeDirs="extensions-core/:runExtensionsCoreTests,extensions/:runExtensionsTests,extensions-support/:runExtensionsSupportTests,test-framework/:runTestFrameworkTests,tooling/:runToolingTests,catalog/:runCatalogTests"
 \
             -N ${CQ_MAVEN_ARGS}
 
           if [ -f target/incremental-build.json ]; then
@@ -370,11 +346,19 @@ jobs:
             echo "integration-tests-jvm-modules=$(echo "$DATA" | jq -r 
'.integrationTestsJvm.modules')" >> $GITHUB_OUTPUT
             echo "run-extensions-core-tests=$(echo "$DATA" | jq -r 
'.functionalTestScope.runExtensionsCoreTests')" >> $GITHUB_OUTPUT
             echo "run-extensions-tests=$(echo "$DATA" | jq -r 
'.functionalTestScope.runExtensionsTests')" >> $GITHUB_OUTPUT
+            echo "run-extensions-support-tests=$(echo "$DATA" | jq -r 
'.functionalTestScope.runExtensionsSupportTests')" >> $GITHUB_OUTPUT
             echo "run-examples=$(echo "$DATA" | jq -r '.runExamples')" >> 
$GITHUB_OUTPUT
             echo "run-test-framework-tests=$(echo "$DATA" | jq -r 
'.functionalTestScope.runTestFrameworkTests')" >> $GITHUB_OUTPUT
             echo "run-tooling-tests=$(echo "$DATA" | jq -r 
'.functionalTestScope.runToolingTests')" >> $GITHUB_OUTPUT
             echo "run-catalog-tests=$(echo "$DATA" | jq -r 
'.functionalTestScope.runCatalogTests')" >> $GITHUB_OUTPUT
 
+            # Any extensions-core change runs the functional tests with 
Scalpel trim mode instead of only the affected modules
+            if [ "$(echo "$DATA" | jq -r 
'.functionalTestScope.runExtensionsCoreTests')" == "true" ]; then
+              echo "functional-test-modules={}" >> $GITHUB_OUTPUT
+            else
+              echo "functional-test-modules=$(echo "$DATA" | jq -c 
'.functionalTestModules // {}')" >> $GITHUB_OUTPUT
+            fi
+
             {
               echo "### Incremental Native Test Matrix"
               echo ""
@@ -385,6 +369,7 @@ jobs:
             echo "run-integration-tests-jvm=true" >> $GITHUB_OUTPUT
             echo "run-extensions-core-tests=true" >> $GITHUB_OUTPUT
             echo "run-extensions-tests=true" >> $GITHUB_OUTPUT
+            echo "run-extensions-support-tests=true" >> $GITHUB_OUTPUT
             echo "run-examples=true" >> $GITHUB_OUTPUT
             echo "run-test-framework-tests=true" >> $GITHUB_OUTPUT
             echo "run-tooling-tests=true" >> $GITHUB_OUTPUT
@@ -547,6 +532,7 @@ jobs:
       (github.event_name != 'pull_request' || 
!contains(github.event.pull_request.labels.*.name, 'JVM'))
       && (needs.initial-mvn-install.outputs.run-extensions-core-tests == 'true'
       || needs.initial-mvn-install.outputs.run-extensions-tests == 'true'
+      || needs.initial-mvn-install.outputs.run-extensions-support-tests == 
'true'
       || needs.initial-mvn-install.outputs.run-test-framework-tests == 'true'
       || needs.initial-mvn-install.outputs.run-tooling-tests == 'true'
       || needs.initial-mvn-install.outputs.run-catalog-tests == 'true')
@@ -571,13 +557,14 @@ jobs:
           download-maven-repo: 'true'
       - name: cd extensions-core && mvn test
         if: needs.initial-mvn-install.outputs.run-extensions-core-tests == 
'true'
+        env:
+          MODULES: ${{ 
fromJson(needs.initial-mvn-install.outputs.functional-test-modules)['extensions-core']
 }}
         run: |
           cd extensions-core
+          if [ -n "${MODULES}" ]; then MODULE_ARGS="-pl ${MODULES}"; else 
MODULE_ARGS="${SCALPEL_TRIM_ARGS} ${SCALPEL_FULL_BUILD_TRIGGERS} 
${SCALPEL_EXCLUDE_PATHS}"; fi
           ../mvnw ${CQ_MAVEN_ARGS} ${BRANCH_OPTIONS} \
             ${CQ_MAVEN_SKIP_CHECKS} \
-            ${SCALPEL_TRIM_ARGS} \
-            ${SCALPEL_FULL_BUILD_TRIGGERS} \
-            ${SCALPEL_EXCLUDE_PATHS} \
+            ${MODULE_ARGS} \
             --fail-at-end \
             test
       - name: Report test failures
@@ -587,13 +574,14 @@ jobs:
           test-report-xml-base-dir: extensions-core
       - name: cd extensions && mvn test
         if: needs.initial-mvn-install.outputs.run-extensions-tests == 'true'
+        env:
+          MODULES: ${{ 
fromJson(needs.initial-mvn-install.outputs.functional-test-modules)['extensions']
 }}
         run: |
           cd extensions
+          if [ -n "${MODULES}" ]; then MODULE_ARGS="-pl ${MODULES}"; else 
MODULE_ARGS="${SCALPEL_TRIM_ARGS} ${SCALPEL_FULL_BUILD_TRIGGERS} 
${SCALPEL_EXCLUDE_PATHS}"; fi
           ../mvnw ${CQ_MAVEN_ARGS} ${BRANCH_OPTIONS} \
             ${CQ_MAVEN_SKIP_CHECKS} \
-            ${SCALPEL_TRIM_ARGS} \
-            ${SCALPEL_FULL_BUILD_TRIGGERS} \
-            ${SCALPEL_EXCLUDE_PATHS} \
+            ${MODULE_ARGS} \
             --fail-at-end \
             test
       - name: Report test failures
@@ -601,15 +589,34 @@ jobs:
         if: ${{ failure() }}
         with:
           test-report-xml-base-dir: extensions
+      - name: cd extensions-support && mvn package
+        if: needs.initial-mvn-install.outputs.run-extensions-support-tests == 
'true'
+        env:
+          MODULES: ${{ 
fromJson(needs.initial-mvn-install.outputs.functional-test-modules)['extensions-support']
 }}
+        run: |
+          cd extensions-support
+          if [ -n "${MODULES}" ]; then MODULE_ARGS="-pl ${MODULES}"; else 
MODULE_ARGS="${SCALPEL_TRIM_ARGS} ${SCALPEL_FULL_BUILD_TRIGGERS} 
${SCALPEL_EXCLUDE_PATHS}"; fi
+          # package rather than test, so that shaded modules such as 
spring/shade provide their jar to the reactor
+          ../mvnw ${CQ_MAVEN_ARGS} ${BRANCH_OPTIONS} \
+            ${CQ_MAVEN_SKIP_CHECKS} \
+            ${MODULE_ARGS} \
+            --fail-at-end \
+            package
+      - name: Report test failures
+        uses: ./.github/actions/test-summary-report
+        if: ${{ failure() }}
+        with:
+          test-report-xml-base-dir: extensions-support
       - name: cd test-framework && mvn test
         if: needs.initial-mvn-install.outputs.run-test-framework-tests == 
'true'
+        env:
+          MODULES: ${{ 
fromJson(needs.initial-mvn-install.outputs.functional-test-modules)['test-framework']
 }}
         run: |
           cd test-framework
+          if [ -n "${MODULES}" ]; then MODULE_ARGS="-pl ${MODULES}"; else 
MODULE_ARGS="${SCALPEL_TRIM_ARGS} ${SCALPEL_FULL_BUILD_TRIGGERS} 
${SCALPEL_EXCLUDE_PATHS}"; fi
           ../mvnw ${CQ_MAVEN_ARGS} ${BRANCH_OPTIONS} \
             ${CQ_MAVEN_SKIP_CHECKS} \
-            ${SCALPEL_TRIM_ARGS} \
-            ${SCALPEL_FULL_BUILD_TRIGGERS} \
-            ${SCALPEL_EXCLUDE_PATHS} \
+            ${MODULE_ARGS} \
             --fail-at-end \
             test
       - name: Report test failures
@@ -619,13 +626,14 @@ jobs:
           test-report-xml-base-dir: test-framework
       - name: cd tooling && mvn verify
         if: needs.initial-mvn-install.outputs.run-tooling-tests == 'true'
+        env:
+          MODULES: ${{ 
fromJson(needs.initial-mvn-install.outputs.functional-test-modules)['tooling'] 
}}
         run: |
           cd tooling
+          if [ -n "${MODULES}" ]; then MODULE_ARGS="-pl ${MODULES}"; else 
MODULE_ARGS="${SCALPEL_TRIM_ARGS} ${SCALPEL_FULL_BUILD_TRIGGERS} 
${SCALPEL_EXCLUDE_PATHS}"; fi
           ../mvnw ${CQ_MAVEN_ARGS} ${BRANCH_OPTIONS} \
             ${CQ_MAVEN_SKIP_CHECKS} \
-            ${SCALPEL_TRIM_ARGS} \
-            ${SCALPEL_FULL_BUILD_TRIGGERS} \
-            ${SCALPEL_EXCLUDE_PATHS} \
+            ${MODULE_ARGS} \
             --fail-at-end \
             verify
       - name: Report test failures
@@ -635,13 +643,14 @@ jobs:
           test-report-xml-base-dir: tooling
       - name: cd catalog && mvn test
         if: needs.initial-mvn-install.outputs.run-catalog-tests == 'true'
+        env:
+          MODULES: ${{ 
fromJson(needs.initial-mvn-install.outputs.functional-test-modules)['catalog'] 
}}
         run: |
           cd catalog
+          if [ -n "${MODULES}" ]; then MODULE_ARGS="-pl ${MODULES}"; else 
MODULE_ARGS="${SCALPEL_TRIM_ARGS} ${SCALPEL_FULL_BUILD_TRIGGERS} 
${SCALPEL_EXCLUDE_PATHS}"; fi
           ../mvnw ${CQ_MAVEN_ARGS} ${BRANCH_OPTIONS} \
             ${CQ_MAVEN_SKIP_CHECKS} \
-            ${SCALPEL_TRIM_ARGS} \
-            ${SCALPEL_FULL_BUILD_TRIGGERS} \
-            ${SCALPEL_EXCLUDE_PATHS} \
+            ${MODULE_ARGS} \
             test
       - name: Report test failures
         uses: ./.github/actions/test-summary-report
diff --git a/.mvn/extensions.xml b/.mvn/extensions.xml
index 0cf409fa71..a6935fd899 100644
--- a/.mvn/extensions.xml
+++ b/.mvn/extensions.xml
@@ -2,7 +2,7 @@
        <extension>
                <groupId>eu.maveniverse.maven.scalpel</groupId>
                <artifactId>extension</artifactId>
-               <version>0.3.10</version>
+               <version>0.4.3</version>
        </extension>
        <extension>
                <groupId>com.gradle</groupId>
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 fb244e6f28..a492260b93 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
@@ -42,23 +42,13 @@ import org.apache.maven.plugins.annotations.Mojo;
 import org.apache.maven.plugins.annotations.Parameter;
 
 /**
- * Unified mojo for incremental build analysis and matrix generation.
- * <p>
- * Supports multiple actions via the {@code -Dcq.action} parameter:
- * <ul>
- * <li>{@code analyze} - Performs all analysis operations and outputs 
comprehensive JSON (recommended)</li>
- * <li>{@code filter-modules} - Extracts affected modules from Scalpel 
report</li>
- * <li>{@code native-matrix} - Generates native test matrix with balanced 
distribution</li>
- * <li>{@code alternate-jvm-matrix} - Generates alternate JVM test matrix</li>
- * <li>{@code functional-scope} - Detects which functional test scopes are 
affected</li>
- * <li>{@code jvm-tests} - Detects affected JVM-only test modules</li>
- * </ul>
+ * Analyzes the Scalpel report and outputs JSON describing the affected 
modules, the native test matrix, the functional
+ * test scope and the affected JVM-only tests.
  * <p>
  * Usage:
  *
  * <pre>
  * mvn org.apache.camel.quarkus:camel-quarkus-maven-plugin:incremental-build \
- *   -Dcq.action=analyze \
  *   -Dcq.useIncrementalBuild=true \
  *   -N
  * </pre>
@@ -78,20 +68,6 @@ public class IncrementalBuildMojo extends AbstractMojo {
 
     private static final String MULTI_MODULE_DIR_PLACEHOLDER = 
"${maven.multiModuleProjectDirectory}/";
 
-    /**
-     * Action to perform. Supported values:
-     * <ul>
-     * <li>analyze - Full analysis (all operations)</li>
-     * <li>filter-modules - Extract affected modules</li>
-     * <li>native-matrix - Generate native test matrix</li>
-     * <li>alternate-jvm-matrix - Generate alternate JVM matrix</li>
-     * <li>functional-scope - Detect functional test scope</li>
-     * <li>jvm-tests - Detect JVM-only tests</li>
-     * </ul>
-     */
-    @Parameter(property = "cq.action", required = true)
-    String action;
-
     /**
      * Path to Scalpel's JSON report file
      */
@@ -174,9 +150,9 @@ public class IncrementalBuildMojo extends AbstractMojo {
      * Comma-separated list of directory prefixes for functional test scope 
detection.
      * Format: prefix:scopeName
      * Default:
-     * 
extensions-core/:runExtensionsCoreTests,extensions/:runExtensionsTests,test-framework/:runTestFrameworkTests,tooling/:runToolingTests,catalog/:runCatalogTests
+     * 
extensions-core/:runExtensionsCoreTests,extensions/:runExtensionsTests,extensions-support/:runExtensionsSupportTests,test-framework/:runTestFrameworkTests,tooling/:runToolingTests,catalog/:runCatalogTests
      */
-    @Parameter(property = "cq.functionalScopeDirs", defaultValue = 
"extensions-core/:runExtensionsCoreTests,extensions/:runExtensionsTests,test-framework/:runTestFrameworkTests,tooling/:runToolingTests,catalog/:runCatalogTests")
+    @Parameter(property = "cq.functionalScopeDirs", defaultValue = 
"extensions-core/:runExtensionsCoreTests,extensions/:runExtensionsTests,extensions-support/:runExtensionsSupportTests,test-framework/:runTestFrameworkTests,tooling/:runToolingTests,catalog/:runCatalogTests")
     String functionalScopeDirs;
 
     /**
@@ -186,15 +162,6 @@ public class IncrementalBuildMojo extends AbstractMojo {
     @Parameter(property = "cq.integrationTestSupportPrefix", defaultValue = 
"integration-tests-support/")
     String integrationTestSupportPrefix;
 
-    /**
-     * Comma-separated list of changed container image property names (e.g.
-     * {@code kafka.container.image,mysql.container.image}).
-     * When set, TestResource.java files are scanned to find which test 
modules reference these
-     * properties, and those modules are added to the affected set.
-     */
-    @Parameter(property = "cq.changedContainerProperties")
-    String changedContainerProperties;
-
     /**
      * Project root directory used for scanning TestResource files.
      */
@@ -208,35 +175,8 @@ public class IncrementalBuildMojo extends AbstractMojo {
     @Override
     public void execute() throws MojoExecutionException, MojoFailureException {
         try {
-            Map<String, Object> result;
-
-            switch (action) {
-            case "analyze":
-                result = performFullAnalysis();
-                break;
-            case "filter-modules":
-                result = filterModules();
-                break;
-            case "native-matrix": {
-                ScalpelReport report = readScalpelReport();
-                ContainerAffectedModules containerModules = 
detectContainerAffectedModules();
-                List<String> modules = (List<String>) filterModules(report, 
containerModules).get("modules");
-                result = generateNativeMatrix(modules);
-                break;
-            }
-            case "functional-scope":
-                result = detectFunctionalScope(readScalpelReport());
-                break;
-            case "jvm-tests":
-                result = detectJvmTests(readScalpelReport(), 
detectContainerAffectedModules());
-                break;
-            default:
-                throw new MojoExecutionException("Unknown action: " + action + 
". Supported: analyze, filter-modules, "
-                        + "native-matrix, alternate-jvm-matrix, 
functional-scope, jvm-tests");
-            }
-
-            writeOutput(result);
-            getLog().info("Incremental build analysis complete (action=" + 
action + ")");
+            writeOutput(performFullAnalysis());
+            getLog().info("Incremental build analysis complete");
 
         } catch (Exception e) {
             throw new MojoExecutionException("Failed to execute incremental 
build analysis", e);
@@ -247,7 +187,7 @@ public class IncrementalBuildMojo extends AbstractMojo {
         Map<String, Object> result = new LinkedHashMap<>();
 
         ScalpelReport report = readScalpelReport();
-        ContainerAffectedModules containerModules = 
detectContainerAffectedModules();
+        ContainerAffectedModules containerModules = 
detectContainerAffectedModules(report);
 
         Map<String, Object> moduleData = filterModules(report, 
containerModules);
         result.put("incrementalBuild", moduleData.get("incrementalBuild"));
@@ -257,6 +197,7 @@ public class IncrementalBuildMojo extends AbstractMojo {
         List<String> modules = (List<String>) moduleData.get("modules");
         result.put("nativeTestMatrix", generateNativeMatrix(modules));
         result.put("functionalTestScope", detectFunctionalScope(report));
+        result.put("functionalTestModules", 
detectFunctionalTestModules(report));
         result.put("integrationTestsJvm", detectJvmTests(report, 
containerModules));
         result.put("runExamples", shouldRunExamples(report));
 
@@ -268,9 +209,20 @@ public class IncrementalBuildMojo extends AbstractMojo {
             return null;
         }
         Map<String, Object> raw = 
jsonMapper.readValue(scalpelReportJson.toFile(), JSON_TYPE_REF);
-        return expandGeneratedSourceConsumers(
+        ScalpelReport report = expandGeneratedSourceConsumers(
                 Boolean.TRUE.equals(raw.get("fullBuildTriggered")),
                 (List<Map<String, Object>>) raw.get("affectedModules"));
+
+        // Matches the properties written by 
tooling/scripts/generate-test-containers-config-properties.groovy
+        List<String> changedProperties = (List<String>) 
raw.get("changedProperties");
+        if (changedProperties != null) {
+            for (String property : changedProperties) {
+                if (property.endsWith("container.image")) {
+                    report.changedContainerProperties.add(property);
+                }
+            }
+        }
+        return report;
     }
 
     /**
@@ -461,6 +413,11 @@ public class IncrementalBuildMojo extends AbstractMojo {
          * grouped module name heuristic is skipped for these.
          */
         final Set<String> resolvedPaths;
+        /**
+         * Changed container image properties. Test modules read these at 
runtime via a generated config file rather
+         * than referencing them in their POM, so Scalpel cannot attribute the 
change to them.
+         */
+        final Set<String> changedContainerProperties = new LinkedHashSet<>();
 
         ScalpelReport(boolean fullBuildTriggered, List<Map<String, Object>> 
affectedModules,
                 Set<String> resolvedPaths) {
@@ -470,10 +427,6 @@ public class IncrementalBuildMojo extends AbstractMojo {
         }
     }
 
-    private Map<String, Object> filterModules() throws IOException {
-        return filterModules(readScalpelReport(), 
detectContainerAffectedModules());
-    }
-
     private Map<String, Object> filterModules(ScalpelReport report, 
ContainerAffectedModules containerModules) {
         Map<String, Object> result = new LinkedHashMap<>();
 
@@ -601,24 +554,14 @@ public class IncrementalBuildMojo extends AbstractMojo {
      * modules depend on the affected support module via POM dependency 
grep</li>
      * </ul>
      */
-    private ContainerAffectedModules detectContainerAffectedModules() throws 
IOException {
+    private ContainerAffectedModules 
detectContainerAffectedModules(ScalpelReport report) throws IOException {
         ContainerAffectedModules result = new ContainerAffectedModules();
 
-        if (changedContainerProperties == null || 
changedContainerProperties.isBlank()) {
+        if (report == null || report.changedContainerProperties.isEmpty()) {
             return result;
         }
 
-        Set<String> changedProps = new LinkedHashSet<>();
-        for (String prop : changedContainerProperties.split(",")) {
-            String trimmed = prop.trim();
-            if (!trimmed.isEmpty()) {
-                changedProps.add(trimmed);
-            }
-        }
-
-        if (changedProps.isEmpty()) {
-            return result;
-        }
+        Set<String> changedProps = report.changedContainerProperties;
 
         getLog().info("Scanning for changed container properties: " + 
changedProps);
 
@@ -809,18 +752,9 @@ public class IncrementalBuildMojo extends AbstractMojo {
     }
 
     private Map<String, Object> detectFunctionalScope(ScalpelReport report) {
-        Map<String, String> prefixToScope = new LinkedHashMap<>();
+        Map<String, String> prefixToScope = functionalScopePrefixes();
         Map<String, Boolean> scope = new LinkedHashMap<>();
-
-        for (String entry : functionalScopeDirs.split(",")) {
-            String[] parts = entry.trim().split(":");
-            if (parts.length == 2) {
-                String prefix = parts[0].trim();
-                String scopeName = parts[1].trim();
-                prefixToScope.put(prefix, scopeName);
-                scope.put(scopeName, false);
-            }
-        }
+        prefixToScope.values().forEach(scopeName -> scope.put(scopeName, 
false));
 
         if (report == null) {
             scope.replaceAll((k, v) -> true);
@@ -840,7 +774,7 @@ public class IncrementalBuildMojo extends AbstractMojo {
 
             // Check each prefix and set corresponding scope flag
             for (Map.Entry<String, String> entry : prefixToScope.entrySet()) {
-                if (path.startsWith(entry.getKey())) {
+                if (relativeToScopeDir(normalizePath(path), entry.getKey()) != 
null) {
                     scope.put(entry.getValue(), true);
                 }
             }
@@ -851,6 +785,73 @@ public class IncrementalBuildMojo extends AbstractMojo {
         return new LinkedHashMap<>(scope);
     }
 
+    /**
+     * Returns the affected modules of each functional scope directory as a 
comma-separated list of paths relative to
+     * that directory, suitable for passing to {@code -pl} when building from 
within it. Directories without affected
+     * modules are omitted. For full builds the result is empty, meaning that 
each scope must be built in its entirety.
+     * <p>
+     * The affected modules come from the Scalpel report of the whole reactor. 
Scalpel running within a scope directory
+     * cannot see changes outside of it, such as a dependency version change 
in the root {@code pom.xml} that reaches
+     * the scope modules via the BOM.
+     */
+    private Map<String, Object> detectFunctionalTestModules(ScalpelReport 
report) {
+        Map<String, Object> result = new LinkedHashMap<>();
+        if (report == null || report.fullBuildTriggered) {
+            return result;
+        }
+
+        for (String prefix : functionalScopePrefixes().keySet()) {
+            Set<String> modules = new LinkedHashSet<>();
+            for (Map<String, Object> module : report.affectedModules) {
+                String path = (String) module.get("path");
+                if (path == null || "UPSTREAM".equals(module.get("category"))) 
{
+                    continue;
+                }
+
+                String relativePath = relativeToScopeDir(normalizePath(path), 
prefix);
+                if (relativePath != null) {
+                    modules.add(relativePath.isEmpty() ? "." : relativePath);
+                }
+            }
+
+            if (!modules.isEmpty()) {
+                result.put(normalizePath(prefix), String.join(",", modules));
+            }
+        }
+
+        getLog().info("Functional test modules: " + result);
+        return result;
+    }
+
+    /**
+     * Returns the functional scope directory prefixes, mapped to their scope 
names.
+     */
+    private Map<String, String> functionalScopePrefixes() {
+        Map<String, String> prefixToScope = new LinkedHashMap<>();
+        for (String entry : functionalScopeDirs.split(",")) {
+            String[] parts = entry.trim().split(":");
+            if (parts.length == 2) {
+                prefixToScope.put(parts[0].trim(), parts[1].trim());
+            }
+        }
+        return prefixToScope;
+    }
+
+    /**
+     * Returns {@code path} relative to the scope directory {@code prefix}, an 
empty string if {@code path} is the scope
+     * directory itself, or {@code null} if it is outside of it.
+     */
+    private static String relativeToScopeDir(String path, String prefix) {
+        String dir = normalizePath(prefix);
+        if (path.equals(dir)) {
+            return "";
+        }
+        if (path.startsWith(dir + "/")) {
+            return path.substring(dir.length() + 1);
+        }
+        return null;
+    }
+
     private Map<String, Object> detectJvmTests(ScalpelReport report, 
ContainerAffectedModules containerModules) {
         Map<String, Object> result = new LinkedHashMap<>();
         result.put("runTests", false);

Reply via email to