From f74619ed22a77e1bccd7019b530f6dc8588f3b89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Dinis=20Ferreira?= Date: Fri, 10 Jul 2026 23:42:55 +0200 Subject: [PATCH 1/3] ci: run CPD without a compile pass in the lint lane CPD tokenizes sources under src/ and needs neither bytecode nor a resolved target platform, so the second lint invocation drops its compile goals. cpd.xml outputs are identical with and without the compile pass, verified at the current token threshold and at the PMD default of 100 (timestamp attributes aside). Measured locally (warm tree, JDK 21): 6.8s vs 44.3s at the current threshold; 4.5s vs 28.5s at threshold 100. In CI the invocation was 53s, ~40s of it redundant recompilation and JVM startup. Co-Authored-By: Claude Fable 5 --- .github/workflows/verify.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 9cff8f9a6..5a4aea0ca 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -55,11 +55,13 @@ jobs: - 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. run: | mvn -T 2C -f ./ddk-parent/pom.xml --batch-mode --fail-never \ - compile \ pmd:cpd-check - name: Merge per-module SARIFs (PMD + Checkstyle) From 3ecbf45d028bb9b0151e1caef3cad08ae09acd57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Dinis=20Ferreira?= Date: Fri, 10 Jul 2026 23:48:30 +0200 Subject: [PATCH 2/3] ci: scope PMD/CPD/Checkstyle to changed modules in the lint lane MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit compute-spotbugs-skip.sh becomes compute-analysis-skip.sh with a mode argument: `spotbugs` injects spotbugs.skip as before, `lint` injects pmd.skip, cpd.skip and checkstyle.skip, and each mode exports its -pl/-am reactor scope args. The lint lane gains the scope step and passes LINT_SCOPE_ARGS to both invocations; `compile` stays in the PMD/Checkstyle invocation because PMD's type-resolving rules need Tycho's aux-classpath (skip-injected -am dependencies compile but are not analysed). Changes under ddk-configuration (rulesets, filters) now also trigger the full-scan fail-safe in both lanes. Code Scanning note: repo-wide alert state reflects the default branch, which receives no lint/spotbugs analyses (verify runs on pull_request only), so a scoped upload that omits unchanged modules can only affect PR-context annotations — the same property the spotbugs category has had since the per-module skip landed. Co-Authored-By: Claude Fable 5 --- ...tbugs-skip.sh => compute-analysis-skip.sh} | 64 +++++++++++-------- .github/workflows/verify.yml | 24 +++++-- 2 files changed, 58 insertions(+), 30 deletions(-) rename .github/scripts/{compute-spotbugs-skip.sh => compute-analysis-skip.sh} (53%) 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 5a4aea0ca..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,17 +42,29 @@ 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 @@ -60,8 +74,10 @@ jobs: # 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 \ + 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) @@ -144,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). From ab946e186ed5ebb04360cdcfa8c1218ae79a14ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Dinis=20Ferreira?= Date: Fri, 10 Jul 2026 23:51:14 +0200 Subject: [PATCH 3/3] probe: scoped lint timing measurement (temporary) --- .../com/avaloq/tools/ddk/xtext/format/FormatRuntimeModule.java | 2 ++ 1 file changed, 2 insertions(+) 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)