Skip to content

Commit 5bc6f59

Browse files
joaodinissfclaude
andcommitted
ci: build only changed modules + upstream deps in the spotbugs lane
compute-spotbugs-skip.sh now also exports SPOTBUGS_SCOPE_ARGS ("-pl ../ddk-target,<changed modules> -am") and the spotbugs lane passes it to mvn, so a scoped run builds only the PR's changed modules plus their upstream dependencies instead of the full 64-module reactor. The -am-pulled unchanged dependencies keep the injected spotbugs.skip: they compile (complete aux-classpath) but are not analysed. ddk-target is pinned into every scoped reactor: the target-definition artifact is referenced by target-platform-configuration, not by any bundle MANIFEST, so -am alone never pulls it — Tycho then falls back to a local-repository copy of the .target, which fails on a cold cache and can silently resolve a stale target definition on a warm one (verified: a June install of the same sequenceNumber still pointed at the 2026-03 release train while the tree's points at 2026-06). The gate verifies that every scanned source-bearing module produced its SARIF (and that a full scan produced any at all): --fail-never swallows even target-resolution failures, so without a presence check a dead build uploads nothing, counts zero violations, and passes vacuously. Fail-safes are unchanged: a build/config change means a full reactor and full scan, and a PR touching no reactor module builds the full reactor with every analysis skipped. Measured locally (single-module change, clean tree, JDK 21): 34s wall vs 75s for the full-reactor equivalent, resolving the current target platform (jdt.core 3.46.0) with findings identical to the full scan. Verified against a repository with no installed ddk-target: without the pin the run reproduces the swallowed resolution failure with zero SARIFs; with it the scoped run succeeds. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent cea62e7 commit 5bc6f59

2 files changed

Lines changed: 57 additions & 7 deletions

File tree

.github/scripts/compute-spotbugs-skip.sh

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@
1414
# only trimmed ~17% of the goal vs ~88% for this per-module skip (measured on this
1515
# reactor). A small upstream SpotBugs early-exit (skip the run when no application class
1616
# matches the screener) would make onlyAnalyze competitive; if that ever lands, switch
17-
# to onlyAnalyze and delete this script.
17+
# to onlyAnalyze and delete this script (tracked in #1455 / spotbugs/spotbugs#3796).
18+
#
19+
# On top of the skips, the changed reactor modules are exported as SPOTBUGS_SCOPE_ARGS
20+
# ("-pl <changed> -am") so the lane builds only those modules plus their upstream
21+
# dependencies instead of the full reactor. The -am-pulled unchanged dependencies still
22+
# carry the injected skip: they compile (complete aux-classpath) but are not analysed.
1823
#
1924
# Run from the repository root. Usage: compute-spotbugs-skip.sh <base-sha>
2025
set -euo pipefail
@@ -29,6 +34,9 @@ while IFS= read -r f; do
2934
case "$f" in
3035
pom.xml | ddk-parent/* | .mvn/* | *.target | .github/* | *[Ss]pot[Bb]ugs*[Ee]xclude*)
3136
echo "Build/config change ($f) -> full SpotBugs scan (no skips)."
37+
if [ -n "${GITHUB_ENV:-}" ]; then
38+
echo "SPOTBUGS_FULL_SCAN=true" >> "$GITHUB_ENV"
39+
fi
3240
exit 0
3341
;;
3442
esac
@@ -59,13 +67,23 @@ inject_skip() {
5967
rm -f "$pom.bak"
6068
}
6169

62-
# 5) Skip every reactor module that was not touched by this PR.
70+
# 5) Skip every reactor module that was not touched by this PR. Kept modules with a
71+
# bundle MANIFEST are expected to produce an analysis report — the gate checks this
72+
# so a swallowed resolution/compile failure can never pass as "nothing to scan".
6373
kept=0
6474
skipped=0
75+
kept_pl=""
76+
expect_reports=""
6577
while IFS= read -r mod; do
6678
[ -n "$mod" ] || continue
6779
if printf '%s\n' "${changed_mods}" | grep -qx "$mod"; then
6880
kept=$((kept + 1))
81+
kept_pl="${kept_pl:+${kept_pl},}../${mod}"
82+
# Only bundles with sources reliably emit a report (a source-less bundle,
83+
# e.g. pure branding, has nothing for the analyzer to write a SARIF about).
84+
if [ -f "${mod}/META-INF/MANIFEST.MF" ] && [ -d "${mod}/src" ]; then
85+
expect_reports="${expect_reports:+${expect_reports} }${mod}"
86+
fi
6987
else
7088
inject_skip "$mod"
7189
skipped=$((skipped + 1))
@@ -74,5 +92,22 @@ done <<EOF
7492
${module_dirs}
7593
EOF
7694

95+
# 6) Scope the reactor to the changed modules + their upstream dependencies. ddk-target
96+
# is always kept in the -pl list: the target-definition artifact is referenced by
97+
# target-platform-configuration, not by any MANIFEST, so -am never pulls it — without
98+
# it in the reactor Tycho falls back to a local-repository copy, which fails on a
99+
# cold cache and can silently resolve a stale target definition on a warm one.
100+
# With no changed reactor module (e.g. a docs-only PR) the full reactor builds with
101+
# every analysis skipped — same result, no flags needed.
102+
if [ "$kept" -gt 0 ] && [ -n "${GITHUB_ENV:-}" ]; then
103+
echo "SPOTBUGS_SCOPE_ARGS=-pl ../ddk-target,${kept_pl} -am" >> "$GITHUB_ENV"
104+
echo "SPOTBUGS_EXPECT_REPORTS=${expect_reports}" >> "$GITHUB_ENV"
105+
fi
106+
77107
echo "SpotBugs scope: scanning ${kept} changed module(s), skipping ${skipped} unchanged."
78108
echo "Changed modules: ${changed_mods:-<none>}"
109+
if [ -n "${kept_pl}" ]; then
110+
echo "Reactor scope args: -pl ${kept_pl} -am"
111+
else
112+
echo "Reactor scope args: <full reactor>"
113+
fi

.github/workflows/verify.yml

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -137,10 +137,11 @@ jobs:
137137
restore-keys: ${{ runner.os }}-maven-publish-
138138

139139
- name: Scope SpotBugs to the PR's changed modules
140-
# Injects <spotbugs.skip>true> into unchanged module poms so the per-module
141-
# SpotBugs fork is skipped for them (the lever that actually scopes the cost).
142-
# Full compile is preserved (correct aux-classpath); a build/config change ->
143-
# full scan. pull_request only; master/snapshot run a full scan.
140+
# Injects <spotbugs.skip>true> into unchanged module poms so their analysis is
141+
# skipped, and exports SPOTBUGS_SCOPE_ARGS (-pl <changed> -am) so only the
142+
# changed modules and their upstream deps build at all (skip-injected deps
143+
# compile for the aux-classpath but are not analysed). A build/config change ->
144+
# full scan, full reactor. pull_request only; master/snapshot run a full scan.
144145
run: bash .github/scripts/compute-spotbugs-skip.sh "${{ github.event.pull_request.base.sha }}"
145146

146147
- name: SpotBugs report (SARIF)
@@ -153,7 +154,7 @@ jobs:
153154
# JVM per module (59 forks); the shared heap is governed by MAVEN_OPTS above
154155
# (the plugin's maxHeap applies to forks only).
155156
run: |
156-
mvn -T 2C -f ./ddk-parent/pom.xml --batch-mode --fail-never \
157+
mvn -T 2C -f ./ddk-parent/pom.xml ${SPOTBUGS_SCOPE_ARGS:-} --batch-mode --fail-never \
157158
compile \
158159
spotbugs:spotbugs \
159160
-Dspotbugs.sarifOutput=true \
@@ -178,8 +179,22 @@ jobs:
178179
fi
179180
180181
- name: Gate on SpotBugs violations
182+
# Presence checks close the vacuous-green hole: --fail-never swallows even
183+
# target-platform resolution failures, so "no SARIF produced" must only ever
184+
# mean "every module was skip-injected", never "the build silently died".
181185
run: |
182186
set -eu
187+
for mod in ${SPOTBUGS_EXPECT_REPORTS:-}; do
188+
if [ ! -s "${mod}/target/spotbugsSarif.json" ]; then
189+
echo "::error::${mod} was scanned but produced no SpotBugs SARIF — a build failure was swallowed by --fail-never."
190+
exit 1
191+
fi
192+
done
193+
if [ "${SPOTBUGS_FULL_SCAN:-}" = "true" ] && \
194+
[ "$(find . -path '*/target/spotbugsSarif.json' | wc -l)" -eq 0 ]; then
195+
echo "::error::Full scan produced no SpotBugs SARIFs — a build failure was swallowed by --fail-never."
196+
exit 1
197+
fi
183198
sb_total=$(jq '[.runs[].results[]?] | length' .sarif-merged/spotbugs.sarif 2>/dev/null || echo 0)
184199
echo "SpotBugs SARIF violations: $sb_total"
185200
if [ "$sb_total" != "0" ]; then

0 commit comments

Comments
 (0)