Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 44 additions & 18 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ on:
default: ''
clean_task:
description: >-
sbt task that cleans the working directory. Defaults to `cleanFull` on sbt 2 and
`clean` on sbt 1, detected from project/build.properties.
Optional sbt task to run before the coverage build. Empty by default: the build output is
restored from cache between runs, and `cleanFull` drops sbt 2's action cache.
type: string
default: ''
sonar:
Expand Down Expand Up @@ -76,23 +76,20 @@ jobs:
echo "test=test" >> "$GITHUB_OUTPUT"
fi

- name: resolve clean task
id: clean-task
env:
CLEAN_TASK: ${{ inputs.clean_task }}
# sbt 1 has neither an action cache nor a `target/out`, so the coverage job skips the caching
# steps there rather than pointing them at paths that do not exist.
- name: detect sbt 2
id: uses-sbt2
run: |
if [[ -n "$CLEAN_TASK" ]]; then
echo "clean=$CLEAN_TASK" >> "$GITHUB_OUTPUT"
elif grep -qE '^sbt\.version\s*=\s*2\.' project/build.properties; then
# in sbt 2 the `clean` doesn't remove all generated classes
echo "clean=cleanFull" >> "$GITHUB_OUTPUT"
if grep -qE '^sbt\.version\s*=\s*2\.' project/build.properties; then
echo "uses-sbt2=true" >> "$GITHUB_OUTPUT"
else
echo "clean=clean" >> "$GITHUB_OUTPUT"
echo "uses-sbt2=false" >> "$GITHUB_OUTPUT"
fi

outputs:
test: ${{ steps.test-task.outputs.test }}
clean: ${{ steps.clean-task.outputs.clean }}
uses-sbt2: ${{ steps.uses-sbt2.outputs.uses-sbt2 }}

test-coverage:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -123,15 +120,44 @@ jobs:
- name: setup SBT
uses: sbt/setup-sbt@v1
with:
# sbt 2's disk cache is restored across runs. Disable disk cache to force full coverage run
# the action's own disk cache is shared by every job and every matrix leg under one key,
# so it cannot be paired with this job's `target`. Cached below instead.
disk-cache: false

# The coverage build runs before any other compile: scoverage's instrumentation is not part of
# sbt's compile cache key, so a plain compile done first would be reused here and the coverage
# report would come out empty.
# sbt's action cache restores `classes` but not `scoverage-data`, which the compiler writes as
# a side effect of the instrumented compile. Restore the action cache without `target` and the
# tests die writing their measurements into a directory that is not there, so the two are kept
# in one entry under one key: either both come back or neither does.
- name: restore build output
if: needs.sbt-tasks.outputs.uses-sbt2 == 'true'
uses: actions/cache@v6
with:
path: |
~/.cache/sbt
target/out
key: sbt-coverage-${{ runner.os }}-java${{ inputs.java_version }}-scala${{ matrix.scala }}-${{ github.sha }}
restore-keys: sbt-coverage-${{ runner.os }}-java${{ inputs.java_version }}-scala${{ matrix.scala }}-

# Measurements are written per run and never pruned, so a restored `target` would fold the
# previous run's hits into this report. The reports go too, otherwise the empty-report check
# below can pass on a stale file.
- name: drop stale coverage data
if: needs.sbt-tasks.outputs.uses-sbt2 == 'true'
run: |
find . -path '*/scoverage-data/scoverage.measurements.*' -delete
find . -type d \( -name coverage-report -o -name scoverage-report \) -prune -exec rm -rf {} +

- name: build ${{ matrix.scala }}
env:
CLEAN_TASK: ${{ inputs.clean_task }}
TEST_TASK: ${{ needs.sbt-tasks.outputs.test }}
SCALA: ${{ matrix.scala }}
run: |
sbt "++${{ matrix.scala }}; ${{ needs.sbt-tasks.outputs.clean }}; coverage; ${{ needs.sbt-tasks.outputs.test }}; coverageAggregate"
tasks="coverage; $TEST_TASK; coverageAggregate"
if [[ -n "$CLEAN_TASK" ]]; then
tasks="$CLEAN_TASK; $tasks"
fi
sbt "++$SCALA; $tasks"

- name: locate coverage report
id: coverage
Expand Down
15 changes: 9 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ alternatives and drops the security rating to C:
| `java_version` | `'17'` | |
| `java_distribution` | `'temurin'` | |
| `test_task` | auto | `testFull` on sbt 2, `test` on sbt 1, read from `project/build.properties` |
| `clean_task` | auto | `cleanFull` on sbt 2, `clean` on sbt 1, read from `project/build.properties` |
| `clean_task` | `''` | optional sbt task run before the coverage build; empty means no clean |
| `sonar` | `false` | run a SonarQube Cloud scan, see below |
| `sonar_project_key` | `<owner>_<repo>` | |
| `sonar_args` | `''` | extra `-D` arguments for the scanner |
Expand All @@ -72,11 +72,14 @@ All checks are run concurrently! Ideally, we must strive to keep them all green,
some checks are red, for example if code formatting is not introduced, yet. Such red checks must be treated as nudge
to improve the quality of code in repo!

* `test-coverage` - runs with disabled disk cache for SBT setup action (`disk-cache: false`) to make sure that
test coverage gets run with fully instrumented compilation. The workflow also fails if the produced Cobertura
report has no valid lines, so a silently empty report is an error rather than a green build.
If project has `sonar` integration configured and
enabled, then `sonar scan` will get run after coverage reports are uploaded
* `test-coverage` - runs the instrumented build and uploads the Cobertura report to Coveralls. On sbt 2 the
action cache (`~/.cache/sbt`) and the build output (`target/out`) are cached together, as a single entry under
one key: sbt's action cache restores `classes` but not scoverage's `scoverage-data`, which the compiler writes
as a side effect, and a run that gets one without the other fails while the tests write their measurements.
Stale measurement files and reports are deleted before the build, so a restored `target/out` cannot fold the
previous run's coverage into this one. The workflow also fails if the produced Cobertura report has no valid
lines, so a silently empty report is an error rather than a green build. If project has `sonar` integration
configured and enabled, then `sonar scan` will get run after coverage reports are uploaded
* `binary-compatibility` - runs [sbt-version-policy](https://github.com/scalacenter/sbt-version-policy/)'s
`versionPolicyCheck` task on repo with full history (`fetch-depth: 0`) to make sure that plugin can find the tag
for previous version
Expand Down