Skip to content
Merged
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
126 changes: 70 additions & 56 deletions .github/workflows/csharp-ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ on:
`[{ "name", "os", "arch", "passed" }]`, one entry per matrix shard,
suitable to feed (after tagging each entry with a `lang`) into the
`update-badges` reusable workflow's `matrix-data` input.
value: ${{ jobs.matrix-output.outputs.matrix-status }}
value: ${{ jobs.coverage-output.outputs.matrix-status }}
secrets:
BOT_GITHUB_TOKEN:
description: >-
Expand Down Expand Up @@ -184,6 +184,22 @@ on:
required: false
type: string
default: 'nupkg'
plumbing-runner:
description: >-
Runner for the small plumbing jobs (`normalize matrix`,
`coverage-output`, `pack`) that do no build work. Each such job is
billed as at least one full minute on GitHub-hosted runners, so they
are worth keeping off the paid pool. A plain label (e.g.
'ubuntu-latest') or a JSON array string (e.g.
'["self-hosted","hetzner"]'). Empty (the default) follows
`matrix-mode` / the `CI_MATRIX_MODE` variable: `cheap` on a
private or internal repository routes them to the self-hosted
Hetzner pool, anything else (including every public repository)
keeps `ubuntu-latest`. Independent of the `runner` of each
build-matrix shard.
required: false
type: string
default: ''
artifact-prefix:
description: >-
Prefix applied to per-run artifact names (coverage-percent,
Expand All @@ -202,7 +218,7 @@ concurrency:
jobs:
normalize:
name: normalize matrix
runs-on: ubuntu-latest
runs-on: ${{ inputs.plumbing-runner != '' && (startsWith(inputs.plumbing-runner, '[') && fromJSON(inputs.plumbing-runner) || inputs.plumbing-runner) || ((inputs.matrix-mode != '' && inputs.matrix-mode || vars.CI_MATRIX_MODE) == 'cheap' && github.event.repository.private == true && fromJSON('["self-hosted","hetzner"]') || 'ubuntu-latest') }}
Comment thread
monsieurleberre marked this conversation as resolved.
permissions:
contents: read
outputs:
Expand Down Expand Up @@ -428,46 +444,23 @@ jobs:
coverage-output:
name: coverage-output
needs: [normalize, build-and-test]
if: ${{ success() }}
runs-on: ubuntu-latest
if: ${{ !cancelled() }}
runs-on: ${{ inputs.plumbing-runner != '' && (startsWith(inputs.plumbing-runner, '[') && fromJSON(inputs.plumbing-runner) || inputs.plumbing-runner) || ((inputs.matrix-mode != '' && inputs.matrix-mode || vars.CI_MATRIX_MODE) == 'cheap' && github.event.repository.private == true && fromJSON('["self-hosted","hetzner"]') || 'ubuntu-latest') }}
permissions:
actions: read
contents: read
env:
COVERAGE_EXPECTED: ${{ contains(fromJSON(needs.normalize.outputs.matrix).*.coverage, true) }}
pull-requests: write
outputs:
coverage: ${{ steps.read.outputs.coverage }}
matrix-status: ${{ steps.aggregate.outputs.matrix-status }}
steps:
- name: Download coverage value
if: ${{ env.COVERAGE_EXPECTED == 'true' }}
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ${{ inputs.artifact-prefix }}-coverage-percent
path: coverage

- name: Read coverage value
id: read
- name: Clear stale outputs from earlier jobs on this runner
env:
OUTPUT_DIR: ${{ runner.temp }}/coverage-output
run: |
set -euo pipefail
coverage=""
if [ -f coverage/coverage-percent.txt ]; then
coverage=$(tr -d '[:space:]' < coverage/coverage-percent.txt)
fi
if [ "$COVERAGE_EXPECTED" = "true" ] && [ -z "$coverage" ]; then
echo "::error::the coverage shard uploaded a coverage-percent artifact but no coverage value reached coverage-output — failing instead of emitting a blank coverage badge"
exit 1
fi
echo "coverage=$coverage" >> "$GITHUB_OUTPUT"
rm -rf "$OUTPUT_DIR"

matrix-output:
name: matrix-output
needs: build-and-test
if: ${{ !cancelled() }}
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
matrix-status: ${{ steps.aggregate.outputs.matrix-status }}
steps:
- name: Checkout CI helpers
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
Expand All @@ -479,54 +472,75 @@ jobs:
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: ${{ inputs.artifact-prefix }}-ci-result-*
path: ci-results
path: ${{ runner.temp }}/coverage-output/ci-results
merge-multiple: true

- name: Aggregate shard results
id: aggregate
env:
RESULTS_DIR: ${{ runner.temp }}/coverage-output/ci-results
run: |
set -euo pipefail
status="$(.github-actions-helpers/scripts/aggregate_matrix_status.py ci-results)"
status="$(.github-actions-helpers/scripts/aggregate_matrix_status.py "$RESULTS_DIR")"
echo "matrix-status=$status" >> "$GITHUB_OUTPUT"

matrix-comment:
name: matrix-comment (${{ inputs.artifact-prefix }})
needs: build-and-test
if: ${{ !cancelled() && inputs.build-matrix != '' && github.event_name == 'pull_request' && github.actor != 'dependabot[bot]' }}
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
pull-requests: write
steps:
- name: Checkout CI helpers
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Download coverage value
if: ${{ needs.build-and-test.result == 'success' && contains(fromJSON(needs.normalize.outputs.matrix || '[]').*.coverage, true) }}
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
repository: peacefulstudio/github-actions
ref: ${{ job.workflow_sha }}
path: .github-actions-helpers
name: ${{ inputs.artifact-prefix }}-coverage-percent
path: ${{ runner.temp }}/coverage-output/coverage

- name: Read coverage value
id: read
env:
COVERAGE_EXPECTED: ${{ needs.build-and-test.result == 'success' && contains(fromJSON(needs.normalize.outputs.matrix || '[]').*.coverage, true) }}
COVERAGE_FILE: ${{ runner.temp }}/coverage-output/coverage/coverage-percent.txt
run: |
set -euo pipefail
coverage=""
if [ -f "$COVERAGE_FILE" ]; then
coverage=$(tr -d '[:space:]' < "$COVERAGE_FILE")
fi
echo "coverage=$coverage" >> "$GITHUB_OUTPUT"
if [ "$COVERAGE_EXPECTED" = "true" ] && [ -z "$coverage" ]; then
echo "::error::the coverage shard uploaded a coverage-percent artifact but no coverage value reached coverage-output — failing instead of emitting a blank coverage badge"
exit 1
fi

- name: Render build-matrix table
id: render
if: ${{ !cancelled() && inputs.build-matrix != '' && github.event_name == 'pull_request' && github.actor != 'dependabot[bot]' }}
continue-on-error: true
env:
GH_TOKEN: ${{ github.token }}
SELF_JOB_NAME: matrix-comment (${{ inputs.artifact-prefix }})
MATRIX_TITLE: 'C# build matrix'
COMMENT_FILE: ${{ runner.temp }}/coverage-output/build-matrix-results.md
run: |
set -euo pipefail
gh api --paginate "repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/jobs" \
| .github-actions-helpers/scripts/render_matrix_comment.py "$SELF_JOB_NAME" "$MATRIX_TITLE" \
> build-matrix-results.md
| .github-actions-helpers/scripts/render_matrix_comment.py "coverage-output" "$MATRIX_TITLE" "$RUNNER_NAME" \
> "$COMMENT_FILE"

- name: Add build-matrix PR comment
id: post
if: ${{ !cancelled() && steps.render.outcome == 'success' }}
continue-on-error: true
uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4
with:
recreate: true
header: ${{ inputs.artifact-prefix }}-build-matrix
path: build-matrix-results.md
path: ${{ runner.temp }}/coverage-output/build-matrix-results.md

- name: Warn when the build-matrix comment was not posted
if: ${{ !cancelled() && (steps.render.outcome == 'failure' || steps.post.outcome == 'failure') }}
run: echo "::warning::the build-matrix PR comment was not rendered or posted; coverage-output outputs and result are unaffected"

pack:
name: pack
if: ${{ inputs.pack }}
needs: build-and-test
runs-on: ubuntu-latest
runs-on: ${{ inputs.plumbing-runner != '' && (startsWith(inputs.plumbing-runner, '[') && fromJSON(inputs.plumbing-runner) || inputs.plumbing-runner) || ((inputs.matrix-mode != '' && inputs.matrix-mode || vars.CI_MATRIX_MODE) == 'cheap' && github.event.repository.private == true && fromJSON('["self-hosted","hetzner"]') || 'ubuntu-latest') }}
permissions:
contents: read
packages: read
Expand Down
125 changes: 70 additions & 55 deletions .github/workflows/scala-ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ on:
description: >-
JSON array of per-shard build results
`[{ "name", "os", "arch", "passed" }]`, one entry per matrix shard.
value: ${{ jobs.matrix-output.outputs.matrix-status }}
value: ${{ jobs.coverage-output.outputs.matrix-status }}
inputs:
working-directory:
description: >-
Expand Down Expand Up @@ -119,6 +119,22 @@ on:
required: false
type: string
default: 'scala-coverage'
plumbing-runner:
description: >-
Runner for the small plumbing jobs (`normalize matrix`,
`coverage-output`) that do no build work. Each such job is
billed as at least one full minute on GitHub-hosted runners, so they
are worth keeping off the paid pool. A plain label (e.g.
'ubuntu-latest') or a JSON array string (e.g.
'["self-hosted","hetzner"]'). Empty (the default) follows
`matrix-mode` / the `CI_MATRIX_MODE` variable: `cheap` on a
private or internal repository routes them to the self-hosted
Hetzner pool, anything else (including every public repository)
keeps `ubuntu-latest`. Independent of the `runner` of each
build-matrix shard.
required: false
type: string
default: ''
artifact-prefix:
description: >-
Prefix applied to per-run artifact names (coverage-percent,
Expand Down Expand Up @@ -159,7 +175,7 @@ on:
jobs:
normalize:
name: normalize matrix
runs-on: ubuntu-latest
runs-on: ${{ inputs.plumbing-runner != '' && (startsWith(inputs.plumbing-runner, '[') && fromJSON(inputs.plumbing-runner) || inputs.plumbing-runner) || ((inputs.matrix-mode != '' && inputs.matrix-mode || vars.CI_MATRIX_MODE) == 'cheap' && github.event.repository.private == true && fromJSON('["self-hosted","hetzner"]') || 'ubuntu-latest') }}
permissions:
contents: read
outputs:
Expand Down Expand Up @@ -358,46 +374,23 @@ jobs:
coverage-output:
name: coverage-output
needs: [normalize, build-and-test]
if: ${{ success() }}
runs-on: ubuntu-latest
if: ${{ !cancelled() }}
runs-on: ${{ inputs.plumbing-runner != '' && (startsWith(inputs.plumbing-runner, '[') && fromJSON(inputs.plumbing-runner) || inputs.plumbing-runner) || ((inputs.matrix-mode != '' && inputs.matrix-mode || vars.CI_MATRIX_MODE) == 'cheap' && github.event.repository.private == true && fromJSON('["self-hosted","hetzner"]') || 'ubuntu-latest') }}
permissions:
actions: read
contents: read
env:
COVERAGE_EXPECTED: ${{ contains(fromJSON(needs.normalize.outputs.matrix).*.coverage, true) }}
pull-requests: write
outputs:
coverage: ${{ steps.read.outputs.coverage }}
matrix-status: ${{ steps.aggregate.outputs.matrix-status }}
steps:
- name: Download coverage value
if: ${{ env.COVERAGE_EXPECTED == 'true' }}
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ${{ inputs.artifact-prefix }}-coverage-percent
path: coverage

- name: Read coverage value
id: read
- name: Clear stale outputs from earlier jobs on this runner
env:
OUTPUT_DIR: ${{ runner.temp }}/coverage-output
run: |
set -euo pipefail
coverage=""
if [ -f coverage/coverage-percent.txt ]; then
coverage=$(tr -d '[:space:]' < coverage/coverage-percent.txt)
fi
if [ "$COVERAGE_EXPECTED" = "true" ] && [ -z "$coverage" ]; then
echo "::error::the coverage shard uploaded a coverage-percent artifact but no coverage value reached coverage-output — failing instead of emitting a blank coverage badge"
exit 1
fi
echo "coverage=$coverage" >> "$GITHUB_OUTPUT"
rm -rf "$OUTPUT_DIR"

matrix-output:
name: matrix-output
needs: build-and-test
if: ${{ !cancelled() }}
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
matrix-status: ${{ steps.aggregate.outputs.matrix-status }}
steps:
- name: Checkout CI helpers
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
Expand All @@ -409,45 +402,67 @@ jobs:
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: ${{ inputs.artifact-prefix }}-ci-result-*
path: ci-results
path: ${{ runner.temp }}/coverage-output/ci-results
merge-multiple: true

- name: Aggregate shard results
id: aggregate
env:
RESULTS_DIR: ${{ runner.temp }}/coverage-output/ci-results
run: |
set -euo pipefail
status="$(.github-actions-helpers/scripts/aggregate_matrix_status.py ci-results)"
status="$(.github-actions-helpers/scripts/aggregate_matrix_status.py "$RESULTS_DIR")"
echo "matrix-status=$status" >> "$GITHUB_OUTPUT"

matrix-comment:
name: matrix-comment (${{ inputs.artifact-prefix }})
needs: build-and-test
if: ${{ !cancelled() && inputs.build-matrix != '' && github.event_name == 'pull_request' && github.actor != 'dependabot[bot]' }}
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
pull-requests: write
steps:
- name: Checkout CI helpers
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Download coverage value
if: ${{ needs.build-and-test.result == 'success' && contains(fromJSON(needs.normalize.outputs.matrix || '[]').*.coverage, true) }}
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
repository: peacefulstudio/github-actions
ref: ${{ job.workflow_sha }}
path: .github-actions-helpers
name: ${{ inputs.artifact-prefix }}-coverage-percent
path: ${{ runner.temp }}/coverage-output/coverage

- name: Read coverage value
id: read
env:
COVERAGE_EXPECTED: ${{ needs.build-and-test.result == 'success' && contains(fromJSON(needs.normalize.outputs.matrix || '[]').*.coverage, true) }}
COVERAGE_FILE: ${{ runner.temp }}/coverage-output/coverage/coverage-percent.txt
run: |
set -euo pipefail
coverage=""
if [ -f "$COVERAGE_FILE" ]; then
coverage=$(tr -d '[:space:]' < "$COVERAGE_FILE")
fi
echo "coverage=$coverage" >> "$GITHUB_OUTPUT"
if [ "$COVERAGE_EXPECTED" = "true" ] && [ -z "$coverage" ]; then
echo "::error::the coverage shard uploaded a coverage-percent artifact but no coverage value reached coverage-output — failing instead of emitting a blank coverage badge"
exit 1
fi

- name: Render build-matrix table
id: render
if: ${{ !cancelled() && inputs.build-matrix != '' && github.event_name == 'pull_request' && github.actor != 'dependabot[bot]' }}
continue-on-error: true
env:
GH_TOKEN: ${{ github.token }}
SELF_JOB_NAME: matrix-comment (${{ inputs.artifact-prefix }})
MATRIX_TITLE: 'Scala build matrix'
COMMENT_FILE: ${{ runner.temp }}/coverage-output/build-matrix-results.md
run: |
set -euo pipefail
gh api --paginate "repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/jobs" \
| .github-actions-helpers/scripts/render_matrix_comment.py "$SELF_JOB_NAME" "$MATRIX_TITLE" \
> build-matrix-results.md
| .github-actions-helpers/scripts/render_matrix_comment.py "coverage-output" "$MATRIX_TITLE" "$RUNNER_NAME" \
> "$COMMENT_FILE"

- name: Add build-matrix PR comment
id: post
if: ${{ !cancelled() && steps.render.outcome == 'success' }}
continue-on-error: true
uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4
with:
recreate: true
header: ${{ inputs.artifact-prefix }}-build-matrix
path: build-matrix-results.md
path: ${{ runner.temp }}/coverage-output/build-matrix-results.md

- name: Warn when the build-matrix comment was not posted
if: ${{ !cancelled() && (steps.render.outcome == 'failure' || steps.post.outcome == 'failure') }}
run: echo "::warning::the build-matrix PR comment was not rendered or posted; coverage-output outputs and result are unaffected"

Loading
Loading