mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
ci(v8): gate Windows source builds on relevant changes (#27715)
Avoid rebuilding sandboxed Windows MSVC V8 artifacts for unrelated changes to `codex-rs/Cargo.toml`. The V8 canary now compares the resolved V8 version between the base and head commits and only runs the Windows source-build matrix when: - the resolved V8 crate version changes; - Windows artifact-production scripts or workflows change; or - the workflow is manually dispatched. The existing Bazel V8 matrix is unchanged. ## Why The Windows MSVC source builds take roughly two to three hours and currently run whenever any entry in the broad `v8-canary` path filter changes.
This commit is contained in:
committed by
GitHub
Unverified
parent
69b0f52b2a
commit
16c7c79540
@@ -0,0 +1,98 @@
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from v8_canary_changes import changed_files
|
||||
from v8_canary_changes import merge_base
|
||||
from v8_canary_changes import resolved_v8_version
|
||||
from v8_canary_changes import windows_source_required
|
||||
|
||||
|
||||
class V8CanaryChangesTest(unittest.TestCase):
|
||||
def test_resolved_v8_version(self) -> None:
|
||||
cargo_lock = b"""\
|
||||
[[package]]
|
||||
name = "other"
|
||||
version = "1.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "v8"
|
||||
version = "149.2.0"
|
||||
"""
|
||||
|
||||
self.assertEqual(resolved_v8_version(cargo_lock), "149.2.0")
|
||||
|
||||
def test_unrelated_cargo_manifest_change_does_not_require_source_build(
|
||||
self,
|
||||
) -> None:
|
||||
self.assertFalse(
|
||||
windows_source_required(
|
||||
{"codex-rs/Cargo.toml"},
|
||||
"149.2.0",
|
||||
"149.2.0",
|
||||
)
|
||||
)
|
||||
|
||||
def test_v8_version_change_requires_source_build(self) -> None:
|
||||
self.assertTrue(windows_source_required(set(), "149.2.0", "150.0.0"))
|
||||
|
||||
def test_module_helper_change_requires_source_build(self) -> None:
|
||||
self.assertTrue(
|
||||
windows_source_required(
|
||||
{".github/scripts/rusty_v8_module_bazel.py"},
|
||||
"149.2.0",
|
||||
"149.2.0",
|
||||
)
|
||||
)
|
||||
|
||||
def test_manual_dispatch_requires_source_build(self) -> None:
|
||||
self.assertTrue(
|
||||
windows_source_required(
|
||||
set(),
|
||||
"149.2.0",
|
||||
"149.2.0",
|
||||
force=True,
|
||||
)
|
||||
)
|
||||
|
||||
def test_changed_files_excludes_changes_made_only_on_base_branch(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
root = Path(temp_dir)
|
||||
self.run_git(root, "init", "--initial-branch=main")
|
||||
self.run_git(root, "config", "user.name", "Test User")
|
||||
self.run_git(root, "config", "user.email", "test@example.com")
|
||||
|
||||
self.write_and_commit(root, "initial", "initial.txt")
|
||||
common = self.run_git(root, "rev-parse", "HEAD")
|
||||
self.run_git(root, "switch", "-c", "feature")
|
||||
self.run_git(root, "switch", "main")
|
||||
self.write_and_commit(root, "base-only", "base-only.txt")
|
||||
base = self.run_git(root, "rev-parse", "HEAD")
|
||||
|
||||
self.run_git(root, "switch", "feature")
|
||||
self.write_and_commit(root, "feature-only", "feature-only.txt")
|
||||
head = self.run_git(root, "rev-parse", "HEAD")
|
||||
|
||||
self.assertEqual(
|
||||
changed_files(base, head, root=root),
|
||||
{"feature-only.txt"},
|
||||
)
|
||||
self.assertEqual(merge_base(base, head, root=root), common)
|
||||
|
||||
def write_and_commit(self, root: Path, contents: str, path: str) -> None:
|
||||
(root / path).write_text(contents)
|
||||
self.run_git(root, "add", path)
|
||||
self.run_git(root, "commit", "-m", contents)
|
||||
|
||||
def run_git(self, root: Path, *args: str) -> str:
|
||||
return subprocess.check_output(
|
||||
["git", *args],
|
||||
cwd=root,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
).strip()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import subprocess
|
||||
import tomllib
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
WINDOWS_SOURCE_BUILD_PATHS = {
|
||||
".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",
|
||||
}
|
||||
|
||||
|
||||
def resolved_v8_version(cargo_lock: bytes) -> str:
|
||||
versions = sorted(
|
||||
{
|
||||
package["version"]
|
||||
for package in tomllib.loads(cargo_lock.decode())["package"]
|
||||
if package["name"] == "v8"
|
||||
}
|
||||
)
|
||||
if len(versions) != 1:
|
||||
raise ValueError(f"expected exactly one resolved v8 version, found: {versions}")
|
||||
return versions[0]
|
||||
|
||||
|
||||
def windows_source_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 not changed_files.isdisjoint(WINDOWS_SOURCE_BUILD_PATHS)
|
||||
)
|
||||
|
||||
|
||||
def git_output(*args: str, root: Path = ROOT) -> bytes:
|
||||
return subprocess.check_output(["git", *args], cwd=root)
|
||||
|
||||
|
||||
def v8_version_at_revision(revision: str, *, root: Path = ROOT) -> str:
|
||||
return resolved_v8_version(
|
||||
git_output("show", f"{revision}:codex-rs/Cargo.lock", root=root)
|
||||
)
|
||||
|
||||
|
||||
def merge_base(base: str, head: str, *, root: Path = ROOT) -> str:
|
||||
return git_output("merge-base", base, head, root=root).decode().strip()
|
||||
|
||||
|
||||
def changed_files(base: str, head: str, *, root: Path = ROOT) -> set[str]:
|
||||
output = git_output(
|
||||
"diff",
|
||||
"--name-only",
|
||||
"--no-renames",
|
||||
f"{base}...{head}",
|
||||
root=root,
|
||||
)
|
||||
return set(output.decode().splitlines())
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--base")
|
||||
parser.add_argument("--head")
|
||||
parser.add_argument("--force", action="store_true")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = parse_args()
|
||||
if args.force:
|
||||
required = True
|
||||
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)
|
||||
if base_version != head_version:
|
||||
reason = f"v8 version changed from {base_version} to {head_version}"
|
||||
else:
|
||||
matched_paths = sorted(files & WINDOWS_SOURCE_BUILD_PATHS)
|
||||
reason = (
|
||||
", ".join(matched_paths) if matched_paths else "no relevant changes"
|
||||
)
|
||||
|
||||
print(f"windows_source_required={str(required).lower()}")
|
||||
print(f"windows_source_reason={reason}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user