This is an automated email from the ASF dual-hosted git repository. jamesnetherton pushed a commit to branch 3.40.x in repository https://gitbox.apache.org/repos/asf/camel-quarkus.git
commit 79b0d8551cdad60cd7c7503247c701fa443e0f2f 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]> (cherry picked from commit c628ce2192855e214eb3e88bd6b6cfcd6e29be1a) --- .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 30b623f4a5..3ca8415dc3 100644 --- a/.github/workflows/ci-build.yaml +++ b/.github/workflows/ci-build.yaml @@ -109,8 +109,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" @@ -200,12 +200,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 }} @@ -281,6 +283,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} \ @@ -299,31 +304,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' @@ -335,18 +315,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 @@ -388,11 +364,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 "" @@ -403,6 +387,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 @@ -565,6 +550,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') @@ -589,13 +575,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 @@ -605,13 +592,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 @@ -619,15 +607,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 @@ -637,13 +644,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 @@ -653,13 +661,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 a935578ba7..665b22c899 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);
