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 { 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() )) }