mirror of
https://github.com/openai/codex.git
synced 2026-08-23 13:09:46 +00:00
## Why Windows Cargo and Bazel jobs spend significant time in filesystem-heavy build and cache directories. Route those directories through one CI build root so Windows can use its Dev Drive and Unix can use a stable cache root. ## What - Have `setup-ci` define `CI_BUILD_ROOT`, `CARGO_TARGET_DIR`, Bazel cache/output paths, and temp paths. - Require Windows to find or provision a verified Dev Drive instead of falling back to `C:`. - Pass the shared Bazel output base to `setup-bazel` so its explicit `output_base` does not defeat Dev Drive routing. - Point nextest, release, and V8 source-build paths at the shared environment contract. ## Benchmark results One-off cold-cache WPR/ETW traces show the explicit Bazel output-base routing removes the dominant `C:` traffic: | sample | `C:\_bazel` | summed `C:` traffic | traced test step | |---|---:|---:|---:| | shard 1 before | 62.2 GiB | 85.2 GiB | 16m22s | | shard 1 updated | 0 | 16.5 GiB | 12m05s | | shard 3 before | 67.2 GiB | 84.6 GiB | 16m48s | | shard 3 updated | 0 | 13.5 GiB | 11m08s | For a cold x64 V8 source build, the retained build-tail sample showed `D:\cargo-target` at ~1.29 GiB while measured `C:` roots totaled ~0.45 GiB (`C:\Users` ~0.33 GiB, `C:\Program Files` ~0.06 GiB, `C:\Windows` ~0.03 GiB). The full cold build took 2h20m36s. The Bazel timing improvement is directional because both refreshed shards failed tests. The V8 trace is a bounded build-tail sample, not the full build. All final samples had zero lost ETW events; VHDX traffic was excluded from the optimization ranking. Runs: [baseline Bazel](https://github.com/openai/codex/actions/runs/28911908527), [updated Bazel](https://github.com/openai/codex/actions/runs/28917133701), [V8 build tail](https://github.com/openai/codex/actions/runs/28933626678). ## Manual validation - Ran `just fmt`. - Ran `just test-github-scripts` (35 tests). - Parsed GitHub Actions YAML with `yq`. - Ran `git diff --check`. ## Stack - [#31332](https://github.com/openai/codex/pull/31332) — parameterize Cargo target paths - [#31356](https://github.com/openai/codex/pull/31356) — Windows 2025 runner bump - [#31357](https://github.com/openai/codex/pull/31357) — Dev Drive I/O routing
218 lines
7.4 KiB
Python
Executable File
218 lines
7.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
from collections.abc import Mapping
|
|
from collections.abc import Sequence
|
|
from pathlib import Path
|
|
|
|
|
|
OPENAI_REPOSITORY = "openai/codex"
|
|
# Remote configurations select cache/BES/download endpoints. Their -rbe forms
|
|
# also select the matching remote executor endpoint.
|
|
GENERIC_REMOTE_CONFIG = "buildbuddy-generic"
|
|
OPENAI_REMOTE_CONFIG = "buildbuddy-openai"
|
|
# These CI configurations require remote build execution. The wrapper supplies
|
|
# an RBE configuration, which also includes the common `remote` settings.
|
|
REMOTE_EXECUTION_CONFIGS = {
|
|
"--config=ci-linux",
|
|
"--config=ci-macos",
|
|
"--config=ci-v8",
|
|
"--config=ci-windows-cross",
|
|
}
|
|
# Honor either explicit setting so the wrapper never overrides the caller's
|
|
# choice when it supplies the CI default below.
|
|
REMOTE_REPO_CONTENTS_CACHE_STARTUP_OPTIONS = {
|
|
"--experimental_remote_repo_contents_cache",
|
|
"--noexperimental_remote_repo_contents_cache",
|
|
}
|
|
|
|
|
|
def startup_args(args: Sequence[str], env: Mapping[str, str]) -> list[str]:
|
|
"""Return shared startup options that are missing from a Bazel invocation.
|
|
|
|
Bazel startup options must precede the command, and changing them restarts
|
|
the server and discards its analysis cache. GitHub Actions invokes Bazel
|
|
through several helpers, so normalize their startup options here while
|
|
preserving any explicit choice made by the caller.
|
|
"""
|
|
command_idx = next(
|
|
(idx for idx, arg in enumerate(args) if not arg.startswith("-")),
|
|
len(args),
|
|
)
|
|
configured_startup_args = args[:command_idx]
|
|
injected_args = []
|
|
|
|
output_user_root = env.get("BAZEL_OUTPUT_USER_ROOT")
|
|
if output_user_root and not any(
|
|
arg.startswith("--output_user_root=") for arg in configured_startup_args
|
|
):
|
|
injected_args.append(f"--output_user_root={output_user_root}")
|
|
|
|
if env.get("GITHUB_ACTIONS") == "true" and not any(
|
|
arg in REMOTE_REPO_CONTENTS_CACHE_STARTUP_OPTIONS
|
|
for arg in configured_startup_args
|
|
):
|
|
# Work around Bazel 9 overlay materialization failures seen in CI. This
|
|
# disables only the startup-level repo contents cache; keyed runs still
|
|
# use BuildBuddy.
|
|
injected_args.append("--noexperimental_remote_repo_contents_cache")
|
|
|
|
return injected_args
|
|
|
|
|
|
# Only authenticated workflow runs executing trusted upstream code may use the
|
|
# OpenAI BuildBuddy host. A pull request event without proof that its head is
|
|
# in the upstream repository fails closed to the generic host.
|
|
def is_trusted_upstream_run(env: Mapping[str, str]) -> bool:
|
|
# `GITHUB_REPOSITORY` is easy to set locally. Requiring GitHub's workflow
|
|
# marker prevents a local command from opting itself into the OpenAI host.
|
|
if (
|
|
env.get("GITHUB_ACTIONS") != "true"
|
|
or env.get("GITHUB_REPOSITORY") != OPENAI_REPOSITORY
|
|
):
|
|
return False
|
|
# Non-PR workflow runs in `openai/codex` execute upstream refs, so they are
|
|
# trusted. Fork code reaches these workflows only through pull requests.
|
|
if env.get("GITHUB_EVENT_NAME") != "pull_request":
|
|
return True
|
|
|
|
event_path = env.get("GITHUB_EVENT_PATH")
|
|
if not event_path:
|
|
return False
|
|
try:
|
|
event = json.loads(Path(event_path).read_text(encoding="utf-8"))
|
|
except (OSError, json.JSONDecodeError):
|
|
return False
|
|
|
|
try:
|
|
return event["pull_request"]["head"]["repo"]["fork"] is False
|
|
except (KeyError, TypeError):
|
|
return False
|
|
|
|
|
|
def uses_openai_host(env: Mapping[str, str]) -> bool:
|
|
return bool(env.get("BUILDBUDDY_API_KEY")) and is_trusted_upstream_run(env)
|
|
|
|
|
|
def uses_remote_execution(args: Sequence[str]) -> bool:
|
|
try:
|
|
separator_idx = args.index("--")
|
|
except ValueError:
|
|
separator_idx = len(args)
|
|
return any(arg in REMOTE_EXECUTION_CONFIGS for arg in args[:separator_idx])
|
|
|
|
|
|
def remote_config(args: Sequence[str], env: Mapping[str, str]) -> str | None:
|
|
if not env.get("BUILDBUDDY_API_KEY"):
|
|
return None
|
|
|
|
config = OPENAI_REMOTE_CONFIG if uses_openai_host(env) else GENERIC_REMOTE_CONFIG
|
|
if uses_remote_execution(args):
|
|
config += "-rbe"
|
|
return config
|
|
|
|
|
|
def bazel_args_without_remote_execution(args: Sequence[str]) -> list[str]:
|
|
# Remote CI configs require BuildBuddy credentials. Removing them preserves
|
|
# the local fallback used for fork pull requests.
|
|
try:
|
|
separator_idx = args.index("--")
|
|
except ValueError:
|
|
separator_idx = len(args)
|
|
return [
|
|
*(arg for arg in args[:separator_idx] if arg not in REMOTE_EXECUTION_CONFIGS),
|
|
*args[separator_idx:],
|
|
]
|
|
|
|
|
|
def bazel_args_with_remote_config(
|
|
args: Sequence[str], env: Mapping[str, str]
|
|
) -> list[str]:
|
|
command_idx = next(
|
|
(idx for idx, arg in enumerate(args) if not arg.startswith("-")),
|
|
None,
|
|
)
|
|
if command_idx is None:
|
|
raise ValueError("expected a Bazel command")
|
|
|
|
config = remote_config(args, env)
|
|
if config is None:
|
|
configured_args = bazel_args_without_remote_execution(args)
|
|
else:
|
|
# `remote_config()` returns a configuration only when this key is present.
|
|
api_key = env["BUILDBUDDY_API_KEY"]
|
|
remote_args = [
|
|
f"--config={config}",
|
|
f"--remote_header=x-buildbuddy-api-key={api_key}",
|
|
]
|
|
|
|
# Insert immediately after the Bazel command. This keeps wrapper-added
|
|
# options out of positional payloads and lets later CI configs override
|
|
# shared RBE defaults such as the Windows cross-compilation exec platforms.
|
|
configured_args = [
|
|
*args[: command_idx + 1],
|
|
*remote_args,
|
|
*args[command_idx + 1 :],
|
|
]
|
|
|
|
try:
|
|
separator_idx = configured_args.index("--")
|
|
except ValueError:
|
|
separator_idx = len(configured_args)
|
|
|
|
cache_args = [
|
|
f"{option_prefix}{env[env_name]}"
|
|
for env_name, option_prefix in (
|
|
("BAZEL_REPO_CONTENTS_CACHE", "--repo_contents_cache="),
|
|
("BAZEL_REPOSITORY_CACHE", "--repository_cache="),
|
|
)
|
|
if env.get(env_name)
|
|
and not any(
|
|
arg.startswith(option_prefix) for arg in configured_args[:separator_idx]
|
|
)
|
|
]
|
|
return [
|
|
*configured_args[:separator_idx],
|
|
*cache_args,
|
|
*configured_args[separator_idx:],
|
|
]
|
|
|
|
|
|
def bazel_command(*args: str, env: Mapping[str, str] | None = None) -> list[str]:
|
|
env = os.environ if env is None else env
|
|
bazel = env.get("CODEX_BAZEL_BIN", "bazel")
|
|
return [bazel, *startup_args(args, env), *bazel_args_with_remote_config(args, env)]
|
|
|
|
|
|
def main() -> None:
|
|
config = remote_config(sys.argv[1:], os.environ)
|
|
if config is None:
|
|
print(
|
|
"BuildBuddy key unavailable; using local Bazel configuration.",
|
|
file=sys.stderr,
|
|
)
|
|
else:
|
|
host_description = (
|
|
"OpenAI tenant" if uses_openai_host(os.environ) else "generic"
|
|
)
|
|
print(
|
|
f"Using {host_description} BuildBuddy configuration: {config}.",
|
|
file=sys.stderr,
|
|
)
|
|
|
|
command = bazel_command(*sys.argv[1:])
|
|
if os.name == "nt":
|
|
# Windows CRT exec can split arguments containing spaces and lose the
|
|
# eventual child exit status. Wait for Bazel and propagate its status.
|
|
result = subprocess.run(command, check=False)
|
|
raise SystemExit(result.returncode)
|
|
|
|
os.execvp(command[0], command)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|