build: enable JaCoCo coverage report generation across all modules #5
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: 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" |