From 6471f8b31aba50c87d5616cc0a203160a80b5322 Mon Sep 17 00:00:00 2001 From: "Adam Perry @ OpenAI" Date: Tue, 2 Jun 2026 16:22:32 -0700 Subject: [PATCH] [codex] Fix Windows BuildBuddy Bazel wrapper execution (#25915) ## Why #25156 moved Bazel CI launches into a shared Python wrapper. On Windows, launching Bazel with `os.execvp` can split the spaced `--test_env=PATH=...` argument and fail to propagate the eventual Bazel exit status, allowing jobs to pass without running tests. This reapplies the wrapper after #25909 with a Windows-safe launch path. ## What changed Use a waited `subprocess.run` launch on Windows while preserving `os.execvp` on Unix. Add a process-level regression test for spaced arguments and child exit status, and run it on Windows Bazel shard 1. ## Experiment To confirm Bazel was actually invoking tests, patch `87b61d0be6` temporarily added an intentionally failing `codex-core` unit test. Bazel failed on that sentinel on all three major platforms: - [Linux Bazel test](https://github.com/openai/codex/actions/runs/26841132773/job/79151062486) - [macOS Bazel test](https://github.com/openai/codex/actions/runs/26841132773/job/79151062362) - [Windows Bazel test shard 1/4](https://github.com/openai/codex/actions/runs/26841132773/job/79151062155) The sentinel was removed after collecting this evidence. Windows Bazel [clippy](https://github.com/openai/codex/actions/runs/26841132773/job/79151062914) and [release verification](https://github.com/openai/codex/actions/runs/26841132773/job/79151062739) also passed. ## Validation After removing the sentinel, `just test -p codex-core` no longer reported it. The local run retained two unrelated environment-specific failures. --- .bazelrc | 46 ++-- .github/scripts/run-bazel-ci.sh | 126 ++++------- .github/scripts/run-bazel-query-ci.sh | 56 +---- .github/scripts/run_bazel_with_buildbuddy.py | 147 ++++++++++++ .github/scripts/rusty_v8_bazel.py | 63 +++--- .../scripts/test_run_bazel_with_buildbuddy.py | 214 ++++++++++++++++++ .github/scripts/test_rusty_v8_bazel.py | 49 ++-- .github/workflows/bazel.yml | 13 +- .github/workflows/rusty-v8-release.yml | 5 +- .github/workflows/v8-canary.yml | 7 +- codex-rs/docs/bazel.md | 114 +++++++++- justfile | 11 +- scripts/list-bazel-clippy-targets.sh | 24 +- 13 files changed, 657 insertions(+), 218 deletions(-) create mode 100755 .github/scripts/run_bazel_with_buildbuddy.py create mode 100644 .github/scripts/test_run_bazel_with_buildbuddy.py diff --git a/.bazelrc b/.bazelrc index 6357eb83e..e39a3aff2 100644 --- a/.bazelrc +++ b/.bazelrc @@ -38,24 +38,50 @@ common:windows --test_env=WINDIR common --test_env=RUST_MIN_STACK=8388608 # 8 MiB common --test_output=errors -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 +# These settings tune BuildBuddy/RBE behavior but do not contact a remote +# service unless a `buildbuddy-*` configuration below supplies an endpoint. +common --remote_download_toplevel common --remote_timeout=3600 common --noexperimental_throttle_remote_action_building common --experimental_remote_execution_keepalive common --grpc_keepalive_time=30s -common --experimental_remote_downloader=grpcs://remote.buildbuddy.io + +# 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 # 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. @@ -146,15 +172,11 @@ 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 @@ -162,9 +184,7 @@ 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 @@ -180,8 +200,6 @@ 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 f98e4d8cb..89f937a99 100755 --- a/.github/scripts/run-bazel-ci.sh +++ b/.github/scripts/run-bazel-ci.sh @@ -53,11 +53,20 @@ fi run_bazel() { if [[ "${RUNNER_OS:-}" == "Windows" ]]; then - MSYS2_ARG_CONV_EXCL='*' bazel "$@" + MSYS2_ARG_CONV_EXCL='*' "$(dirname "${BASH_SOURCE[0]}")/run_bazel_with_buildbuddy.py" "$@" return fi - bazel "$@" + "$(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 "$@" } ci_config=ci-linux @@ -77,23 +86,16 @@ 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 (( ${#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}" - ) + # `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}") 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. @@ -105,7 +107,7 @@ print_bazel_test_log_tails() { esac done - testlogs_dir="$(run_bazel "${bazel_info_cmd[@]:1}" \ + testlogs_dir="$(run_bazel_with_startup_args \ --noexperimental_remote_repo_contents_cache \ "${bazel_info_args[@]}" \ bazel-testlogs 2>/dev/null || echo bazel-testlogs)" @@ -254,8 +256,9 @@ 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 - # Fork PRs do not receive the BuildBuddy secret needed for the remote - # cross-compile config. Preserve the previous local Windows build shape. + # Windows cross-compilation depends on authenticated RBE. Preserve the local + # Windows build shape when credentials are unavailable. + ci_config=ci-windows windows_msvc_host_platform=1 fi @@ -297,9 +300,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 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. + # The Windows cross-compile config depends on authenticated remote + # execution. When credentials are unavailable, keep the local build shape + # and its lower concurrency cap. post_config_bazel_args+=(--jobs=8) fi @@ -377,70 +380,31 @@ fi bazel_console_log="$(mktemp)" trap 'rm -f "$bazel_console_log"' EXIT -bazel_cmd=(bazel) -if (( ${#bazel_startup_args[@]} > 0 )); then - bazel_cmd+=("${bazel_startup_args[@]}") -fi - +bazel_run_args=( + "${bazel_args[@]}" +) if [[ -n "${BUILDBUDDY_API_KEY:-}" ]]; then echo "BuildBuddy API key is available; using remote Bazel configuration." - # 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 + bazel_run_args+=("--config=${ci_config}") 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 dd03b6716..f5d4f56f4 100755 --- a/.github/scripts/run-bazel-query-ci.sh +++ b/.github/scripts/run-bazel-query-ci.sh @@ -2,48 +2,17 @@ set -euo pipefail -# 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. +# 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. -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 +if [[ $# -lt 2 || "${@: -2:1}" != "--" ]]; then + echo "Usage: $0 [...] -- " >&2 exit 1 fi -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 +query_args=("${@:1:$#-2}") +query_expression="${@: -1}" bazel_startup_args=() if [[ -n "${BAZEL_OUTPUT_USER_ROOT:-}" ]]; then @@ -60,12 +29,6 @@ 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}") @@ -75,7 +38,10 @@ if [[ -n "${BAZEL_REPOSITORY_CACHE:-}" ]]; then bazel_query_args+=("--repository_cache=${BAZEL_REPOSITORY_CACHE}") fi -bazel_query_args+=("${query_args[@]}" "$query_expression") +if (( ${#query_args[@]} > 0 )); then + bazel_query_args+=("${query_args[@]}") +fi +bazel_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 new file mode 100755 index 000000000..f9f329d68 --- /dev/null +++ b/.github/scripts/run_bazel_with_buildbuddy.py @@ -0,0 +1,147 @@ +#!/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", +} +# 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:]) + 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() diff --git a/.github/scripts/rusty_v8_bazel.py b/.github/scripts/rusty_v8_bazel.py index 2f46daf45..329d3f6c5 100644 --- a/.github/scripts/rusty_v8_bazel.py +++ b/.github/scripts/rusty_v8_bazel.py @@ -5,7 +5,6 @@ from __future__ import annotations import argparse import gzip import hashlib -import os import re import shutil import subprocess @@ -13,6 +12,7 @@ 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,33 +29,22 @@ 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: - result = subprocess.run( - ["bazel", "info", "execution_root"], + output = subprocess.check_output( + bazel_command("info", "execution_root"), cwd=ROOT, - check=True, - capture_output=True, text=True, ) - return Path(result.stdout.strip()) + return Path(output.strip()) def bazel_output_base() -> Path: - result = subprocess.run( - ["bazel", "info", "output_base"], + output = subprocess.check_output( + bazel_command("info", "output_base"), cwd=ROOT, - check=True, - capture_output=True, text=True, ) - return Path(result.stdout.strip()) + return Path(output.strip()) def bazel_output_path(path: str) -> Path: @@ -72,24 +61,22 @@ def bazel_output_files( ) -> list[Path]: expression = "set(" + " ".join(labels) + ")" bazel_configs = bazel_configs or [] - result = subprocess.run( - [ - "bazel", + output = subprocess.check_output( + bazel_command( "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 result.stdout.splitlines() if line.strip()] + return [ + bazel_output_path(line.strip()) for line in output.splitlines() if line.strip() + ] def bazel_build( @@ -102,17 +89,15 @@ def bazel_build( bazel_configs = bazel_configs or [] download_args = ["--remote_download_toplevel"] if download_toplevel else [] subprocess.run( - [ - "bazel", + bazel_command( "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, ) @@ -172,7 +157,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, ) ) @@ -234,13 +219,17 @@ 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: @@ -270,7 +259,9 @@ 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" @@ -338,7 +329,9 @@ 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 new file mode 100644 index 000000000..0f594b794 --- /dev/null +++ b/.github/scripts/test_run_bazel_with_buildbuddy.py @@ -0,0 +1,214 @@ +#!/usr/bin/env python3 + +import json +import os +import subprocess +import sys +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"], + ) + + def test_main_preserves_spaced_argument_and_child_exit_status(self) -> None: + spaced_arg = ( + r"--test_env=PATH=C:\Program Files\PowerShell\7;C:\Program Files\Git\bin" + ) + child_code = ( + f"import sys; sys.exit(37 if sys.argv[1] == {spaced_arg!r} else 91)" + ) + env = os.environ.copy() + env["CODEX_BAZEL_BIN"] = sys.executable + env.pop("BUILDBUDDY_API_KEY", None) + + result = subprocess.run( + [ + sys.executable, + str(Path(run_bazel_with_buildbuddy.__file__)), + "-c", + child_code, + spaced_arg, + ], + env=env, + check=False, + capture_output=True, + text=True, + ) + + self.assertEqual(result.returncode, 37, result.stderr) + + +if __name__ == "__main__": + unittest.main() diff --git a/.github/scripts/test_rusty_v8_bazel.py b/.github/scripts/test_rusty_v8_bazel.py index 19690dbec..0b5c03f43 100644 --- a/.github/scripts/test_rusty_v8_bazel.py +++ b/.github/scripts/test_rusty_v8_bazel.py @@ -88,24 +88,49 @@ class RustyV8BazelTest(unittest.TestCase): ), ) - def test_bazel_remote_args_include_buildbuddy_header_when_present(self) -> None: - with patch.dict(environ, {"BUILDBUDDY_API_KEY": "token"}, clear=False): + def test_bazel_commands_use_shared_buildbuddy_remote_config_library(self) -> None: + with patch.dict(environ, {}, clear=True): self.assertEqual( - ["--remote_header=x-buildbuddy-api-key=token"], - rusty_v8_bazel.bazel_remote_args(), + [ + "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", + ), ) - 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: + 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", @@ -205,11 +230,7 @@ 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 153ace0fc..c6dfd6023 100644 --- a/.github/workflows/bazel.yml +++ b/.github/workflows/bazel.yml @@ -15,6 +15,7 @@ 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 @@ -55,12 +56,17 @@ 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 - python3 -m unittest discover -s .github/scripts -p test_rusty_v8_bazel.py + just test-github-scripts - name: Prepare Bazel CI id: prepare_bazel @@ -152,6 +158,11 @@ jobs: ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} persist-credentials: false + - name: Test BuildBuddy Bazel wrapper + if: matrix.shard == 1 + shell: pwsh + run: python .github/scripts/test_run_bazel_with_buildbuddy.py + - name: Prepare Bazel CI id: prepare_bazel uses: ./.github/actions/prepare-bazel-ci diff --git a/.github/workflows/rusty-v8-release.yml b/.github/workflows/rusty-v8-release.yml index 4b5320dcb..d6fb73e96 100644 --- a/.github/workflows/rusty-v8-release.yml +++ b/.github/workflows/rusty-v8-release.yml @@ -191,11 +191,10 @@ jobs: bazel_args+=(--config=v8-release-compat) fi - bazel \ + ./.github/scripts/run_bazel_with_buildbuddy.py \ --noexperimental_remote_repo_contents_cache \ "${bazel_args[@]}" \ - "--config=${{ matrix.bazel_config }}" \ - "--remote_header=x-buildbuddy-api-key=${BUILDBUDDY_API_KEY}" + "--config=${{ matrix.bazel_config }}" - name: Stage release pair env: diff --git a/.github/workflows/v8-canary.yml b/.github/workflows/v8-canary.yml index 71ce5d786..0ad9f850d 100644 --- a/.github/workflows/v8-canary.yml +++ b/.github/workflows/v8-canary.yml @@ -5,6 +5,7 @@ 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" @@ -23,6 +24,7 @@ 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" @@ -203,11 +205,10 @@ jobs: bazel_args+=(--config=v8-release-compat) fi - bazel \ + ./.github/scripts/run_bazel_with_buildbuddy.py \ --noexperimental_remote_repo_contents_cache \ "${bazel_args[@]}" \ - "--config=${{ matrix.bazel_config }}" \ - "--remote_header=x-buildbuddy-api-key=${BUILDBUDDY_API_KEY}" + "--config=${{ matrix.bazel_config }}" - name: Stage release pair env: diff --git a/codex-rs/docs/bazel.md b/codex-rs/docs/bazel.md index a124688a2..085c15992 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 1/9/2026, this setup is still experimental as we stabilize it. +As of 6/1/2026, this setup is still experimental as we stabilize it. ## High-level layout @@ -20,6 +20,118 @@ As of 1/9/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 34b5115ee..fe7e7349b 100644 --- a/justfile +++ b/justfile @@ -83,6 +83,12 @@ 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} @@ -129,11 +135,8 @@ 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 --config=remote + bazel build //codex-rs/cli:release_binaries # 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 b76fc2a83..d12a256d0 100755 --- a/scripts/list-bazel-clippy-targets.sh +++ b/scripts/list-bazel-clippy-targets.sh @@ -20,23 +20,13 @@ 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. 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 +# 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/...))' +)" 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.