mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
[codex] group blocking and postmerge CI workflows (#30146)
## Why It's hard to change the set of required jobs when they're managed in the GitHub UI, and when each workflow is responsible for choosing it's own scheduling it's easy to end up with skew between what we enforce on PRs vs. on main. ## What - add a `blocking-ci` caller workflow, triggered by pull requests and pushes to `main`, for Bazel, blob size, cargo-deny, Codespell, `repo-checks`, rust CI, and SDK CI - add an `always()` terminal job named `CI required` that fails unless every called workflow succeeds - add a `postmerge-ci` caller workflow for `rust-ci-full` and `v8-canary`, with a terminal `Postmerge CI results` job - centralize V8 relevance detection in `v8_canary_changes.py`; unrelated PR and postmerge runs execute metadata only and skip the expensive build matrices - leave `v8-canary` outside the blocking gate and leave the external `cla` check independent ## Rollout A repository admin must replace the existing required GitHub Actions contexts with `CI required` in the main-branch ruleset. Retain `cla` as a separate required check. Until that change is coordinated, this PR cannot satisfy the old standalone check names. In-flight PRs will need to be rebased after this lands.
This commit is contained in:
committed by
GitHub
Unverified
parent
6509f3148a
commit
1168254bd9
@@ -0,0 +1,34 @@
|
||||
#!/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"])
|
||||
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()
|
||||
@@ -1,12 +1,44 @@
|
||||
#!/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
|
||||
from fnmatch import fnmatchcase
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
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/**",
|
||||
".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 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",
|
||||
@@ -16,6 +48,30 @@ WINDOWS_SOURCE_BUILD_PATHS = {
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
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 whether the general V8 build matrix should run."""
|
||||
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(
|
||||
{
|
||||
@@ -36,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
|
||||
@@ -58,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",
|
||||
@@ -79,25 +138,44 @@ def parse_args() -> argparse.Namespace:
|
||||
def main() -> None:
|
||||
args = parse_args()
|
||||
if args.force:
|
||||
required = True
|
||||
reason = "manual workflow dispatch"
|
||||
# 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
|
||||
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__":
|
||||
|
||||
Reference in New Issue
Block a user