From 3588c1f4da87edb909666d3531f5c5eca22ca26c Mon Sep 17 00:00:00 2001 From: Adam Perry Date: Fri, 26 Jun 2026 00:33:23 +0000 Subject: [PATCH 1/9] add single required CI gate --- .github/workflows/bazel.yml | 2 +- .github/workflows/blob-size-policy.yml | 2 +- .github/workflows/cargo-deny.yml | 2 +- .github/workflows/ci.yml | 2 +- .github/workflows/codespell.yml | 3 +- .github/workflows/pr-ci.yml | 76 ++++++++++++++++++++++++++ .github/workflows/rust-ci.yml | 2 +- .github/workflows/sdk.yml | 2 +- 8 files changed, 83 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/pr-ci.yml diff --git a/.github/workflows/bazel.yml b/.github/workflows/bazel.yml index 7a6db869e012..f3e0841dbb5e 100644 --- a/.github/workflows/bazel.yml +++ b/.github/workflows/bazel.yml @@ -4,7 +4,7 @@ name: Bazel # https://github.com/cerisier/toolchains_llvm_bootstrapped/blob/main/.github/workflows/ci.yaml on: - pull_request: {} + workflow_call: push: branches: - main diff --git a/.github/workflows/blob-size-policy.yml b/.github/workflows/blob-size-policy.yml index ea50e8ee2e55..6fe516f121d3 100644 --- a/.github/workflows/blob-size-policy.yml +++ b/.github/workflows/blob-size-policy.yml @@ -1,7 +1,7 @@ name: blob-size-policy on: - pull_request: {} + workflow_call: jobs: check: diff --git a/.github/workflows/cargo-deny.yml b/.github/workflows/cargo-deny.yml index aa37d7cec29b..5aba258319bd 100644 --- a/.github/workflows/cargo-deny.yml +++ b/.github/workflows/cargo-deny.yml @@ -1,7 +1,7 @@ name: cargo-deny on: - pull_request: + workflow_call: push: branches: - main diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a78ae034edf3..f83254e1cfdc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,7 @@ name: ci on: - pull_request: {} + workflow_call: push: { branches: [main] } jobs: diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index b642a6dbe189..825dd7f7f08e 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -3,10 +3,9 @@ name: Codespell on: + workflow_call: push: branches: [main] - pull_request: - branches: [main] permissions: contents: read diff --git a/.github/workflows/pr-ci.yml b/.github/workflows/pr-ci.yml new file mode 100644 index 000000000000..a5662cf89429 --- /dev/null +++ b/.github/workflows/pr-ci.yml @@ -0,0 +1,76 @@ +name: pr-ci + +on: + pull_request: {} + +jobs: + bazel: + name: Bazel + uses: ./.github/workflows/bazel.yml + secrets: inherit + + blob-size-policy: + name: Blob size policy + uses: ./.github/workflows/blob-size-policy.yml + secrets: inherit + + cargo-deny: + name: cargo-deny + uses: ./.github/workflows/cargo-deny.yml + secrets: inherit + + ci: + name: ci + uses: ./.github/workflows/ci.yml + secrets: inherit + + codespell: + name: Codespell + uses: ./.github/workflows/codespell.yml + secrets: inherit + + rust-ci: + name: rust-ci + uses: ./.github/workflows/rust-ci.yml + secrets: inherit + + sdk: + name: sdk + uses: ./.github/workflows/sdk.yml + secrets: inherit + + required: + name: CI required + if: ${{ always() }} + needs: + - bazel + - blob-size-policy + - cargo-deny + - ci + - codespell + - rust-ci + - sdk + runs-on: ubuntu-24.04 + steps: + - name: Require successful dependencies + env: + NEEDS: ${{ toJSON(needs) }} + shell: bash + run: | + set -euo pipefail + + failures="$( + jq --raw-output ' + to_entries[] + | select(.value.result != "success") + | "\(.key): \(.value.result)" + ' <<<"$NEEDS" + )" + + if [[ -n "$failures" ]]; then + echo 'Required CI dependencies did not succeed:' >&2 + echo "$failures" >&2 + exit 1 + fi + + echo 'All required CI dependencies succeeded.' diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index f693994d1a77..cf1afdfe2de1 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -1,6 +1,6 @@ name: rust-ci on: - pull_request: {} + workflow_call: workflow_dispatch: # Cargo's libgit2 transport has been flaky when fetching git dependencies with diff --git a/.github/workflows/sdk.yml b/.github/workflows/sdk.yml index dcb94067989f..2850a60070c0 100644 --- a/.github/workflows/sdk.yml +++ b/.github/workflows/sdk.yml @@ -1,9 +1,9 @@ name: sdk on: + workflow_call: push: branches: [main] - pull_request: {} jobs: python-sdk: From 82f5dc4764adfd48d60302f29bcfe44efa83758c Mon Sep 17 00:00:00 2001 From: Adam Perry Date: Fri, 26 Jun 2026 03:17:32 +0000 Subject: [PATCH 2/9] rename PR workflow to blocking CI --- .github/workflows/{pr-ci.yml => blocking-ci.yml} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .github/workflows/{pr-ci.yml => blocking-ci.yml} (98%) diff --git a/.github/workflows/pr-ci.yml b/.github/workflows/blocking-ci.yml similarity index 98% rename from .github/workflows/pr-ci.yml rename to .github/workflows/blocking-ci.yml index a5662cf89429..9871120e89ad 100644 --- a/.github/workflows/pr-ci.yml +++ b/.github/workflows/blocking-ci.yml @@ -1,4 +1,4 @@ -name: pr-ci +name: blocking-ci on: pull_request: {} From bfd5f922a968632b56f8fec68b7753db7da71aa6 Mon Sep 17 00:00:00 2001 From: Adam Perry Date: Fri, 26 Jun 2026 03:36:46 +0000 Subject: [PATCH 3/9] rename CI workflow to repo checks --- .github/workflows/blocking-ci.yml | 8 ++++---- .github/workflows/{ci.yml => repo-checks.yml} | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) rename .github/workflows/{ci.yml => repo-checks.yml} (99%) diff --git a/.github/workflows/blocking-ci.yml b/.github/workflows/blocking-ci.yml index 9871120e89ad..d26f29ef2550 100644 --- a/.github/workflows/blocking-ci.yml +++ b/.github/workflows/blocking-ci.yml @@ -19,9 +19,9 @@ jobs: uses: ./.github/workflows/cargo-deny.yml secrets: inherit - ci: - name: ci - uses: ./.github/workflows/ci.yml + repo-checks: + name: repo-checks + uses: ./.github/workflows/repo-checks.yml secrets: inherit codespell: @@ -46,7 +46,7 @@ jobs: - bazel - blob-size-policy - cargo-deny - - ci + - repo-checks - codespell - rust-ci - sdk diff --git a/.github/workflows/ci.yml b/.github/workflows/repo-checks.yml similarity index 99% rename from .github/workflows/ci.yml rename to .github/workflows/repo-checks.yml index f83254e1cfdc..12a512652e84 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/repo-checks.yml @@ -1,4 +1,4 @@ -name: ci +name: repo-checks on: workflow_call: From b726f79b7b48e83867136870cfc49db17ba7a656 Mon Sep 17 00:00:00 2001 From: Adam Perry Date: Fri, 26 Jun 2026 03:42:03 +0000 Subject: [PATCH 4/9] group main checks under blocking CI --- .github/workflows/bazel.yml | 3 --- .github/workflows/blob-size-policy.yml | 15 ++++++++++++--- .github/workflows/blocking-ci.yml | 14 ++++++++------ .github/workflows/cargo-deny.yml | 3 --- .github/workflows/codespell.yml | 2 -- .github/workflows/repo-checks.yml | 1 - .github/workflows/sdk.yml | 2 -- 7 files changed, 20 insertions(+), 20 deletions(-) diff --git a/.github/workflows/bazel.yml b/.github/workflows/bazel.yml index f3e0841dbb5e..10dd8670de0a 100644 --- a/.github/workflows/bazel.yml +++ b/.github/workflows/bazel.yml @@ -5,9 +5,6 @@ name: Bazel on: workflow_call: - push: - branches: - - main workflow_dispatch: concurrency: diff --git a/.github/workflows/blob-size-policy.yml b/.github/workflows/blob-size-policy.yml index 6fe516f121d3..a5f937420e40 100644 --- a/.github/workflows/blob-size-policy.yml +++ b/.github/workflows/blob-size-policy.yml @@ -14,13 +14,22 @@ jobs: fetch-depth: 0 persist-credentials: false - - name: Determine PR comparison range + - name: Determine comparison range id: range shell: bash run: | set -euo pipefail - echo "base=${{ github.event.pull_request.base.sha }}" >> "$GITHUB_OUTPUT" - echo "head=${{ github.event.pull_request.head.sha }}" >> "$GITHUB_OUTPUT" + + if [[ "${{ github.event_name }}" == "pull_request" ]]; then + base='${{ github.event.pull_request.base.sha }}' + head='${{ github.event.pull_request.head.sha }}' + else + base='${{ github.event.before }}' + head='${{ github.sha }}' + fi + + echo "base=$base" >> "$GITHUB_OUTPUT" + echo "head=$head" >> "$GITHUB_OUTPUT" - name: Check changed blob sizes env: diff --git a/.github/workflows/blocking-ci.yml b/.github/workflows/blocking-ci.yml index d26f29ef2550..e0ce7d8a79ad 100644 --- a/.github/workflows/blocking-ci.yml +++ b/.github/workflows/blocking-ci.yml @@ -2,6 +2,8 @@ name: blocking-ci on: pull_request: {} + push: + branches: [main] jobs: bazel: @@ -19,16 +21,16 @@ jobs: uses: ./.github/workflows/cargo-deny.yml secrets: inherit - repo-checks: - name: repo-checks - uses: ./.github/workflows/repo-checks.yml - secrets: inherit - codespell: name: Codespell uses: ./.github/workflows/codespell.yml secrets: inherit + repo-checks: + name: repo-checks + uses: ./.github/workflows/repo-checks.yml + secrets: inherit + rust-ci: name: rust-ci uses: ./.github/workflows/rust-ci.yml @@ -46,8 +48,8 @@ jobs: - bazel - blob-size-policy - cargo-deny - - repo-checks - codespell + - repo-checks - rust-ci - sdk runs-on: ubuntu-24.04 diff --git a/.github/workflows/cargo-deny.yml b/.github/workflows/cargo-deny.yml index 5aba258319bd..be00e1147d12 100644 --- a/.github/workflows/cargo-deny.yml +++ b/.github/workflows/cargo-deny.yml @@ -2,9 +2,6 @@ name: cargo-deny on: workflow_call: - push: - branches: - - main # Cargo's libgit2 transport has been flaky when fetching git dependencies with # nested submodules. Prefer the system git CLI across every Cargo invocation. diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index 825dd7f7f08e..a1751c8d2941 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -4,8 +4,6 @@ name: Codespell on: workflow_call: - push: - branches: [main] permissions: contents: read diff --git a/.github/workflows/repo-checks.yml b/.github/workflows/repo-checks.yml index 12a512652e84..3a76a202eae8 100644 --- a/.github/workflows/repo-checks.yml +++ b/.github/workflows/repo-checks.yml @@ -2,7 +2,6 @@ name: repo-checks on: workflow_call: - push: { branches: [main] } jobs: build-test: diff --git a/.github/workflows/sdk.yml b/.github/workflows/sdk.yml index 2850a60070c0..41f887471220 100644 --- a/.github/workflows/sdk.yml +++ b/.github/workflows/sdk.yml @@ -2,8 +2,6 @@ name: sdk on: workflow_call: - push: - branches: [main] jobs: python-sdk: From 2efdd80cf0868b49fd581cf93e01a9f5edc7c8c5 Mon Sep 17 00:00:00 2001 From: Adam Perry Date: Fri, 26 Jun 2026 03:50:05 +0000 Subject: [PATCH 5/9] group postmerge checks under parent workflow --- .github/workflows/postmerge-ci.yml | 97 ++++++++++++++++++++++++++++++ .github/workflows/rust-ci-full.yml | 2 +- .github/workflows/v8-canary.yml | 22 +------ 3 files changed, 100 insertions(+), 21 deletions(-) create mode 100644 .github/workflows/postmerge-ci.yml diff --git a/.github/workflows/postmerge-ci.yml b/.github/workflows/postmerge-ci.yml new file mode 100644 index 000000000000..f75ba0ee852c --- /dev/null +++ b/.github/workflows/postmerge-ci.yml @@ -0,0 +1,97 @@ +name: postmerge-ci + +on: + push: + branches: [main] + +jobs: + changes: + name: Detect postmerge changes + runs-on: ubuntu-24.04 + outputs: + v8_canary: ${{ steps.detect.outputs.v8_canary }} + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ github.sha }} + fetch-depth: 0 + persist-credentials: false + + - name: Detect V8 canary changes + id: detect + env: + BASE_SHA: ${{ github.event.before }} + HEAD_SHA: ${{ github.sha }} + shell: bash + run: | + set -euo pipefail + + v8_canary=false + while IFS= read -r file; do + case "$file" in + .bazelrc \ + | .github/actions/setup-bazel-ci/* \ + | .github/scripts/run_bazel_with_buildbuddy.py \ + | .github/scripts/rusty_v8_bazel.py \ + | .github/scripts/rusty_v8_module_bazel.py \ + | .github/scripts/v8_canary_changes.py \ + | .github/workflows/postmerge-ci.yml \ + | .github/workflows/rusty-v8-release.yml \ + | .github/workflows/v8-canary.yml \ + | MODULE.bazel \ + | MODULE.bazel.lock \ + | codex-rs/Cargo.toml \ + | patches/BUILD.bazel \ + | patches/llvm_*.patch \ + | patches/rules_cc_*.patch \ + | patches/v8_*.patch \ + | third_party/v8/*) + v8_canary=true + ;; + esac + done < <(git diff --name-only --no-renames "$BASE_SHA" "$HEAD_SHA") + + echo "v8_canary=$v8_canary" >> "$GITHUB_OUTPUT" + + rust-ci-full: + name: rust-ci-full + uses: ./.github/workflows/rust-ci-full.yml + secrets: inherit + + v8-canary: + name: v8-canary + needs: changes + if: needs.changes.outputs.v8_canary == 'true' + uses: ./.github/workflows/v8-canary.yml + secrets: inherit + + results: + name: Postmerge CI results + needs: + - changes + - rust-ci-full + - v8-canary + if: ${{ always() }} + runs-on: ubuntu-24.04 + steps: + - name: Require successful dependencies + env: + V8_CANARY_REQUIRED: ${{ needs.changes.outputs.v8_canary }} + shell: bash + run: | + set -euo pipefail + + echo "changes : ${{ needs.changes.result }}" + echo "rust-ci-full: ${{ needs.rust-ci-full.result }}" + echo "v8-canary : ${{ needs.v8-canary.result }}" + + [[ '${{ needs.changes.result }}' == 'success' ]] || { echo 'change detection failed'; exit 1; } + [[ '${{ needs.rust-ci-full.result }}' == 'success' ]] || { echo 'rust-ci-full failed'; exit 1; } + + if [[ "$V8_CANARY_REQUIRED" == 'true' ]]; then + [[ '${{ needs.v8-canary.result }}' == 'success' ]] || { echo 'v8-canary failed'; exit 1; } + else + [[ '${{ needs.v8-canary.result }}' == 'skipped' ]] || { echo 'v8-canary ran unexpectedly'; exit 1; } + fi + + echo 'All required postmerge CI dependencies succeeded.' diff --git a/.github/workflows/rust-ci-full.yml b/.github/workflows/rust-ci-full.yml index 7ad1d4b3adcf..b60c0b7ca24e 100644 --- a/.github/workflows/rust-ci-full.yml +++ b/.github/workflows/rust-ci-full.yml @@ -1,8 +1,8 @@ name: rust-ci-full on: + workflow_call: push: branches: - - main - "**full-ci**" workflow_dispatch: diff --git a/.github/workflows/v8-canary.yml b/.github/workflows/v8-canary.yml index efa06aaad743..6f10aba64c76 100644 --- a/.github/workflows/v8-canary.yml +++ b/.github/workflows/v8-canary.yml @@ -1,6 +1,7 @@ name: v8-canary on: + workflow_call: pull_request: paths: - ".bazelrc" @@ -9,26 +10,7 @@ on: - ".github/scripts/rusty_v8_bazel.py" - ".github/scripts/rusty_v8_module_bazel.py" - ".github/scripts/v8_canary_changes.py" - - ".github/workflows/rusty-v8-release.yml" - - ".github/workflows/v8-canary.yml" - - "MODULE.bazel" - - "MODULE.bazel.lock" - - "codex-rs/Cargo.toml" - - "patches/BUILD.bazel" - - "patches/llvm_*.patch" - - "patches/rules_cc_*.patch" - - "patches/v8_*.patch" - - "third_party/v8/**" - push: - branches: - - main - paths: - - ".bazelrc" - - ".github/actions/setup-bazel-ci/**" - - ".github/scripts/run_bazel_with_buildbuddy.py" - - ".github/scripts/rusty_v8_bazel.py" - - ".github/scripts/rusty_v8_module_bazel.py" - - ".github/scripts/v8_canary_changes.py" + - ".github/workflows/postmerge-ci.yml" - ".github/workflows/rusty-v8-release.yml" - ".github/workflows/v8-canary.yml" - "MODULE.bazel" From cef1d748f784595b6bb86c9b4e45645149bf1e88 Mon Sep 17 00:00:00 2001 From: Adam Perry Date: Fri, 26 Jun 2026 04:01:43 +0000 Subject: [PATCH 6/9] move CI orchestration logic into scripts --- .github/scripts/check_ci_results.py | 25 +++++++++ .github/scripts/v8_canary_changes.py | 77 ++++++++++++++++++++++++---- .github/workflows/blocking-ci.yml | 25 +++------ .github/workflows/postmerge-ci.yml | 77 +++------------------------- .github/workflows/v8-canary.yml | 24 ++------- 5 files changed, 110 insertions(+), 118 deletions(-) create mode 100644 .github/scripts/check_ci_results.py diff --git a/.github/scripts/check_ci_results.py b/.github/scripts/check_ci_results.py new file mode 100644 index 000000000000..483649227194 --- /dev/null +++ b/.github/scripts/check_ci_results.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 + +import json +import os + + +def main() -> None: + needs = json.loads(os.environ["NEEDS"]) + failures = sorted( + (name, dependency["result"]) + for name, dependency in needs.items() + if dependency["result"] != "success" + ) + + if failures: + print("CI dependencies did not succeed:") + for name, result in failures: + print(f"{name}: {result}") + raise SystemExit(1) + + print("All CI dependencies succeeded.") + + +if __name__ == "__main__": + main() diff --git a/.github/scripts/v8_canary_changes.py b/.github/scripts/v8_canary_changes.py index 6d9693c60a9f..c8c539163238 100644 --- a/.github/scripts/v8_canary_changes.py +++ b/.github/scripts/v8_canary_changes.py @@ -3,10 +3,30 @@ import argparse import subprocess import tomllib +from fnmatch import fnmatchcase from pathlib import Path ROOT = Path(__file__).resolve().parents[2] +CANARY_PATH_PATTERNS = { + ".bazelrc", + ".github/actions/setup-bazel-ci/**", + ".github/scripts/run_bazel_with_buildbuddy.py", + ".github/scripts/rusty_v8_bazel.py", + ".github/scripts/rusty_v8_module_bazel.py", + ".github/scripts/v8_canary_changes.py", + ".github/workflows/postmerge-ci.yml", + ".github/workflows/rusty-v8-release.yml", + ".github/workflows/v8-canary.yml", + "MODULE.bazel", + "MODULE.bazel.lock", + "codex-rs/Cargo.toml", + "patches/BUILD.bazel", + "patches/llvm_*.patch", + "patches/rules_cc_*.patch", + "patches/v8_*.patch", + "third_party/v8/**", +} WINDOWS_SOURCE_BUILD_PATHS = { ".github/scripts/rusty_v8_bazel.py", ".github/scripts/rusty_v8_module_bazel.py", @@ -16,6 +36,28 @@ } +def matching_canary_paths(changed_files: set[str]) -> set[str]: + return { + path + for path in changed_files + if any(fnmatchcase(path, pattern) for pattern in CANARY_PATH_PATTERNS) + } + + +def canary_required( + changed_files: set[str], + base_v8_version: str, + head_v8_version: str, + *, + force: bool = False, +) -> bool: + return ( + force + or base_v8_version != head_v8_version + or bool(matching_canary_paths(changed_files)) + ) + + def resolved_v8_version(cargo_lock: bytes) -> str: versions = sorted( { @@ -79,25 +121,42 @@ def parse_args() -> argparse.Namespace: def main() -> None: args = parse_args() if args.force: - required = True - reason = "manual workflow dispatch" + canary = True + canary_reason = "manual workflow dispatch" + windows_source = True + windows_source_reason = "manual workflow dispatch" elif not args.base or not args.head: raise SystemExit("--base and --head are required unless --force is set") else: files = changed_files(args.base, args.head) base_version = v8_version_at_revision(merge_base(args.base, args.head)) head_version = v8_version_at_revision(args.head) - required = windows_source_required(files, base_version, head_version) + + matched_canary_paths = sorted(matching_canary_paths(files)) + canary = canary_required(files, base_version, head_version) + windows_source = windows_source_required(files, base_version, head_version) if base_version != head_version: - reason = f"v8 version changed from {base_version} to {head_version}" + canary_reason = ( + f"v8 version changed from {base_version} to {head_version}" + ) + windows_source_reason = canary_reason else: - matched_paths = sorted(files & WINDOWS_SOURCE_BUILD_PATHS) - reason = ( - ", ".join(matched_paths) if matched_paths else "no relevant changes" + canary_reason = ( + ", ".join(matched_canary_paths) + if matched_canary_paths + else "no relevant changes" + ) + matched_windows_paths = sorted(files & WINDOWS_SOURCE_BUILD_PATHS) + windows_source_reason = ( + ", ".join(matched_windows_paths) + if matched_windows_paths + else "no relevant changes" ) - print(f"windows_source_required={str(required).lower()}") - print(f"windows_source_reason={reason}") + print(f"canary_required={str(canary).lower()}") + print(f"canary_reason={canary_reason}") + print(f"windows_source_required={str(windows_source).lower()}") + print(f"windows_source_reason={windows_source_reason}") if __name__ == "__main__": diff --git a/.github/workflows/blocking-ci.yml b/.github/workflows/blocking-ci.yml index e0ce7d8a79ad..52731efeb678 100644 --- a/.github/workflows/blocking-ci.yml +++ b/.github/workflows/blocking-ci.yml @@ -54,25 +54,12 @@ jobs: - sdk runs-on: ubuntu-24.04 steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} + persist-credentials: false + - name: Require successful dependencies env: NEEDS: ${{ toJSON(needs) }} - shell: bash - run: | - set -euo pipefail - - failures="$( - jq --raw-output ' - to_entries[] - | select(.value.result != "success") - | "\(.key): \(.value.result)" - ' <<<"$NEEDS" - )" - - if [[ -n "$failures" ]]; then - echo 'Required CI dependencies did not succeed:' >&2 - echo "$failures" >&2 - exit 1 - fi - - echo 'All required CI dependencies succeeded.' + run: python3 .github/scripts/check_ci_results.py diff --git a/.github/workflows/postmerge-ci.yml b/.github/workflows/postmerge-ci.yml index f75ba0ee852c..f5f6bd153b9c 100644 --- a/.github/workflows/postmerge-ci.yml +++ b/.github/workflows/postmerge-ci.yml @@ -5,54 +5,6 @@ on: branches: [main] jobs: - changes: - name: Detect postmerge changes - runs-on: ubuntu-24.04 - outputs: - v8_canary: ${{ steps.detect.outputs.v8_canary }} - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - ref: ${{ github.sha }} - fetch-depth: 0 - persist-credentials: false - - - name: Detect V8 canary changes - id: detect - env: - BASE_SHA: ${{ github.event.before }} - HEAD_SHA: ${{ github.sha }} - shell: bash - run: | - set -euo pipefail - - v8_canary=false - while IFS= read -r file; do - case "$file" in - .bazelrc \ - | .github/actions/setup-bazel-ci/* \ - | .github/scripts/run_bazel_with_buildbuddy.py \ - | .github/scripts/rusty_v8_bazel.py \ - | .github/scripts/rusty_v8_module_bazel.py \ - | .github/scripts/v8_canary_changes.py \ - | .github/workflows/postmerge-ci.yml \ - | .github/workflows/rusty-v8-release.yml \ - | .github/workflows/v8-canary.yml \ - | MODULE.bazel \ - | MODULE.bazel.lock \ - | codex-rs/Cargo.toml \ - | patches/BUILD.bazel \ - | patches/llvm_*.patch \ - | patches/rules_cc_*.patch \ - | patches/v8_*.patch \ - | third_party/v8/*) - v8_canary=true - ;; - esac - done < <(git diff --name-only --no-renames "$BASE_SHA" "$HEAD_SHA") - - echo "v8_canary=$v8_canary" >> "$GITHUB_OUTPUT" - rust-ci-full: name: rust-ci-full uses: ./.github/workflows/rust-ci-full.yml @@ -60,38 +12,23 @@ jobs: v8-canary: name: v8-canary - needs: changes - if: needs.changes.outputs.v8_canary == 'true' uses: ./.github/workflows/v8-canary.yml secrets: inherit results: name: Postmerge CI results needs: - - changes - rust-ci-full - v8-canary if: ${{ always() }} runs-on: ubuntu-24.04 steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ github.sha }} + persist-credentials: false + - name: Require successful dependencies env: - V8_CANARY_REQUIRED: ${{ needs.changes.outputs.v8_canary }} - shell: bash - run: | - set -euo pipefail - - echo "changes : ${{ needs.changes.result }}" - echo "rust-ci-full: ${{ needs.rust-ci-full.result }}" - echo "v8-canary : ${{ needs.v8-canary.result }}" - - [[ '${{ needs.changes.result }}' == 'success' ]] || { echo 'change detection failed'; exit 1; } - [[ '${{ needs.rust-ci-full.result }}' == 'success' ]] || { echo 'rust-ci-full failed'; exit 1; } - - if [[ "$V8_CANARY_REQUIRED" == 'true' ]]; then - [[ '${{ needs.v8-canary.result }}' == 'success' ]] || { echo 'v8-canary failed'; exit 1; } - else - [[ '${{ needs.v8-canary.result }}' == 'skipped' ]] || { echo 'v8-canary ran unexpectedly'; exit 1; } - fi - - echo 'All required postmerge CI dependencies succeeded.' + NEEDS: ${{ toJSON(needs) }} + run: python3 .github/scripts/check_ci_results.py diff --git a/.github/workflows/v8-canary.yml b/.github/workflows/v8-canary.yml index 6f10aba64c76..14941e1cef5f 100644 --- a/.github/workflows/v8-canary.yml +++ b/.github/workflows/v8-canary.yml @@ -2,25 +2,7 @@ name: v8-canary on: workflow_call: - pull_request: - paths: - - ".bazelrc" - - ".github/actions/setup-bazel-ci/**" - - ".github/scripts/run_bazel_with_buildbuddy.py" - - ".github/scripts/rusty_v8_bazel.py" - - ".github/scripts/rusty_v8_module_bazel.py" - - ".github/scripts/v8_canary_changes.py" - - ".github/workflows/postmerge-ci.yml" - - ".github/workflows/rusty-v8-release.yml" - - ".github/workflows/v8-canary.yml" - - "MODULE.bazel" - - "MODULE.bazel.lock" - - "codex-rs/Cargo.toml" - - "patches/BUILD.bazel" - - "patches/llvm_*.patch" - - "patches/rules_cc_*.patch" - - "patches/v8_*.patch" - - "third_party/v8/**" + pull_request: {} workflow_dispatch: # Cargo's libgit2 transport has been flaky when fetching git dependencies with @@ -36,6 +18,7 @@ jobs: metadata: runs-on: ubuntu-latest outputs: + canary_required: ${{ steps.changes.outputs.canary_required }} v8_version: ${{ steps.v8_version.outputs.version }} windows_source_required: ${{ steps.changes.outputs.windows_source_required }} @@ -59,7 +42,7 @@ jobs: version="$(python3 .github/scripts/rusty_v8_bazel.py resolved-v8-crate-version)" echo "version=${version}" >> "$GITHUB_OUTPUT" - - name: Detect whether Windows source artifacts need rebuilding + - name: Detect V8 canary changes id: changes env: BASE_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }} @@ -86,6 +69,7 @@ jobs: build: name: Build ${{ matrix.variant }} ${{ matrix.target }} needs: metadata + if: ${{ needs.metadata.outputs.canary_required == 'true' }} runs-on: ${{ matrix.runner }} permissions: contents: read From ee61530d747b9f8fd06966aa731c3d2456ef3225 Mon Sep 17 00:00:00 2001 From: Adam Perry Date: Fri, 26 Jun 2026 04:20:45 +0000 Subject: [PATCH 7/9] document CI orchestration invariants --- .github/scripts/check_ci_results.py | 9 +++++++++ .github/scripts/v8_canary_changes.py | 19 +++++++++++++++++++ .github/workflows/blob-size-policy.yml | 2 ++ .github/workflows/blocking-ci.yml | 9 +++++++++ .github/workflows/postmerge-ci.yml | 6 ++++++ .github/workflows/rust-ci-full.yml | 2 ++ .github/workflows/v8-canary.yml | 12 +++++++++++- 7 files changed, 58 insertions(+), 1 deletion(-) diff --git a/.github/scripts/check_ci_results.py b/.github/scripts/check_ci_results.py index 483649227194..36d1eed3b4ee 100644 --- a/.github/scripts/check_ci_results.py +++ b/.github/scripts/check_ci_results.py @@ -1,10 +1,19 @@ #!/usr/bin/env python3 +"""Fail a terminal CI job unless every serialized dependency succeeded. + +Parent workflows pass GitHub's `toJSON(needs)` object through the NEEDS +environment variable. Treat skipped and cancelled dependencies as failures too: +for a required fan-in job, only an explicit success is safe to accept. +""" + import json import os def main() -> None: + # Keep result policy in one script so blocking-ci and postmerge-ci cannot + # drift in how they interpret dependency conclusions. needs = json.loads(os.environ["NEEDS"]) failures = sorted( (name, dependency["result"]) diff --git a/.github/scripts/v8_canary_changes.py b/.github/scripts/v8_canary_changes.py index c8c539163238..0acc4ec3f6bc 100644 --- a/.github/scripts/v8_canary_changes.py +++ b/.github/scripts/v8_canary_changes.py @@ -1,5 +1,13 @@ #!/usr/bin/env python3 +"""Decide which V8 canary work is needed for a commit range. + +The workflow deliberately has no trigger-level path filters because it is both +directly triggered for pull requests and called by postmerge-ci. Keeping the +patterns here gives those entrypoints one source of truth; unrelated events +still run metadata but skip the expensive build matrices. +""" + import argparse import subprocess import tomllib @@ -8,6 +16,8 @@ ROOT = Path(__file__).resolve().parents[2] +# These patterns replace the old pull_request/push path filters. Include parent +# workflow changes because they can alter whether the canary is invoked. CANARY_PATH_PATTERNS = { ".bazelrc", ".github/actions/setup-bazel-ci/**", @@ -27,6 +37,8 @@ "patches/v8_*.patch", "third_party/v8/**", } +# Windows source builds are a narrower, more expensive subset of the canary. +# A V8 version change also requires them even when no path below changed. WINDOWS_SOURCE_BUILD_PATHS = { ".github/scripts/rusty_v8_bazel.py", ".github/scripts/rusty_v8_module_bazel.py", @@ -37,6 +49,7 @@ def matching_canary_paths(changed_files: set[str]) -> set[str]: + """Return changed paths that require the general V8 build matrix.""" return { path for path in changed_files @@ -51,6 +64,7 @@ def canary_required( *, force: bool = False, ) -> bool: + """Return whether the general V8 build matrix should run.""" return ( force or base_v8_version != head_v8_version @@ -78,6 +92,7 @@ def windows_source_required( *, force: bool = False, ) -> bool: + """Return whether Windows must rebuild rusty_v8 from source.""" return ( force or base_v8_version != head_v8_version @@ -100,6 +115,8 @@ def merge_base(base: str, head: str, *, root: Path = ROOT) -> str: def changed_files(base: str, head: str, *, root: Path = ROOT) -> set[str]: + # Three-dot diff gives PRs merge-base semantics while remaining equivalent + # to before/after for ordinary linear pushes to main. output = git_output( "diff", "--name-only", @@ -121,6 +138,8 @@ def parse_args() -> argparse.Namespace: def main() -> None: args = parse_args() if args.force: + # workflow_dispatch has no comparison range, and callers use it as a + # manual retry path, so it intentionally runs every variant. canary = True canary_reason = "manual workflow dispatch" windows_source = True diff --git a/.github/workflows/blob-size-policy.yml b/.github/workflows/blob-size-policy.yml index a5f937420e40..fddc51afe7c2 100644 --- a/.github/workflows/blob-size-policy.yml +++ b/.github/workflows/blob-size-policy.yml @@ -20,6 +20,8 @@ jobs: run: | set -euo pipefail + # PRs inspect the proposed diff; main pushes inspect only the commit + # range that just landed. Both paths feed the same blob-size checker. if [[ "${{ github.event_name }}" == "pull_request" ]]; then base='${{ github.event.pull_request.base.sha }}' head='${{ github.event.pull_request.head.sha }}' diff --git a/.github/workflows/blocking-ci.yml b/.github/workflows/blocking-ci.yml index 52731efeb678..9cfde767f92c 100644 --- a/.github/workflows/blocking-ci.yml +++ b/.github/workflows/blocking-ci.yml @@ -1,11 +1,15 @@ name: blocking-ci +# This is the single entrypoint for checks that block a PR merge. It also runs +# after pushes to main so the same check family stays grouped in the Actions UI. on: pull_request: {} push: branches: [main] jobs: + # Keep reusable workflow calls alphabetized. The `required` job below is the + # version-controlled list that the main-branch ruleset should require. bazel: name: Bazel uses: ./.github/workflows/bazel.yml @@ -43,6 +47,8 @@ jobs: required: name: CI required + # Without `always()`, GitHub skips this job after a failed dependency and a + # required check can appear successful instead of reporting the failure. if: ${{ always() }} needs: - bazel @@ -54,6 +60,9 @@ jobs: - sdk runs-on: ubuntu-24.04 steps: + # Match the child workflows by checking out the PR head. During the + # initial rollout, PR branches created before check_ci_results.py exists + # must rebase onto main before this gate can run. - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} diff --git a/.github/workflows/postmerge-ci.yml b/.github/workflows/postmerge-ci.yml index f5f6bd153b9c..ac2b46d1db4c 100644 --- a/.github/workflows/postmerge-ci.yml +++ b/.github/workflows/postmerge-ci.yml @@ -1,10 +1,14 @@ name: postmerge-ci +# This is the single entrypoint for main-only CI that is intentionally outside +# the merge-blocking suite. It keeps the broader postmerge signal in one run. on: push: branches: [main] jobs: + # Keep reusable workflow calls alphabetized. Each child retains its own + # workflow_dispatch trigger so maintainers can rerun flaky suites directly. rust-ci-full: name: rust-ci-full uses: ./.github/workflows/rust-ci-full.yml @@ -23,6 +27,8 @@ jobs: if: ${{ always() }} runs-on: ubuntu-24.04 steps: + # Postmerge runs use the pushed main commit, so this helper always comes + # from the same revision that defined the parent workflow. - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: ${{ github.sha }} diff --git a/.github/workflows/rust-ci-full.yml b/.github/workflows/rust-ci-full.yml index b60c0b7ca24e..cb99c8911e11 100644 --- a/.github/workflows/rust-ci-full.yml +++ b/.github/workflows/rust-ci-full.yml @@ -3,6 +3,8 @@ on: workflow_call: push: branches: + # Main pushes enter through postmerge-ci. Keep this opt-in branch trigger + # for developers who want the full suite before merging. - "**full-ci**" workflow_dispatch: diff --git a/.github/workflows/v8-canary.yml b/.github/workflows/v8-canary.yml index 14941e1cef5f..53d9585108e8 100644 --- a/.github/workflows/v8-canary.yml +++ b/.github/workflows/v8-canary.yml @@ -1,5 +1,9 @@ name: v8-canary +# Do not use trigger-level path filters here. This workflow is also called by +# postmerge-ci, and GitHub cannot share a path filter between pull_request and +# workflow_call. v8_canary_changes.py is the single source of truth instead; +# unrelated events run only the cheap metadata job below. on: workflow_call: pull_request: {} @@ -18,7 +22,10 @@ jobs: metadata: runs-on: ubuntu-latest outputs: - canary_required: ${{ steps.changes.outputs.canary_required }} + # A stale PR head can contain the old detector, which does not emit this + # output. Missing must mean "run" so older branches cannot silently skip + # the expensive V8 coverage while reporting success. + canary_required: ${{ steps.changes.outputs.canary_required || 'true' }} v8_version: ${{ steps.v8_version.outputs.version }} windows_source_required: ${{ steps.changes.outputs.windows_source_required }} @@ -52,6 +59,8 @@ jobs: run: | set -euo pipefail + # Manual runs have no meaningful before/after range. Force every V8 + # variant so workflow_dispatch remains a reliable retry path. if [[ "${EVENT_NAME}" == "workflow_dispatch" ]]; then output="$(python3 .github/scripts/v8_canary_changes.py --force)" else @@ -69,6 +78,7 @@ jobs: build: name: Build ${{ matrix.variant }} ${{ matrix.target }} needs: metadata + # Metadata always runs; only relevant changes pay for the large matrix. if: ${{ needs.metadata.outputs.canary_required == 'true' }} runs-on: ${{ matrix.runner }} permissions: From 9997831b9c1bdfed715f5746edf7323036a2fb74 Mon Sep 17 00:00:00 2001 From: Adam Perry Date: Fri, 26 Jun 2026 19:17:51 +0000 Subject: [PATCH 8/9] trust CI gate helper on PRs --- .github/workflows/blocking-ci.yml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/blocking-ci.yml b/.github/workflows/blocking-ci.yml index 9cfde767f92c..e73263ab8d7f 100644 --- a/.github/workflows/blocking-ci.yml +++ b/.github/workflows/blocking-ci.yml @@ -60,12 +60,18 @@ jobs: - sdk runs-on: ubuntu-24.04 steps: - # Match the child workflows by checking out the PR head. During the - # initial rollout, PR branches created before check_ci_results.py exists - # must rebase onto main before this gate can run. + # The terminal gate is policy, not code under test. For pull requests, + # load its helper from the trusted base commit instead of the PR head or + # synthetic merge commit. Otherwise a PR could change the helper to + # report success after one of the required child workflows failed. + # + # Using the base commit also lets branches created before this helper was + # added pick it up after main advances, without requiring a rebase. Main + # pushes use the pushed commit because it is already on the protected + # branch. - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} + ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.sha }} persist-credentials: false - name: Require successful dependencies From eb158bfb5a1551a71bd574ad00eb63289cd6e188 Mon Sep 17 00:00:00 2001 From: Adam Perry Date: Fri, 26 Jun 2026 19:35:17 +0000 Subject: [PATCH 9/9] restore CI gate head checkout --- .github/workflows/blocking-ci.yml | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/.github/workflows/blocking-ci.yml b/.github/workflows/blocking-ci.yml index e73263ab8d7f..fe66fdd50952 100644 --- a/.github/workflows/blocking-ci.yml +++ b/.github/workflows/blocking-ci.yml @@ -60,18 +60,17 @@ jobs: - sdk runs-on: ubuntu-24.04 steps: - # The terminal gate is policy, not code under test. For pull requests, - # load its helper from the trusted base commit instead of the PR head or - # synthetic merge commit. Otherwise a PR could change the helper to - # report success after one of the required child workflows failed. + # Keep the helper on the same revision as the caller and child workflows. + # CI workflow uploads are restricted, so this repository does not need a + # separate trusted-base checkout for the terminal policy step. Using the + # PR head also lets the introducing PR exercise a newly added helper. # - # Using the base commit also lets branches created before this helper was - # added pick it up after main advances, without requiring a rebase. Main - # pushes use the pushed commit because it is already on the protected - # branch. + # During the initial rollout, PR branches created before + # check_ci_results.py exists must rebase onto main before this gate can + # run. - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.sha }} + ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} persist-credentials: false - name: Require successful dependencies