clang-tidy-bazel-post #123
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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: | |
| # Run on success AND failure: Stage A intentionally exits non-zero when | |
| # clang-tidy findings exist on the PR diff (to fail the required PR | |
| # check), but the artifact is uploaded *before* that fail step, so the | |
| # findings are still posted as review comments. Only skip on cancelled. | |
| if: ${{ github.event.workflow_run.conclusion != 'cancelled' }} | |
| 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| 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@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 | |
| 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@d8a7baabd7f3e8544ee4dbde3ee41d0011c3a93f # v1.5.0 | |
| with: | |
| reviewdog_version: latest | |
| - name: Dump reviewdog-visible env and event payload | |
| # Gated on debug re-runs. Reviewdog emits "this is not PullRequest | |
| # build" and posts nothing for fork PRs even though the synthesized | |
| # pull_request payload is well-formed (verified locally against | |
| # reviewdog v0.21.0). Suspect: step-level `env:` cannot override | |
| # runner-protected GITHUB_EVENT_PATH / GITHUB_EVENT_NAME, so reviewdog | |
| # reads the runner's workflow_run event payload instead of our synth. | |
| # This step proves what reviewdog actually sees on the next debug | |
| # re-run. | |
| if: runner.debug == '1' | |
| env: | |
| 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: | | |
| echo "::group::GITHUB_* env visible to next step" | |
| env | grep -E '^(GITHUB_|RUNNER_|REVIEWDOG_)' | sort | |
| echo "::endgroup::" | |
| echo "::group::Synth event file location and content" | |
| echo "steps.event.outputs.event_path = ${{ steps.event.outputs.event_path }}" | |
| echo "Effective GITHUB_EVENT_PATH = ${GITHUB_EVENT_PATH}" | |
| if [ -f "${GITHUB_EVENT_PATH}" ]; then | |
| echo "--- file exists, content: ---" | |
| cat "${GITHUB_EVENT_PATH}" | |
| else | |
| echo "::warning::GITHUB_EVENT_PATH file does not exist" | |
| fi | |
| echo "::endgroup::" | |
| echo "::group::Synth file at literal path (should match above)" | |
| SYNTH="${{ steps.event.outputs.event_path }}" | |
| [ -f "${SYNTH}" ] && cat "${SYNTH}" || echo "missing" | |
| echo "::endgroup::" | |
| - 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: | | |
| # On debug re-runs add reviewdog's own -log-level=debug so the | |
| # cienv detection trace is visible alongside the env dump above. | |
| LOG_FLAGS=() | |
| if [ "${RUNNER_DEBUG}" = "1" ]; then | |
| LOG_FLAGS+=(-log-level=debug) | |
| fi | |
| reviewdog \ | |
| "${LOG_FLAGS[@]}" \ | |
| -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 |