diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 16f9ee9..482a2fc 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -9,12 +9,6 @@ on: jobs: test-jre21: runs-on: ${{ matrix.os }} - # contents:write is needed by the "Publish coverage badge" step below, which - # commits the regenerated coverage.json back to master on push events. The - # step itself is gated on push + canonical matrix entry, but the permission - # has to be declared at the job level. - permissions: - contents: write strategy: fail-fast: false matrix: @@ -61,25 +55,6 @@ jobs: USE_BAZEL_VERSION: ${{ matrix.bazel }} COVERAGE_THRESHOLD: '90' run: ~/go/bin/bazelisk run //tools:coverage-check -- --badge-json coverage.json bazel-out/_coverage/_coverage_report.dat - - name: Publish coverage badge to master - # Only the canonical Linux + Bazel 9.x runner pushes the badge so the - # other matrix entries don't race to commit the same file. Skipped on - # pull_request because forks lack write access and the badge should - # only ever reflect master. - if: github.event_name == 'push' && github.ref == 'refs/heads/master' && matrix.os == 'ubuntu-latest' && matrix.bazel == '9.x' - run: | - if [ -n "$(git status --porcelain coverage.json)" ]; then - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add coverage.json - # [skip ci] is belt-and-suspenders — GITHUB_TOKEN pushes already do - # not retrigger workflows, but the marker makes the intent explicit - # in the log. - git commit -m "ci: update coverage badge [skip ci]" - git push origin HEAD:master - else - echo "coverage.json unchanged; nothing to publish." - fi - name: Upload test logs uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/coverage_badge.yaml b/.github/workflows/coverage_badge.yaml new file mode 100644 index 0000000..dbd3418 --- /dev/null +++ b/.github/workflows/coverage_badge.yaml @@ -0,0 +1,67 @@ +name: Coverage badge + +# Regenerates the coverage badge (coverage.json) on a daily schedule and opens +# a PR if the value has changed. This replaces the previous approach of pushing +# the badge directly to master from CI, which raced/force-pushed and failed CI. +on: + schedule: + # 13:00 UTC daily. + - cron: '0 13 * * *' + workflow_dispatch: + +jobs: + update-coverage-badge: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - name: Setup Java JDK + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '21' + - name: Setup Go environment + uses: actions/setup-go@v5 + with: + go-version: ^1.17 + id: go + - name: Setup Bazelisk + run: go install github.com/bazelbuild/bazelisk@latest && export PATH=$PATH:$(go env GOPATH)/bin + - uses: actions/checkout@v4 + - name: Run bazel-diff tests with coverage + env: + USE_BAZEL_VERSION: '9.x' + run: ~/go/bin/bazelisk coverage --combined_report=lcov //cli/... //tools:coverage_check_test --enable_bzlmod=true --enable_workspace=false + - name: Regenerate coverage badge + env: + USE_BAZEL_VERSION: '9.x' + COVERAGE_THRESHOLD: '90' + run: ~/go/bin/bazelisk run //tools:coverage-check -- --badge-json coverage.json bazel-out/_coverage/_coverage_report.dat + - name: Open PR if coverage badge changed + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + if [ -z "$(git status --porcelain coverage.json)" ]; then + echo "coverage.json unchanged; nothing to do." + exit 0 + fi + NEW_COVERAGE="$(python3 -c 'import json,sys; print(json.load(open("coverage.json"))["message"])')" + BRANCH="ci/coverage-badge-update" + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git checkout -B "$BRANCH" + git add coverage.json + git commit -m "ci: update coverage badge to ${NEW_COVERAGE}" + # Force-push the dedicated, bot-owned branch so re-runs reuse one PR + # instead of accumulating stale branches. This never touches master. + git push --force origin "$BRANCH" + if gh pr view "$BRANCH" --json state --jq '.state' 2>/dev/null | grep -q OPEN; then + echo "PR already open for $BRANCH; it now points at the latest badge." + else + gh pr create \ + --base master \ + --head "$BRANCH" \ + --title "ci: update coverage badge to ${NEW_COVERAGE}" \ + --body "Automated coverage badge refresh. Main-source line coverage is now **${NEW_COVERAGE}**." + fi