ci: consolidate pmd, checkstyle, spotbugs into one static-analysis job #3224
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |