diff --git a/.github/scripts/run-bazel-ci.sh b/.github/scripts/run-bazel-ci.sh index 4ed4a9777..cf50135c2 100755 --- a/.github/scripts/run-bazel-ci.sh +++ b/.github/scripts/run-bazel-ci.sh @@ -4,6 +4,7 @@ set -euo pipefail print_failed_bazel_test_logs=0 use_node_test_env=0 +remote_download_toplevel=0 while [[ $# -gt 0 ]]; do case "$1" in @@ -15,6 +16,10 @@ while [[ $# -gt 0 ]]; do use_node_test_env=1 shift ;; + --remote-download-toplevel) + remote_download_toplevel=1 + shift + ;; --) shift break @@ -27,7 +32,7 @@ while [[ $# -gt 0 ]]; do done if [[ $# -eq 0 ]]; then - echo "Usage: $0 [--print-failed-test-logs] [--use-node-test-env] -- -- " >&2 + echo "Usage: $0 [--print-failed-test-logs] [--use-node-test-env] [--remote-download-toplevel] -- -- " >&2 exit 1 fi @@ -114,6 +119,13 @@ if [[ $use_node_test_env -eq 1 && "${RUNNER_OS:-}" != "Windows" ]]; then bazel_args+=("--test_env=CODEX_JS_REPL_NODE_PATH=${node_bin}") fi +post_config_bazel_args=() +if [[ $remote_download_toplevel -eq 1 ]]; then + # Override the CI config's remote_download_minimal setting when callers need + # the built artifact to exist on disk after the command completes. + post_config_bazel_args+=(--remote_download_toplevel) +fi + bazel_console_log="$(mktemp)" trap 'rm -f "$bazel_console_log"' EXIT @@ -128,12 +140,18 @@ if [[ -n "${BUILDBUDDY_API_KEY:-}" ]]; then # 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 "${bazel_cmd[@]}" \ --noexperimental_remote_repo_contents_cache \ - "${bazel_args[@]}" \ - "--config=${ci_config}" \ - "--remote_header=x-buildbuddy-api-key=${BUILDBUDDY_API_KEY}" \ + "${bazel_run_args[@]}" \ -- \ "${bazel_targets[@]}" \ 2>&1 | tee "$bazel_console_log" @@ -157,12 +175,18 @@ else # 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 "${bazel_cmd[@]}" \ --noexperimental_remote_repo_contents_cache \ - "${bazel_args[@]}" \ - --remote_cache= \ - --remote_executor= \ + "${bazel_run_args[@]}" \ -- \ "${bazel_targets[@]}" \ 2>&1 | tee "$bazel_console_log" diff --git a/.github/workflows/sdk.yml b/.github/workflows/sdk.yml index 97ed66593..45c983ac1 100644 --- a/.github/workflows/sdk.yml +++ b/.github/workflows/sdk.yml @@ -33,11 +33,72 @@ jobs: node-version: 22 cache: pnpm - - uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0 + - name: Set up Bazel CI + id: setup_bazel + uses: ./.github/actions/setup-bazel-ci + with: + target: x86_64-unknown-linux-gnu - - name: build codex - run: cargo build --bin codex - working-directory: codex-rs + - name: Build codex with Bazel + env: + BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }} + shell: bash + run: | + set -euo pipefail + # Use the shared CI wrapper so fork PRs fall back cleanly when + # BuildBuddy credentials are unavailable. This workflow needs the + # built `codex` binary on disk afterwards, so ask the wrapper to + # override CI's default remote_download_minimal behavior. + ./.github/scripts/run-bazel-ci.sh \ + --remote-download-toplevel \ + -- \ + build \ + --build_metadata=COMMIT_SHA=${GITHUB_SHA} \ + --build_metadata=TAG_job=sdk \ + -- \ + //codex-rs/cli:codex + + # Resolve the exact output file using the same wrapper/config path as + # the build instead of guessing which Bazel convenience symlink is + # available on the runner. + cquery_output="$( + ./.github/scripts/run-bazel-ci.sh \ + -- \ + cquery \ + --output=files \ + -- \ + //codex-rs/cli:codex \ + | grep -E '^(/|bazel-out/)' \ + | tail -n 1 + )" + if [[ "${cquery_output}" = /* ]]; then + codex_bazel_output_path="${cquery_output}" + else + codex_bazel_output_path="${GITHUB_WORKSPACE}/${cquery_output}" + fi + if [[ -z "${codex_bazel_output_path}" ]]; then + echo "Bazel did not report an output path for //codex-rs/cli:codex." >&2 + exit 1 + fi + if [[ ! -e "${codex_bazel_output_path}" ]]; then + echo "Unable to locate the Bazel-built codex binary at ${codex_bazel_output_path}." >&2 + exit 1 + fi + + # Stage the binary into the workspace and point the SDK tests at that + # stable path. The tests spawn `codex` directly many times, so using a + # normal executable path is more reliable than invoking Bazel for each + # test process. + install_dir="${GITHUB_WORKSPACE}/.tmp/sdk-ci" + mkdir -p "${install_dir}" + install -m 755 "${codex_bazel_output_path}" "${install_dir}/codex" + echo "CODEX_EXEC_PATH=${install_dir}/codex" >> "$GITHUB_ENV" + + - name: Warm up Bazel-built codex + shell: bash + run: | + set -euo pipefail + "${CODEX_EXEC_PATH}" --version - name: Install dependencies run: pnpm install --frozen-lockfile @@ -50,3 +111,12 @@ jobs: - name: Test SDK packages run: pnpm -r --filter ./sdk/typescript run test + + - name: Save bazel repository cache + if: always() && !cancelled() && steps.setup_bazel.outputs.cache-hit != 'true' + continue-on-error: true + uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 + with: + path: | + ~/.cache/bazel-repo-cache + key: bazel-cache-x86_64-unknown-linux-gnu-${{ hashFiles('MODULE.bazel', 'codex-rs/Cargo.lock', 'codex-rs/Cargo.toml') }} diff --git a/sdk/typescript/tests/testCodex.ts b/sdk/typescript/tests/testCodex.ts index a4cac8b14..a95a68516 100644 --- a/sdk/typescript/tests/testCodex.ts +++ b/sdk/typescript/tests/testCodex.ts @@ -3,7 +3,9 @@ import path from "node:path"; import { Codex } from "../src/codex"; import type { CodexConfigObject } from "../src/codexOptions"; -export const codexExecPath = path.join(process.cwd(), "..", "..", "codex-rs", "target", "debug", "codex"); +export const codexExecPath = + process.env.CODEX_EXEC_PATH ?? + path.join(process.cwd(), "..", "..", "codex-rs", "target", "debug", "codex"); type CreateTestClientOptions = { apiKey?: string;