Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,39 +1,49 @@
#!/usr/bin/env bash
#
# Scope SpotBugs to a pull request's changed modules.
# Scope static analysis (SpotBugs, or PMD/CPD/Checkstyle) to a pull request's
# changed modules.
#
# Default is RUN (analyze). On a PR this injects <spotbugs.skip>true</spotbugs.skip>
# into every UNCHANGED reactor module's pom, so spotbugs-maven-plugin skips the goal —
# and therefore the per-module JVM fork (SpotBugsMojo gates on `skip` before forking) —
# for those modules. The full-reactor compile is left intact (a changed module is still
# analysed with its complete aux-classpath). Master/snapshot builds run a full scan;
# this script is invoked on pull_request only.
# Default is RUN (analyze). On a PR this injects <TOOL.skip>true</TOOL.skip>
# properties into every UNCHANGED reactor module's pom, so the analysis mojos
# skip those modules — for SpotBugs that also skips the per-module JVM fork
# (SpotBugsMojo gates on `skip` before forking). A changed module is still
# analysed with its complete aux-classpath: the -am-pulled unchanged
# dependencies compile but are not analysed. Master/snapshot builds run a full
# scan; this script is invoked on pull_request only.
#
# Why this and not -Dspotbugs.onlyAnalyze: onlyAnalyze is one clean flag, but SpotBugs
# applies its class screener too late (after the per-module fork + class scan), so it
# only trimmed ~17% of the goal vs ~88% for this per-module skip (measured on this
# reactor). A small upstream SpotBugs early-exit (skip the run when no application class
# matches the screener) would make onlyAnalyze competitive; if that ever lands, switch
# to onlyAnalyze and delete this script (tracked in #1455 / spotbugs/spotbugs#3796).
# to onlyAnalyze and delete the spotbugs mode here (tracked in #1455 /
# spotbugs/spotbugs#3796).
#
# On top of the skips, the changed reactor modules are exported as SPOTBUGS_SCOPE_ARGS
# ("-pl <changed> -am") so the lane builds only those modules plus their upstream
# dependencies instead of the full reactor. The -am-pulled unchanged dependencies still
# carry the injected skip: they compile (complete aux-classpath) but are not analysed.
# On top of the skips, the changed reactor modules are exported as
# SPOTBUGS_SCOPE_ARGS / LINT_SCOPE_ARGS ("-pl <changed> -am") so the lane builds
# only those modules plus their upstream dependencies instead of the full reactor.
#
# Run from the repository root. Usage: compute-spotbugs-skip.sh <base-sha>
# Run from the repository root. Usage: compute-analysis-skip.sh <base-sha> <spotbugs|lint>
set -euo pipefail
base="${1:?base sha required}"
mode="${2:?mode required: spotbugs|lint}"

case "$mode" in
spotbugs) props="spotbugs.skip"; scope_var="SPOTBUGS_SCOPE_ARGS" ;;
lint) props="pmd.skip cpd.skip checkstyle.skip"; scope_var="LINT_SCOPE_ARGS" ;;
*) echo "unknown mode: $mode" >&2; exit 2 ;;
esac

changed=$(git diff --name-only --diff-filter=ACMR "${base}...HEAD")

# 1) A change to shared build/config can affect any module -> full scan (skip nothing).
# ddk-configuration holds the analyzers' rulesets and filters, so it counts too.
# Fail safe: the worst case here is "analyse everything", never "analyse nothing".
while IFS= read -r f; do
[ -n "$f" ] || continue
case "$f" in
pom.xml | ddk-parent/* | .mvn/* | *.target | .github/* | *[Ss]pot[Bb]ugs*[Ee]xclude*)
echo "Build/config change ($f) -> full SpotBugs scan (no skips)."
pom.xml | ddk-parent/* | .mvn/* | *.target | .github/* | ddk-configuration/* | *[Ss]pot[Bb]ugs*[Ee]xclude*)
echo "Build/config change ($f) -> full ${mode} scan (no skips)."
exit 0
;;
esac
Expand All @@ -50,18 +60,20 @@ changed_mods=$(printf '%s\n' "${changed}" | grep '/' | cut -d/ -f1 | sort -u)
module_dirs=$(grep -oE '<module>\.\./[^<]+</module>' ddk-parent/pom.xml \
| sed -E 's#.*\.\./([^<]+)</module>#\1#')

# 4) Idempotently inject the skip property; handle poms with and without <properties>.
# 4) Idempotently inject the skip properties; handle poms with and without <properties>.
# sed -i.bak + rm is portable across GNU (CI) and BSD (local) sed.
inject_skip() {
local pom="$1/pom.xml"
local pom="$1/pom.xml" prop
[ -f "$pom" ] || return 0
if grep -q '<spotbugs\.skip>' "$pom"; then return 0; fi
if grep -q '<properties>' "$pom"; then
sed -i.bak 's#<properties>#<properties>\n <spotbugs.skip>true</spotbugs.skip>#' "$pom"
else
sed -i.bak 's#</project># <properties>\n <spotbugs.skip>true</spotbugs.skip>\n </properties>\n</project>#' "$pom"
fi
rm -f "$pom.bak"
for prop in $props; do
if grep -q "<${prop//./\\.}>" "$pom"; then continue; fi
if grep -q '<properties>' "$pom"; then
sed -i.bak "s#<properties>#<properties>\n <${prop}>true</${prop}>#" "$pom"
else
sed -i.bak "s#</project># <properties>\n <${prop}>true</${prop}>\n </properties>\n</project>#" "$pom"
fi
rm -f "$pom.bak"
done
}

# 5) Skip every reactor module that was not touched by this PR.
Expand All @@ -85,10 +97,10 @@ EOF
# changed reactor module (e.g. a docs-only PR) the full reactor builds with every
# analysis skipped — same result, no flags needed.
if [ "$kept" -gt 0 ] && [ -n "${GITHUB_ENV:-}" ]; then
echo "SPOTBUGS_SCOPE_ARGS=-pl ${kept_pl} -am" >> "$GITHUB_ENV"
echo "${scope_var}=-pl ${kept_pl} -am" >> "$GITHUB_ENV"
fi

echo "SpotBugs scope: scanning ${kept} changed module(s), skipping ${skipped} unchanged."
echo "${mode} scope: scanning ${kept} changed module(s), skipping ${skipped} unchanged."
echo "Changed modules: ${changed_mods:-<none>}"
if [ -n "${kept_pl}" ]; then
echo "Reactor scope args: -pl ${kept_pl} -am"
Expand Down
28 changes: 23 additions & 5 deletions .github/workflows/verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ jobs:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0 # need the PR base commit to diff the changed modules
- uses: actions/setup-java@0f481fcb613427c0f801b606911222b5b6f3083a # v5
with:
distribution: 'temurin'
Expand All @@ -40,26 +42,42 @@ jobs:
key: ${{ runner.os }}-maven-publish-${{ hashFiles('**/pom.xml', '**/*.target') }}
restore-keys: ${{ runner.os }}-maven-publish-

- name: Scope static analysis to the PR's changed modules
# Injects pmd/cpd/checkstyle skip properties into unchanged module poms and
# exports LINT_SCOPE_ARGS (-pl <changed> -am) so only the changed modules and
# their upstream deps build (skip-injected deps compile for PMD's type
# resolution but are not analysed). Build/config change -> full scan, full
# reactor. pull_request only; master/snapshot run a full scan.
run: bash .github/scripts/compute-analysis-skip.sh "${{ github.event.pull_request.base.sha }}" lint

- name: PMD + Checkstyle reports (SARIF)
# PMD: SarifRenderer FQCN — emits pmd.sarif.json AND keeps pmd.xml.
# Checkstyle: output.format=sarif — SARIF content in checkstyle-result.xml.
# CPD is excluded here: the global -Dformat flag uses PMD's Renderer
# hierarchy and would ClassCastException CPD's CPDReportRenderer.
# `compile` stays: PMD's type-resolving rules need Tycho's aux-classpath.
# jgit.dirtyWorkingTree=ignore: the scope step edits poms (see the spotbugs
# lane for the rationale; this job releases nothing).
run: |
mvn -T 2C -f ./ddk-parent/pom.xml --batch-mode --fail-never \
mvn -T 2C -f ./ddk-parent/pom.xml ${LINT_SCOPE_ARGS:-} --batch-mode --fail-never \
compile \
pmd:pmd checkstyle:checkstyle \
-Dformat=net.sourceforge.pmd.renderers.SarifRenderer \
-Dcheckstyle.output.format=sarif
-Dcheckstyle.output.format=sarif \
-Djgit.dirtyWorkingTree=ignore

- name: CPD report (separate invocation — no SARIF support)
# CPD has no SARIF renderer; emits cpd.xml only. Run standalone so the
# PMD -Dformat flag isn't in scope.
# No `compile`: CPD is token-based over src/ and needs neither bytecode
# nor the target platform — outputs are identical with and without a
# compile pass (verified at the current token threshold and at 100).
# NOTE: project CPD token threshold is currently very high (issue #1339),
# which effectively disables detection; re-tune once #1339 lands.
# No jgit flag needed: a direct goal invocation runs no lifecycle, so the
# build-qualifier's dirty-tree check never executes here.
run: |
mvn -T 2C -f ./ddk-parent/pom.xml --batch-mode --fail-never \
compile \
mvn -T 2C -f ./ddk-parent/pom.xml ${LINT_SCOPE_ARGS:-} --batch-mode --fail-never \
pmd:cpd-check

- name: Merge per-module SARIFs (PMD + Checkstyle)
Expand Down Expand Up @@ -142,7 +160,7 @@ jobs:
# changed modules and their upstream deps build at all (skip-injected deps
# compile for the aux-classpath but are not analysed). A build/config change ->
# full scan, full reactor. pull_request only; master/snapshot run a full scan.
run: bash .github/scripts/compute-spotbugs-skip.sh "${{ github.event.pull_request.base.sha }}"
run: bash .github/scripts/compute-analysis-skip.sh "${{ github.event.pull_request.base.sha }}" spotbugs

- name: SpotBugs report (SARIF)
# sarifOutput=true emits spotbugsSarif.json (also writes spotbugsXml.xml).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,5 @@ public void configure(final Binder binder) {
binder.bind(IXtextProjectConfig.class).to(XtextProjectConfig.class);
}
}

// CI scoped-lint probe (temporary)
Loading