Skip to content

ci: consolidate pmd, checkstyle, spotbugs into one static-analysis job #3224

ci: consolidate pmd, checkstyle, spotbugs into one static-analysis job

ci: consolidate pmd, checkstyle, spotbugs into one static-analysis job #3224

Workflow file for this run

name: verify
on:
push:
branches: [master]
pull_request:
jobs:
static-analysis:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
with:
distribution: 'temurin'
java-version: '21'
- name: Set up Workspace Environment Variable
run: echo "WORKSPACE=${{ github.workspace }}" >> $GITHUB_ENV
- name: Cache Maven dependencies
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: /home/runner/.m2/repository
key: ${{ runner.os }}-maven-0-${{ hashFiles('**/pom.xml') }}
- name: Compile + generate PMD/Checkstyle reports
# Report goals (pmd:pmd, pmd:cpd, checkstyle:checkstyle) never fail the build, so
# every module's XML is produced — no Maven cascade-skip. Compile first for PMD's
# type-resolving rules. Note: pmd:pmd may miss a few edge-case rules that only
# surface in pmd:check (see PR description for the maven-pmd-plugin quirk).
run: |
mvn -T 2C -f ./ddk-parent/pom.xml --batch-mode --fail-at-end \
compile \
pmd:pmd pmd:cpd \
checkstyle:checkstyle
- name: Annotate + count PMD/Checkstyle violations
# Parse every pmd.xml and checkstyle-result.xml in one Python pass. Emits a GitHub
# workflow-command annotation per violation (visible inline in PR Files-changed) and
# exits 1 if total > 0 — fail-fast that bypasses SpotBugs (slow) when there are
# already known PMD/Checkstyle issues. Runs even if a previous step failed.
if: always()
run: |
python3 - <<'EOF'
import os, sys
from xml.etree import ElementTree as ET
from pathlib import Path
root = Path(os.environ.get('GITHUB_WORKSPACE', '.'))
count = 0
def annot(level, file, line, title, msg):
global count
count += 1
msg = (msg or '').strip().replace('\n', ' ')[:1000]
try:
rel = Path(file).relative_to(root)
except ValueError:
rel = file
print(f"::{level} file={rel},line={line},title={title}::{msg}")
for xml in root.rglob('target/pmd.xml'):
for f in ET.parse(xml).getroot().findall('file'):
for v in f.findall('violation'):
level = 'error' if v.attrib.get('priority') == '1' else 'warning'
annot(level, f.attrib['name'], v.attrib.get('beginline', '1'),
f"PMD/{v.attrib.get('rule','')}", v.text)
for xml in root.rglob('target/checkstyle-result.xml'):
for f in ET.parse(xml).getroot().findall('file'):
for e in f.findall('error'):
level = 'error' if e.attrib.get('severity') == 'error' else 'warning'
annot(level, f.attrib['name'], e.attrib.get('line', '1'),
f"Checkstyle/{e.attrib.get('source','').split('.')[-1]}",
e.attrib.get('message',''))
print(f"PMD/Checkstyle violations: {count}")
if count > 0:
sys.exit(1)
EOF
- name: Enforce PMD/Checkstyle thresholds (Maven safety-net)
# Redundant in the success case (Python check above already passed). Provides
# official-tool validation in case the Python parser misreads schema.
run: |
mvn -T 2C -f ./ddk-parent/pom.xml --batch-mode --fail-at-end \
pmd:check pmd:cpd-check \
checkstyle:check
- name: Generate SpotBugs report
run: |
mvn -T 2C -f ./ddk-parent/pom.xml --batch-mode --fail-at-end \
spotbugs:spotbugs
- name: Annotate + count SpotBugs violations
if: always()
run: |
python3 - <<'EOF'
import os, sys
from xml.etree import ElementTree as ET
from pathlib import Path
root = Path(os.environ.get('GITHUB_WORKSPACE', '.'))
count = 0
def annot(level, file, line, title, msg):
global count
count += 1
msg = (msg or '').strip().replace('\n', ' ')[:1000]
try:
rel = Path(file).relative_to(root)
except ValueError:
rel = file
print(f"::{level} file={rel},line={line},title={title}::{msg}")
for xml in root.rglob('target/spotbugsXml.xml'):
for b in ET.parse(xml).getroot().findall('BugInstance'):
sl = b.find('SourceLine')
lm = b.find('LongMessage')
if sl is not None and lm is not None:
annot('warning', sl.attrib.get('sourcepath', '?'),
sl.attrib.get('start', '1'),
f"SpotBugs/{b.attrib.get('type','')}", lm.text)
print(f"SpotBugs violations: {count}")
if count > 0:
sys.exit(1)
EOF
- name: Enforce SpotBugs threshold (Maven safety-net)
run: |
mvn -T 2C -f ./ddk-parent/pom.xml --batch-mode --fail-at-end \
spotbugs:check
line-endings:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Check LF line endings in index
# .gitattributes declares `* text=auto eol=lf` with .bat/.cmd/.ps1
# exempted. Git's clean filter normalizes on commit, but verify it
# explicitly in case a file is miscategorized as binary or a filter
# is bypassed.
run: |
violations=$(git ls-files --eol \
| grep -E "^i/(crlf|mixed)" \
| grep -vE "\.(bat|cmd|ps1)$" || true)
if [ -n "$violations" ]; then
echo "Files with CRLF/mixed line endings stored in the index:"
echo "$violations"
exit 1
fi
maven-verify:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Set up JDK 21
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
with:
distribution: 'temurin'
java-version: '21'
- name: Log Maven version
run: mvn --version
- name: Set up Workspace Environment Variable
run: echo "WORKSPACE=${{ github.workspace }}" >> $GITHUB_ENV
- name: Cache Maven dependencies
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: /home/runner/.m2/repository
key: ${{ runner.os }}-maven-0-${{ hashFiles('**/pom.xml') }}
- name: Build with Maven within a virtual X Server Environment
run: xvfb-run mvn clean verify -f ./ddk-parent/pom.xml --batch-mode --fail-at-end
- name: Fail on missing surefire reports
# Tycho-Surefire writes no TEST-*.xml when discovery is empty — fail the job in that case.
if: always()
run: |
if ! find . -path '*/target/surefire-reports/TEST-*.xml' -print -quit | grep -q .; then
echo "::error::No surefire reports found. Test discovery is likely broken."
exit 1
fi
- name: Archive Tycho Surefire Plugin
if: ${{ failure() }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: tycho-surefire-plugin
path: ${{ env.GITHUB_WORKSPACE }}/com.avaloq.tools.ddk.xtext.test/target/work/data/.metadata/.log