# .github/workflows/sonar.yml # TRUSTED CONTEXT. Has SONAR_TOKEN. # Rule: never execute anything that came out of the artifact. No build, no # npm/pip install, no scripts, no PR-supplied scanner config. Data only. # # NOTE: workflow_run workflows are only picked up from the DEFAULT branch. # Changes to this file take effect after they land on master. name: SonarQube Cloud on: workflow_run: workflows: [ "Build" ] types: [ requested, completed ] # Default to nothing; each job opts in to exactly what it needs. permissions: {} env: SHA: ${{ github.event.workflow_run.head_sha }} RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} jobs: # Build has just started. Park a pending status on the commit so branch # protection blocks the merge until the analysis reports back. pending: name: Mark pending if: github.event.action == 'requested' runs-on: ubuntu-24.04 permissions: statuses: write steps: - name: Post pending status env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | gh api -X POST "repos/${{ github.repository }}/statuses/$SHA" \ -f state=pending \ -f context="SonarQube Cloud" \ -f description="Waiting for build to finish" \ -f target_url="$RUN_URL" # Build failed or was cancelled, so no analysis will happen. Clear the # pending status, otherwise the PR stays unmergeable forever. aborted: name: Build did not succeed if: github.event.action == 'completed' && github.event.workflow_run.conclusion != 'success' runs-on: ubuntu-24.04 permissions: statuses: write steps: - name: Post error status env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | gh api -X POST "repos/${{ github.repository }}/statuses/$SHA" \ -f state=error \ -f context="SonarQube Cloud" \ -f description="Build ${{ github.event.workflow_run.conclusion }}, analysis skipped" \ -f target_url="$RUN_URL" sonar: name: Analyze if: github.event.action == 'completed' && github.event.workflow_run.conclusion == 'success' runs-on: ubuntu-24.04 permissions: contents: read # checkout trusted config actions: read # download artifact from the Build run statuses: write # post the commit status pull-requests: read # verify PR head sha via the API env: BUILD_WRAPPER_OUT_DIR: bw-output SONAR_EXCLUSIONS: betterRenderer/**,ref/**,imgui/**,stb/**,build/**,bw-output/** SONAR_SCANNER_JAVA_OPTS: -Xmx5g steps: - name: Post pending status env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | gh api -X POST "repos/${{ github.repository }}/statuses/$SHA" \ -f state=pending \ -f context="SonarQube Cloud" \ -f description="Analysis in progress" \ -f target_url="$RUN_URL" # Trusted copy of the repo config, used to overwrite anything the PR # might have tampered with (sonar-project.properties etc). - name: Checkout trusted config from master uses: actions/checkout@v5 with: ref: master path: .trusted - name: Download analysis input uses: actions/download-artifact@v4 with: name: sonar-input path: /tmp/sonar-input run-id: ${{ github.event.workflow_run.id }} github-token: ${{ secrets.GITHUB_TOKEN }} - name: Unpack analysis input run: | set -euo pipefail tar -xzf /tmp/sonar-input/workspace.tar.gz -C "$GITHUB_WORKSPACE" # Strip PR-controlled scanner configuration, restore the trusted one. rm -f "$GITHUB_WORKSPACE/sonar-project.properties" \ "$GITHUB_WORKSPACE/.sonarcloud.properties" if [ -f "$GITHUB_WORKSPACE/.trusted/sonar-project.properties" ]; then cp "$GITHUB_WORKSPACE/.trusted/sonar-project.properties" "$GITHUB_WORKSPACE/" fi - name: Resolve and verify PR metadata id: meta env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} REPO: ${{ github.repository }} run: | set -euo pipefail cd "$GITHUB_WORKSPACE" event=$(tr -d '\r\n' < .sonar-meta/event) echo "event=$event" >> "$GITHUB_OUTPUT" if [ "$event" != "pull_request" ]; then echo "Branch build, no PR parameters." exit 0 fi pr=$(tr -d '\r\n' < .sonar-meta/pr_number) sha=$(tr -d '\r\n' < .sonar-meta/head_sha) # The artifact is attacker-controlled: validate shape, then confirm # against the API that this PR really has this head commit. [[ "$pr" =~ ^[0-9]+$ ]] || { echo "::error::bad PR number"; exit 1; } [[ "$sha" =~ ^[0-9a-f]{40}$ ]] || { echo "::error::bad head sha"; exit 1; } api_sha=$(gh api "repos/$REPO/pulls/$pr" --jq '.head.sha') base=$(gh api "repos/$REPO/pulls/$pr" --jq '.base.ref') head=$(gh api "repos/$REPO/pulls/$pr" --jq '.head.ref') if [ "$api_sha" != "$sha" ]; then echo "::error::head sha mismatch (artifact=$sha api=$api_sha)" exit 1 fi { echo "pr=$pr" echo "base=$base" echo "head=$head" } >> "$GITHUB_OUTPUT" # sonar.cfamily.taintAnalysis=false # it's a deliberate choice, due to memory exhausting of a Github-hosted runner. # # Why off?: The jobs were OOM-killed. It's a trade-off, we still get everything else. # Most bugs are caught though. # # sonar.qualitygate.wait=true blocks until the server finishes processing, # so this step's exit code reflects the actual gate result. - name: SonarQube Cloud scan (pull request) if: steps.meta.outputs.event == 'pull_request' uses: SonarSource/sonarqube-scan-action@fd88b7d7ccbaefd23d8f36f73b59db7a3d246602 # v6.0.0 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} SONAR_HOST_URL: https://sonarcloud.io with: args: > --define sonar.cfamily.compile-commands=${{ env.BUILD_WRAPPER_OUT_DIR }}/compile_commands.json --define sonar.cfamily.taintAnalysis=false --define sonar.exclusions=${{ env.SONAR_EXCLUSIONS }} --define sonar.qualitygate.wait=true --define sonar.scm.revision=${{ github.event.workflow_run.head_sha }} --define sonar.pullrequest.key=${{ steps.meta.outputs.pr }} --define sonar.pullrequest.branch=${{ steps.meta.outputs.head }} --define sonar.pullrequest.base=${{ steps.meta.outputs.base }} # No sonar.branch.name here on purpose: branch analysis is a paid feature, # and passing it would push the scanner onto that code path. Omitting it # means this is a plain main-branch analysis, which the Free plan allows. - name: SonarQube Cloud scan (main branch) if: steps.meta.outputs.event != 'pull_request' uses: SonarSource/sonarqube-scan-action@fd88b7d7ccbaefd23d8f36f73b59db7a3d246602 # v6.0.0 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} SONAR_HOST_URL: https://sonarcloud.io with: args: > --define sonar.cfamily.compile-commands=${{ env.BUILD_WRAPPER_OUT_DIR }}/compile_commands.json --define sonar.cfamily.taintAnalysis=false --define sonar.exclusions=${{ env.SONAR_EXCLUSIONS }} --define sonar.qualitygate.wait=true --define sonar.scm.revision=${{ github.event.workflow_run.head_sha }} - name: Report success if: success() env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | gh api -X POST "repos/${{ github.repository }}/statuses/$SHA" \ -f state=success \ -f context="SonarQube Cloud" \ -f description="Quality gate passed" \ -f target_url="$RUN_URL" - name: Report failure if: failure() env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | gh api -X POST "repos/${{ github.repository }}/statuses/$SHA" \ -f state=failure \ -f context="SonarQube Cloud" \ -f description="Quality gate failed or analysis errored" \ -f target_url="$RUN_URL"