diff --git a/.github/scripts/compute-spotbugs-skip.sh b/.github/scripts/compute-analysis-skip.sh
similarity index 53%
rename from .github/scripts/compute-spotbugs-skip.sh
rename to .github/scripts/compute-analysis-skip.sh
index f82e6829a..7ea6569b4 100755
--- a/.github/scripts/compute-spotbugs-skip.sh
+++ b/.github/scripts/compute-analysis-skip.sh
@@ -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 true
-# 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 true
+# 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 -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 -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
+# Run from the repository root. Usage: compute-analysis-skip.sh
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
@@ -50,18 +60,20 @@ changed_mods=$(printf '%s\n' "${changed}" | grep '/' | cut -d/ -f1 | sort -u)
module_dirs=$(grep -oE '\.\./[^<]+' ddk-parent/pom.xml \
| sed -E 's#.*\.\./([^<]+)#\1#')
-# 4) Idempotently inject the skip property; handle poms with and without .
+# 4) Idempotently inject the skip properties; handle poms with and without .
# 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 '' "$pom"; then return 0; fi
- if grep -q '' "$pom"; then
- sed -i.bak 's##\n true#' "$pom"
- else
- sed -i.bak 's## \n true\n \n#' "$pom"
- fi
- rm -f "$pom.bak"
+ for prop in $props; do
+ if grep -q "<${prop//./\\.}>" "$pom"; then continue; fi
+ if grep -q '' "$pom"; then
+ sed -i.bak "s##\n <${prop}>true${prop}>#" "$pom"
+ else
+ sed -i.bak "s## \n <${prop}>true${prop}>\n \n#" "$pom"
+ fi
+ rm -f "$pom.bak"
+ done
}
# 5) Skip every reactor module that was not touched by this PR.
@@ -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:-}"
if [ -n "${kept_pl}" ]; then
echo "Reactor scope args: -pl ${kept_pl} -am"
diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml
index 9cff8f9a6..9a659482e 100644
--- a/.github/workflows/verify.yml
+++ b/.github/workflows/verify.yml
@@ -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'
@@ -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 -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)
@@ -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).
diff --git a/com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/FormatRuntimeModule.java b/com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/FormatRuntimeModule.java
index 98f0db796..d6f15bd03 100644
--- a/com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/FormatRuntimeModule.java
+++ b/com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/FormatRuntimeModule.java
@@ -123,3 +123,5 @@ public void configure(final Binder binder) {
binder.bind(IXtextProjectConfig.class).to(XtextProjectConfig.class);
}
}
+
+// CI scoped-lint probe (temporary)