Skip to content

build: enable JaCoCo coverage report generation across all modules #5

build: enable JaCoCo coverage report generation across all modules

build: enable JaCoCo coverage report generation across all modules #5

Workflow file for this run

name: Scan parity (self-scan ↔ SonarQube Cloud)
# Runs BOTH scans on the same commit and diffs their issue lists. Every PR
# answers: "does our daemon find what SonarSource's own pipeline finds?".
#
# This workflow supersedes .github/workflows/sonarqube-cloud.yml — that file
# was removed in the same commit; the Cloud scan now happens here, alongside
# the self-scan and the parity comparison. The standalone self-scan
# (sonar.yml) is kept because it works on fork PRs (no SONAR_TOKEN needed),
# whereas this one requires the token and so skips for forks.
#
# Setup required (one-time, by the repo admin):
# 1. Sign in to https://sonarcloud.io with the repo's GitHub org.
# 2. Import this repo as a SonarQube Cloud project.
# Project key: RandomCodeSpace_sonar-predict, organisation: randomcodespace
# (adjust the env values below if SonarCloud assigns different ones).
# 3. Configure "Analysis Method" → "With GitHub Actions"; copy SONAR_TOKEN.
# 4. Add SONAR_TOKEN as a repo secret.
permissions:
contents: read
pull-requests: read
on:
pull_request:
branches: [main]
push:
branches: [main]
workflow_dispatch:
env:
SONAR_PROJECT_KEY: RandomCodeSpace_sonar-predict
SONAR_ORGANIZATION: randomcodespace
SONAR_HOST_URL: https://sonarcloud.io
jobs:
parity:
name: Scan parity
runs-on: ubuntu-latest
# Skip when the token isn't reachable (fork PRs) — the standalone
# self-scan workflow still gates fork PRs on our daemon's findings.
if: ${{ github.event_name == 'workflow_dispatch' || github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'
- name: Set up Node.js 20
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Cache Maven repository
uses: actions/cache@v4
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
- name: Cache SonarQube Cloud packages
uses: actions/cache@v4
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
restore-keys: |
${{ runner.os }}-sonar
# Fail fast if the SONAR_TOKEN secret isn't configured. The Cloud step
# below would just error obscurely; this is a clearer signal.
- name: Verify SONAR_TOKEN
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: |
if [ -z "${SONAR_TOKEN:-}" ]; then
echo "::error::SONAR_TOKEN not set — parity workflow needs SonarQube Cloud access. See the header of this workflow for setup."
exit 1
fi
echo "SONAR_TOKEN configured."
# Single build that both scans then consume. `verify` also produces
# the per-module JaCoCo XMLs that both scans use as coverage evidence.
- name: Build and test (generates JaCoCo XML)
run: mvn -B -ntp -pl '!dist' verify -Dsurefire.failIfNoSpecifiedTests=false
- name: Build dist (skill bundle for self-scan)
run: mvn -B -ntp -pl dist -am package -DskipTests
# --- (A) Self-scan ------------------------------------------------------
- name: Run self-scan
run: |
set +e
export SONAR_PREDICTOR_HOME="$(pwd)/dist/target/skill/sonar-predictor"
./plugin/skills/sonar-predictor/bin/sonar agent-scan analyze . \
--coverage protocol/target/site/jacoco/jacoco.xml \
--coverage daemon/target/site/jacoco/jacoco.xml \
--coverage cli/target/site/jacoco/jacoco.xml
rc=$?
set -e
echo "Self-scan exit code: $rc (0=clean, 1=issues found, 2+=tool error)"
if [ "$rc" -ge 2 ]; then
echo "::error::Self-scan tool error (exit $rc)"
exit "$rc"
fi
# --- (B) SonarQube Cloud scan ------------------------------------------
- name: Run SonarQube Cloud scan
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
mvn -B -ntp -pl '!dist' \
org.sonarsource.scanner.maven:sonar-maven-plugin:sonar \
-Dsonar.projectKey="${SONAR_PROJECT_KEY}" \
-Dsonar.organization="${SONAR_ORGANIZATION}" \
-Dsonar.host.url="${SONAR_HOST_URL}" \
-Dsonar.qualitygate.wait=false \
-Dsonar.coverage.jacoco.xmlReportPaths=target/site/jacoco/jacoco.xml,../protocol/target/site/jacoco/jacoco.xml,../daemon/target/site/jacoco/jacoco.xml,../cli/target/site/jacoco/jacoco.xml
# SonarQube Cloud processes the scan asynchronously after upload. Poll
# the last completed analysis until the timestamp moves past the start
# of this run, or give up after ~3 minutes (most analyses complete in
# 30-60s; longer polls aren't worth holding the runner for).
- name: Wait for SonarQube Cloud processing
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: |
START_TS=$(date +%s)
for attempt in $(seq 1 18); do
# --proto-redir =https refuses to follow a redirect onto an insecure
# http:// URL (githubactions:S6506). Belt-and-braces with SONAR_HOST_URL
# already being https://, but the rule wants the explicit constraint.
RESP=$(curl -fsSL --proto-redir =https -u "$SONAR_TOKEN:" \
"${SONAR_HOST_URL}/api/project_analyses/search?project=${SONAR_PROJECT_KEY}&ps=1" || echo '{}')
ANALYSIS_TS=$(echo "$RESP" | jq -r '.analyses[0].date // empty' || true)
if [ -n "$ANALYSIS_TS" ]; then
ANALYSIS_EPOCH=$(date -d "$ANALYSIS_TS" +%s 2>/dev/null || echo 0)
if [ "$ANALYSIS_EPOCH" -ge "$START_TS" ]; then
echo "Latest analysis ($ANALYSIS_TS) is from this run."
exit 0
fi
echo "Attempt $attempt: latest analysis is $ANALYSIS_TS (before run start), waiting…"
else
echo "Attempt $attempt: no analyses yet on SonarQube Cloud, waiting…"
fi
sleep 10
done
echo "::warning::Timed out waiting for SonarQube Cloud to publish this run's analysis. Parity diff will use the most-recent published state."
# --- (C) Parity diff ----------------------------------------------------
- name: Diff self-scan vs SonarQube Cloud
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: |
PR_ARG=""
BRANCH_ARG=""
if [ "${{ github.event_name }}" = "pull_request" ]; then
PR_ARG="--pull-request ${{ github.event.pull_request.number }}"
else
BRANCH_ARG="--branch ${{ github.ref_name }}"
fi
python3 scripts/scan_parity.py \
--self-scan .sonar-predictor/scan.json \
--project-key "${SONAR_PROJECT_KEY}" \
--organization "${SONAR_ORGANIZATION}" \
--host "${SONAR_HOST_URL}" \
$PR_ARG $BRANCH_ARG \
--out .sonar-predictor/parity.json
- name: Upload scan + parity artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: scan-parity-${{ github.run_id }}
path: |
.sonar-predictor/scan.json
.sonar-predictor/parity.json
retention-days: 14
- name: Link to SonarQube Cloud dashboard
if: always()
run: |
{
echo ""
echo "---"
echo ""
echo "**SonarQube Cloud dashboard:** ${SONAR_HOST_URL}/project/overview?id=${SONAR_PROJECT_KEY}"
if [ "${{ github.event_name }}" = "pull_request" ]; then
echo ""
echo "**PR-scoped view:** ${SONAR_HOST_URL}/project/issues?id=${SONAR_PROJECT_KEY}&pullRequest=${{ github.event.pull_request.number }}"
fi
} >> "$GITHUB_STEP_SUMMARY"