Files
codex/codex-rs/bwrap/build.rs
viyatb-oai 9766d3d51c fix(bwrap): emit libcap after standalone archive (#21285)
## Why

#21255 added the standalone `codex-bwrap` binary. In the Cargo build,
[`pkg_config::probe("libcap")`](a736cb55a2/codex-rs/bwrap/build.rs (L37-L39))
emits `-lcap` before
[`cc::Build::compile("standalone_bwrap")`](a736cb55a2/codex-rs/bwrap/build.rs (L50-L67))
adds the static bwrap archive. The Linux musl link then sees `-lcap
-lstandalone_bwrap`; because static archives are resolved left-to-right,
`cap_from_name` is still undefined once `standalone_bwrap` introduces
that reference.

The musl setup already builds `libcap.a` and exposes it through
[`libcap.pc`](a736cb55a2/.github/scripts/install-musl-build-tools.sh (L78-L88)),
so the failure is link ordering rather than a missing dependency.

## What changed

- probe `libcap` with `cargo_metadata(false)` so `pkg-config` does not
emit its link flags early
- emit the discovered `libcap` search paths and libraries after
`standalone_bwrap` is compiled, preserving the needed static-link order

## Verification

- `cargo test -p codex-bwrap`
- `cargo clippy -p codex-bwrap --all-targets`

The affected Linux musl release link is exercised by CI, which is the
path this fix targets.
2026-05-05 22:22:01 -07:00

107 lines
3.8 KiB
Rust

use std::env;
use std::path::Path;
use std::path::PathBuf;
fn main() {
println!("cargo:rustc-check-cfg=cfg(bwrap_available)");
println!("cargo:rerun-if-env-changed=CODEX_BWRAP_SOURCE_DIR");
println!("cargo:rerun-if-env-changed=PKG_CONFIG_ALLOW_CROSS");
println!("cargo:rerun-if-env-changed=PKG_CONFIG_PATH");
println!("cargo:rerun-if-env-changed=PKG_CONFIG_SYSROOT_DIR");
println!("cargo:rerun-if-env-changed=CODEX_SKIP_BWRAP_BUILD");
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap_or_default());
let vendor_dir = manifest_dir.join("../vendor/bubblewrap");
for source in ["bubblewrap.c", "bind-mount.c", "network.c", "utils.c"] {
println!(
"cargo:rerun-if-changed={}",
vendor_dir.join(source).display()
);
}
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
if target_os != "linux" || env::var_os("CODEX_SKIP_BWRAP_BUILD").is_some() {
return;
}
if let Err(err) = try_build_bwrap() {
panic!("failed to compile bubblewrap for Linux target: {err}");
}
}
fn try_build_bwrap() -> Result<(), String> {
let manifest_dir =
PathBuf::from(env::var("CARGO_MANIFEST_DIR").map_err(|err| err.to_string())?);
let out_dir = PathBuf::from(env::var("OUT_DIR").map_err(|err| err.to_string())?);
let src_dir = resolve_bwrap_source_dir(&manifest_dir)?;
let libcap = pkg_config::Config::new()
.cargo_metadata(false)
.probe("libcap")
.map_err(|err| format!("libcap not available via pkg-config: {err}"))?;
let config_h = out_dir.join("config.h");
std::fs::write(
&config_h,
r#"#pragma once
#define PACKAGE_STRING "bubblewrap built for Codex"
"#,
)
.map_err(|err| format!("failed to write {}: {err}", config_h.display()))?;
let mut build = cc::Build::new();
build
.file(src_dir.join("bubblewrap.c"))
.file(src_dir.join("bind-mount.c"))
.file(src_dir.join("network.c"))
.file(src_dir.join("utils.c"))
.include(&out_dir)
.include(&src_dir)
.define("_GNU_SOURCE", None)
// Rename `main` so the Rust wrapper can expose the Cargo-built binary.
.define("main", Some("bwrap_main"));
for include_path in libcap.include_paths {
// Use -idirafter so target sysroot headers win (musl cross builds),
// while still allowing libcap headers from the host toolchain.
build.flag(format!("-idirafter{}", include_path.display()));
}
build.compile("standalone_bwrap");
for link_path in libcap.link_paths {
println!("cargo:rustc-link-search=native={}", link_path.display());
}
for lib in libcap.libs {
println!("cargo:rustc-link-lib={lib}");
}
println!("cargo:rustc-cfg=bwrap_available");
Ok(())
}
/// Resolve the bubblewrap source directory used for build-time compilation.
///
/// Priority:
/// 1. `CODEX_BWRAP_SOURCE_DIR` points at an existing bubblewrap checkout.
/// 2. The vendored bubblewrap tree under `codex-rs/vendor/bubblewrap`.
fn resolve_bwrap_source_dir(manifest_dir: &Path) -> Result<PathBuf, String> {
if let Ok(path) = env::var("CODEX_BWRAP_SOURCE_DIR") {
let src_dir = PathBuf::from(path);
if src_dir.exists() {
return Ok(src_dir);
}
return Err(format!(
"CODEX_BWRAP_SOURCE_DIR was set but does not exist: {}",
src_dir.display()
));
}
let vendor_dir = manifest_dir.join("../vendor/bubblewrap");
if vendor_dir.exists() {
return Ok(vendor_dir);
}
Err(format!(
"expected vendored bubblewrap at {}, but it was not found.\n\
Set CODEX_BWRAP_SOURCE_DIR to an existing checkout or vendor bubblewrap under codex-rs/vendor.",
vendor_dir.display()
))
}