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#" "$pom" + else + sed -i.bak "s## \n <${prop}>true\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)