Skip to content

Commit fcc8f3d

Browse files
joaodinissfclaude
andcommitted
ci: consolidate pmd, checkstyle, spotbugs into one static-analysis job with inline PR annotations
Replaces the separate pmd and checkstyle jobs with a single static-analysis job. Step 1 compiles and generates PMD/Checkstyle reports (no *:check goals → no Maven cascade-skip; every module's XML is produced). Step 2 is a Python pass that emits GitHub workflow-command annotations from each violation and exits 1 if any are found — fail-fast that bypasses SpotBugs (slow) when there are PMD/Checkstyle issues. Step 3 re-runs Maven `pmd:check pmd:cpd-check checkstyle:check` as a safety-net for official-tool validation. Steps 4-6 mirror the same pattern for SpotBugs (only run if PMD/Checkstyle are clean). Drops the now-redundant pmd/checkstyle/spotbugs goals from maven-verify (now just `mvn clean verify`). line-endings stays separate. Uses `-T 2C` (8 worker threads on a 4 vCPU runner; benchmark on this branch showed ~25% wall-clock saving over sequential). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent d920b3b commit fcc8f3d

1 file changed

Lines changed: 110 additions & 18 deletions

File tree

.github/workflows/verify.yml

Lines changed: 110 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ on:
44
branches: [master]
55
pull_request:
66
jobs:
7-
pmd:
7+
static-analysis:
88
runs-on: ubuntu-24.04
99
steps:
1010
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
@@ -14,20 +14,114 @@ jobs:
1414
java-version: '21'
1515
- name: Set up Workspace Environment Variable
1616
run: echo "WORKSPACE=${{ github.workspace }}" >> $GITHUB_ENV
17-
- name: PMD Check
18-
run: mvn pmd:pmd pmd:cpd pmd:check pmd:cpd-check -f ./ddk-parent/pom.xml --batch-mode --fail-at-end
19-
checkstyle:
20-
runs-on: ubuntu-24.04
21-
steps:
22-
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
23-
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
17+
- name: Cache Maven dependencies
18+
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
2419
with:
25-
distribution: 'temurin'
26-
java-version: '21'
27-
- name: Set up Workspace Environment Variable
28-
run: echo "WORKSPACE=${{ github.workspace }}" >> $GITHUB_ENV
29-
- name: Checkstyle Check
30-
run: mvn checkstyle:checkstyle checkstyle:check -f ./ddk-parent/pom.xml --batch-mode --fail-at-end
20+
path: /home/runner/.m2/repository
21+
key: ${{ runner.os }}-maven-0-${{ hashFiles('**/pom.xml') }}
22+
- name: Compile + generate PMD/Checkstyle reports
23+
# Report goals (pmd:pmd, pmd:cpd, checkstyle:checkstyle) never fail the build, so
24+
# every module's XML is produced — no Maven cascade-skip. Compile first for PMD's
25+
# type-resolving rules. Note: pmd:pmd may miss a few edge-case rules that only
26+
# surface in pmd:check (see PR description for the maven-pmd-plugin quirk).
27+
run: |
28+
mvn -T 2C -f ./ddk-parent/pom.xml --batch-mode --fail-at-end \
29+
compile \
30+
pmd:pmd pmd:cpd \
31+
checkstyle:checkstyle
32+
- name: Annotate + count PMD/Checkstyle violations
33+
# Parse every pmd.xml and checkstyle-result.xml in one Python pass. Emits a GitHub
34+
# workflow-command annotation per violation (visible inline in PR Files-changed) and
35+
# exits 1 if total > 0 — fail-fast that bypasses SpotBugs (slow) when there are
36+
# already known PMD/Checkstyle issues. Runs even if a previous step failed.
37+
if: always()
38+
run: |
39+
python3 - <<'EOF'
40+
import os, sys
41+
from xml.etree import ElementTree as ET
42+
from pathlib import Path
43+
44+
root = Path(os.environ.get('GITHUB_WORKSPACE', '.'))
45+
count = 0
46+
47+
def annot(level, file, line, title, msg):
48+
global count
49+
count += 1
50+
msg = (msg or '').strip().replace('\n', ' ')[:1000]
51+
try:
52+
rel = Path(file).relative_to(root)
53+
except ValueError:
54+
rel = file
55+
print(f"::{level} file={rel},line={line},title={title}::{msg}")
56+
57+
for xml in root.rglob('target/pmd.xml'):
58+
for f in ET.parse(xml).getroot().findall('file'):
59+
for v in f.findall('violation'):
60+
level = 'error' if v.attrib.get('priority') == '1' else 'warning'
61+
annot(level, f.attrib['name'], v.attrib.get('beginline', '1'),
62+
f"PMD/{v.attrib.get('rule','')}", v.text)
63+
64+
for xml in root.rglob('target/checkstyle-result.xml'):
65+
for f in ET.parse(xml).getroot().findall('file'):
66+
for e in f.findall('error'):
67+
level = 'error' if e.attrib.get('severity') == 'error' else 'warning'
68+
annot(level, f.attrib['name'], e.attrib.get('line', '1'),
69+
f"Checkstyle/{e.attrib.get('source','').split('.')[-1]}",
70+
e.attrib.get('message',''))
71+
72+
print(f"PMD/Checkstyle violations: {count}")
73+
if count > 0:
74+
sys.exit(1)
75+
EOF
76+
- name: Enforce PMD/Checkstyle thresholds (Maven safety-net)
77+
# Redundant in the success case (Python check above already passed). Provides
78+
# official-tool validation in case the Python parser misreads schema.
79+
run: |
80+
mvn -T 2C -f ./ddk-parent/pom.xml --batch-mode --fail-at-end \
81+
pmd:check pmd:cpd-check \
82+
checkstyle:check
83+
- name: Generate SpotBugs report
84+
run: |
85+
mvn -T 2C -f ./ddk-parent/pom.xml --batch-mode --fail-at-end \
86+
spotbugs:spotbugs
87+
- name: Annotate + count SpotBugs violations
88+
if: always()
89+
run: |
90+
python3 - <<'EOF'
91+
import os, sys
92+
from xml.etree import ElementTree as ET
93+
from pathlib import Path
94+
95+
root = Path(os.environ.get('GITHUB_WORKSPACE', '.'))
96+
count = 0
97+
98+
def annot(level, file, line, title, msg):
99+
global count
100+
count += 1
101+
msg = (msg or '').strip().replace('\n', ' ')[:1000]
102+
try:
103+
rel = Path(file).relative_to(root)
104+
except ValueError:
105+
rel = file
106+
print(f"::{level} file={rel},line={line},title={title}::{msg}")
107+
108+
for xml in root.rglob('target/spotbugsXml.xml'):
109+
for b in ET.parse(xml).getroot().findall('BugInstance'):
110+
sl = b.find('SourceLine')
111+
lm = b.find('LongMessage')
112+
if sl is not None and lm is not None:
113+
annot('warning', sl.attrib.get('sourcepath', '?'),
114+
sl.attrib.get('start', '1'),
115+
f"SpotBugs/{b.attrib.get('type','')}", lm.text)
116+
117+
print(f"SpotBugs violations: {count}")
118+
if count > 0:
119+
sys.exit(1)
120+
EOF
121+
- name: Enforce SpotBugs threshold (Maven safety-net)
122+
run: |
123+
mvn -T 2C -f ./ddk-parent/pom.xml --batch-mode --fail-at-end \
124+
spotbugs:check
31125
line-endings:
32126
runs-on: ubuntu-24.04
33127
steps:
@@ -64,10 +158,8 @@ jobs:
64158
with:
65159
path: /home/runner/.m2/repository
66160
key: ${{ runner.os }}-maven-0-${{ hashFiles('**/pom.xml') }}
67-
- name: Build with Maven within a virtual X Server Environment
68-
# Run pmd:pmd and pmd:cpd first to generate reports for all modules, then run pmd:check and pmd:cpd-check
69-
# This ensures all violations are collected and reported before the build fails
70-
run: xvfb-run mvn clean verify checkstyle:check pmd:pmd pmd:cpd pmd:check pmd:cpd-check spotbugs:check -f ./ddk-parent/pom.xml --batch-mode --fail-at-end
161+
- name: Build with Maven within a virtual X Server Environment
162+
run: xvfb-run mvn clean verify -f ./ddk-parent/pom.xml --batch-mode --fail-at-end
71163
- name: Fail on missing surefire reports
72164
# Tycho-Surefire writes no TEST-*.xml when discovery is empty — fail the job in that case.
73165
if: always()

0 commit comments

Comments
 (0)