diff --git a/.bazelrc b/.bazelrc index e39a3aff2..6357eb83e 100644 --- a/.bazelrc +++ b/.bazelrc @@ -38,50 +38,24 @@ common:windows --test_env=WINDIR common --test_env=RUST_MIN_STACK=8388608 # 8 MiB common --test_output=errors -common --nobuild_runfile_links -# These settings tune BuildBuddy/RBE behavior but do not contact a remote -# service unless a `buildbuddy-*` configuration below supplies an endpoint. +common --bes_results_url=https://app.buildbuddy.io/invocation/ +common --bes_backend=grpcs://remote.buildbuddy.io +common --remote_cache=grpcs://remote.buildbuddy.io common --remote_download_toplevel +common --nobuild_runfile_links common --remote_timeout=3600 common --noexperimental_throttle_remote_action_building common --experimental_remote_execution_keepalive common --grpc_keepalive_time=30s - -# Opt-in remote configurations selected by -# `.github/scripts/run_bazel_with_buildbuddy.py`. Plain Bazel commands do not -# contact BuildBuddy unless a user selects one of these configurations. -# Use the generic host for cache, BES, and downloads without remote execution. -common:buildbuddy-generic --bes_backend=grpcs://remote.buildbuddy.io -common:buildbuddy-generic --bes_results_url=https://app.buildbuddy.io/invocation/ -common:buildbuddy-generic --remote_cache=grpcs://remote.buildbuddy.io -common:buildbuddy-generic --experimental_remote_downloader=grpcs://remote.buildbuddy.io - -# Add remote execution on the generic host. -common:buildbuddy-generic-rbe --config=buildbuddy-generic -common:buildbuddy-generic-rbe --config=remote -common:buildbuddy-generic-rbe --remote_executor=grpcs://remote.buildbuddy.io - -# Use the OpenAI tenant for cache, BES, and downloads without remote execution. -common:buildbuddy-openai --bes_backend=grpcs://openai.buildbuddy.io -common:buildbuddy-openai --bes_results_url=https://openai.buildbuddy.io/invocation/ -common:buildbuddy-openai --remote_cache=grpcs://openai.buildbuddy.io -common:buildbuddy-openai --experimental_remote_downloader=grpcs://openai.buildbuddy.io - -# Add remote execution on the OpenAI tenant. -common:buildbuddy-openai-rbe --config=buildbuddy-openai -common:buildbuddy-openai-rbe --config=remote -common:buildbuddy-openai-rbe --remote_executor=grpcs://openai.buildbuddy.io +common --experimental_remote_downloader=grpcs://remote.buildbuddy.io # This limits both in-flight executions and concurrent downloads. Even with high number # of jobs execution will still be limited by CPU cores, so this just pays a bit of # memory in exchange for higher download concurrency. common --jobs=30 -# Shared remote execution policy. The endpoint-bearing `buildbuddy-*-rbe` -# configurations include this group; CI configs override TestRunner below -# when tests must remain local on their runner. -common:remote --strategy=remote common:remote --extra_execution_platforms=//:rbe +common:remote --remote_executor=grpcs://remote.buildbuddy.io common:remote --jobs=800 # TODO(team): Evaluate if this actually helps, zbarsky is not sure, everything seems bottlenecked on `core` either way. # Enable pipelined compilation since we are not bound by local CPU count. @@ -172,11 +146,15 @@ common:ci-windows --repo_contents_cache=D:/a/.cache/bazel-repo-contents-cache # Linux crossbuilds don't work until we untangle the libc constraint mess. common:ci-linux --config=ci-bazel common:ci-linux --build_metadata=TAG_os=linux +common:ci-linux --config=remote +common:ci-linux --strategy=remote common:ci-linux --platforms=//:rbe # On mac, we can run all the build actions remotely but test actions locally. common:ci-macos --config=ci-bazel common:ci-macos --build_metadata=TAG_os=macos +common:ci-macos --config=remote +common:ci-macos --strategy=remote common:ci-macos --strategy=TestRunner=darwin-sandbox,local # On Windows, use Linux remote execution for build actions but keep test actions @@ -184,7 +162,9 @@ common:ci-macos --strategy=TestRunner=darwin-sandbox,local # still run against Windows binaries. common:ci-windows-cross --config=ci-windows common:ci-windows-cross --build_metadata=TAG_windows_cross_compile=true +common:ci-windows-cross --config=remote common:ci-windows-cross --host_platform=//:rbe +common:ci-windows-cross --strategy=remote common:ci-windows-cross --strategy=TestRunner=local common:ci-windows-cross --local_test_jobs=4 common:ci-windows-cross --test_env=RUST_TEST_THREADS=1 @@ -200,6 +180,8 @@ common:ci-windows-cross --extra_toolchains=//:windows_gnullvm_tests_on_msvc_host common:ci-v8 --config=ci common:ci-v8 --build_metadata=TAG_workflow=v8 common:ci-v8 --build_metadata=TAG_os=linux +common:ci-v8 --config=remote +common:ci-v8 --strategy=remote # Source-built Bazel V8 artifacts use the in-process sandbox by default. This # does not affect Cargo's default prebuilt rusty_v8 path. diff --git a/.github/scripts/run-bazel-ci.sh b/.github/scripts/run-bazel-ci.sh index 89f937a99..f98e4d8cb 100755 --- a/.github/scripts/run-bazel-ci.sh +++ b/.github/scripts/run-bazel-ci.sh @@ -53,20 +53,11 @@ fi run_bazel() { if [[ "${RUNNER_OS:-}" == "Windows" ]]; then - MSYS2_ARG_CONV_EXCL='*' "$(dirname "${BASH_SOURCE[0]}")/run_bazel_with_buildbuddy.py" "$@" + MSYS2_ARG_CONV_EXCL='*' bazel "$@" return fi - "$(dirname "${BASH_SOURCE[0]}")/run_bazel_with_buildbuddy.py" "$@" -} - -run_bazel_with_startup_args() { - if (( ${#bazel_startup_args[@]} > 0 )); then - run_bazel "${bazel_startup_args[@]}" "$@" - return - fi - - run_bazel "$@" + bazel "$@" } ci_config=ci-linux @@ -86,16 +77,23 @@ esac print_bazel_test_log_tails() { local console_log="$1" local testlogs_dir - + local -a bazel_info_cmd=(bazel) local -a bazel_info_args=(info) - if [[ -n "${BUILDBUDDY_API_KEY:-}" ]]; then - # `bazel info` needs the same CI config as the failed test invocation so - # platform-specific output roots match. On Windows, omitting `ci-windows` - # would point at `local_windows-fastbuild` even when the test ran with the - # MSVC host platform under `local_windows_msvc-fastbuild`. - bazel_info_args+=("--config=${ci_config}") + + if (( ${#bazel_startup_args[@]} > 0 )); then + bazel_info_cmd+=("${bazel_startup_args[@]}") fi + # `bazel info` needs the same CI config as the failed test invocation so + # platform-specific output roots match. On Windows, omitting `ci-windows` + # would point at `local_windows-fastbuild` even when the test ran with the + # MSVC host platform under `local_windows_msvc-fastbuild`. + if [[ -n "${BUILDBUDDY_API_KEY:-}" ]]; then + bazel_info_args+=( + "--config=${ci_config}" + "--remote_header=x-buildbuddy-api-key=${BUILDBUDDY_API_KEY}" + ) + fi # Only pass flags that affect Bazel's output-root selection or repository # lookup. Test/build-only flags such as execution logs or remote download # mode can make `bazel info` fail, which would hide the real test log path. @@ -107,7 +105,7 @@ print_bazel_test_log_tails() { esac done - testlogs_dir="$(run_bazel_with_startup_args \ + testlogs_dir="$(run_bazel "${bazel_info_cmd[@]:1}" \ --noexperimental_remote_repo_contents_cache \ "${bazel_info_args[@]}" \ bazel-testlogs 2>/dev/null || echo bazel-testlogs)" @@ -256,9 +254,8 @@ if [[ ${#bazel_args[@]} -eq 0 || ${#bazel_targets[@]} -eq 0 ]]; then fi if [[ "${RUNNER_OS:-}" == "Windows" && $windows_cross_compile -eq 1 && -z "${BUILDBUDDY_API_KEY:-}" ]]; then - # Windows cross-compilation depends on authenticated RBE. Preserve the local - # Windows build shape when credentials are unavailable. - ci_config=ci-windows + # Fork PRs do not receive the BuildBuddy secret needed for the remote + # cross-compile config. Preserve the previous local Windows build shape. windows_msvc_host_platform=1 fi @@ -300,9 +297,9 @@ if [[ "${RUNNER_OS:-}" == "Windows" && $windows_cross_compile -eq 1 && -n "${BUI fi if [[ "${RUNNER_OS:-}" == "Windows" && $windows_cross_compile -eq 1 && -z "${BUILDBUDDY_API_KEY:-}" ]]; then - # The Windows cross-compile config depends on authenticated remote - # execution. When credentials are unavailable, keep the local build shape - # and its lower concurrency cap. + # The Windows cross-compile config depends on remote execution. Fork PRs do + # not receive the BuildBuddy secret, so fall back to the existing local build + # shape and keep its lower concurrency cap. post_config_bazel_args+=(--jobs=8) fi @@ -380,31 +377,70 @@ fi bazel_console_log="$(mktemp)" trap 'rm -f "$bazel_console_log"' EXIT -bazel_run_args=( - "${bazel_args[@]}" -) +bazel_cmd=(bazel) +if (( ${#bazel_startup_args[@]} > 0 )); then + bazel_cmd+=("${bazel_startup_args[@]}") +fi + if [[ -n "${BUILDBUDDY_API_KEY:-}" ]]; then echo "BuildBuddy API key is available; using remote Bazel configuration." - bazel_run_args+=("--config=${ci_config}") + # Work around Bazel 9 remote repo contents cache / overlay materialization failures + # seen in CI (for example "is not a symlink" or permission errors while + # materializing external repos such as rules_perl). We still use BuildBuddy for + # remote execution/cache; this only disables the startup-level repo contents cache. + bazel_run_args=( + "${bazel_args[@]}" + "--config=${ci_config}" + "--remote_header=x-buildbuddy-api-key=${BUILDBUDDY_API_KEY}" + ) + if (( ${#post_config_bazel_args[@]} > 0 )); then + bazel_run_args+=("${post_config_bazel_args[@]}") + fi + set +e + run_bazel "${bazel_cmd[@]:1}" \ + --noexperimental_remote_repo_contents_cache \ + "${bazel_run_args[@]}" \ + -- \ + "${bazel_targets[@]}" \ + 2>&1 | tee "$bazel_console_log" + bazel_status=${PIPESTATUS[0]} + set -e else echo "BuildBuddy API key is not available; using local Bazel configuration." + # Keep fork/community PRs on Bazel but disable remote services that are + # configured in .bazelrc and require auth. + # + # Flag docs: + # - Command-line reference: https://bazel.build/reference/command-line-reference + # - Remote caching overview: https://bazel.build/remote/caching + # - Remote execution overview: https://bazel.build/remote/rbe + # - Build Event Protocol overview: https://bazel.build/remote/bep + # + # --noexperimental_remote_repo_contents_cache: + # disable remote repo contents cache enabled in .bazelrc startup options. + # https://bazel.build/reference/command-line-reference#startup_options-flag--experimental_remote_repo_contents_cache + # --remote_cache= and --remote_executor=: + # clear remote cache/execution endpoints configured in .bazelrc. + # https://bazel.build/reference/command-line-reference#common_options-flag--remote_cache + # https://bazel.build/reference/command-line-reference#common_options-flag--remote_executor + bazel_run_args=( + "${bazel_args[@]}" + --remote_cache= + --remote_executor= + ) + if (( ${#post_config_bazel_args[@]} > 0 )); then + bazel_run_args+=("${post_config_bazel_args[@]}") + fi + set +e + run_bazel "${bazel_cmd[@]:1}" \ + --noexperimental_remote_repo_contents_cache \ + "${bazel_run_args[@]}" \ + -- \ + "${bazel_targets[@]}" \ + 2>&1 | tee "$bazel_console_log" + bazel_status=${PIPESTATUS[0]} + set -e fi -if (( ${#post_config_bazel_args[@]} > 0 )); then - bazel_run_args+=("${post_config_bazel_args[@]}") -fi -set +e -# Work around Bazel 9 remote repo contents cache / overlay materialization -# failures seen in CI (for example "is not a symlink" or permission errors -# while materializing external repos such as rules_perl). This only disables -# the startup-level repo contents cache; keyed runs still use BuildBuddy. -run_bazel_with_startup_args \ - --noexperimental_remote_repo_contents_cache \ - "${bazel_run_args[@]}" \ - -- \ - "${bazel_targets[@]}" \ - 2>&1 | tee "$bazel_console_log" -bazel_status=${PIPESTATUS[0]} -set -e if [[ ${bazel_status:-0} -ne 0 ]]; then if [[ $print_failed_bazel_action_summary -eq 1 ]]; then diff --git a/.github/scripts/run-bazel-query-ci.sh b/.github/scripts/run-bazel-query-ci.sh index f5d4f56f4..dd03b6716 100755 --- a/.github/scripts/run-bazel-query-ci.sh +++ b/.github/scripts/run-bazel-query-ci.sh @@ -2,17 +2,48 @@ set -euo pipefail -# Run target-discovery queries with the same startup settings as the main -# build/test invocation so they can reuse the same Bazel server. Queries only -# enumerate labels, so they intentionally do not select CI or remote configs. +# Run Bazel queries with the same CI startup settings as the main build/test +# invocation so target-discovery queries can reuse the same Bazel server. -if [[ $# -lt 2 || "${@: -2:1}" != "--" ]]; then - echo "Usage: $0 [...] -- " >&2 +query_args=() +windows_cross_compile=0 +while [[ $# -gt 0 ]]; do + case "$1" in + --windows-cross-compile) + windows_cross_compile=1 + shift + ;; + --) + shift + break + ;; + *) + query_args+=("$1") + shift + ;; + esac +done + +if [[ $# -ne 1 ]]; then + echo "Usage: $0 [--windows-cross-compile] [...] -- " >&2 exit 1 fi -query_args=("${@:1:$#-2}") -query_expression="${@: -1}" +query_expression="$1" + +ci_config=ci-linux +case "${RUNNER_OS:-}" in + macOS) + ci_config=ci-macos + ;; + Windows) + if [[ $windows_cross_compile -eq 1 ]]; then + ci_config=ci-windows-cross + else + ci_config=ci-windows + fi + ;; +esac bazel_startup_args=() if [[ -n "${BAZEL_OUTPUT_USER_ROOT:-}" ]]; then @@ -29,6 +60,12 @@ run_bazel() { } bazel_query_args=(--noexperimental_remote_repo_contents_cache query) +if [[ -n "${BUILDBUDDY_API_KEY:-}" ]]; then + bazel_query_args+=( + "--config=${ci_config}" + "--remote_header=x-buildbuddy-api-key=${BUILDBUDDY_API_KEY}" + ) +fi if [[ -n "${BAZEL_REPO_CONTENTS_CACHE:-}" ]]; then bazel_query_args+=("--repo_contents_cache=${BAZEL_REPO_CONTENTS_CACHE}") @@ -38,10 +75,7 @@ if [[ -n "${BAZEL_REPOSITORY_CACHE:-}" ]]; then bazel_query_args+=("--repository_cache=${BAZEL_REPOSITORY_CACHE}") fi -if (( ${#query_args[@]} > 0 )); then - bazel_query_args+=("${query_args[@]}") -fi -bazel_query_args+=("$query_expression") +bazel_query_args+=("${query_args[@]}" "$query_expression") if (( ${#bazel_startup_args[@]} > 0 )); then run_bazel "${bazel_startup_args[@]}" "${bazel_query_args[@]}" diff --git a/.github/scripts/run_bazel_with_buildbuddy.py b/.github/scripts/run_bazel_with_buildbuddy.py deleted file mode 100755 index add95a99e..000000000 --- a/.github/scripts/run_bazel_with_buildbuddy.py +++ /dev/null @@ -1,142 +0,0 @@ -#!/usr/bin/env python3 - -import json -import os -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", -} -# 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]: - config = remote_config(args, env) - if config is None: - return bazel_args_without_remote_execution(args) - - # `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. - insertion_idx = next( - (idx + 1 for idx, arg in enumerate(args) if not arg.startswith("-")), - len(args), - ) - return [*args[:insertion_idx], *remote_args, *args[insertion_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, *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:]) - # Replace the wrapper so Bazel receives signals directly and supplies the - # command exit status; a subprocess parent would have no remaining work. - os.execvp(command[0], command) - - -if __name__ == "__main__": - main() diff --git a/.github/scripts/rusty_v8_bazel.py b/.github/scripts/rusty_v8_bazel.py index 329d3f6c5..2f46daf45 100644 --- a/.github/scripts/rusty_v8_bazel.py +++ b/.github/scripts/rusty_v8_bazel.py @@ -5,6 +5,7 @@ from __future__ import annotations import argparse import gzip import hashlib +import os import re import shutil import subprocess @@ -12,7 +13,6 @@ import sys import tomllib from pathlib import Path -from run_bazel_with_buildbuddy import bazel_command from rusty_v8_module_bazel import ( RustyV8ChecksumError, check_module_bazel, @@ -29,22 +29,33 @@ SANDBOX_ARTIFACT_PROFILE = "ptrcomp_sandbox_release" ARTIFACT_BAZEL_CONFIGS = ["rusty-v8-upstream-libcxx"] +def bazel_remote_args() -> list[str]: + buildbuddy_api_key = os.environ.get("BUILDBUDDY_API_KEY") + if not buildbuddy_api_key: + return [] + return [f"--remote_header=x-buildbuddy-api-key={buildbuddy_api_key}"] + + def bazel_execroot() -> Path: - output = subprocess.check_output( - bazel_command("info", "execution_root"), + result = subprocess.run( + ["bazel", "info", "execution_root"], cwd=ROOT, + check=True, + capture_output=True, text=True, ) - return Path(output.strip()) + return Path(result.stdout.strip()) def bazel_output_base() -> Path: - output = subprocess.check_output( - bazel_command("info", "output_base"), + result = subprocess.run( + ["bazel", "info", "output_base"], cwd=ROOT, + check=True, + capture_output=True, text=True, ) - return Path(output.strip()) + return Path(result.stdout.strip()) def bazel_output_path(path: str) -> Path: @@ -61,22 +72,24 @@ def bazel_output_files( ) -> list[Path]: expression = "set(" + " ".join(labels) + ")" bazel_configs = bazel_configs or [] - output = subprocess.check_output( - bazel_command( + result = subprocess.run( + [ + "bazel", "cquery", "-c", compilation_mode, f"--platforms=@llvm//platforms:{platform}", *[f"--config={config}" for config in bazel_configs], + *bazel_remote_args(), "--output=files", expression, - ), + ], cwd=ROOT, + check=True, + capture_output=True, text=True, ) - return [ - bazel_output_path(line.strip()) for line in output.splitlines() if line.strip() - ] + return [bazel_output_path(line.strip()) for line in result.stdout.splitlines() if line.strip()] def bazel_build( @@ -89,15 +102,17 @@ def bazel_build( bazel_configs = bazel_configs or [] download_args = ["--remote_download_toplevel"] if download_toplevel else [] subprocess.run( - bazel_command( + [ + "bazel", "build", "-c", compilation_mode, f"--platforms=@llvm//platforms:{platform}", *[f"--config={config}" for config in bazel_configs], + *bazel_remote_args(), *download_args, *labels, - ), + ], cwd=ROOT, check=True, ) @@ -157,7 +172,7 @@ def resolved_v8_crate_version() -> str: matches = sorted( set( re.findall( - r"https://static\.crates\.io/crates/v8/v8-([0-9]+\.[0-9]+\.[0-9]+)\.crate", + r'https://static\.crates\.io/crates/v8/v8-([0-9]+\.[0-9]+\.[0-9]+)\.crate', module_bazel, ) ) @@ -219,17 +234,13 @@ def stage_artifacts( output_dir: Path, sandbox: bool, ) -> None: - missing_paths = [ - str(path) for path in [lib_path, binding_path] if not path.exists() - ] + missing_paths = [str(path) for path in [lib_path, binding_path] if not path.exists()] if missing_paths: raise SystemExit(f"missing release outputs for {target}: {missing_paths}") output_dir.mkdir(parents=True, exist_ok=True) artifact_profile = SANDBOX_ARTIFACT_PROFILE if sandbox else RELEASE_ARTIFACT_PROFILE - staged_library = output_dir / staged_archive_name( - target, lib_path, artifact_profile - ) + staged_library = output_dir / staged_archive_name(target, lib_path, artifact_profile) staged_binding = output_dir / staged_binding_name(target, artifact_profile) with lib_path.open("rb") as src, staged_library.open("wb") as dst: @@ -259,9 +270,7 @@ def stage_artifacts( def upstream_release_pair_paths(source_root: Path, target: str) -> tuple[Path, Path]: - lib_name = ( - "rusty_v8.lib" if target.endswith("-pc-windows-msvc") else "librusty_v8.a" - ) + lib_name = "rusty_v8.lib" if target.endswith("-pc-windows-msvc") else "librusty_v8.a" gn_out = source_root / "target" / target / "release" / "gn_out" return gn_out / "obj" / lib_name, gn_out / "src_binding.rs" @@ -329,9 +338,7 @@ def parse_args() -> argparse.Namespace: stage_upstream_release_pair_parser = subparsers.add_parser( "stage-upstream-release-pair" ) - stage_upstream_release_pair_parser.add_argument( - "--source-root", type=Path, required=True - ) + stage_upstream_release_pair_parser.add_argument("--source-root", type=Path, required=True) stage_upstream_release_pair_parser.add_argument("--target", required=True) stage_upstream_release_pair_parser.add_argument("--output-dir", required=True) stage_upstream_release_pair_parser.add_argument("--sandbox", action="store_true") diff --git a/.github/scripts/test_run_bazel_with_buildbuddy.py b/.github/scripts/test_run_bazel_with_buildbuddy.py deleted file mode 100644 index bab4ad5cc..000000000 --- a/.github/scripts/test_run_bazel_with_buildbuddy.py +++ /dev/null @@ -1,184 +0,0 @@ -#!/usr/bin/env python3 - -import json -import unittest -from pathlib import Path -from tempfile import TemporaryDirectory - -import run_bazel_with_buildbuddy - - -class RunBazelWithBuildBuddyTest(unittest.TestCase): - def github_env( - self, - temp_dir: str, - *, - repository: str = "openai/codex", - fork: bool = False, - event_name: str = "pull_request", - ) -> dict[str, str]: - event_path = Path(temp_dir) / "event.json" - event_path.write_text( - json.dumps({"pull_request": {"head": {"repo": {"fork": fork}}}}), - encoding="utf-8", - ) - return { - "BUILDBUDDY_API_KEY": "token", - "GITHUB_ACTIONS": "true", - "GITHUB_EVENT_NAME": event_name, - "GITHUB_EVENT_PATH": str(event_path), - "GITHUB_REPOSITORY": repository, - } - - def test_keyless_invocation_drops_remote_ci_configuration(self) -> None: - self.assertIsNone( - run_bazel_with_buildbuddy.remote_config( - ["build", "--config=ci-linux", "//codex-rs/cli:codex"], - {}, - ) - ) - self.assertEqual( - run_bazel_with_buildbuddy.bazel_args_with_remote_config( - ["build", "--config=ci-linux", "--", "//codex-rs/cli:codex"], - {}, - ), - ["build", "--", "//codex-rs/cli:codex"], - ) - - def test_program_arguments_after_separator_do_not_select_or_lose_rbe(self) -> None: - args = ["run", "//codex-rs/cli:codex", "--", "--config=remote"] - - self.assertEqual( - run_bazel_with_buildbuddy.bazel_args_with_remote_config(args, {}), - args, - ) - self.assertEqual( - run_bazel_with_buildbuddy.remote_config( - args, {"BUILDBUDDY_API_KEY": "fork-token"} - ), - "buildbuddy-generic", - ) - - def test_upstream_push_selects_openai_rbe_before_target_separator(self) -> None: - with TemporaryDirectory() as temp_dir: - env = self.github_env(temp_dir, event_name="push") - - self.assertEqual( - run_bazel_with_buildbuddy.bazel_args_with_remote_config( - ["build", "--config=ci-linux", "--", "//codex-rs/cli:codex"], - env, - ), - [ - "build", - "--config=buildbuddy-openai-rbe", - "--remote_header=x-buildbuddy-api-key=token", - "--config=ci-linux", - "--", - "//codex-rs/cli:codex", - ], - ) - - def test_windows_cross_ci_configuration_follows_remote_configuration(self) -> None: - env = {"BUILDBUDDY_API_KEY": "fork-token"} - - self.assertEqual( - run_bazel_with_buildbuddy.bazel_args_with_remote_config( - ["build", "--config=ci-windows-cross", "//codex-rs/cli:codex"], - env, - ), - [ - "build", - "--config=buildbuddy-generic-rbe", - "--remote_header=x-buildbuddy-api-key=fork-token", - "--config=ci-windows-cross", - "//codex-rs/cli:codex", - ], - ) - - def test_query_remote_configuration_is_inserted_before_expression(self) -> None: - expression = 'kind("rust_library rule", //codex-rs/...)' - env = {"BUILDBUDDY_API_KEY": "fork-token"} - - for command in ("query", "cquery", "aquery"): - with self.subTest(command=command): - self.assertEqual( - run_bazel_with_buildbuddy.bazel_args_with_remote_config( - [ - command, - "--config=ci-windows-cross", - "--output=label", - expression, - ], - env, - ), - [ - command, - "--config=buildbuddy-generic-rbe", - "--remote_header=x-buildbuddy-api-key=fork-token", - "--config=ci-windows-cross", - "--output=label", - expression, - ], - ) - - def test_same_repository_pull_request_selects_openai_host(self) -> None: - with TemporaryDirectory() as temp_dir: - self.assertEqual( - run_bazel_with_buildbuddy.remote_config( - ["build", "--config=ci-v8"], self.github_env(temp_dir) - ), - "buildbuddy-openai-rbe", - ) - - def test_fork_pull_request_cannot_select_openai_host(self) -> None: - with TemporaryDirectory() as temp_dir: - env = self.github_env(temp_dir, fork=True) - - self.assertEqual( - run_bazel_with_buildbuddy.remote_config( - ["build", "--config=ci-v8"], env - ), - "buildbuddy-generic-rbe", - ) - - def test_run_in_fork_repository_cannot_select_openai_host(self) -> None: - with TemporaryDirectory() as temp_dir: - env = self.github_env(temp_dir, repository="contributor/codex") - - self.assertEqual( - run_bazel_with_buildbuddy.remote_config( - ["build", "--config=ci-v8"], env - ), - "buildbuddy-generic-rbe", - ) - - def test_pull_request_without_readable_event_payload_fails_closed(self) -> None: - for event_path in (None, "missing-event.json"): - env = { - "BUILDBUDDY_API_KEY": "token", - "GITHUB_ACTIONS": "true", - "GITHUB_EVENT_NAME": "pull_request", - "GITHUB_REPOSITORY": "openai/codex", - } - if event_path is not None: - env["GITHUB_EVENT_PATH"] = event_path - - with self.subTest(event_path=event_path): - self.assertEqual( - run_bazel_with_buildbuddy.remote_config(["build"], env), - "buildbuddy-generic", - ) - - def test_bazel_command_uses_configured_binary_locally(self) -> None: - self.assertEqual( - run_bazel_with_buildbuddy.bazel_command( - "info", - "execution_root", - env={"CODEX_BAZEL_BIN": "fake-bazel"}, - ), - ["fake-bazel", "info", "execution_root"], - ) - - -if __name__ == "__main__": - unittest.main() diff --git a/.github/scripts/test_rusty_v8_bazel.py b/.github/scripts/test_rusty_v8_bazel.py index 0b5c03f43..19690dbec 100644 --- a/.github/scripts/test_rusty_v8_bazel.py +++ b/.github/scripts/test_rusty_v8_bazel.py @@ -88,49 +88,24 @@ class RustyV8BazelTest(unittest.TestCase): ), ) - def test_bazel_commands_use_shared_buildbuddy_remote_config_library(self) -> None: - with patch.dict(environ, {}, clear=True): + def test_bazel_remote_args_include_buildbuddy_header_when_present(self) -> None: + with patch.dict(environ, {"BUILDBUDDY_API_KEY": "token"}, clear=False): self.assertEqual( - [ - "bazel", - "build", - "//third_party/v8:release", - ], - rusty_v8_bazel.bazel_command( - "build", - "--config=ci-v8", - "//third_party/v8:release", - ), - ) - with patch.dict(environ, {"BUILDBUDDY_API_KEY": "token"}, clear=True): - self.assertEqual( - [ - "bazel", - "build", - "--config=buildbuddy-generic-rbe", - "--remote_header=x-buildbuddy-api-key=token", - "--config=ci-v8", - "//third_party/v8:release", - ], - rusty_v8_bazel.bazel_command( - "build", - "--config=ci-v8", - "//third_party/v8:release", - ), + ["--remote_header=x-buildbuddy-api-key=token"], + rusty_v8_bazel.bazel_remote_args(), ) - def test_release_pair_labels_and_staged_names_distinguish_sandbox_artifacts( - self, - ) -> None: + with patch.dict(environ, {}, clear=True): + self.assertEqual([], rusty_v8_bazel.bazel_remote_args()) + + def test_release_pair_labels_and_staged_names_distinguish_sandbox_artifacts(self) -> None: self.assertEqual( "//third_party/v8:rusty_v8_release_pair_x86_64_unknown_linux_musl", rusty_v8_bazel.release_pair_label("x86_64-unknown-linux-musl"), ) self.assertEqual( "//third_party/v8:rusty_v8_sandbox_release_pair_x86_64_unknown_linux_musl", - rusty_v8_bazel.release_pair_label( - "x86_64-unknown-linux-musl", sandbox=True - ), + rusty_v8_bazel.release_pair_label("x86_64-unknown-linux-musl", sandbox=True), ) self.assertEqual( "//third_party/v8:rusty_v8_sandbox_release_pair_x86_64_apple_darwin", @@ -230,7 +205,11 @@ class RustyV8BazelTest(unittest.TestCase): with TemporaryDirectory() as source_dir, TemporaryDirectory() as output_dir: source_root = Path(source_dir) gn_out = ( - source_root / "target" / "x86_64-pc-windows-msvc" / "release" / "gn_out" + source_root + / "target" + / "x86_64-pc-windows-msvc" + / "release" + / "gn_out" ) (gn_out / "obj").mkdir(parents=True) (gn_out / "obj" / "rusty_v8.lib").write_bytes(b"archive") diff --git a/.github/workflows/bazel.yml b/.github/workflows/bazel.yml index e2782b490..153ace0fc 100644 --- a/.github/workflows/bazel.yml +++ b/.github/workflows/bazel.yml @@ -15,7 +15,6 @@ concurrency: # See https://docs.github.com/en/actions/using-jobs/using-concurrency and https://docs.github.com/en/actions/learn-github-actions/contexts for more info. group: concurrency-group::${{ github.workflow }}::${{ github.event.pull_request.number > 0 && format('pr-{0}', github.event.pull_request.number) || github.ref_name }}${{ github.ref_name == 'main' && format('::{0}', github.run_id) || ''}} cancel-in-progress: ${{ github.ref_name != 'main' }} - jobs: test: # PRs use the sharded Windows cross-compiled test jobs below. Post-merge @@ -56,17 +55,12 @@ jobs: ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} persist-credentials: false - - uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49 - if: matrix.os == 'ubuntu-24.04' && matrix.target == 'x86_64-unknown-linux-gnu' - with: - tool: just - - name: Check rusty_v8 MODULE.bazel checksums if: matrix.os == 'ubuntu-24.04' && matrix.target == 'x86_64-unknown-linux-gnu' shell: bash run: | python3 .github/scripts/rusty_v8_bazel.py check-module-bazel - just test-github-scripts + python3 -m unittest discover -s .github/scripts -p test_rusty_v8_bazel.py - name: Prepare Bazel CI id: prepare_bazel diff --git a/.github/workflows/rusty-v8-release.yml b/.github/workflows/rusty-v8-release.yml index d6fb73e96..4b5320dcb 100644 --- a/.github/workflows/rusty-v8-release.yml +++ b/.github/workflows/rusty-v8-release.yml @@ -191,10 +191,11 @@ jobs: bazel_args+=(--config=v8-release-compat) fi - ./.github/scripts/run_bazel_with_buildbuddy.py \ + bazel \ --noexperimental_remote_repo_contents_cache \ "${bazel_args[@]}" \ - "--config=${{ matrix.bazel_config }}" + "--config=${{ matrix.bazel_config }}" \ + "--remote_header=x-buildbuddy-api-key=${BUILDBUDDY_API_KEY}" - name: Stage release pair env: diff --git a/.github/workflows/v8-canary.yml b/.github/workflows/v8-canary.yml index 0ad9f850d..71ce5d786 100644 --- a/.github/workflows/v8-canary.yml +++ b/.github/workflows/v8-canary.yml @@ -5,7 +5,6 @@ on: 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/workflows/rusty-v8-release.yml" @@ -24,7 +23,6 @@ on: 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/workflows/rusty-v8-release.yml" @@ -205,10 +203,11 @@ jobs: bazel_args+=(--config=v8-release-compat) fi - ./.github/scripts/run_bazel_with_buildbuddy.py \ + bazel \ --noexperimental_remote_repo_contents_cache \ "${bazel_args[@]}" \ - "--config=${{ matrix.bazel_config }}" + "--config=${{ matrix.bazel_config }}" \ + "--remote_header=x-buildbuddy-api-key=${BUILDBUDDY_API_KEY}" - name: Stage release pair env: diff --git a/codex-rs/docs/bazel.md b/codex-rs/docs/bazel.md index 085c15992..a124688a2 100644 --- a/codex-rs/docs/bazel.md +++ b/codex-rs/docs/bazel.md @@ -4,7 +4,7 @@ This repository uses Bazel to build the Rust workspace under `codex-rs`. Cargo remains the source of truth for crates and features, while Bazel provides hermetic builds, toolchains, and cross-platform artifacts. -As of 6/1/2026, this setup is still experimental as we stabilize it. +As of 1/9/2026, this setup is still experimental as we stabilize it. ## High-level layout @@ -20,118 +20,6 @@ As of 6/1/2026, this setup is still experimental as we stabilize it. makes some adjustments if the crate needs additional compile-time or runtime data, or other customizations. -## Running Bazel locally - -The repository root `justfile` exposes the common Bazel entry points: - -```bash -just bazel-test -just bazel-clippy -``` - -Ordinary local `bazel` and `just` invocations run locally. BuildBuddy cache, -build event upload, downloads, and remote execution are opt-in configurations. - -## BuildBuddy - -Codex uses BuildBuddy for a shared Bazel cache and remoted builds and tests. To use it -to speed up your builds and tests you'll need to provide an API key and select a -configuration. - -### BuildBuddy API key - -If you're an OpenAI employee, log in to https://openai.buildbuddy.io and use Google sign-in. - -Create a BuildBuddy API key as described in BuildBuddy's [Authentication Guide][bb-auth-guide], -then add it to `~/.bazelrc`: - -```bazelrc -# Local machine only; this file contains a BuildBuddy credential. -common --remote_header=x-buildbuddy-api-key= -``` - -Keeping the credential outside the workspace reduces the risk of accidentally -committing it. - -If you need different API keys for different projects, put the API key in -`%workspace%/user.bazelrc` instead. The checked-in `.bazelrc` optionally imports -that file, and `.gitignore` excludes it. Do not commit or share a file containing -the credential. - -[bb-auth-guide]: https://www.buildbuddy.io/docs/guide-auth/#managing-keys - -### Selecting a remote build configuration - -OpenAI employees should default to the OpenAI host with remote execution unless -they have a reason to choose another configuration. Add the following configuration -to `%workspace%/user.bazelrc`: - -```bazelrc -common --config=buildbuddy-openai-rbe -``` - -OpenAI employees who don't want remote execution can use `buildbuddy-openai`. External users -should use `buildbuddy-generic-rbe` or `buildbuddy-generic`. See below for details on these -configurations. - -### All remote configurations - -GitHub Actions routes Bazel build and output-resolution commands through -`.github/scripts/run_bazel_with_buildbuddy.py`. Higher-level helpers such as -`.github/scripts/run-bazel-ci.sh` and `.github/scripts/rusty_v8_bazel.py` -delegate remote configuration selection to that wrapper. The wrapper reads the -GitHub Actions repository and event payload rather than relying on workflow -files to duplicate tenant-selection logic. - -Loading-phase target-discovery `bazel query` commands run locally because they -only enumerate labels and do not need remote caches or execution. - -The `Cache/BES` host is also used for remote downloads. - -| Invocation/config | Key Required | Cache/BES | Build exec | Test exec | -| --- | --- | --- | --- | --- | -| `bazel ...` | No | None | Local | Local | -| `bazel ... --config=buildbuddy-generic` | Yes | `remote.buildbuddy.io` | Local | Local | -| `bazel ... --config=buildbuddy-generic-rbe` | Yes | `remote.buildbuddy.io` | Remote | Remote | -| `bazel ... --config=buildbuddy-openai` | Yes | `openai.buildbuddy.io` | Local | Local | -| `bazel ... --config=buildbuddy-openai-rbe` | Yes | `openai.buildbuddy.io` | Remote | Remote | - -Without an API key, the wrapper removes remote CI configurations and runs -locally. With a key, workflows choose the host as follows: - -| Run | Key | Uses OpenAI BuildBuddy Host | -| --- | --- | --- | -| Push to `main` in `openai/codex` | Yes | Yes | -| `workflow_dispatch` in `openai/codex` | Yes | Yes | -| Same-repository pull request in `openai/codex` | Yes | Yes | -| Fork pull request into `openai/codex` | No | No; local | -| Push or `workflow_dispatch` in a fork with a key | Yes | No; generic host | -| Pull request run in a fork repository with a key | Yes | No; generic host | - -CI configurations determine whether builds and tests execute remotely: - -| CI config | Remote config | Build exec | Test exec | -| --- | --- | --- | --- | -| `ci-linux` | `*-rbe` | Remote host | Remote host | -| `ci-v8` | `*-rbe` | Remote host | Remote host | -| `ci-macos` | `*-rbe` | Remote host | Local | -| `ci-windows-cross` | `*-rbe` | Remote host | Local | -| `ci-windows` | non-RBE | Local | Local | -| Keyless CI fallback | none | Local | Local | - -To exercise the generic remote configuration with your key: - -```bash -BUILDBUDDY_API_KEY=... GITHUB_REPOSITORY=my-fork/codex \ - ./.github/scripts/run_bazel_with_buildbuddy.py \ - build --config=ci-linux //codex-rs/cli:codex -``` - -The wrapper selects the OpenAI host only inside GitHub Actions for a trusted -run in `openai/codex`. A missing or malformed pull request event -payload fails closed to the generic host. For local OpenAI host access, use -the `user.bazelrc` configuration above. - ## Evolving the setup When you add or change Rust dependencies, update the Cargo.toml/Cargo.lock as normal. diff --git a/justfile b/justfile index fe7e7349b..34b5115ee 100644 --- a/justfile +++ b/justfile @@ -83,12 +83,6 @@ test *args: $env:RUST_MIN_STACK = "{{ rust_min_stack }}"; cargo nextest run --no-fail-fast @($args | Select-Object -Skip 1) just bench-smoke -# Run from the repository root so scripts that resolve paths from `cwd` see -# the same layout they use in GitHub Actions. -[no-cd] -test-github-scripts: - {{ python }} -m unittest discover -s {{ justfile_directory() }}/.github/scripts -p 'test_*.py' - # Run explicit workspace benchmark targets. bench *args: cargo bench --workspace --bench '*' {args} @@ -135,8 +129,11 @@ bazel-clippy: bazel-argument-comment-lint: bazel build --config=argument-comment-lint -- $({{ justfile_directory() }}/tools/argument-comment-lint/list-bazel-targets.sh) +bazel-remote-test: + bazel test --test_tag_filters=-argument-comment-lint //... --config=remote --platforms=//:rbe --keep_going + build-for-release: - bazel build //codex-rs/cli:release_binaries + bazel build //codex-rs/cli:release_binaries --config=remote # Run the MCP server mcp-server-run *args: diff --git a/scripts/list-bazel-clippy-targets.sh b/scripts/list-bazel-clippy-targets.sh index d12a256d0..b76fc2a83 100755 --- a/scripts/list-bazel-clippy-targets.sh +++ b/scripts/list-bazel-clippy-targets.sh @@ -20,13 +20,23 @@ while [[ $# -gt 0 ]]; do done # Resolve the dynamic targets before printing anything so callers do not -# continue with a partial list if `bazel query` fails. Target discovery is -# local on all platforms. -manual_rust_test_targets="$( - ./.github/scripts/run-bazel-query-ci.sh \ - --output=label \ - -- 'kind("rust_test rule", attr(tags, "manual", //codex-rs/... except //codex-rs/v8-poc/...))' -)" +# continue with a partial list if `bazel query` fails. Reuse the same CI Bazel +# server settings as the subsequent build so Windows jobs do not cold-start a +# second Bazel server just for target discovery. +if [[ $windows_cross_compile -eq 1 ]]; then + manual_rust_test_targets="$( + ./.github/scripts/run-bazel-query-ci.sh \ + --windows-cross-compile \ + --output=label \ + -- 'kind("rust_test rule", attr(tags, "manual", //codex-rs/... except //codex-rs/v8-poc/...))' + )" +else + manual_rust_test_targets="$( + ./.github/scripts/run-bazel-query-ci.sh \ + --output=label \ + -- 'kind("rust_test rule", attr(tags, "manual", //codex-rs/... except //codex-rs/v8-poc/...))' + )" +fi if [[ "${RUNNER_OS:-}" != "Windows" ]]; then # Non-Windows clippy jobs lint the native test binaries; the # Windows-cross binaries exist only for the fast Windows test leg.