diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 17c263c..eebb988 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,6 +37,20 @@ on: description: 'Check that scaladoc builds' type: boolean default: true + sonar: + description: >- + Run a SonarQube Cloud scan. Requires a SONAR_TOKEN secret and an existing project on + SonarQube Cloud with Automatic Analysis turned off, otherwise the two conflict. + type: boolean + default: false + sonar_project_key: + description: 'SonarQube project key. Defaults to _.' + type: string + default: '' + sonar_args: + description: 'Extra -D arguments for the Sonar scanner' + type: string + default: '' concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -142,3 +156,31 @@ jobs: - name: scaladoc ${{ matrix.scala }} if: inputs.doc_check run: sbt "++${{ matrix.scala }}; Compile/doc" + + # Scanned once, on the first Scala version only: SonarQube tracks one analysis per branch, so + # running it per matrix leg would have the legs overwrite each other. + - name: sonar settings + id: sonar + if: inputs.sonar && matrix.scala == fromJSON(inputs.scala_versions)[0] + env: + KEY: ${{ inputs.sonar_project_key }} + run: | + if [[ -z "$KEY" ]]; then + KEY="${GITHUB_REPOSITORY/\//_}" + fi + echo "key=$KEY" >> "$GITHUB_OUTPUT" + # scoverage writes one report per module; Sonar takes a comma separated list + reports=$(find . -path '*/scoverage-report/scoverage.xml' | paste -sd, -) + echo "reports=$reports" >> "$GITHUB_OUTPUT" + + - name: sonar scan + if: inputs.sonar && matrix.scala == fromJSON(inputs.scala_versions)[0] + uses: SonarSource/sonarqube-scan-action@v8.2 + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + with: + args: > + -Dsonar.organization=${{ github.repository_owner }} + -Dsonar.projectKey=${{ steps.sonar.outputs.key }} + -Dsonar.scala.coverage.reportPaths=${{ steps.sonar.outputs.reports }} + ${{ inputs.sonar_args }} diff --git a/README.md b/README.md index dadd2a6..6ff91fe 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,9 @@ so `secrets: inherit` is needed for `GITHUB_TOKEN`. | `version_policy_check` | `true` | requires [sbt-version-policy](https://github.com/scalacenter/sbt-version-policy/) | | `scalafmt_check` | `true` | | | `doc_check` | `true` | runs `Compile/doc` | +| `sonar` | `false` | run a SonarQube Cloud scan, see below | +| `sonar_project_key` | `_` | | +| `sonar_args` | `''` | extra `-D` arguments for the scanner | Example for a project without `sbt-version-policy` and on a different Scala set: @@ -69,6 +72,29 @@ Binary compatibility, formatting and scaladoc run as **explicit sbt tasks**, not `check` alias. An alias can be stubbed out (`addCommandAlias("check", "show version")`), which makes the gate silently guarantee nothing. +The workflow checks out with `fetch-depth: 0`. Without tags sbt-dynver reports the version as +`0.0.0`, `versionPolicyCheck` then has no previous version to compare against, and the binary +compatibility check passes without checking anything. + +### SonarQube Cloud + +Off by default. Enable with `sonar: true`, which scans once, on the first Scala version, importing +scoverage's per-module reports. Configuration is passed as scanner arguments, so no per-repo +`sonar-project.properties` is needed. + +Three prerequisites, all outside this repo: + +1. A `SONAR_TOKEN` organization secret. It does not exist yet, so `sonar: true` will fail until it is + added. +2. The project must already exist on SonarQube Cloud — the scanner reports to a project, it does not + create one. +3. **Automatic Analysis must be turned off** for that project. It and CI-based scanning are mutually + exclusive, and Automatic Analysis wins, so the scan will be rejected while it is on. + +Note that the SonarQube Cloud GitHub App creates a check suite on every commit even in repositories +it never analyses, which leaves a check permanently queued and reporting no result. Repositories not +being analysed should have the app removed rather than left in that state. + ## Scala Release workflow (v3, v4, v5) ### Setup