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# Fail safe: the worst case here is "analyse everything", never "analyse nothing".
5161while IFS= read -r f; do
5262 [ -n " $f " ] || continue
@@ -59,10 +69,10 @@ while IFS= read -r f; do
5969 # full scan analysed all of them (not just >=1) — a mojo death swallowed by
6070 # --fail-never can't pass as long as one sibling reported.
6171 if [ -n " ${GITHUB_ENV:- } " ]; then
62- echo " SPOTBUGS_KEPT =all" >> " $GITHUB_ENV "
63- echo " SPOTBUGS_EXPECT_REPORTS =${all_source_modules} " >> " $GITHUB_ENV "
72+ echo " ${prefix} _KEPT =all" >> " $GITHUB_ENV "
73+ echo " ${prefix} _EXPECT_REPORTS =${all_source_modules} " >> " $GITHUB_ENV "
6474 fi
65- echo " Build/config change ($f ) -> full SpotBugs scan (no skips)."
75+ echo " Build/config change ($f ) -> full ${mode} scan (no skips)."
6676 exit 0
6777 ;;
6878 esac
7585# grep's no-match exit would otherwise kill the script under pipefail.
7686changed_mods=$( printf ' %s\n' " ${changed} " | { grep ' /' || true ; } | cut -d/ -f1 | sort -u)
7787
78- # 3) Idempotently inject the skip property ; handle poms with and without <properties>.
88+ # 3) Idempotently inject the skip properties ; handle poms with and without <properties>.
7989# sed -i.bak + rm is portable across GNU (CI) and BSD (local) sed.
8090inject_skip () {
81- local pom=" $1 /pom.xml"
91+ local pom=" $1 /pom.xml" prop
8292 [ -f " $pom " ] || return 0
83- if grep -q ' <spotbugs\.skip>' " $pom " ; then return 0; fi
84- if grep -q ' <properties>' " $pom " ; then
85- sed -i.bak ' s#<properties>#<properties>\n <spotbugs.skip>true</spotbugs.skip>#' " $pom "
86- else
87- sed -i.bak ' s#</project># <properties>\n <spotbugs.skip>true</spotbugs.skip>\n </properties>\n</project>#' " $pom "
88- fi
89- rm -f " $pom .bak"
93+ for prop in $props ; do
94+ if grep -q " <${prop// ./ \\ .} >" " $pom " ; then continue ; fi
95+ if grep -q ' <properties>' " $pom " ; then
96+ sed -i.bak " s#<properties>#<properties>\n <${prop} >true</${prop} >#" " $pom "
97+ else
98+ sed -i.bak " s#</project># <properties>\n <${prop} >true</${prop} >\n </properties>\n</project>#" " $pom "
99+ fi
100+ rm -f " $pom .bak"
101+ done
90102}
91103
92104# 4) Skip every reactor module that was not touched by this PR. Kept modules with a
@@ -102,7 +114,7 @@ while IFS= read -r mod; do
102114 kept=$(( kept + 1 ))
103115 kept_pl=" ${kept_pl: +${kept_pl} ,} ../${mod} "
104116 # Only bundles with sources reliably emit a report (a source-less bundle,
105- # e.g. pure branding, has nothing for the analyzer to write a SARIF about).
117+ # e.g. pure branding, has nothing for PMD to write a SARIF about).
106118 if [ -f " ${mod} /META-INF/MANIFEST.MF" ] && [ -d " ${mod} /src" ]; then
107119 expect_reports=" ${expect_reports: +${expect_reports} }${mod} "
108120 fi
@@ -114,6 +126,13 @@ done <<EOF
114126${module_dirs}
115127EOF
116128
129+ # 5) Scope the reactor to the changed modules + their upstream dependencies. ddk-target
130+ # is always kept in the -pl list: the target-definition artifact is referenced by
131+ # target-platform-configuration, not by any MANIFEST, so -am never pulls it — without
132+ # it in the reactor Tycho falls back to a local-repository copy, which fails on a
133+ # cold cache and can silently resolve a stale target definition on a warm one.
134+ # With no analysable changed module (docs-only, or source-less-only) no scope args
135+ # are exported; the workflow skips the lane's Maven step(s) entirely on <MODE>_KEPT=0.
117136# The gate's presence check needs to distinguish "all modules skip-injected"
118137# (zero reports is the expected state) from "the analysis silently died".
119138# If the only changed modules are source-less (feature / target / repository — nothing
@@ -126,25 +145,18 @@ else
126145 effective_kept=$kept
127146fi
128147if [ -n " ${GITHUB_ENV:- } " ]; then
129- echo " SPOTBUGS_KEPT =${effective_kept} " >> " $GITHUB_ENV "
148+ echo " ${prefix} _KEPT =${effective_kept} " >> " $GITHUB_ENV "
130149fi
131150
132- # 5) Scope the reactor to the changed modules + their upstream dependencies. ddk-target
133- # is always kept in the -pl list: the target-definition artifact is referenced by
134- # target-platform-configuration, not by any MANIFEST, so -am never pulls it — without
135- # it in the reactor Tycho falls back to a local-repository copy, which fails on a
136- # cold cache and can silently resolve a stale target definition on a warm one.
137- # With no analysable changed module (docs-only, or source-less-only) no scope args
138- # are exported; the workflow skips the Maven step entirely on SPOTBUGS_KEPT=0.
139151if [ " $effective_kept " -gt 0 ] && [ -n " ${GITHUB_ENV:- } " ]; then
140- echo " SPOTBUGS_SCOPE_ARGS =-pl ../ddk-target,${kept_pl} -am" >> " $GITHUB_ENV "
141- echo " SPOTBUGS_EXPECT_REPORTS =${expect_reports} " >> " $GITHUB_ENV "
152+ echo " ${prefix} _SCOPE_ARGS =-pl ../ddk-target,${kept_pl} -am" >> " $GITHUB_ENV "
153+ echo " ${prefix} _EXPECT_REPORTS =${expect_reports} " >> " $GITHUB_ENV "
142154fi
143155
144- echo " SpotBugs scope: scanning ${effective_kept} changed module(s), skipping ${skipped} unchanged."
156+ echo " ${mode} scope: scanning ${effective_kept} changed module(s), skipping ${skipped} unchanged."
145157echo " Changed modules: ${changed_mods:- <none>} "
146158if [ " $kept " -gt 0 ] && [ " $effective_kept " -eq 0 ]; then
147- echo " Only source-less modules changed (no analysable sources) -> no-op (KEPT =0)."
159+ echo " Only source-less modules changed (no analysable sources) -> no-op (${prefix} _KEPT =0)."
148160fi
149161if [ " $effective_kept " -gt 0 ]; then
150162 echo " Reactor scope args: -pl ../ddk-target,${kept_pl} -am"
0 commit comments