11#! /usr/bin/env bash
22#
3- # Scope SpotBugs to a pull request's changed modules.
3+ # Scope static analysis (SpotBugs, or PMD/CPD/Checkstyle) to a pull request's
4+ # changed modules.
45#
5- # Default is RUN (analyze). On a PR this injects <spotbugs.skip>true</spotbugs.skip>
6- # into every UNCHANGED reactor module's pom, so spotbugs-maven-plugin skips the goal —
7- # and therefore the per-module JVM fork (SpotBugsMojo gates on `skip` before forking) —
8- # for those modules. The full-reactor compile is left intact (a changed module is still
9- # analysed with its complete aux-classpath). Master/snapshot builds run a full scan;
10- # this script is invoked on pull_request only.
6+ # Default is RUN (analyze). On a PR this injects <TOOL.skip>true</TOOL.skip>
7+ # properties into every UNCHANGED reactor module's pom, so the analysis mojos
8+ # skip those modules — for SpotBugs that also skips the per-module JVM fork
9+ # (SpotBugsMojo gates on `skip` before forking). A changed module is still
10+ # analysed with its complete aux-classpath: the -am-pulled unchanged
11+ # dependencies compile but are not analysed. Master/snapshot builds run a full
12+ # scan; this script is invoked on pull_request only.
1113#
1214# Why this and not -Dspotbugs.onlyAnalyze: onlyAnalyze is one clean flag, but SpotBugs
1315# applies its class screener too late (after the per-module fork + class scan), so it
1416# only trimmed ~17% of the goal vs ~88% for this per-module skip (measured on this
1517# reactor). A small upstream SpotBugs early-exit (skip the run when no application class
1618# matches the screener) would make onlyAnalyze competitive; if that ever lands, switch
17- # to onlyAnalyze and delete this script (tracked in #1455 / spotbugs/spotbugs#3796).
19+ # to onlyAnalyze and delete the spotbugs mode here (tracked in #1455 /
20+ # spotbugs/spotbugs#3796).
1821#
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.
22+ # On top of the skips, the changed reactor modules are exported as
23+ # SPOTBUGS_SCOPE_ARGS / LINT_SCOPE_ARGS ("-pl <changed> -am") so the lane builds
24+ # only those modules plus their upstream dependencies instead of the full reactor.
25+ # The lane's gate cross-checks <MODE>_KEPT / <MODE>_EXPECT_REPORTS so a build
26+ # failure swallowed by --fail-never can never pass as "nothing to scan".
2327#
24- # Run from the repository root. Usage: compute-spotbugs -skip.sh <base-sha>
28+ # Run from the repository root. Usage: compute-analysis -skip.sh <base-sha> <spotbugs|lint >
2529set -euo pipefail
2630base=" ${1:? base sha required} "
31+ mode=" ${2:? mode required: spotbugs|lint} "
32+
33+ case " $mode " in
34+ spotbugs) props=" spotbugs.skip" ; prefix=" SPOTBUGS" ;;
35+ lint) props=" pmd.skip cpd.skip checkstyle.skip" ; prefix=" LINT" ;;
36+ * ) echo " unknown mode: $mode " >&2 ; exit 2 ;;
37+ esac
2738
2839changed=$( git diff --name-only --diff-filter=ACMR " ${base} ...HEAD" )
2940
@@ -45,8 +56,7 @@ ${module_dirs}
4556EOF
4657
4758# 1) A change to shared build/config can affect any module -> full scan (skip nothing).
48- # ddk-configuration holds the analyzers' rulesets and filters (e.g. the SpotBugs
49- # exclusion-filter), so a change there must re-scan everything, not skip silently.
59+ # ddk-configuration holds the analyzers' rulesets and filters, so it counts too.
5060# ddk-target defines the target platform every module resolves against.
5161# Fail safe: the worst case here is "analyse everything", never "analyse nothing".
5262while IFS= read -r f; do
@@ -60,10 +70,10 @@ while IFS= read -r f; do
6070 # full scan analysed all of them (not just >=1) — a mojo death swallowed by
6171 # --fail-never can't pass as long as one sibling reported.
6272 if [ -n " ${GITHUB_ENV:- } " ]; then
63- echo " SPOTBUGS_KEPT =all" >> " $GITHUB_ENV "
64- echo " SPOTBUGS_EXPECT_REPORTS =${all_source_modules} " >> " $GITHUB_ENV "
73+ echo " ${prefix} _KEPT =all" >> " $GITHUB_ENV "
74+ echo " ${prefix} _EXPECT_REPORTS =${all_source_modules} " >> " $GITHUB_ENV "
6575 fi
66- echo " Build/config change ($f ) -> full SpotBugs scan (no skips)."
76+ echo " Build/config change ($f ) -> full ${mode} scan (no skips)."
6777 exit 0
6878 ;;
6979 esac
7686# grep's no-match exit would otherwise kill the script under pipefail.
7787changed_mods=$( printf ' %s\n' " ${changed} " | { grep ' /' || true ; } | cut -d/ -f1 | sort -u)
7888
79- # 3) Idempotently inject the skip property ; handle poms with and without <properties>.
89+ # 3) Idempotently inject the skip properties ; handle poms with and without <properties>.
8090# sed -i.bak + rm is portable across GNU (CI) and BSD (local) sed.
8191inject_skip () {
82- local pom=" $1 /pom.xml"
92+ local pom=" $1 /pom.xml" prop
8393 [ -f " $pom " ] || return 0
84- if grep -q ' <spotbugs\.skip>' " $pom " ; then return 0; fi
85- if grep -q ' <properties>' " $pom " ; then
86- sed -i.bak ' s#<properties>#<properties>\n <spotbugs.skip>true</spotbugs.skip>#' " $pom "
87- else
88- sed -i.bak ' s#</project># <properties>\n <spotbugs.skip>true</spotbugs.skip>\n </properties>\n</project>#' " $pom "
89- fi
90- rm -f " $pom .bak"
94+ for prop in $props ; do
95+ if grep -q " <${prop// ./ \\ .} >" " $pom " ; then continue ; fi
96+ if grep -q ' <properties>' " $pom " ; then
97+ sed -i.bak " s#<properties>#<properties>\n <${prop} >true</${prop} >#" " $pom "
98+ else
99+ sed -i.bak " s#</project># <properties>\n <${prop} >true</${prop} >\n </properties>\n</project>#" " $pom "
100+ fi
101+ rm -f " $pom .bak"
102+ done
91103}
92104
93105# 4) Skip every reactor module that was not touched by this PR. Kept modules with a
@@ -103,7 +115,7 @@ while IFS= read -r mod; do
103115 kept=$(( kept + 1 ))
104116 kept_pl=" ${kept_pl: +${kept_pl} ,} ../${mod} "
105117 # Only bundles with sources reliably emit a report (a source-less bundle,
106- # e.g. pure branding, has nothing for the analyzer to write a SARIF about).
118+ # e.g. pure branding, has nothing for PMD to write a SARIF about).
107119 if [ -f " ${mod} /META-INF/MANIFEST.MF" ] && [ -d " ${mod} /src" ]; then
108120 expect_reports=" ${expect_reports: +${expect_reports} }${mod} "
109121 fi
@@ -115,6 +127,13 @@ done <<EOF
115127${module_dirs}
116128EOF
117129
130+ # 5) Scope the reactor to the changed modules + their upstream dependencies. ddk-target
131+ # is always kept in the -pl list: the target-definition artifact is referenced by
132+ # target-platform-configuration, not by any MANIFEST, so -am never pulls it — without
133+ # it in the reactor Tycho falls back to a local-repository copy, which fails on a
134+ # cold cache and can silently resolve a stale target definition on a warm one.
135+ # With no analysable changed module (docs-only, or source-less-only) no scope args
136+ # are exported; the workflow skips the lane's Maven step(s) entirely on <MODE>_KEPT=0.
118137# The gate's presence check needs to distinguish "all modules skip-injected"
119138# (zero reports is the expected state) from "the analysis silently died".
120139# If the only changed modules are source-less (feature / target / repository — nothing
@@ -127,25 +146,18 @@ else
127146 effective_kept=$kept
128147fi
129148if [ -n " ${GITHUB_ENV:- } " ]; then
130- echo " SPOTBUGS_KEPT =${effective_kept} " >> " $GITHUB_ENV "
149+ echo " ${prefix} _KEPT =${effective_kept} " >> " $GITHUB_ENV "
131150fi
132151
133- # 5) Scope the reactor to the changed modules + their upstream dependencies. ddk-target
134- # is always kept in the -pl list: the target-definition artifact is referenced by
135- # target-platform-configuration, not by any MANIFEST, so -am never pulls it — without
136- # it in the reactor Tycho falls back to a local-repository copy, which fails on a
137- # cold cache and can silently resolve a stale target definition on a warm one.
138- # With no analysable changed module (docs-only, or source-less-only) no scope args
139- # are exported; the workflow skips the Maven step entirely on SPOTBUGS_KEPT=0.
140152if [ " $effective_kept " -gt 0 ] && [ -n " ${GITHUB_ENV:- } " ]; then
141- echo " SPOTBUGS_SCOPE_ARGS =-pl ../ddk-target,${kept_pl} -am" >> " $GITHUB_ENV "
142- echo " SPOTBUGS_EXPECT_REPORTS =${expect_reports} " >> " $GITHUB_ENV "
153+ echo " ${prefix} _SCOPE_ARGS =-pl ../ddk-target,${kept_pl} -am" >> " $GITHUB_ENV "
154+ echo " ${prefix} _EXPECT_REPORTS =${expect_reports} " >> " $GITHUB_ENV "
143155fi
144156
145- echo " SpotBugs scope: scanning ${effective_kept} changed module(s), skipping ${skipped} unchanged."
157+ echo " ${mode} scope: scanning ${effective_kept} changed module(s), skipping ${skipped} unchanged."
146158echo " Changed modules: ${changed_mods:- <none>} "
147159if [ " $kept " -gt 0 ] && [ " $effective_kept " -eq 0 ]; then
148- echo " Only source-less modules changed (no analysable sources) -> no-op (KEPT =0)."
160+ echo " Only source-less modules changed (no analysable sources) -> no-op (${prefix} _KEPT =0)."
149161fi
150162if [ " $effective_kept " -gt 0 ]; then
151163 echo " Reactor scope args: -pl ../ddk-target,${kept_pl} -am"
0 commit comments