feat: add support for building with Bazel (#8875)

This PR configures Codex CLI so it can be built with
[Bazel](https://bazel.build) in addition to Cargo. The `.bazelrc`
includes configuration so that remote builds can be done using
[BuildBuddy](https://www.buildbuddy.io).

If you are familiar with Bazel, things should work as you expect, e.g.,
run `bazel test //... --keep-going` to run all the tests in the repo,
but we have also added some new aliases in the `justfile` for
convenience:

- `just bazel-test` to run tests locally
- `just bazel-remote-test` to run tests remotely (currently, the remote
build is for x86_64 Linux regardless of your host platform). Note we are
currently seeing the following test failures in the remote build, so we
still need to figure out what is happening here:

```
failures:
    suite::compact::manual_compact_twice_preserves_latest_user_messages
    suite::compact_resume_fork::compact_resume_after_second_compaction_preserves_history
    suite::compact_resume_fork::compact_resume_and_fork_preserve_model_history_view
```

- `just build-for-release` to build release binaries for all
platforms/architectures remotely

To setup remote execution:
- [Create a buildbuddy account](https://app.buildbuddy.io/) (OpenAI
employees should also request org access at
https://openai.buildbuddy.io/join/ with their `@openai.com` email
address.)
- [Copy your API key](https://app.buildbuddy.io/docs/setup/) to
`~/.bazelrc` (add the line `build
--remote_header=x-buildbuddy-api-key=YOUR_KEY`)
- Use `--config=remote` in your `bazel` invocations (or add `common
--config=remote` to your `~/.bazelrc`, or use the `just` commands)

## CI

In terms of CI, this PR introduces `.github/workflows/bazel.yml`, which
uses Bazel to run the tests _locally_ on Mac and Linux GitHub runners
(we are working on supporting Windows, but that is not ready yet). Note
that the failures we are seeing in `just bazel-remote-test` do not occur
on these GitHub CI jobs, so everything in `.github/workflows/bazel.yml`
is green right now.

The `bazel.yml` uses extra config in `.github/workflows/ci.bazelrc` so
that macOS CI jobs build _remotely_ on Linux hosts (using the
`docker://docker.io/mbolin491/codex-bazel` Docker image declared in the
root `BUILD.bazel`) using cross-compilation to build the macOS
artifacts. Then these artifacts are downloaded locally to GitHub's macOS
runner so the tests can be executed natively. This is the relevant
config that enables this:

```
common:macos --config=remote
common:macos --strategy=remote
common:macos --strategy=TestRunner=darwin-sandbox,local
```

Because of the remote caching benefits we get from BuildBuddy, these new
CI jobs can be extremely fast! For example, consider these two jobs that
ran all the tests on Linux x86_64:

- Bazel 1m37s
https://github.com/openai/codex/actions/runs/20861063212/job/59940545209?pr=8875
- Cargo 9m20s
https://github.com/openai/codex/actions/runs/20861063192/job/59940559592?pr=8875

For now, we will continue to run both the Bazel and Cargo jobs for PRs,
but once we add support for Windows and running Clippy, we should be
able to cutover to using Bazel exclusively for PRs, which should still
speed things up considerably. We will probably continue to run the Cargo
jobs post-merge for commits that land on `main` as a sanity check.

Release builds will also continue to be done by Cargo for now.

Earlier attempt at this PR: https://github.com/openai/codex/pull/8832
Earlier attempt to add support for Buck2, now abandoned:
https://github.com/openai/codex/pull/8504

---------

Co-authored-by: David Zbarsky <dzbarsky@gmail.com>
Co-authored-by: Michael Bolin <mbolin@openai.com>
This commit is contained in:
zbarsky-openai
2026-01-09 14:09:43 -05:00
committed by GitHub
parent 7daaabc795
commit 2a06d64bc9
69 changed files with 2256 additions and 0 deletions

45
.bazelrc Normal file
View File

@@ -0,0 +1,45 @@
common --repo_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1
common --repo_env=BAZEL_NO_APPLE_CPP_TOOLCHAIN=1
common --disk_cache=~/.cache/bazel-disk-cache
common --repo_contents_cache=~/.cache/bazel-repo-contents-cache
common --repository_cache=~/.cache/bazel-repo-cache
common --experimental_platform_in_output_dir
common --enable_platform_specific_config
# TODO(zbarsky): We need to untangle these libc constraints to get linux remote builds working.
common:linux --host_platform=//:local
common --@rules_cc//cc/toolchains/args/archiver_flags:use_libtool_on_macos=False
common --@toolchains_llvm_bootstrapped//config:experimental_stub_libgcc_s
# We need to use the sh toolchain on windows so we don't send host bash paths to the linux executor.
common:windows --@rules_rust//rust/settings:experimental_use_sh_toolchain_for_bootstrap_process_wrapper
# TODO(zbarsky): rules_rust doesn't implement this flag properly with remote exec...
# common --@rules_rust//rust/settings:pipelined_compilation
common --incompatible_strict_action_env
# Not ideal, but We need to allow dotslash to be found
common --test_env=PATH=/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
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
common --remote_timeout=3600
common --noexperimental_throttle_remote_action_building
common --experimental_remote_execution_keepalive
common --grpc_keepalive_time=30s
# 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
common:remote --extra_execution_platforms=//:rbe
common:remote --remote_executor=grpcs://remote.buildbuddy.io
common:remote --jobs=800

20
.github/workflows/Dockerfile.bazel vendored Normal file
View File

@@ -0,0 +1,20 @@
FROM ubuntu:24.04
# TODO(mbolin): Published to docker.io/mbolin491/codex-bazel:latest for
# initial debugging, but we should publish to a more proper location.
#
# docker buildx create --use
# docker buildx build --platform linux/amd64 -f .github/workflows/Dockerfile.bazel -t mbolin491/codex-bazel:latest --push .
RUN apt-get update && \
apt-get install -y --no-install-recommends \
curl git python3 ca-certificates && \
rm -rf /var/lib/apt/lists/*
# Install dotslash.
RUN curl -LSfs "https://github.com/facebook/dotslash/releases/download/v0.5.8/dotslash-ubuntu-22.04.$(uname -m).tar.gz" | tar fxz - -C /usr/local/bin
# Ubuntu 24.04 ships with user 'ubuntu' already created with UID 1000.
USER ubuntu
WORKDIR /workspace

110
.github/workflows/bazel.yml vendored Normal file
View File

@@ -0,0 +1,110 @@
name: Bazel (experimental)
# Note this workflow was originally derived from:
# https://github.com/cerisier/toolchains_llvm_bootstrapped/blob/main/.github/workflows/ci.yaml
on:
pull_request: {}
push:
branches:
- main
workflow_dispatch:
concurrency:
# Cancel previous actions from the same PR or branch except 'main' branch.
# 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:
strategy:
fail-fast: false
matrix:
include:
# macOS
- os: macos-15-xlarge
target: aarch64-apple-darwin
- os: macos-15-xlarge
target: x86_64-apple-darwin
# Linux
- os: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
- os: ubuntu-24.04
target: x86_64-unknown-linux-gnu
- os: ubuntu-24.04-arm
target: aarch64-unknown-linux-musl
- os: ubuntu-24.04
target: x86_64-unknown-linux-musl
# TODO: Enable Windows once we fix the toolchain issues there.
#- os: windows-latest
# target: x86_64-pc-windows-gnullvm
runs-on: ${{ matrix.os }}
# Configure a human readable name for each job
name: Local Bazel build on ${{ matrix.os }} for ${{ matrix.target }}
steps:
- uses: actions/checkout@v6
# Some integration tests rely on DotSlash being installed.
# See https://github.com/openai/codex/pull/7617.
- name: Install DotSlash
uses: facebook/install-dotslash@v2
- name: Make DotSlash available in PATH (Unix)
if: runner.os != 'Windows'
run: cp "$(which dotslash)" /usr/local/bin
- name: Make DotSlash available in PATH (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: Copy-Item (Get-Command dotslash).Source -Destination "$env:LOCALAPPDATA\Microsoft\WindowsApps\dotslash.exe"
# Install Bazel via Bazelisk
- name: Set up Bazel
uses: bazelbuild/setup-bazelisk@v3
# TODO(mbolin): Bring this back once we have caching working. Currently,
# we never seem to get a cache hit but we still end up paying the cost of
# uploading at the end of the build, which takes over a minute!
#
# Cache build and external artifacts so that the next ci build is incremental.
# Because github action caches cannot be updated after a build, we need to
# store the contents of each build in a unique cache key, then fall back to loading
# it on the next ci run. We use hashFiles(...) in the key and restore-keys- with
# the prefix to load the most recent cache for the branch on a cache miss. You
# should customize the contents of hashFiles to capture any bazel input sources,
# although this doesn't need to be perfect. If none of the input sources change
# then a cache hit will load an existing cache and bazel won't have to do any work.
# In the case of a cache miss, you want the fallback cache to contain most of the
# previously built artifacts to minimize build time. The more precise you are with
# hashFiles sources the less work bazel will have to do.
# - name: Mount bazel caches
# uses: actions/cache@v4
# with:
# path: |
# ~/.cache/bazel-repo-cache
# ~/.cache/bazel-repo-contents-cache
# key: bazel-cache-${{ matrix.os }}-${{ hashFiles('**/BUILD.bazel', '**/*.bzl', 'MODULE.bazel') }}
# restore-keys: |
# bazel-cache-${{ matrix.os }}
- name: Configure Bazel startup args (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
# Use a very short path to reduce argv/path length issues.
"BAZEL_STARTUP_ARGS=--output_user_root=C:\" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
- name: bazel test //...
env:
BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
shell: bash
run: |
bazel $BAZEL_STARTUP_ARGS --bazelrc=.github/workflows/ci.bazelrc test //... \
--build_metadata=REPO_URL=https://github.com/openai/codex.git \
--build_metadata=COMMIT_SHA=$(git rev-parse HEAD) \
--build_metadata=ROLE=CI \
--build_metadata=VISIBILITY=PUBLIC \
"--remote_header=x-buildbuddy-api-key=$BUILDBUDDY_API_KEY"

15
.github/workflows/ci.bazelrc vendored Normal file
View File

@@ -0,0 +1,15 @@
common --remote_download_minimal
common --nobuild_runfile_links
common --keep_going
# Prefer to run the build actions entirely remotely so we can dial up the concurrency.
# Currently remote builds only work on Mac hosts, until we untangle the libc constraints mess on linux.
common:macos --config=remote
common:macos --strategy=remote
# We have platform-specific tests, so execute the tests locally using the strongest sandboxing available on each platform.
common:macos --strategy=TestRunner=darwin-sandbox,local
# Note: linux-sandbox is stronger, but not available in GHA.
common:linux --strategy=TestRunner=processwrapper-sandbox,local
common:windows --strategy=TestRunner=local

29
BUILD.bazel Normal file
View File

@@ -0,0 +1,29 @@
# We mark the local platform as glibc-compatible so that rust can grab a toolchain for us.
# TODO(zbarsky): Upstream a better libc constraint into rules_rust.
# We only enable this on linux though for sanity, and because it breaks remote execution.
platform(
name = "local",
constraint_values = [
"@toolchains_llvm_bootstrapped//constraints/libc:gnu.2.28",
],
parents = [
"@platforms//host",
],
)
platform(
name = "rbe",
constraint_values = [
"@platforms//cpu:x86_64",
"@platforms//os:linux",
"@bazel_tools//tools/cpp:clang",
"@toolchains_llvm_bootstrapped//constraints/libc:gnu.2.28",
],
exec_properties = {
# Ubuntu-based image that includes git, python3, dotslash, and other
# tools that various integration tests need.
# Verify at https://hub.docker.com/layers/mbolin491/codex-bazel/latest/images/sha256:8c9ff94187ea7c08a31e9a81f5fe8046ea3972a6768983c955c4079fa30567fb
"container-image": "docker://docker.io/mbolin491/codex-bazel@sha256:8c9ff94187ea7c08a31e9a81f5fe8046ea3972a6768983c955c4079fa30567fb",
"OSFamily": "Linux",
},
)

122
MODULE.bazel Normal file
View File

@@ -0,0 +1,122 @@
bazel_dep(name = "platforms", version = "1.0.0")
bazel_dep(name = "toolchains_llvm_bootstrapped", version = "0.3.1")
archive_override(
module_name = "toolchains_llvm_bootstrapped",
integrity = "sha256-9ks21bgEqbQWmwUIvqeLA64+Jk6o4ZVjC8KxjVa2Vw8=",
strip_prefix = "toolchains_llvm_bootstrapped-e3775e66a7b6d287c705ca0cd24497ef4a77c503",
urls = ["https://github.com/cerisier/toolchains_llvm_bootstrapped/archive/e3775e66a7b6d287c705ca0cd24497ef4a77c503/master.tar.gz"],
patch_strip = 1,
patches = [
"//patches:llvm_toolchain_archive_params.patch",
],
)
osx = use_extension("@toolchains_llvm_bootstrapped//toolchain/extension:osx.bzl", "osx")
osx.framework(name = "ApplicationServices")
osx.framework(name = "AppKit")
osx.framework(name = "ColorSync")
osx.framework(name = "CoreFoundation")
osx.framework(name = "CoreGraphics")
osx.framework(name = "CoreServices")
osx.framework(name = "CoreText")
osx.framework(name = "CFNetwork")
osx.framework(name = "Foundation")
osx.framework(name = "ImageIO")
osx.framework(name = "Kernel")
osx.framework(name = "OSLog")
osx.framework(name = "Security")
osx.framework(name = "SystemConfiguration")
register_toolchains(
"@toolchains_llvm_bootstrapped//toolchain:all",
)
bazel_dep(name = "rules_cc", version = "0.2.16")
bazel_dep(name = "rules_platform", version = "0.1.0")
bazel_dep(name = "rules_rust", version = "0.68.1")
single_version_override(
module_name = "rules_rust",
patch_strip = 1,
patches = [
"//patches:rules_rust.patch",
"//patches:rules_rust_windows_gnu.patch",
"//patches:rules_rust_musl.patch",
],
)
RUST_TRIPLES = [
"aarch64-unknown-linux-musl",
"aarch64-apple-darwin",
"aarch64-pc-windows-gnullvm",
"x86_64-unknown-linux-musl",
"x86_64-apple-darwin",
"x86_64-pc-windows-gnullvm",
]
rust = use_extension("@rules_rust//rust:extensions.bzl", "rust")
rust.toolchain(
edition = "2024",
extra_target_triples = RUST_TRIPLES,
versions = ["1.90.0"],
)
use_repo(rust, "rust_toolchains")
register_toolchains("@rust_toolchains//:all")
bazel_dep(name = "rules_rs", version = "0.0.23")
crate = use_extension("@rules_rs//rs:extensions.bzl", "crate")
crate.from_cargo(
cargo_lock = "//codex-rs:Cargo.lock",
cargo_toml = "//codex-rs:Cargo.toml",
platform_triples = RUST_TRIPLES,
)
bazel_dep(name = "openssl", version = "3.5.4.bcr.0")
crate.annotation(
build_script_data = [
"@openssl//:gen_dir",
],
build_script_env = {
"OPENSSL_DIR": "$(execpath @openssl//:gen_dir)",
"OPENSSL_NO_VENDOR": "1",
"OPENSSL_STATIC": "1",
},
crate = "openssl-sys",
data = ["@openssl//:gen_dir"],
)
inject_repo(crate, "openssl")
# Fix readme inclusions
crate.annotation(
crate = "windows-link",
patch_args = ["-p1"],
patches = [
"//patches:windows-link.patch"
],
)
WINDOWS_IMPORT_LIB = """
load("@rules_cc//cc:defs.bzl", "cc_import")
cc_import(
name = "windows_import_lib",
static_library = glob(["lib/*.a"])[0],
)
"""
crate.annotation(
additive_build_file_content = WINDOWS_IMPORT_LIB,
crate = "windows_x86_64_gnullvm",
gen_build_script = "off",
deps = [":windows_import_lib"],
)
crate.annotation(
additive_build_file_content = WINDOWS_IMPORT_LIB,
crate = "windows_aarch64_gnullvm",
gen_build_script = "off",
deps = [":windows_import_lib"],
)
use_repo(crate, "crates")

1097
MODULE.bazel.lock generated Normal file

File diff suppressed because one or more lines are too long

1
codex-rs/BUILD.bazel Normal file
View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,6 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "ansi-escape",
crate_name = "codex_ansi_escape",
)

View File

@@ -0,0 +1,6 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "app-server-protocol",
crate_name = "codex_app_server_protocol",
)

View File

@@ -0,0 +1,6 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "codex-app-server-test-client",
crate_name = "codex_app_server_test_client",
)

View File

@@ -0,0 +1,8 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "app-server",
crate_name = "codex_app_server",
integration_deps_extra = ["//codex-rs/app-server/tests/common:common"],
test_tags = ["no-sandbox"],
)

View File

@@ -0,0 +1,7 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "common",
crate_name = "app_test_support",
crate_srcs = glob(["*.rs"]),
)

View File

@@ -0,0 +1,11 @@
load("//:defs.bzl", "codex_rust_crate")
exports_files(["apply_patch_tool_instructions.md"])
codex_rust_crate(
name = "apply-patch",
crate_name = "codex_apply_patch",
compile_data = [
"apply_patch_tool_instructions.md",
],
)

View File

@@ -0,0 +1,6 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "arg0",
crate_name = "codex_arg0",
)

View File

@@ -0,0 +1,6 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "async-utils",
crate_name = "codex_async_utils",
)

View File

@@ -0,0 +1,7 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "backend-client",
crate_name = "codex_backend_client",
compile_data = glob(["tests/fixtures/**"]),
)

View File

@@ -0,0 +1,6 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "chatgpt",
crate_name = "codex_chatgpt",
)

10
codex-rs/cli/BUILD.bazel Normal file
View File

@@ -0,0 +1,10 @@
load("//:defs.bzl", "codex_rust_crate", "multiplatform_binaries")
codex_rust_crate(
name = "cli",
crate_name = "codex_cli",
)
multiplatform_binaries(
name = "codex",
)

View File

@@ -0,0 +1,10 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "cloud-tasks-client",
crate_name = "codex_cloud_tasks_client",
crate_features = [
"mock",
"online",
],
)

View File

@@ -0,0 +1,6 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "cloud-tasks",
crate_name = "codex_cloud_tasks",
)

View File

@@ -0,0 +1,6 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "codex-api",
crate_name = "codex_api",
)

View File

@@ -0,0 +1,6 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "codex-backend-openapi-models",
crate_name = "codex_backend_openapi_models",
)

View File

@@ -0,0 +1,6 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "codex-client",
crate_name = "codex_client",
)

View File

@@ -0,0 +1,11 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "common",
crate_name = "codex_common",
crate_features = [
"cli",
"elapsed",
"sandbox_summary",
],
)

31
codex-rs/core/BUILD.bazel Normal file
View File

@@ -0,0 +1,31 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "core",
crate_name = "codex_core",
# TODO(mbolin): Eliminate the use of features in the version of the
# rust_library() that is used by rust_binary() rules for release artifacts
# such as the Codex CLI.
crate_features = ["deterministic_process_ids", "test-support"],
compile_data = glob(
include = ["**"],
exclude = [
"**/* *",
"BUILD.bazel",
"Cargo.toml",
],
allow_empty = True,
),
integration_compile_data_extra = [
"//codex-rs/apply-patch:apply_patch_tool_instructions.md",
"prompt.md",
],
integration_deps_extra = ["//codex-rs/core/tests/common:common"],
test_tags = ["no-sandbox"],
extra_binaries = [
"//codex-rs/linux-sandbox:codex-linux-sandbox",
"//codex-rs/rmcp-client:test_stdio_server",
"//codex-rs/rmcp-client:test_streamable_http_server",
"//codex-rs/cli:codex",
],
)

View File

@@ -0,0 +1,7 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "common",
crate_name = "core_test_support",
crate_srcs = glob(["*.rs"]),
)

52
codex-rs/docs/bazel.md Normal file
View File

@@ -0,0 +1,52 @@
# Bazel in codex-rs
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.
## High-level layout
- `../MODULE.bazel` defines Bazel dependencies and Rust toolchains.
- `rules_rs` imports third-party crates from `codex-rs/Cargo.toml` and
`codex-rs/Cargo.lock` via `crate.from_cargo(...)` and exposes them under
`@crates`.
- `../defs.bzl` provides `codex_rust_crate`, which wraps `rust_library`,
`rust_binary`, and `rust_test` so Bazel targets line up with Cargo conventions.
It provides a sane set of defaults that work for most first-party crates, but may
need tweaks in some cases.
- Each crate in `codex-rs/*/BUILD.bazel` typically uses `codex_rust_crate` and
makes some adjustments if the crate needs additional compile-time or runtime data,
or other customizations.
## Evolving the setup
When you add or change Rust dependencies, update the Cargo.toml/Cargo.lock as normal.
The Bazel build should automatically pick things up without any manual action needed.
In some cases, an upstream crate may need a patch or a `crate.annotation` in `../MODULE.bzl`
to have it build in Bazel's sandbox or make it cross-compilation-friendly. If you see issues,
feel free to ping zbarsky or mbolin.
When you add a new crate or binary:
1. Add it to the Cargo workspace as usual.
2. Create a `BUILD.bazel` that calls `codex_rust_crate` (see nearby crates for
examples).
3. If a dependency needs special handling (compile/runtime data, additional binaries
for integration tests, env vars, etc) you may need to adjust the parameters to
`codex_rust_crate` to configure it.
One common customization is setting `test_tags = ["no-sandbox]` to run the test
unsandboxed. Prefer to avoid it, but it is necessary in some cases such as when the
test itself uses Seatbelt (the sandbox does as well, and it cannot be nested).
To limit the blast radius, consider isolating such tests to a separate crate.
If you see build issue and are not sure how to apply the proper customizations, feel free to ping zbarsky or mbolin.
## References
- Bazel overview: https://bazel.build/
- Bzlmod (module system): https://bazel.build/external/overview
- rules_rust: https://github.com/bazelbuild/rules_rust
- rules_rs: https://github.com/bazelbuild/rules_rs

View File

@@ -0,0 +1,11 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "exec-server",
crate_name = "codex_exec_server",
integration_deps_extra = ["//codex-rs/exec-server/tests/common:common"],
test_tags = ["no-sandbox"],
extra_binaries = [
"//codex-rs/cli:codex",
],
)

View File

@@ -0,0 +1,7 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "common",
crate_name = "exec_server_test_support",
crate_srcs = glob(["*.rs"]),
)

View File

@@ -0,0 +1,7 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "exec",
crate_name = "codex_exec",
test_tags = ["no-sandbox"],
)

View File

@@ -0,0 +1,7 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "execpolicy-legacy",
crate_name = "codex_execpolicy_legacy",
compile_data = ["src/default.policy"],
)

View File

@@ -0,0 +1,6 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "execpolicy",
crate_name = "codex_execpolicy",
)

View File

@@ -0,0 +1,6 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "feedback",
crate_name = "codex_feedback",
)

View File

@@ -0,0 +1,6 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "file-search",
crate_name = "codex_file_search",
)

View File

@@ -0,0 +1,6 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "keyring-store",
crate_name = "codex_keyring_store",
)

View File

@@ -0,0 +1,6 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "linux-sandbox",
crate_name = "codex_linux_sandbox",
)

View File

@@ -0,0 +1,6 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "lmstudio",
crate_name = "codex_lmstudio",
)

View File

@@ -0,0 +1,7 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "login",
crate_name = "codex_login",
compile_data = ["src/assets/success.html"],
)

View File

@@ -0,0 +1,7 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "mcp-server",
crate_name = "codex_mcp_server",
integration_deps_extra = ["//codex-rs/mcp-server/tests/common:common"],
)

View File

@@ -0,0 +1,7 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "common",
crate_name = "mcp_test_support",
crate_srcs = glob(["*.rs"]),
)

View File

@@ -0,0 +1,6 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "mcp-types",
crate_name = "mcp_types",
)

View File

@@ -0,0 +1,6 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "ollama",
crate_name = "codex_ollama",
)

View File

@@ -0,0 +1,6 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "otel",
crate_name = "codex_otel",
)

View File

@@ -0,0 +1,6 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "process-hardening",
crate_name = "codex_process_hardening",
)

View File

@@ -0,0 +1,6 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "protocol",
crate_name = "codex_protocol",
)

View File

@@ -0,0 +1,6 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "responses-api-proxy",
crate_name = "codex_responses_api_proxy",
)

View File

@@ -0,0 +1,6 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "rmcp-client",
crate_name = "codex_rmcp_client",
)

View File

@@ -0,0 +1,6 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "stdio-to-uds",
crate_name = "codex_stdio_to_uds",
)

17
codex-rs/tui/BUILD.bazel Normal file
View File

@@ -0,0 +1,17 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "tui",
crate_name = "codex_tui",
compile_data = glob(
include = ["**"],
exclude = [
"**/* *",
"BUILD.bazel",
"Cargo.toml",
],
allow_empty = True,
),
test_data_extra = glob(["src/**/snapshots/**"]),
integration_compile_data_extra = ["src/test_backend.rs"],
)

17
codex-rs/tui2/BUILD.bazel Normal file
View File

@@ -0,0 +1,17 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "tui2",
crate_name = "codex_tui2",
compile_data = glob(
include = ["**"],
exclude = [
"**/* *",
"BUILD.bazel",
"Cargo.toml",
],
allow_empty = True,
),
test_data_extra = glob(["src/**/snapshots/**"]),
integration_compile_data_extra = ["src/test_backend.rs"],
)

View File

@@ -0,0 +1,6 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "absolute-path",
crate_name = "codex_utils_absolute_path",
)

6
codex-rs/utils/cache/BUILD.bazel vendored Normal file
View File

@@ -0,0 +1,6 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "cache",
crate_name = "codex_utils_cache",
)

View File

@@ -0,0 +1,6 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "cargo-bin",
crate_name = "codex_utils_cargo_bin",
)

View File

@@ -0,0 +1,6 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "git",
crate_name = "codex_git",
)

View File

@@ -0,0 +1,6 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "image",
crate_name = "codex_utils_image",
)

View File

@@ -0,0 +1,6 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "json-to-toml",
crate_name = "codex_utils_json_to_toml",
)

View File

@@ -0,0 +1,6 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "pty",
crate_name = "codex_utils_pty",
)

View File

@@ -0,0 +1,6 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "readiness",
crate_name = "codex_utils_readiness",
)

View File

@@ -0,0 +1,6 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "string",
crate_name = "codex_utils_string",
)

View File

@@ -0,0 +1,11 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "windows-sandbox-rs",
crate_name = "codex_windows_sandbox",
build_script_data = [
"Cargo.toml",
"codex-windows-sandbox-setup.manifest",
],
crate_edition = "2021",
)

177
defs.bzl Normal file
View File

@@ -0,0 +1,177 @@
load("@crates//:data.bzl", "DEP_DATA")
load("@crates//:defs.bzl", "all_crate_deps")
load("@rules_platform//platform_data:defs.bzl", "platform_data")
load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_library", "rust_test")
load("@rules_rust//cargo/private:cargo_build_script_wrapper.bzl", "cargo_build_script")
PLATFORMS = [
"linux_arm64_musl",
"linux_amd64_musl",
"macos_amd64",
"macos_arm64",
"windows_amd64",
"windows_arm64",
]
def multiplatform_binaries(name, platforms = PLATFORMS):
for platform in platforms:
platform_data(
name = name + "_" + platform,
platform = "@toolchains_llvm_bootstrapped//platforms:" + platform,
target = name,
tags = ["manual"],
)
native.filegroup(
name = "release_binaries",
srcs = [name + "_" + platform for platform in platforms],
tags = ["manual"],
)
def codex_rust_crate(
name,
crate_name,
crate_features = [],
crate_srcs = None,
crate_edition = None,
build_script_data = [],
compile_data = [],
deps_extra = [],
integration_deps_extra = [],
integration_compile_data_extra = [],
test_data_extra = [],
test_tags = [],
extra_binaries = []):
"""Defines a Rust crate with library, binaries, and tests wired for Bazel + Cargo parity.
The macro mirrors Cargo conventions: it builds a library when `src/` exists,
wires build scripts, exports `CARGO_BIN_EXE_*` for integration tests, and
creates unit + integration test targets. Dependency buckets map to the
Cargo.lock resolution in `@crates`.
Args:
name: Bazel target name for the library, should be the directory name.
Example: `app-server`.
crate_name: Cargo crate name from Cargo.toml
Example: `codex_app_server`.
crate_features: Cargo features to enable for this crate.
Crates are only compiled in a single configuration across the workspace, i.e.
with all features in this list enabled. So use sparingly, and prefer to refactor
optional functionality to a separate crate.
crate_srcs: Optional explicit srcs; defaults to `src/**/*.rs`.
crate_edition: Rust edition override, if not default.
You probably don't want this, it's only here for a single caller.
build_script_data: Data files exposed to the build script at runtime.
compile_data: Non-Rust compile-time data for the library target.
deps_extra: Extra normal deps beyond @crates resolution.
Typically only needed when features add additional deps.
integration_deps_extra: Extra deps for integration tests only.
integration_compile_data_extra: Extra compile_data for integration tests.
test_data_extra: Extra runtime data for tests.
test_tags: Tags applied to unit + integration test targets.
Typically used to disable the sandbox, but see https://bazel.build/reference/be/common-definitions#common.tags
extra_binaries: Additional binary labels to surface as test data and
`CARGO_BIN_EXE_*` environment variables. These are only needed for binaries from a different crate.
"""
deps = all_crate_deps(normal = True) + deps_extra
dev_deps = all_crate_deps(normal_dev = True)
proc_macro_deps = all_crate_deps(proc_macro = True)
proc_macro_dev_deps = all_crate_deps(proc_macro_dev = True)
test_env = {
"INSTA_WORKSPACE_ROOT": ".",
"INSTA_SNAPSHOT_PATH": "src",
}
rustc_env = {
"BAZEL_PACKAGE": native.package_name(),
}
binaries = DEP_DATA.get(native.package_name())["binaries"]
# TODO(zbarsky): cargo_build_script support?
lib_srcs = crate_srcs or native.glob(["src/**/*.rs"], exclude = binaries.values(), allow_empty = True)
if native.glob(["build.rs"], allow_empty = True):
cargo_build_script(
name = name + "-build-script",
srcs = ["build.rs"],
deps = all_crate_deps(build = True),
proc_macro_deps = all_crate_deps(build_proc_macro = True),
data = build_script_data,
# Some build script deps sniff version-related env vars...
version = "0.0.0",
)
deps = deps + [name + "-build-script"]
if lib_srcs:
rust_library(
name = name,
crate_name = crate_name,
crate_features = crate_features,
deps = deps,
proc_macro_deps = proc_macro_deps,
compile_data = compile_data,
srcs = lib_srcs,
edition = crate_edition,
rustc_env = rustc_env,
visibility = ["//visibility:public"],
)
rust_test(
name = name + "-unit-tests",
crate = name,
env = test_env,
deps = deps + dev_deps,
proc_macro_deps = proc_macro_deps + proc_macro_dev_deps,
rustc_env = rustc_env,
data = test_data_extra,
tags = test_tags,
)
maybe_lib = [name]
else:
maybe_lib = []
sanitized_binaries = []
cargo_env = {}
for binary, main in binaries.items():
#binary = binary.replace("-", "_")
sanitized_binaries.append(binary)
cargo_env["CARGO_BIN_EXE_" + binary] = "$(rootpath :%s)" % binary
rust_binary(
name = binary,
crate_name = binary.replace("-", "_"),
crate_root = main,
deps = maybe_lib + deps,
proc_macro_deps = proc_macro_deps,
edition = crate_edition,
srcs = native.glob(["src/**/*.rs"]),
visibility = ["//visibility:public"],
)
for binary_label in extra_binaries:
sanitized_binaries.append(binary_label)
binary = Label(binary_label).name
cargo_env["CARGO_BIN_EXE_" + binary] = "$(rootpath %s)" % binary_label
for test in native.glob(["tests/*.rs"], allow_empty = True):
test_name = name + "-" + test.removeprefix("tests/").removesuffix(".rs").replace("/", "-")
if not test_name.endswith("-test"):
test_name += "-test"
rust_test(
name = test_name,
crate_root = test,
srcs = [test],
data = native.glob(["tests/**"], allow_empty = True) + sanitized_binaries + test_data_extra,
compile_data = native.glob(["tests/**"], allow_empty = True) + integration_compile_data_extra,
deps = maybe_lib + deps + dev_deps + integration_deps_extra,
proc_macro_deps = proc_macro_deps + proc_macro_dev_deps,
rustc_env = rustc_env,
env = test_env | cargo_env,
tags = test_tags,
)

View File

@@ -44,6 +44,15 @@ install:
test:
cargo nextest run --no-fail-fast
bazel-test:
bazel test //... --keep_going
bazel-remote-test:
bazel test //... --config=remote --platforms=//:rbe --keep_going
build-for-release:
bazel build //codex-rs/cli:release_binaries --config=remote
# Run the MCP server
mcp-server-run *args:
cargo run -p codex-mcp-server -- "$@"

0
patches/BUILD.bazel Normal file
View File

View File

@@ -0,0 +1,52 @@
diff --git a/toolchain/cc_toolchain.bzl b/toolchain/cc_toolchain.bzl
index 58da8ec..bf1fbdd 100644
--- a/toolchain/cc_toolchain.bzl
+++ b/toolchain/cc_toolchain.bzl
@@ -26,6 +26,7 @@ def cc_toolchain(name, tool_map):
cc_feature_set(
name = name + "_runtimes_only_known_features",
all_of = [
+ "//toolchain/features:archive_param_file",
# Always last (contains user_compile_flags and user_link_flags who should apply last).
"@rules_cc//cc/toolchains/args:experimental_replace_legacy_action_config_features",
],
@@ -45,6 +46,7 @@ def cc_toolchain(name, tool_map):
],
"@platforms//os:none": [],
}) + [
+ "//toolchain/features:archive_param_file",
"//toolchain/features/legacy:all_legacy_builtin_features",
# Always last (contains user_compile_flags and user_link_flags who should apply last).
"@rules_cc//cc/toolchains/args:experimental_replace_legacy_action_config_features",
@@ -54,6 +56,7 @@ def cc_toolchain(name, tool_map):
cc_feature_set(
name = name + "_runtimes_only_enabled_features",
all_of = [
+ "//toolchain/features:archive_param_file",
# Always last (contains user_compile_flags and user_link_flags who should apply last).
"@rules_cc//cc/toolchains/args:experimental_replace_legacy_action_config_features",
],
diff --git a/toolchain/features/BUILD.bazel b/toolchain/features/BUILD.bazel
index e787484..bccd45b 100644
--- a/toolchain/features/BUILD.bazel
+++ b/toolchain/features/BUILD.bazel
@@ -17,6 +17,11 @@ cc_feature(
overrides = "@rules_cc//cc/toolchains/features:static_link_cpp_runtimes",
)
+cc_feature(
+ name = "archive_param_file",
+ feature_name = "archive_param_file",
+)
+
cc_args(
name = "opt_link_flags",
actions = [
@@ -124,6 +129,7 @@ cc_feature(
cc_feature_set(
name = "all_non_legacy_builtin_features",
all_of = [
+ ":archive_param_file",
":opt",
":opt_stub",
":dbg",

34
patches/rules_rust.patch Normal file
View File

@@ -0,0 +1,34 @@
diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl
index a28ad50b7..af627fe50 100644
--- a/rust/private/rustc.bzl
+++ b/rust/private/rustc.bzl
@@ -2361,19 +2361,19 @@ def _get_make_link_flag_funcs(target_os, target_abi, use_direct_link_driver):
- callable: The function for producing link args.
- callable: The function for formatting link library names.
"""
+
+ get_lib_name = get_lib_name_default
+
if target_os == "windows":
- make_link_flags_windows_msvc = _make_link_flags_windows_msvc_direct if use_direct_link_driver else _make_link_flags_windows_msvc_indirect
- make_link_flags_windows_gnu = _make_link_flags_windows_gnu_direct if use_direct_link_driver else _make_link_flags_windows_gnu_indirect
- make_link_flags = make_link_flags_windows_msvc if target_abi == "msvc" else make_link_flags_windows_gnu
- get_lib_name = get_lib_name_for_windows
+ if target_abi == "msvc":
+ make_link_flags = _make_link_flags_windows_msvc_direct if use_direct_link_driver else _make_link_flags_windows_msvc_indirect
+ get_lib_name = get_lib_name_for_windows
+ else:
+ make_link_flags = _make_link_flags_windows_gnu_direct if use_direct_link_driver else _make_link_flags_windows_gnu_indirect
elif target_os.startswith(("mac", "darwin", "ios")):
- make_link_flags_darwin = _make_link_flags_darwin_direct if use_direct_link_driver else _make_link_flags_darwin_indirect
- make_link_flags = make_link_flags_darwin
- get_lib_name = get_lib_name_default
+ make_link_flags = _make_link_flags_darwin_direct if use_direct_link_driver else _make_link_flags_darwin_indirect
else:
- make_link_flags_default = _make_link_flags_default_direct if use_direct_link_driver else _make_link_flags_default_indirect
- make_link_flags = make_link_flags_default
- get_lib_name = get_lib_name_default
+ make_link_flags = _make_link_flags_default_direct if use_direct_link_driver else _make_link_flags_default_indirect
return (make_link_flags, get_lib_name)

View File

@@ -0,0 +1,27 @@
diff -uNr rust/platform/triple_mappings.bzl rust/platform/triple_mappings.bzl
--- a/rust/platform/triple_mappings.bzl
+++ b/rust/platform/triple_mappings.bzl
@@ -1,6 +1,7 @@
"""Helpers for constructing supported Rust platform triples"""
load("//rust/platform:triple.bzl", "triple")
+load("@@toolchains_llvm_bootstrapped+//constraints/libc:libc_versions.bzl", "DEFAULT_LIBC")
def _support(*, std = False, host_tools = False):
"""Identify the type of support an associated platform triple has.
@@ -334,10 +335,11 @@
all_abi_constraints = []
- # add constraints for MUSL static compilation and linking
- # to separate the MUSL from the non-MUSL toolchain on x86_64
- # if abi == "musl" and system == "linux" and arch == "x86_64":
- # all_abi_constraints.append("//rust/platform/constraints:musl_on")
+ if system == "linux":
+ if abi == "musl":
+ all_abi_constraints.append("@@toolchains_llvm_bootstrapped+//constraints/libc:musl")
+ elif abi.startswith("gnu"):
+ all_abi_constraints.append("@@toolchains_llvm_bootstrapped+//constraints/libc:{}".format(DEFAULT_LIBC))
# add constraints for iOS + watchOS simulator and device triples
if system in ["ios", "watchos"]:

View File

@@ -0,0 +1,58 @@
diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl
index af627fe50..d1c5142cb 100644
--- a/rust/private/rustc.bzl
+++ b/rust/private/rustc.bzl
@@ -560,9 +560,12 @@ def _symlink_for_ambiguous_lib(actions, toolchain, crate_info, lib):
# Take the absolute value of hash() since it could be negative.
path_hash = abs(hash(lib.path))
- lib_name = get_lib_name_for_windows(lib) if toolchain.target_os.startswith("windows") else get_lib_name_default(lib)
-
- if toolchain.target_os.startswith("windows"):
+ if toolchain.target_os.startswith("windows") and toolchain.target_abi == "msvc":
+ lib_name = get_lib_name_for_windows(lib)
+ else:
+ lib_name = get_lib_name_default(lib)
+
+ if toolchain.target_os.startswith("windows") and toolchain.target_abi == "msvc":
prefix = ""
extension = ".lib"
elif lib_name.endswith(".pic"):
@@ -1495,7 +1498,7 @@ def rustc_compile_action(
pdb_file = None
dsym_folder = None
if crate_info.type in ("cdylib", "bin") and not experimental_use_cc_common_link:
- if toolchain.target_os == "windows" and compilation_mode.strip_level == "none":
+ if toolchain.target_os == "windows" and toolchain.target_abi == "msvc" and compilation_mode.strip_level == "none":
pdb_file = ctx.actions.declare_file(crate_info.output.basename[:-len(crate_info.output.extension)] + "pdb", sibling = crate_info.output)
action_outputs.append(pdb_file)
elif toolchain.target_os in ["macos", "darwin"]:
@@ -1626,7 +1629,7 @@ def rustc_compile_action(
additional_linker_outputs = []
- if crate_info.type in ("cdylib", "bin") and cc_common.is_enabled(feature_configuration = feature_configuration, feature_name = "generate_pdb_file"):
+ if crate_info.type in ("cdylib", "bin") and toolchain.target_abi == "msvc" and cc_common.is_enabled(feature_configuration = feature_configuration, feature_name = "generate_pdb_file"):
pdb_file = ctx.actions.declare_file(crate_info.output.basename[:-len(crate_info.output.extension)] + "pdb", sibling = crate_info.output)
additional_linker_outputs.append(pdb_file)
@@ -2248,8 +2251,8 @@ def _portable_link_flags(lib, use_pic, ambiguous_libs, get_lib_name, for_windows
]
else:
return [
- "-lstatic=%s" % get_lib_name(artifact),
- "-Clink-arg=-l{}".format(artifact.basename),
+ "-lstatic=%s" % get_lib_name(artifact),
+ "-Clink-arg=-l{}".format(get_lib_name(artifact)),
]
else:
return [
@@ -2281,7 +2284,8 @@ def _make_link_flags_windows(make_link_flags_args, flavor_msvc, use_direct_drive
("-Clink-arg=%s--no-whole-archive" % prefix),
])
elif include_link_flags:
- ret.extend(_portable_link_flags(lib, use_pic, ambiguous_libs, get_lib_name_for_windows, for_windows = True, flavor_msvc = flavor_msvc))
+ get_name_fn = get_lib_name_for_windows if flavor_msvc else get_lib_name_default
+ ret.extend(_portable_link_flags(lib, use_pic, ambiguous_libs, get_name_fn, for_windows = True, flavor_msvc = flavor_msvc))
_add_user_link_flags(ret, linker_input)
return ret

View File

@@ -0,0 +1,10 @@
diff --git a/src/lib.rs b/src/lib.rs
index 2d5a2a2..6e8c4cd 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,4 +1,4 @@
-#![doc = include_str!("../readme.md")]
+#![doc = "windows-link"]
#![no_std]
/// Defines an external function to import.