Align Bazel release builds with Cargo and fix platform compatibility (#51848)

## Why

Bazel release builds need Cargo-compatible Rust and native build-script settings. Platform-specific issues also include Windows command-line limits, MSVC runtime conflicts with V8's allocator, and a missing V8 `__clear_cache` symbol on ARM64 musl.

## What changed

- Add `--config=release` settings for thin LTO, four target codegen units, and disabled debug assertions and overflow checks, with separate settings for build tools.
- Add opt-in Cargo release profile support for build-script optimization and debug information, preserving explicit C/C++ flags. Give optimized Bubblewrap builds `-O3`, debug symbols, and enabled native assertions.
- Statically link the MSVC CRT for Rust and C/C++ target and host dependencies, and disable SQLite intrinsics on x64 MSVC to avoid startup crashes on older CPUs.
- Let the Rust process wrapper read its arguments from a multiline parameter file to avoid Windows command-line limits.
- Select the ARM64 musl V8 archive containing compiler-rt builtins and raise the Rust recursion limit to `256` in the app-server client, exec, and TUI entry points for release compilation.

GitOrigin-RevId: 8f61e62b8735c36209ee92e04a0e0339a70badc5
This commit is contained in:
Dmitriy Kovalenko
2026-10-07 22:10:23 +00:00
committed by copyberry
parent 6ea82c59c6
commit 745d865521
14 changed files with 257 additions and 24 deletions
+30
View File
@@ -11,6 +11,12 @@ startup --experimental_remote_repo_contents_cache
common --experimental_platform_in_output_dir
# Match Cargo's MSVC static CRT policy and V8's allocator linkage. Only the
# MSVC C/C++ toolchain consumes this feature; other toolchains ignore it.
# Include host dependencies so native build tools use the same runtime.
build --features=static_link_msvcrt
build --host_features=static_link_msvcrt
build --workspace_status_command=./scripts/workspace-status.sh
build:windows --workspace_status_command=./scripts/workspace-status.cmd
@@ -225,5 +231,29 @@ common:v8-target-arm64 --@v8//bazel/config:v8_target_cpu=arm64
# the matching runtime objects.
common:rusty-v8-upstream-libcxx --@v8//:v8_use_rusty_v8_custom_libcxx=True
# Release profile settings are also defined in codex-rs/Cargo.toml.
# Packaging extracts symbols before stripping; the Rust toolchains retain
# line tables and use split-debuginfo=off. opt mode selects Rust opt-level=3;
# Cargo release disables assertions/checks even if a smoke build overrides
# opt-level to 0, so these are explicit rather than inferred from optimization.
build:release --compilation_mode=opt
build:release --@rules_rust//rust/settings:lto=thin
build:release --@rules_rust//rust/settings:codegen_units=4
build:release --@rules_rust//rust/settings:extra_rustc_flag=-Cdebug-assertions=no
build:release --@rules_rust//rust/settings:extra_rustc_flag=-Coverflow-checks=no
# Match observed Cargo 1.95 build-tool commands: opt-level=0, assertions off,
# and rustc's non-incremental default of 16 codegen units (not target's 4).
build:release --@rules_rust//rust/settings:extra_exec_rustc_flag=-Copt-level=0
build:release --@rules_rust//rust/settings:extra_exec_rustc_flag=-Ccodegen-units=16
build:release --@rules_rust//rust/settings:extra_exec_rustc_flag=-Cdebug-assertions=no
build:release --@rules_rust//rust/settings:extra_exec_rustc_flag=-Coverflow-checks=no
# Compatibility: Cargo build scripts consume the Rust profile, including debug
# info, and choose their native optimization. Preserve explicit user C/C++ flags.
# Opt in for target and host dependencies; normal Bazel builds keep defaults.
build:release --features=cargo-release-profile
build:release --host_features=cargo-release-profile
# Bubblewrap and voice set release options on their own native rules.
# Optional per-user local overrides.
try-import %workspace%/user.bazelrc
+21 -2
View File
@@ -207,14 +207,16 @@ rules_rust.patch(
# Carry the OpenAI setup fix that makes build-script tools available
# through their runfiles after the rules_rs upgrade.
"//patches:rules_rust_build_script_tools_transition.patch",
# Opt-in release profile compatibility for native Cargo build scripts.
"//patches:rules_rust_release_build_script_profile.patch",
# Prefer Rust's direct linker for Windows/MSVC and keep hermetic
# LLVM's non-.lib runtime artifacts compatible with it.
"//patches:rules_rust_windows_msvc_direct_link_args.patch",
# Skip transient native-Windows linker outputs while consolidating
# dependency search paths.
"//patches:rules_rust_windows_process_wrapper_skip_temp_outputs.patch",
# Group build-script argument files to avoid Windows command-line limits.
"//patches:rules_rust_group_build_script_arg_files.patch",
# Spill wrapper arguments to a params file to avoid Windows command-line limits.
"//patches:rules_rust_process_wrapper_param_file.patch",
"//patches:rules_rust_windows_execroot_separators.patch",
"//patches:rules_rust_compiler_thp.patch",
],
@@ -292,9 +294,19 @@ nightly_rust.repository_set(
)
use_repo(nightly_rust, "rust_toolchains")
# Match Cargo's MSVC static CRT policy for target and host dependencies. V8's
# allocator shim must not also import the dynamic UCRT. Selecting by target
# triple applies to cross builds without affecting Windows GNU, Linux or macOS.
MSVC_RUSTC_FLAGS = {
"aarch64-pc-windows-msvc": ["-Ctarget-feature=+crt-static"],
"x86_64-pc-windows-msvc": ["-Ctarget-feature=+crt-static"],
}
toolchains = use_extension("@rules_rs//rs/toolchains:module_extension.bzl", "toolchains")
toolchains.toolchain(
edition = "2024",
extra_exec_rustc_flags = MSVC_RUSTC_FLAGS,
extra_rustc_flags = MSVC_RUSTC_FLAGS,
version = "1.95.0",
)
use_repo(toolchains, "default_rust_toolchains")
@@ -592,6 +604,13 @@ inject_repo(crate, "v8_targets")
llvm = use_extension("@llvm//extensions:llvm.bzl", "llvm")
use_repo(llvm, "llvm-project")
crate.annotation(
# Disable SQLite intrinsics that can crash at startup on older x64 Windows CPUs.
build_script_env_select = {
"x86_64-pc-windows-msvc": '{"LIBSQLITE3_FLAGS": "SQLITE_DISABLE_INTRINSIC"}',
},
crate = "libsqlite3-sys",
)
crate.annotation(
# Provide the hermetic SDK path so the build script doesn't try to invoke an unhermetic `xcrun --show-sdk-path`.
build_script_data = [
+4
View File
@@ -1,3 +1,7 @@
// Release builds exceed rustc's default query depth when laying out the async
// MessageProcessor::handle_initialized_client_request future.
#![recursion_limit = "256"]
//! Shared in-process app-server client facade for CLI surfaces.
//!
//! This crate wraps [`codex_app_server::in_process`] behind a single async API
+15 -1
View File
@@ -35,6 +35,11 @@ genrule(
visibility = ["//codex-rs/linux-sandbox:__pkg__"],
)
config_setting(
name = "optimized",
values = {"compilation_mode": "opt"},
)
cc_library(
name = "bwrap-ffi",
srcs = ["//codex-rs/vendor:bubblewrap_c_sources"],
@@ -45,7 +50,16 @@ cc_library(
copts = [
"-D_GNU_SOURCE",
"-Dmain=bwrap_main",
],
] + select({
# Cargo cc-rs uses release OPT_LEVEL=3 and DEBUG=true, without
# the C toolchain's default NDEBUG; retain native symbols for packaging.
":optimized": [
"-O3",
"-g",
"-UNDEBUG",
],
"//conditions:default": [],
}),
includes = ["."],
target_compatible_with = ["@platforms//os:linux"],
visibility = ["//visibility:private"],
+4
View File
@@ -1,3 +1,7 @@
// Release builds exceed rustc's default query depth when laying out the async
// MessageProcessor::handle_initialized_client_request future.
#![recursion_limit = "256"]
//! Entry-point for the `codex-exec` binary.
//!
//! When this CLI is invoked normally, it parses the standard `codex-exec` CLI
+4
View File
@@ -1,3 +1,7 @@
// Release builds exceed rustc's default query depth when laying out the async
// MessageProcessor::handle_initialized_client_request future.
#![recursion_limit = "256"]
use clap::Parser;
use codex_arg0::Arg0DispatchPaths;
use codex_arg0::arg0_dispatch_or_else;
+6 -7
View File
@@ -5,8 +5,9 @@ load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_library", "rust_proc_mac
load("//bazel/rules/testing:foreign_platform_binary.bzl", "foreign_platform_binary")
load("//bazel/rules/testing/wine:wine_runtime.bzl", "WINE_TEST_TARGET_COMPATIBLE_WITH", "wine_test_runtime")
# Match Cargo's Windows linker behavior so Bazel-built binaries and tests use
# the same stack reserve on both Windows ABIs and resolve UCRT imports on MSVC.
# Reserve 8 MiB of stack on Windows and statically link the CRT on MSVC.
# V8's x64 allocator shim defines malloc/free; importing them from dynamic UCRT
# as well replaces the import thunks during linking.
WINDOWS_GNULLVM_RUSTC_LINK_FLAGS = [
"-C",
"link-arg=-Wl,--stack,8388608", # 8 MiB
@@ -18,9 +19,7 @@ WINDOWS_RUSTC_LINK_FLAGS = select({
"-C",
"link-arg=/STACK:8388608", # 8 MiB
"-C",
"link-arg=/NODEFAULTLIB:libucrt.lib",
"-C",
"link-arg=ucrt.lib",
"target-feature=+crt-static",
],
"//conditions:default": [],
})
@@ -414,8 +413,8 @@ def codex_rust_crate(
crate_root = main,
deps = all_crate_deps() + maybe_deps + deps_extra,
edition = crate_edition,
# Keep per-binary Cargo link behavior scoped to the matching
# generated rust_binary instead of leaking it to sibling binaries.
# Scope each binary's compile data and linker flags to its generated
# rust_binary so they do not affect sibling binaries.
compile_data = binary_compile_data_extra.get(binary, []),
rustc_flags = rustc_flags_extra + binary_rustc_flags_extra.get(binary, []) + WINDOWS_RUSTC_LINK_FLAGS,
rustc_env = rustc_env,
+2 -1
View File
@@ -6,7 +6,8 @@ exports_files([
"llvm_windows_mingw_compat.patch",
"rules_rust_build_script_tools_transition.patch",
"rules_rust_compiler_thp.patch",
"rules_rust_group_build_script_arg_files.patch",
"rules_rust_process_wrapper_param_file.patch",
"rules_rust_release_build_script_profile.patch",
"rules_rust_windows_execroot_separators.patch",
"rules_rust_windows_msvc_direct_link_args.patch",
"rules_rust_windows_process_wrapper_skip_temp_outputs.patch",
@@ -1,12 +0,0 @@
diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl
--- a/rust/private/rustc.bzl
+++ b/rust/private/rustc.bzl
@@ -1125,5 +1125,7 @@
for build_env_file in build_env_files:
process_wrapper_flags.add("--env-file", build_env_file)
- process_wrapper_flags.add_all(build_flags_files, before_each = "--arg-file")
+ # The wrapper accepts multiple files per flag. Reduce native Windows
+ # command-line length without changing the files or their order.
+ process_wrapper_flags.add_all("--arg-file", build_flags_files)
@@ -0,0 +1,38 @@
diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl
--- a/rust/private/rustc.bzl
+++ b/rust/private/rustc.bzl
@@ -1121,6 +1121,9 @@
# Wrapper args first
process_wrapper_flags = ctx.actions.args()
+ # Build-script file paths can exceed Windows' command-line limit by themselves.
+ process_wrapper_flags.set_param_file_format("multiline")
+ process_wrapper_flags.use_param_file("@%s")
for build_env_file in build_env_files:
process_wrapper_flags.add("--env-file", build_env_file)
diff --git a/util/process_wrapper/options.rs b/util/process_wrapper/options.rs
--- a/util/process_wrapper/options.rs
+++ b/util/process_wrapper/options.rs
@@ -136,10 +136,17 @@
&mut require_explicit_unstable_features,
);
- let mut child_args = match flags
- .parse(env::args().collect())
- .map_err(OptionError::FlagError)?
- {
+ let mut args: Vec<String> = env::args().collect();
+ // Bazel may spill the leading wrapper arguments to a multiline params file.
+ // Read them verbatim; child rustc @files after `--` are handled separately.
+ if let Some(path) = args.get(1).and_then(|arg| arg.strip_prefix('@')) {
+ let contents = std::fs::read_to_string(path).map_err(|err| {
+ OptionError::Generic(format!("reading wrapper arguments from {path:?}: {err}"))
+ })?;
+ args.splice(1..2, contents.lines().map(str::to_owned));
+ }
+
+ let mut child_args = match flags.parse(args).map_err(OptionError::FlagError)? {
ParseOutcome::Help(help) => {
eprintln!("{help}");
exit(0);
@@ -0,0 +1,118 @@
--- a/cargo/private/cargo_build_script.bzl
+++ b/cargo/private/cargo_build_script.bzl
@@ -3,6 +3,7 @@
load("@apple_support//lib:apple_support.bzl", "apple_support")
load("@bazel_skylib//lib:paths.bzl", "paths")
load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
+load("@platforms//host:constraints.bzl", "HOST_CONSTRAINTS")
load("@rules_cc//cc:action_names.bzl", "ACTION_NAMES")
load("@rules_cc//cc/common:cc_common.bzl", "cc_common")
load("//rust:defs.bzl", "rust_common")
@@ -11,6 +12,7 @@
# buildifier: disable=bzl-visibility
load(
"//rust/private:rustc.bzl",
+ "collect_extra_rustc_flags",
"get_compilation_mode_opts",
"get_linker_and_args",
)
@@ -23,6 +25,7 @@
"expand_dict_value_locations",
"find_cc_toolchain",
"find_toolchain",
+ "is_exec_configuration",
_name_to_crate_name = "name_to_crate_name",
)
@@ -66,6 +69,43 @@
},
executable = True,
)
+
+def _release_user_opts(ctx, flags):
+ # Bazel 9.2's default exec transition appends -g0 to host copts/cxxopts on
+ # non-Windows servers. Remove only that suffix; preserve explicit -g0.
+ # https://github.com/bazelbuild/bazel/blob/9.2.0/src/main/starlark/builtins_bzl/common/builtin_exec_platforms.bzl
+ if is_exec_configuration(ctx) and "@platforms//os:windows" not in HOST_CONSTRAINTS and flags and flags[-1] == "-g0":
+ return flags[:-1]
+ return flags
+
+def _cargo_profile_native_flags(flags):
+ # Let cc-rs/cmake derive optimization and debug info from Cargo's profile.
+ # Filter toolchain defaults only; explicit user flags are appended afterwards.
+ profile_defaults = [
+ "-O0",
+ "-O1",
+ "-O2",
+ "-O3",
+ "-Og",
+ "-Os",
+ "-Oz",
+ "-Ofast",
+ "-g0",
+ "-DNDEBUG",
+ "-DNDEBUG=1",
+ "/Od",
+ "/O1",
+ "/O2",
+ "/Ox",
+ "/Ob0",
+ "/Ob1",
+ "/Ob2",
+ "/Ob3",
+ "/Oi",
+ "/DNDEBUG",
+ "/DNDEBUG=1",
+ ]
+ return [flag for flag in flags if flag not in profile_defaults]
def get_cc_compile_args_and_env(cc_toolchain, feature_configuration):
"""Gather cc environment variables from the given `cc_toolchain`
@@ -469,6 +509,22 @@
env["CARGO_PKG_VERSION_PRE"] = patch[1] if len(patch) > 1 else ""
env["CARGO_PKG_VERSION"] = ctx.attr.version
+ cargo_release_profile = _feature_enabled(ctx, "cargo-release-profile")
+ if cargo_release_profile:
+ # Compatibility with Cargo release profiles that retain debug info:
+ # DEBUG describes symbol generation, not debug assertions. Read the
+ # consuming crate's configuration, not the build-script executable's.
+ debug_info = get_compilation_mode_opts(ctx, toolchain).debug_info
+ flags = collect_extra_rustc_flags(ctx, toolchain, None, "rlib")
+ for flag in flags:
+ # Accept both -Copt-level=N and the split -C opt-level=N spelling.
+ codegen = flag.removeprefix("-C").removeprefix("--codegen=")
+ if codegen.startswith("opt-level="):
+ env["OPT_LEVEL"] = codegen.removeprefix("opt-level=")
+ elif codegen.startswith("debuginfo="):
+ debug_info = codegen.removeprefix("debuginfo=")
+ env["DEBUG"] = "false" if debug_info in ["0", "none"] else "true"
+
# Pull in env vars which may be required for the cc_toolchain to work (e.g. on OSX, the SDK version).
# We hope that the linker env is sufficient for the whole cc_toolchain.
cc_toolchain, feature_configuration = find_cc_toolchain(ctx)
@@ -484,6 +540,13 @@
if cc_toolchain:
# MSVC requires INCLUDE to be set
cc_c_args, cc_cxx_args, cc_env = get_cc_compile_args_and_env(cc_toolchain, feature_configuration)
+ if cargo_release_profile:
+ # Preserve SDK, ABI, CRT and security options. User copts and crate
+ # build_script_env overrides retain precedence over these defaults.
+ copts = _release_user_opts(ctx, ctx.fragments.cpp.copts)
+ cxxopts = _release_user_opts(ctx, ctx.fragments.cpp.cxxopts)
+ cc_c_args = _cargo_profile_native_flags(cc_c_args) + copts + ctx.fragments.cpp.conlyopts
+ cc_cxx_args = _cargo_profile_native_flags(cc_cxx_args) + copts + cxxopts
include = cc_env.get("INCLUDE")
if include:
if toolchain.exec_triple.str.find("windows") > 0:
@@ -837,6 +900,10 @@
"_cargo_manifest_dir_filename_suffixes_to_retain": attr.label(
default = Label("//cargo/settings:cargo_manifest_dir_filename_suffixes_to_retain"),
),
+ "_extra_exec_rustc_flag": attr.label(default = Label("//rust/settings:extra_exec_rustc_flag")),
+ "_extra_exec_rustc_flags": attr.label(default = Label("//rust/settings:extra_exec_rustc_flags")),
+ "_extra_rustc_flag": attr.label(default = Label("//rust/settings:extra_rustc_flag")),
+ "_extra_rustc_flags": attr.label(default = Label("//rust/settings:extra_rustc_flags")),
"_debug_std_streams_output_group": attr.label(
default = Label("//cargo/settings:debug_std_streams_output_group"),
),
+3 -1
View File
@@ -134,7 +134,9 @@ alias(
"@rules_rs//rs/platforms/config:aarch64-pc-windows-gnullvm": ":v8_150_4_0_aarch64_pc_windows_gnullvm",
"@rules_rs//rs/platforms/config:aarch64-pc-windows-msvc": ":v8_150_4_0_aarch64_pc_windows_msvc",
"@rules_rs//rs/platforms/config:aarch64-unknown-linux-gnu": ":v8_150_4_0_aarch64_unknown_linux_gnu_bazel",
":platform_aarch64_unknown_linux_musl": ":v8_150_4_0_aarch64_unknown_linux_musl_release_base",
# Rust's compiler-builtins archive does not supply V8's __clear_cache.
# Use the merged archive containing the ARM64 compiler-rt builtins.
":platform_aarch64_unknown_linux_musl": ":v8_150_4_0_aarch64_unknown_linux_musl_release",
"@rules_rs//rs/platforms/config:x86_64-apple-darwin": ":v8_150_4_0_x86_64_apple_darwin_bazel",
"@rules_rs//rs/platforms/config:x86_64-pc-windows-gnullvm": ":v8_150_4_0_x86_64_pc_windows_gnullvm",
"@rules_rs//rs/platforms/config:x86_64-pc-windows-msvc": ":v8_150_4_0_x86_64_pc_windows_msvc",
+11
View File
@@ -175,6 +175,11 @@ filegroup(
tags = ["manual"],
)
config_setting(
name = "optimized",
values = {"compilation_mode": "opt"},
)
# Configure probes execute target binaries: constrain each action's host too.
_NATIVE_PLATFORMS = [
("macos", "aarch64", "apple-darwin"),
@@ -198,6 +203,12 @@ _NATIVE_PLATFORMS = [
native_prefix(
name = "native_prefix_" + os + "_" + cpu,
archives = [":archives"],
# Preserve the native recipe's Meson/CMake release optimization after
# toolchain flags, without optimizing unrelated native targets or tools.
copts = select({
":optimized": ["-O3"],
"//conditions:default": [],
}),
exec_compatible_with = [
"@platforms//os:" + os,
"@platforms//cpu:" + cpu,
+1
View File
@@ -92,6 +92,7 @@ native_prefix = rule(
implementation = _native_prefix_impl,
attrs = {
"archives": attr.label_list(allow_files = True, mandatory = True),
"copts": attr.string_list(),
"target": attr.string(mandatory = True),
"_driver": attr.label(default = "//third_party/voice:bazel_native.py", allow_single_file = True),
"_recipe": attr.label(default = "//third_party/voice:native_recipe"),