From 4585eebfafcea639be8b7bdebf36875da979bdc6 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Fri, 26 Jun 2026 13:10:30 +0000 Subject: [PATCH 1/6] ci: add Scalpel shadow comparison for skip-tests mode validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a shadow comparison section to CI PR comments showing what Maveniverse Scalpel's skip-tests mode would have tested — without affecting actual test execution. Changes: - incremental-build.sh: configure Scalpel with skipTestsForDownstreamModules and fetchBaseBranch=false, add writeScalpelComparison() for collapsible PR comment section with failure reporting - pr-build-main.yml / sonar-build.yml: add base branch fetch step for Scalpel's merge-base detection in shallow CI clones, restore checkout v7 - CI-ARCHITECTURE.md: document shadow comparison approach and configuration - Scalpel upgraded to 0.3.7: fixes inflated affectedModules count for parent POM property changes (scalpel#39) and skipTestsForDownstreamModules --- .github/CI-ARCHITECTURE.md | 24 +++- .../incremental-build/incremental-build.sh | 124 ++++++++++++++++-- .github/workflows/pr-build-main.yml | 7 + .github/workflows/sonar-build.yml | 5 +- .mvn/extensions.xml | 2 +- 5 files changed, 147 insertions(+), 15 deletions(-) diff --git a/.github/CI-ARCHITECTURE.md b/.github/CI-ARCHITECTURE.md index 948f3e11b96a3..b407c94dc4333 100644 --- a/.github/CI-ARCHITECTURE.md +++ b/.github/CI-ARCHITECTURE.md @@ -154,12 +154,30 @@ Both methods run in parallel. Results are merged (union) before testing. This le 2. **No regression** — If Scalpel fails, grep results are still used 3. **Gradual migration** — Once Scalpel is validated, grep can be removed -Scalpel is configured permanently in `.mvn/extensions.xml` (version `0.1.0`). On developer machines it is a no-op — without CI environment variables (`GITHUB_BASE_REF`), no base branch is detected and Scalpel returns immediately. The `mvn validate` with report mode adds ~60-90 seconds in CI. - -Note: the script overrides `fullBuildTriggers` to empty (`-Dscalpel.fullBuildTriggers=`) because Scalpel's default (`.mvn/**`) would trigger a full build whenever `.mvn/extensions.xml` itself changes (e.g., Dependabot bumping Scalpel). +Scalpel is configured permanently in `.mvn/extensions.xml`. On developer machines it is a no-op (disabled via `-Dscalpel.enabled=false` in `.mvn/maven.config`). The CI script overrides this with `-Dscalpel.enabled=true`. The `mvn validate` with report mode adds ~60-90 seconds in CI. Scalpel is only invoked when a **subdirectory** `pom.xml` is changed (e.g. `parent/pom.xml`, `components/camel-kafka/pom.xml`). Changes to the **root** `pom.xml` are excluded because it contains build-infrastructure config (license plugin, checkstyle, etc.) that does not affect module compilation or test behavior. Without this filter, Scalpel would report every module as affected since they all inherit from the root POM. +#### Scalpel features used for shadow comparison + +- **Source-set-aware propagation**: Distinguishes test-jar dependencies from regular dependencies. A module that depends only on another module's test-jar (e.g., `camel-core`'s test-jar with test utilities) is propagated through the `TEST` source set, not the `MAIN` source set. This prevents a change to test utilities from triggering tests in all ~500 modules that depend on `camel-core`. +- **`skipTestsForDownstreamModules`**: Allows specifying modules whose tests should be skipped when they appear as downstream dependents (mirrors the `EXCLUSION_LIST` in `incremental-build.sh`). This gives Scalpel an accurate picture of what skip-tests mode would actually test. + +#### Shadow comparison + +Scalpel runs in **shadow mode**: it observes what skip-tests mode *would* have done and reports it in a collapsible section of the PR comment, without affecting actual test execution. This allows the team to validate Scalpel's decisions across many PRs before switching to Scalpel-driven test execution. + +The shadow comparison section shows: +- How many modules Scalpel would test (direct + downstream) +- How many downstream modules would have tests skipped (generated code, meta-modules) +- The full list of modules in each category + +#### Configuration notes + +The script overrides `fullBuildTriggers` to empty (`-Dscalpel.fullBuildTriggers=`) because Scalpel's default (`.mvn/**`) would trigger a full build whenever `.mvn/extensions.xml` itself changes (e.g., Dependabot bumping Scalpel). + +The grep-based script fetches the PR diff via the GitHub REST API (unchanged). Scalpel uses local git history to compare effective POM models — the CI workflow pre-fetches the base branch (`git fetch --deepen=200` + fetch of `origin/main`) so Scalpel's JGit can find the merge-base. Scalpel disables its built-in JGit fetch (`-Dscalpel.fetchBaseBranch=false`) to avoid JGit issues in shallow CI clones. The `--deepen=200` fetches only commit metadata (not file blobs), adding ~2-3 seconds to the job. + ## Manual Integration Test Advisories Some modules are excluded from CI's `-amd` expansion (the `EXCLUSION_LIST`) because they are generated code, meta-modules, or expensive integration test suites. When a contributor changes one of these modules, CI cannot automatically test all downstream effects. diff --git a/.github/actions/incremental-build/incremental-build.sh b/.github/actions/incremental-build/incremental-build.sh index 6bd172ef7ae0b..a502bb3d79c1f 100755 --- a/.github/actions/incremental-build/incremental-build.sh +++ b/.github/actions/incremental-build/incremental-build.sh @@ -243,19 +243,20 @@ analyzePomDependencies() { runScalpelDetection() { echo " Running Scalpel change detection..." - # Ensure sufficient git history for JGit merge-base detection - # (CI uses shallow clones; Scalpel needs to find the merge base) - git fetch origin main:refs/remotes/origin/main --depth=200 2>/dev/null || true - git fetch --deepen=200 2>/dev/null || true - # Scalpel is permanently configured in .mvn/extensions.xml. - # On developer machines it's a no-op (no GITHUB_BASE_REF → no base branch detected). + # On developer machines it's a no-op (disabled via -Dscalpel.enabled=false in .mvn/maven.config). + # The CI script overrides this with -Dscalpel.enabled=true. + # Base branch is pre-fetched by the CI workflow (fetchBaseBranch=false). # Run Maven validate with Scalpel in report mode: # - mode=report: write JSON report without trimming the reactor # - fullBuildTriggers="": override .mvn/** default (Scalpel lives in .mvn/extensions.xml) - # - alsoMake/alsoMakeDependents=false: we only want directly affected modules - # (our script handles -amd expansion separately) - local scalpel_args="-Dscalpel.enabled=true -Dscalpel.mode=report -Dscalpel.fullBuildTriggers= -Dscalpel.alsoMake=false -Dscalpel.alsoMakeDependents=false" + # - fetchBaseBranch=false: base branch is pre-fetched by the CI workflow + # - skipTestsForDownstreamModules: derived from EXCLUSION_LIST — tells Scalpel which + # downstream modules should not run tests in skip-tests mode (for shadow comparison) + # Strip the Maven "!:" prefix from each entry to get bare artifact IDs for Scalpel. + local skip_downstream + skip_downstream=$(echo "$EXCLUSION_LIST" | sed 's/!://g') + local scalpel_args="-Dscalpel.enabled=true -Dscalpel.mode=report -Dscalpel.fullBuildTriggers= -Dscalpel.fetchBaseBranch=false -Dscalpel.excludePaths=.github/** -Dscalpel.skipTestsForDownstreamModules=${skip_downstream}" # For workflow_dispatch, GITHUB_BASE_REF may not be set if [ -z "${GITHUB_BASE_REF:-}" ]; then scalpel_args="$scalpel_args -Dscalpel.baseBranch=origin/main" @@ -265,6 +266,7 @@ runScalpelDetection() { ./mvnw -B -q validate $scalpel_args ${MAVEN_EXTRA_ARGS:-} -l /tmp/scalpel-validate.log 2>/dev/null || { echo " WARNING: Scalpel detection failed (exit $?), skipping" grep -i "scalpel" /tmp/scalpel-validate.log 2>/dev/null | head -5 || true + scalpel_failure_reason="Scalpel detection failed (mvn validate exited with error)" return } @@ -273,6 +275,7 @@ runScalpelDetection() { if [ ! -f "$report" ]; then echo " WARNING: Scalpel report not found at $report" grep -i "scalpel" /tmp/scalpel-validate.log 2>/dev/null | head -5 || true + scalpel_failure_reason="Scalpel report not found (merge-base may be unreachable in shallow clone)" return fi @@ -283,6 +286,7 @@ runScalpelDetection() { local trigger_file trigger_file=$(jq -r '.triggerFile // "unknown"' "$report") echo " Scalpel: Full build triggered by change to $trigger_file" + scalpel_failure_reason="Scalpel triggered a full build (changed file: $trigger_file)" return fi @@ -293,9 +297,24 @@ runScalpelDetection() { scalpel_managed_deps=$(jq -r '(.changedManagedDependencies // []) | if length > 0 then join(", ") else "" end' "$report" 2>/dev/null || true) scalpel_managed_plugins=$(jq -r '(.changedManagedPlugins // []) | if length > 0 then join(", ") else "" end' "$report" 2>/dev/null || true) + # Scalpel shadow comparison data: + # - Modules Scalpel skip-tests mode would test (testsSkipped != true) + # - Modules Scalpel would skip (testsSkipped == true, from skipTestsForDownstreamModules) + # - Breakdown by category (DIRECT, DOWNSTREAM) + scalpel_would_test=$(jq -r '[.affectedModules[] | select(.testsSkipped != true)] | map(.artifactId) | sort | join(",")' "$report" 2>/dev/null || true) + scalpel_would_skip=$(jq -r '[.affectedModules[] | select(.testsSkipped == true)] | map(.artifactId) | sort | join(",")' "$report" 2>/dev/null || true) + scalpel_direct_count=$(jq '[.affectedModules[] | select(.category == "DIRECT")] | length' "$report" 2>/dev/null || echo "0") + scalpel_downstream_tested=$(jq '[.affectedModules[] | select(.category == "DOWNSTREAM" and .testsSkipped != true)] | length' "$report" 2>/dev/null || echo "0") + scalpel_downstream_skipped=$(jq '[.affectedModules[] | select(.category == "DOWNSTREAM" and .testsSkipped == true)] | length' "$report" 2>/dev/null || echo "0") + local mod_count mod_count=$(jq '.affectedModules | length' "$report" 2>/dev/null || echo "0") - echo " Scalpel detected $mod_count affected modules" + local test_count=0 + if [ -n "$scalpel_would_test" ]; then + test_count=$(echo "$scalpel_would_test" | tr ',' '\n' | grep -c . || true) + fi + echo " Scalpel detected $mod_count affected modules ($test_count would be tested)" + echo " Direct: $scalpel_direct_count, Downstream tested: $scalpel_downstream_tested, Downstream skipped: $scalpel_downstream_skipped" if [ -n "$scalpel_props" ]; then echo " Changed properties: $scalpel_props" fi @@ -382,6 +401,81 @@ checkManualItTests() { fi } +# ── Scalpel shadow comparison ────────────────────────────────────────── + +# Write Scalpel shadow comparison section to the PR comment. +# Shows what Scalpel skip-tests mode would have tested vs what the current +# approach actually tested — observation only, does not affect test execution. +writeScalpelComparison() { + local comment_file="$1" + + # If Scalpel failed, show why in the PR comment + if [ -n "$scalpel_failure_reason" ]; then + echo "" >> "$comment_file" + echo "
:microscope: Scalpel shadow comparison (skip-tests mode)" >> "$comment_file" + echo "" >> "$comment_file" + echo ":warning: $scalpel_failure_reason" >> "$comment_file" + echo "" >> "$comment_file" + echo "> :information_source: Shadow mode — Scalpel observes but does not affect test execution. [Learn more](https://github.com/maveniverse/scalpel)" >> "$comment_file" + echo "" >> "$comment_file" + echo "
" >> "$comment_file" + return + fi + + # Skip if no Scalpel data (Scalpel was not invoked for this PR) + if [ -z "$scalpel_would_test" ] && [ -z "$scalpel_would_skip" ]; then + return + fi + + local scalpel_test_count=0 + local scalpel_skip_count=0 + if [ -n "$scalpel_would_test" ]; then + scalpel_test_count=$(echo "$scalpel_would_test" | tr ',' '\n' | grep -c . || true) + fi + if [ -n "$scalpel_would_skip" ]; then + scalpel_skip_count=$(echo "$scalpel_would_skip" | tr ',' '\n' | grep -c . || true) + fi + + echo "" >> "$comment_file" + echo "
:microscope: Scalpel shadow comparison (skip-tests mode)" >> "$comment_file" + echo "" >> "$comment_file" + echo "**Scalpel skip-tests mode would test ${scalpel_test_count} modules** (${scalpel_direct_count} direct + ${scalpel_downstream_tested} downstream)" >> "$comment_file" + + if [ "$scalpel_downstream_skipped" -gt 0 ]; then + echo "" >> "$comment_file" + echo "${scalpel_downstream_skipped} downstream module(s) would have tests skipped (generated code, meta-modules)" >> "$comment_file" + fi + + # Show which modules Scalpel would test + if [ -n "$scalpel_would_test" ]; then + echo "" >> "$comment_file" + echo "
Modules Scalpel would test (${scalpel_test_count})" >> "$comment_file" + echo "" >> "$comment_file" + echo "$scalpel_would_test" | tr ',' '\n' | while read -r m; do + [ -n "$m" ] && echo "- \`$m\`" >> "$comment_file" + done + echo "" >> "$comment_file" + echo "
" >> "$comment_file" + fi + + # Show which modules would have tests skipped + if [ -n "$scalpel_would_skip" ]; then + echo "" >> "$comment_file" + echo "
Modules with tests skipped (${scalpel_skip_count})" >> "$comment_file" + echo "" >> "$comment_file" + echo "$scalpel_would_skip" | tr ',' '\n' | while read -r m; do + [ -n "$m" ] && echo "- \`$m\`" >> "$comment_file" + done + echo "" >> "$comment_file" + echo "
" >> "$comment_file" + fi + + echo "" >> "$comment_file" + echo "> :information_source: Shadow mode — Scalpel observes but does not affect test execution. [Learn more](https://github.com/maveniverse/scalpel)" >> "$comment_file" + echo "" >> "$comment_file" + echo "
" >> "$comment_file" +} + # ── Comment generation ───────────────────────────────────────────────── writeComment() { @@ -539,6 +633,13 @@ main() { scalpel_props="" scalpel_managed_deps="" scalpel_managed_plugins="" + # Scalpel shadow comparison data + scalpel_would_test="" + scalpel_would_skip="" + scalpel_direct_count="0" + scalpel_downstream_tested="0" + scalpel_downstream_skipped="0" + scalpel_failure_reason="" # Step 2a: Grep-based detection (existing approach) if [ -n "$pom_files" ]; then @@ -762,6 +863,9 @@ main() { local comment_file="incremental-test-comment.md" writeComment "$comment_file" "$pl" "$dep_module_ids" "$all_changed_props" "$testedDependents" "$extraModules" "$scalpel_managed_deps" "$scalpel_managed_plugins" + # Scalpel shadow comparison (observation only) + writeScalpelComparison "$comment_file" + # Check for tests disabled in CI via @DisabledIfSystemProperty(named = "ci.env.name") local disabled_tests disabled_tests=$(detectDisabledTests "$final_pl") diff --git a/.github/workflows/pr-build-main.yml b/.github/workflows/pr-build-main.yml index 975fa623761e8..74cfc82b3863a 100644 --- a/.github/workflows/pr-build-main.yml +++ b/.github/workflows/pr-build-main.yml @@ -80,6 +80,13 @@ jobs: with: persist-credentials: false ref: ${{ inputs.pr_ref || '' }} + - name: Fetch base branch for Scalpel change detection + if: ${{ !inputs.skip_full_build }} + run: | + # Scalpel needs the merge base between HEAD and the base branch. + # The checkout is depth=1, so deepen both sides for merge-base reachability. + git fetch --deepen=200 2>/dev/null || true + git fetch --no-tags --depth=200 origin "${GITHUB_BASE_REF:-main}:refs/remotes/origin/${GITHUB_BASE_REF:-main}" - id: install-packages uses: ./.github/actions/install-packages - id: install-mvnd diff --git a/.github/workflows/sonar-build.yml b/.github/workflows/sonar-build.yml index 85efb59465492..5dbeb2cdaae52 100644 --- a/.github/workflows/sonar-build.yml +++ b/.github/workflows/sonar-build.yml @@ -45,7 +45,10 @@ jobs: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - + - name: Fetch base branch for Scalpel change detection + run: | + git fetch --deepen=200 2>/dev/null || true + git fetch --no-tags --depth=200 origin "${GITHUB_BASE_REF:-main}:refs/remotes/origin/${GITHUB_BASE_REF:-main}" - id: install-packages uses: ./.github/actions/install-packages diff --git a/.mvn/extensions.xml b/.mvn/extensions.xml index 4a4c3957328d2..14d5d707fcccc 100644 --- a/.mvn/extensions.xml +++ b/.mvn/extensions.xml @@ -23,6 +23,6 @@ eu.maveniverse.maven.scalpel extension3 - 0.3.5 + 0.3.7 From b12c8bd3613b4b738c30e05d7688733e02cefe9c Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Mon, 29 Jun 2026 21:29:17 +0000 Subject: [PATCH 2/6] ci: fix CI comment lost when cancelled build overwrites artifact When a matrix build has one JDK failing and another cancelled, the cancelled JDK's cleanup steps upload a ci-comment artifact without the comment file, overwriting the failed JDK's artifact that had it. Only upload the ci-comment artifact when incremental-test-comment.md actually exists, preventing a cancelled build from clobbering a completed build's comment. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/pr-build-main.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pr-build-main.yml b/.github/workflows/pr-build-main.yml index 74cfc82b3863a..9b99ebea2f8bc 100644 --- a/.github/workflows/pr-build-main.yml +++ b/.github/workflows/pr-build-main.yml @@ -155,8 +155,11 @@ jobs: name: incremental-test-java-${{ matrix.java }}.log path: incremental-test.log # All non-experimental JDK matrix entries upload with overwrite: true. - # This ensures a comment is posted even if one JDK build fails — the - # content is identical across JDKs (same modules tested), so last writer wins. + # The comment content is identical across JDKs (same modules tested), + # so last writer wins. However, we only upload (and overwrite) when the + # comment file actually exists — a cancelled build (e.g., JDK 25 killed + # while JDK 17 fails) won't have the file and must not overwrite an + # artifact from a matrix entry that did produce it. - name: Save PR number and test comment for commenter workflow if: always() && !matrix.experimental shell: bash @@ -174,7 +177,9 @@ jobs: echo ":gear: [View full build and test results](${RUN_URL})" >> ci-comment-artifact/incremental-test-comment.md fi - name: Upload CI comment artifact - if: always() && !matrix.experimental + if: > + always() && !matrix.experimental && + hashFiles('ci-comment-artifact/incremental-test-comment.md') != '' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: ci-comment From 8624aaa8a9ffaf8ba741bb4f2917eb3cba12d3cf Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Tue, 30 Jun 2026 06:28:18 +0000 Subject: [PATCH 3/6] ci: always pass baseBranch explicitly to Scalpel and add diagnostics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Scalpel's auto-detection of GITHUB_BASE_REF via Maven system properties (env.GITHUB_BASE_REF) is fragile — it can fail in CI rerun contexts or with certain Maven wrapper configurations, causing the report to silently not be generated. Fix: always pass -Dscalpel.baseBranch=origin/${GITHUB_BASE_REF:-main} explicitly. Also add a git merge-base pre-check and improved diagnostics (tail of Scalpel log, broader grep) when the report is not found. Co-Authored-By: Claude Opus 4.6 --- .../incremental-build/incremental-build.sh | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/.github/actions/incremental-build/incremental-build.sh b/.github/actions/incremental-build/incremental-build.sh index a502bb3d79c1f..e1b924154f8f1 100755 --- a/.github/actions/incremental-build/incremental-build.sh +++ b/.github/actions/incremental-build/incremental-build.sh @@ -256,13 +256,20 @@ runScalpelDetection() { # Strip the Maven "!:" prefix from each entry to get bare artifact IDs for Scalpel. local skip_downstream skip_downstream=$(echo "$EXCLUSION_LIST" | sed 's/!://g') - local scalpel_args="-Dscalpel.enabled=true -Dscalpel.mode=report -Dscalpel.fullBuildTriggers= -Dscalpel.fetchBaseBranch=false -Dscalpel.excludePaths=.github/** -Dscalpel.skipTestsForDownstreamModules=${skip_downstream}" - # For workflow_dispatch, GITHUB_BASE_REF may not be set - if [ -z "${GITHUB_BASE_REF:-}" ]; then - scalpel_args="$scalpel_args -Dscalpel.baseBranch=origin/main" + # Always pass baseBranch explicitly — relying on Scalpel's env.GITHUB_BASE_REF + # auto-detection is fragile across Maven wrappers and CI rerun contexts. + local base_branch="origin/${GITHUB_BASE_REF:-main}" + local scalpel_args="-Dscalpel.enabled=true -Dscalpel.mode=report -Dscalpel.fullBuildTriggers= -Dscalpel.fetchBaseBranch=false -Dscalpel.baseBranch=${base_branch} -Dscalpel.excludePaths=.github/** -Dscalpel.skipTestsForDownstreamModules=${skip_downstream}" + + # Verify merge base is reachable before running Scalpel + if ! git merge-base HEAD "${base_branch}" >/dev/null 2>&1; then + echo " WARNING: merge base between HEAD and ${base_branch} is not reachable" + echo " HEAD=$(git rev-parse HEAD 2>/dev/null), ${base_branch}=$(git rev-parse ${base_branch} 2>/dev/null || echo 'NOT FOUND')" + scalpel_failure_reason="Merge base not reachable between HEAD and ${base_branch} (shallow clone too shallow?)" + return fi - echo " Scalpel: running mvn validate (report mode)..." + echo " Scalpel: running mvn validate (report mode, base=${base_branch})..." ./mvnw -B -q validate $scalpel_args ${MAVEN_EXTRA_ARGS:-} -l /tmp/scalpel-validate.log 2>/dev/null || { echo " WARNING: Scalpel detection failed (exit $?), skipping" grep -i "scalpel" /tmp/scalpel-validate.log 2>/dev/null | head -5 || true @@ -274,7 +281,10 @@ runScalpelDetection() { local report="target/scalpel-report.json" if [ ! -f "$report" ]; then echo " WARNING: Scalpel report not found at $report" - grep -i "scalpel" /tmp/scalpel-validate.log 2>/dev/null | head -5 || true + echo " Scalpel log (last 10 lines):" + tail -10 /tmp/scalpel-validate.log 2>/dev/null || true + echo " Scalpel-specific messages:" + grep -i "scalpel\|merge.base\|JGit\|no changes" /tmp/scalpel-validate.log 2>/dev/null | head -10 || true scalpel_failure_reason="Scalpel report not found (merge-base may be unreachable in shallow clone)" return fi From 27d18728f8b6ded2708a1ec68dcb7101f816b27f Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Tue, 30 Jun 2026 06:34:58 +0000 Subject: [PATCH 4/6] ci: progressively deepen fetch for merge-base reachability MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of a fixed --depth=200 fetch that can miss the merge base for long-lived or stale branches, try 200 → 1000 → unshallow until git merge-base succeeds. Most PRs resolve at depth 200 (no extra cost); only old branches need the deeper fetches. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/pr-build-main.yml | 22 +++++++++++++++++++--- .github/workflows/sonar-build.yml | 19 +++++++++++++++++-- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pr-build-main.yml b/.github/workflows/pr-build-main.yml index 9b99ebea2f8bc..b8df6435f2321 100644 --- a/.github/workflows/pr-build-main.yml +++ b/.github/workflows/pr-build-main.yml @@ -84,9 +84,25 @@ jobs: if: ${{ !inputs.skip_full_build }} run: | # Scalpel needs the merge base between HEAD and the base branch. - # The checkout is depth=1, so deepen both sides for merge-base reachability. - git fetch --deepen=200 2>/dev/null || true - git fetch --no-tags --depth=200 origin "${GITHUB_BASE_REF:-main}:refs/remotes/origin/${GITHUB_BASE_REF:-main}" + # The checkout is depth=1, so we progressively deepen until the + # merge base is reachable: 200 → 1000 → full history. + # Scalpel is observational — fetch failures must not break the build. + BASE_REF="${GITHUB_BASE_REF:-main}" + for depth in 200 1000; do + git fetch --deepen=$depth 2>/dev/null || true + git fetch --no-tags --depth=$depth origin "${BASE_REF}:refs/remotes/origin/${BASE_REF}" || true + if git merge-base HEAD "origin/${BASE_REF}" >/dev/null 2>&1; then + echo "Merge base reachable at depth $depth" + break + fi + echo "Merge base not reachable at depth $depth, deepening..." + done + # If still not reachable, fetch full history as last resort + if ! git merge-base HEAD "origin/${BASE_REF}" >/dev/null 2>&1; then + echo "Merge base still not reachable, fetching full history" + git fetch --unshallow 2>/dev/null || true + git fetch --no-tags origin "${BASE_REF}:refs/remotes/origin/${BASE_REF}" || true + fi - id: install-packages uses: ./.github/actions/install-packages - id: install-mvnd diff --git a/.github/workflows/sonar-build.yml b/.github/workflows/sonar-build.yml index 5dbeb2cdaae52..5af89733b06fd 100644 --- a/.github/workflows/sonar-build.yml +++ b/.github/workflows/sonar-build.yml @@ -47,8 +47,23 @@ jobs: persist-credentials: false - name: Fetch base branch for Scalpel change detection run: | - git fetch --deepen=200 2>/dev/null || true - git fetch --no-tags --depth=200 origin "${GITHUB_BASE_REF:-main}:refs/remotes/origin/${GITHUB_BASE_REF:-main}" + # Scalpel is observational — fetch failures must not break the build. + BASE_REF="${GITHUB_BASE_REF:-main}" + for depth in 200 1000; do + git fetch --deepen=$depth 2>/dev/null || true + git fetch --no-tags --depth=$depth origin "${BASE_REF}:refs/remotes/origin/${BASE_REF}" || true + if git merge-base HEAD "origin/${BASE_REF}" >/dev/null 2>&1; then + echo "Merge base reachable at depth $depth" + break + fi + echo "Merge base not reachable at depth $depth, deepening..." + done + # If still not reachable, fetch full history as last resort + if ! git merge-base HEAD "origin/${BASE_REF}" >/dev/null 2>&1; then + echo "Merge base still not reachable, fetching full history" + git fetch --unshallow 2>/dev/null || true + git fetch --no-tags origin "${BASE_REF}:refs/remotes/origin/${BASE_REF}" || true + fi - id: install-packages uses: ./.github/actions/install-packages From b2dd2118d8c45ac7fc15282911146b1ccfb3159b Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Tue, 30 Jun 2026 09:31:45 +0000 Subject: [PATCH 5/6] ci: separate grep and Scalpel sections in CI comment Move all Scalpel-related output below a separator line at the end of the CI comment, with a one-line diff summary showing what Scalpel would add/remove vs the current grep-based detection (e.g. "compile: +51, test: +22"). The top section now shows only what the existing grep mechanism found, making it easy to see what each approach contributes. Co-Authored-By: Claude Opus 4.6 --- .../incremental-build/incremental-build.sh | 131 ++++++++++++++---- 1 file changed, 104 insertions(+), 27 deletions(-) diff --git a/.github/actions/incremental-build/incremental-build.sh b/.github/actions/incremental-build/incremental-build.sh index e1b924154f8f1..08b6b2650f7af 100755 --- a/.github/actions/incremental-build/incremental-build.sh +++ b/.github/actions/incremental-build/incremental-build.sh @@ -414,15 +414,19 @@ checkManualItTests() { # ── Scalpel shadow comparison ────────────────────────────────────────── # Write Scalpel shadow comparison section to the PR comment. -# Shows what Scalpel skip-tests mode would have tested vs what the current -# approach actually tested — observation only, does not affect test execution. +# Shows what Scalpel would detect vs what the current grep-based approach found, +# with a one-line diff summary. Observation only — does not affect test execution. +# Args: $1=comment_file, $2=grep_dep_module_ids (colon-prefixed, comma-separated) writeScalpelComparison() { local comment_file="$1" + local grep_dep_ids="${2:-}" # If Scalpel failed, show why in the PR comment if [ -n "$scalpel_failure_reason" ]; then echo "" >> "$comment_file" - echo "
:microscope: Scalpel shadow comparison (skip-tests mode)" >> "$comment_file" + echo "---" >> "$comment_file" + echo "" >> "$comment_file" + echo "
:microscope: Scalpel shadow comparison" >> "$comment_file" echo "" >> "$comment_file" echo ":warning: $scalpel_failure_reason" >> "$comment_file" echo "" >> "$comment_file" @@ -437,8 +441,13 @@ writeScalpelComparison() { return fi + # Count Scalpel modules + local scalpel_total=0 local scalpel_test_count=0 local scalpel_skip_count=0 + if [ -n "$scalpel_module_ids" ]; then + scalpel_total=$(echo "$scalpel_module_ids" | tr ',' '\n' | grep -c . || true) + fi if [ -n "$scalpel_would_test" ]; then scalpel_test_count=$(echo "$scalpel_would_test" | tr ',' '\n' | grep -c . || true) fi @@ -446,15 +455,91 @@ writeScalpelComparison() { scalpel_skip_count=$(echo "$scalpel_would_skip" | tr ',' '\n' | grep -c . || true) fi + # Compute diff vs grep-based detection using set operations. + # Strip colon prefix for uniform comparison. + local scalpel_sorted + scalpel_sorted=$(echo "$scalpel_module_ids" | tr ',' '\n' | sed 's/^://' | sort) + local grep_sorted="" + if [ -n "$grep_dep_ids" ]; then + grep_sorted=$(echo "$grep_dep_ids" | tr ',' '\n' | sed 's/^://' | sort) + fi + # scalpel_would_test has no colon prefix + local scalpel_test_sorted="" + if [ -n "$scalpel_would_test" ]; then + scalpel_test_sorted=$(echo "$scalpel_would_test" | tr ',' '\n' | sort) + fi + + # compile diff: modules in Scalpel but not grep (+), and vice versa (-) + local compile_added=0 compile_removed=0 + if [ -n "$grep_sorted" ]; then + compile_added=$(comm -23 <(echo "$scalpel_sorted") <(echo "$grep_sorted") | grep -c . || true) + compile_removed=$(comm -13 <(echo "$scalpel_sorted") <(echo "$grep_sorted") | grep -c . || true) + else + compile_added=$scalpel_total + fi + + # test diff: modules Scalpel would test vs what grep found + # (current mechanism tests all grep-found modules) + local test_added=0 test_removed=0 + if [ -n "$grep_sorted" ]; then + test_added=$(comm -23 <(echo "$scalpel_test_sorted") <(echo "$grep_sorted") | grep -c . || true) + test_removed=$(comm -13 <(echo "$scalpel_test_sorted") <(echo "$grep_sorted") | grep -c . || true) + else + test_added=$scalpel_test_count + fi + + # Build one-line diff summary + local compile_summary="" + if [ "$compile_added" -gt 0 ] && [ "$compile_removed" -gt 0 ]; then + compile_summary="compile: +${compile_added} −${compile_removed}" + elif [ "$compile_added" -gt 0 ]; then + compile_summary="compile: +${compile_added}" + elif [ "$compile_removed" -gt 0 ]; then + compile_summary="compile: −${compile_removed}" + else + compile_summary="compile: same" + fi + + local test_summary="" + if [ "$test_added" -gt 0 ] && [ "$test_removed" -gt 0 ]; then + test_summary="test: +${test_added} −${test_removed}" + elif [ "$test_added" -gt 0 ]; then + test_summary="test: +${test_added}" + elif [ "$test_removed" -gt 0 ]; then + test_summary="test: −${test_removed}" + else + test_summary="test: same" + fi + echo "" >> "$comment_file" - echo "
:microscope: Scalpel shadow comparison (skip-tests mode)" >> "$comment_file" + echo "---" >> "$comment_file" + echo "" >> "$comment_file" + echo "
:microscope: Scalpel shadow comparison — ${compile_summary}, ${test_summary}" >> "$comment_file" echo "" >> "$comment_file" - echo "**Scalpel skip-tests mode would test ${scalpel_test_count} modules** (${scalpel_direct_count} direct + ${scalpel_downstream_tested} downstream)" >> "$comment_file" - if [ "$scalpel_downstream_skipped" -gt 0 ]; then + # Context line: total counts + local grep_count=0 + if [ -n "$grep_dep_ids" ]; then + grep_count=$(echo "$grep_dep_ids" | tr ',' '\n' | grep -c . || true) + fi + echo "[Maveniverse Scalpel](https://github.com/maveniverse/scalpel) detected **${scalpel_total} affected modules** via effective POM comparison (vs ${grep_count} from grep-based detection)." >> "$comment_file" + echo "" >> "$comment_file" + + # Show Scalpel-detected change details + if [ -n "$scalpel_props" ]; then + echo "Changed properties: ${scalpel_props}" >> "$comment_file" echo "" >> "$comment_file" - echo "${scalpel_downstream_skipped} downstream module(s) would have tests skipped (generated code, meta-modules)" >> "$comment_file" fi + if [ -n "$scalpel_managed_deps" ]; then + echo "Changed managed dependencies: ${scalpel_managed_deps}" >> "$comment_file" + echo "" >> "$comment_file" + fi + if [ -n "$scalpel_managed_plugins" ]; then + echo "Changed managed plugins: ${scalpel_managed_plugins}" >> "$comment_file" + echo "" >> "$comment_file" + fi + + echo "**Skip-tests mode would test ${scalpel_test_count} modules** (${scalpel_direct_count} direct + ${scalpel_downstream_tested} downstream), **skip tests for ${scalpel_skip_count}** (generated code, meta-modules)" >> "$comment_file" # Show which modules Scalpel would test if [ -n "$scalpel_would_test" ]; then @@ -495,8 +580,6 @@ writeComment() { local changed_props_summary="$4" local testedDependents="$5" local extra_modules="$6" - local managed_deps_summary="${7:-}" - local managed_plugins_summary="${8:-}" echo "" > "$comment_file" @@ -514,7 +597,7 @@ writeComment() { fi fi - # Section 2: pom dependency-detected modules + # Section 2: pom dependency-detected modules (grep-based only) if [ -n "$dep_ids" ]; then echo "" >> "$comment_file" echo ":white_check_mark: **POM dependency changes: targeted tests included**" >> "$comment_file" @@ -523,14 +606,6 @@ writeComment() { echo "Changed properties: ${changed_props_summary}" >> "$comment_file" echo "" >> "$comment_file" fi - if [ -n "$managed_deps_summary" ]; then - echo "Changed managed dependencies: ${managed_deps_summary}" >> "$comment_file" - echo "" >> "$comment_file" - fi - if [ -n "$managed_plugins_summary" ]; then - echo "Changed managed plugins: ${managed_plugins_summary}" >> "$comment_file" - echo "" >> "$comment_file" - fi local dep_count dep_count=$(echo "$dep_ids" | tr ',' '\n' | wc -l | tr -d ' ') echo "
Modules affected by dependency changes (${dep_count})" >> "$comment_file" @@ -540,10 +615,6 @@ writeComment() { done echo "" >> "$comment_file" echo "
" >> "$comment_file" - if [ -n "$managed_deps_summary" ] || [ -n "$managed_plugins_summary" ]; then - echo "" >> "$comment_file" - echo "> :microscope: Detected via [Maveniverse Scalpel](https://github.com/maveniverse/scalpel) effective POM comparison" >> "$comment_file" - fi fi # Section 3: extra modules (from /component-test) @@ -686,7 +757,13 @@ main() { runScalpelDetection fi - # Step 2c: Merge grep and Scalpel results (union, deduplicated) + # Save grep-only results for comment attribution (before merging with Scalpel). + # The comment's top section shows only what the current grep mechanism detected; + # the Scalpel comparison section (after a separator) shows the diff. + local grep_dep_module_ids="$dep_module_ids" + local grep_changed_props="$all_changed_props" + + # Step 2c: Merge grep and Scalpel results (union, deduplicated) — for the build if [ -n "$scalpel_module_ids" ]; then dep_module_ids="${dep_module_ids:+${dep_module_ids},}${scalpel_module_ids}" dep_module_ids=$(echo "$dep_module_ids" | tr ',' '\n' | sort -u | tr '\n' ',' | sed 's/,$//') @@ -739,7 +816,7 @@ main() { if [ -z "$final_pl" ]; then echo "" echo "No modules to test" - writeComment "incremental-test-comment.md" "" "" "" "" "" "" "" + writeComment "incremental-test-comment.md" "" "" "" "" "" exit 0 fi @@ -871,10 +948,10 @@ main() { # ── Step 5: Write comment and summary ── local comment_file="incremental-test-comment.md" - writeComment "$comment_file" "$pl" "$dep_module_ids" "$all_changed_props" "$testedDependents" "$extraModules" "$scalpel_managed_deps" "$scalpel_managed_plugins" + writeComment "$comment_file" "$pl" "$grep_dep_module_ids" "$grep_changed_props" "$testedDependents" "$extraModules" - # Scalpel shadow comparison (observation only) - writeScalpelComparison "$comment_file" + # Scalpel shadow comparison (observation only — after separator) + writeScalpelComparison "$comment_file" "$grep_dep_module_ids" # Check for tests disabled in CI via @DisabledIfSystemProperty(named = "ci.env.name") local disabled_tests From 4c29a89795497d1c6c3d08510f5f9d9188eefebd Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Tue, 30 Jun 2026 08:26:42 +0000 Subject: [PATCH 6/6] =?UTF-8?q?[DO=20NOT=20MERGE]=20test:=20jackson2-versi?= =?UTF-8?q?on=202.22.0=20=E2=86=92=202.21.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- parent/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parent/pom.xml b/parent/pom.xml index 228e7d5994674..1aa3dbea26e24 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -251,7 +251,7 @@ 3.0.5 0.2.9 1.6.2 - 2.22.0 + 2.21.2 2.20 3.2.0 2.22.3