-
Notifications
You must be signed in to change notification settings - Fork 913
ci: split clang-tidy-bazel into pull_request + workflow_run stages #10521
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,140 @@ | ||
| name: clang-tidy-bazel-post | ||
|
|
||
| # Runs in the base repository's context with a writable GITHUB_TOKEN, so it | ||
| # can post reviewdog comments on PRs opened from forks (which the upstream | ||
| # `clang-tidy-bazel` workflow cannot, since fork pull_request runs get a | ||
| # read-only token by GitHub's design). | ||
| # | ||
| # Security: this workflow MUST NOT execute untrusted PR code. It only reads | ||
| # the text artifact (clang-tidy.txt) produced by the upstream workflow and | ||
| # the metadata file we wrote there ourselves. No checkout of the PR head, | ||
| # no bazel build, no scripts from the fork. | ||
|
|
||
| on: | ||
| workflow_run: | ||
| workflows: ["clang-tidy-bazel"] | ||
| types: | ||
| - completed | ||
|
|
||
| permissions: | ||
| contents: read | ||
| pull-requests: write | ||
| # Needed for actions/download-artifact@v4 to fetch from another workflow. | ||
| actions: read | ||
|
|
||
| jobs: | ||
| Post-Reviewdog: | ||
| # Skip if the upstream build failed before producing an artifact. | ||
| if: ${{ github.event.workflow_run.conclusion == 'success' }} | ||
| runs-on: ${{ vars.USE_SELF_HOSTED == 'true' && 'self-hosted' || 'ubuntu-latest' }} | ||
| steps: | ||
| # Reviewdog's github-pr-review reporter resolves the local git root | ||
| # before flushing comments and silently no-ops if .git is missing. | ||
| # Check out the base repo (default branch, shallow) so reviewdog has | ||
| # a .git directory to operate against. No fork code involved — this | ||
| # is the base repo at HEAD of its default branch. | ||
| - name: Check out base repo for reviewdog .git requirement | ||
| uses: actions/checkout@v6 | ||
| with: | ||
| fetch-depth: 1 | ||
| # Default ref in workflow_run context is the base repo's default | ||
| # branch, which is the safe choice here. Reviewdog uses GitHub | ||
| # API to fetch the actual PR diff, so the local SHA need not | ||
| # match the PR head. | ||
|
|
||
| - name: Download clang-tidy artifact | ||
| uses: actions/download-artifact@v4 | ||
| with: | ||
| name: clang-tidy-bazel | ||
| run-id: ${{ github.event.workflow_run.id }} | ||
| github-token: ${{ secrets.GITHUB_TOKEN }} | ||
|
|
||
| - name: Load PR metadata | ||
| id: meta | ||
| run: | | ||
| # pr-meta.txt is produced by the upstream workflow from the | ||
| # pull_request event payload. It is text we wrote ourselves — | ||
| # not arbitrary fork content — and is parsed with a strict | ||
| # allowlist below before being exported. | ||
| if [ ! -f pr-meta.txt ]; then | ||
| echo "::error::pr-meta.txt missing from artifact" | ||
| exit 1 | ||
| fi | ||
| while IFS='=' read -r key value; do | ||
| case "$key" in | ||
| pr_number|head_sha|base_sha|head_repo|base_repo) ;; | ||
| *) continue ;; | ||
| esac | ||
| # Validate values: numbers, hex SHAs, or owner/repo slugs only. | ||
| case "$key" in | ||
| pr_number) | ||
| [[ "$value" =~ ^[0-9]+$ ]] || { echo "::error::bad pr_number"; exit 1; } ;; | ||
| head_sha|base_sha) | ||
| [[ "$value" =~ ^[0-9a-f]{40}$ ]] || { echo "::error::bad $key"; exit 1; } ;; | ||
| head_repo|base_repo) | ||
| [[ "$value" =~ ^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$ ]] || { echo "::error::bad $key"; exit 1; } ;; | ||
| esac | ||
| echo "$key=$value" >> "$GITHUB_OUTPUT" | ||
| done < pr-meta.txt | ||
|
|
||
| - name: Synthesize pull_request event payload | ||
| id: event | ||
| env: | ||
| PR_NUMBER: ${{ steps.meta.outputs.pr_number }} | ||
| HEAD_SHA: ${{ steps.meta.outputs.head_sha }} | ||
| BASE_SHA: ${{ steps.meta.outputs.base_sha }} | ||
| HEAD_REPO: ${{ steps.meta.outputs.head_repo }} | ||
| BASE_REPO: ${{ steps.meta.outputs.base_repo }} | ||
| run: | | ||
| # Reviewdog's `github-pr-review` reporter reads GITHUB_EVENT_PATH | ||
| # expecting a pull_request payload. The real event here is | ||
| # workflow_run, so we synthesize the minimum payload reviewdog | ||
| # needs and point GITHUB_EVENT_PATH at it for the next step. | ||
| EVENT_PATH="${RUNNER_TEMP}/pr-event.json" | ||
| python3 - <<'PY' > "$EVENT_PATH" | ||
| import json, os | ||
| payload = { | ||
| "action": "synchronize", | ||
| "number": int(os.environ["PR_NUMBER"]), | ||
| "pull_request": { | ||
| "number": int(os.environ["PR_NUMBER"]), | ||
| "head": { | ||
| "sha": os.environ["HEAD_SHA"], | ||
| "repo": {"full_name": os.environ["HEAD_REPO"]}, | ||
| }, | ||
| "base": { | ||
| "sha": os.environ["BASE_SHA"], | ||
| "repo": {"full_name": os.environ["BASE_REPO"]}, | ||
| }, | ||
| }, | ||
| "repository": { | ||
| "full_name": os.environ["BASE_REPO"], | ||
| "owner": {"login": os.environ["BASE_REPO"].split("/")[0]}, | ||
| "name": os.environ["BASE_REPO"].split("/")[1], | ||
| }, | ||
| } | ||
| print(json.dumps(payload)) | ||
| PY | ||
| echo "event_path=${EVENT_PATH}" >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: Set up reviewdog | ||
| uses: reviewdog/action-setup@v1 | ||
| with: | ||
| reviewdog_version: latest | ||
|
|
||
| - name: Run reviewdog | ||
| env: | ||
| REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| GITHUB_EVENT_NAME: pull_request | ||
| GITHUB_EVENT_PATH: ${{ steps.event.outputs.event_path }} | ||
| GITHUB_SHA: ${{ steps.meta.outputs.head_sha }} | ||
| GITHUB_REPOSITORY: ${{ steps.meta.outputs.base_repo }} | ||
| run: | | ||
| reviewdog \ | ||
| -efm="%E%f:%l:%c: error: %m" \ | ||
| -efm="%W%f:%l:%c: warning: %m" \ | ||
| -name="clang-tidy" \ | ||
| -reporter=github-pr-review \ | ||
| -filter-mode=added \ | ||
| -fail-level=any \ | ||
| < clang-tidy.txt | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,9 +5,13 @@ on: | |
| branches: | ||
| - master | ||
|
|
||
| # Read-only by design: fork PRs get a read-only GITHUB_TOKEN regardless of | ||
| # what this block requests, so this workflow only builds clang-tidy and | ||
| # uploads the findings as an artifact. The companion workflow | ||
| # `clang-tidy-bazel-post` runs on `workflow_run` in the base repo context | ||
| # with a writable token and posts the reviewdog comments. | ||
| permissions: | ||
| contents: read | ||
| pull-requests: write | ||
|
|
||
| jobs: | ||
| Clang-Tidy-Bazel: | ||
|
|
@@ -17,7 +21,8 @@ jobs: | |
| uses: actions/checkout@v6 | ||
| with: | ||
| submodules: 'recursive' | ||
| # Need full history so reviewdog can diff against the PR base. | ||
| # Need full history so the post workflow's reviewdog can diff | ||
| # against the PR base via the API. | ||
| fetch-depth: 0 | ||
|
|
||
| - name: Set up bazel | ||
|
|
@@ -30,11 +35,6 @@ jobs: | |
| bazelisk-version: 1.x | ||
| bazelisk-cache: true | ||
|
|
||
| - name: Set up reviewdog | ||
| uses: reviewdog/action-setup@v1 | ||
| with: | ||
| reviewdog_version: latest | ||
|
|
||
| - name: Run bazel clang-tidy | ||
| env: | ||
| BAZEL_CACHE_PASSWORD: ${{ secrets.BAZEL_CACHE_PASSWORD }} | ||
|
|
@@ -88,15 +88,25 @@ jobs: | |
| echo "::endgroup::" | ||
| echo "Findings: $(wc -l < clang-tidy.txt)" | ||
|
|
||
| - name: Run reviewdog | ||
| env: | ||
| REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| - name: Save PR metadata for post workflow | ||
| run: | | ||
| reviewdog \ | ||
| -efm="%E%f:%l:%c: error: %m" \ | ||
| -efm="%W%f:%l:%c: warning: %m" \ | ||
| -name="clang-tidy" \ | ||
| -reporter=github-pr-review \ | ||
| -filter-mode=added \ | ||
| -fail-level=any \ | ||
| < clang-tidy.txt | ||
| # workflow_run.event.pull_requests[] is empty for fork PRs, so the | ||
| # post workflow needs the PR number and head SHA delivered via the | ||
| # artifact itself. | ||
| { | ||
| echo "pr_number=${{ github.event.pull_request.number }}" | ||
| echo "head_sha=${{ github.event.pull_request.head.sha }}" | ||
| echo "base_sha=${{ github.event.pull_request.base.sha }}" | ||
| echo "head_repo=${{ github.event.pull_request.head.repo.full_name }}" | ||
| echo "base_repo=${{ github.event.pull_request.base.repo.full_name }}" | ||
| } > pr-meta.txt | ||
|
|
||
| - name: Upload clang-tidy artifact | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a PR produces clang-tidy diagnostics, this Useful? React with 👍 / 👎.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Confirmed and fixed in fcb0180. Stage A now re-runs reviewdog with Stage B's guard relaxes from |
||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: clang-tidy-bazel | ||
| path: | | ||
| clang-tidy.txt | ||
| pr-meta.txt | ||
| retention-days: 7 | ||
| if-no-files-found: error | ||
Uh oh!
There was an error while loading. Please reload this page.