diff --git a/.github/scripts/install-musl-build-tools.sh b/.github/scripts/install-musl-build-tools.sh index 06b9a14fa..bd392b854 100644 --- a/.github/scripts/install-musl-build-tools.sh +++ b/.github/scripts/install-musl-build-tools.sh @@ -17,7 +17,7 @@ if [[ -n "${APT_INSTALL_ARGS:-}" ]]; then fi sudo apt-get update "${apt_update_args[@]}" -sudo apt-get install -y "${apt_install_args[@]}" musl-tools pkg-config g++ clang libc++-dev libc++abi-dev lld +sudo apt-get install -y "${apt_install_args[@]}" musl-tools pkg-config libcap-dev g++ clang libc++-dev libc++abi-dev lld case "${TARGET}" in x86_64-unknown-linux-musl) @@ -59,7 +59,19 @@ set -euo pipefail args=() skip_next=0 +pending_include=0 for arg in "\$@"; do + if [[ "\${pending_include}" -eq 1 ]]; then + pending_include=0 + if [[ "\${arg}" == /usr/include || "\${arg}" == /usr/include/* ]]; then + # Keep host-only headers available, but after the target sysroot headers. + args+=("-idirafter" "\${arg}") + else + args+=("-I" "\${arg}") + fi + continue + fi + if [[ "\${skip_next}" -eq 1 ]]; then skip_next=0 continue @@ -77,6 +89,15 @@ for arg in "\$@"; do fi continue ;; + -I) + pending_include=1 + continue + ;; + -I/usr/include|-I/usr/include/*) + # Avoid making glibc headers win over musl headers. + args+=("-idirafter" "\${arg#-I}") + continue + ;; -Wp,-U_FORTIFY_SOURCE) # aws-lc-sys emits this GCC preprocessor forwarding form in debug # builds, but zig cc expects the define flag directly. @@ -95,7 +116,19 @@ set -euo pipefail args=() skip_next=0 +pending_include=0 for arg in "\$@"; do + if [[ "\${pending_include}" -eq 1 ]]; then + pending_include=0 + if [[ "\${arg}" == /usr/include || "\${arg}" == /usr/include/* ]]; then + # Keep host-only headers available, but after the target sysroot headers. + args+=("-idirafter" "\${arg}") + else + args+=("-I" "\${arg}") + fi + continue + fi + if [[ "\${skip_next}" -eq 1 ]]; then skip_next=0 continue @@ -113,6 +146,15 @@ for arg in "\$@"; do fi continue ;; + -I) + pending_include=1 + continue + ;; + -I/usr/include|-I/usr/include/*) + # Avoid making glibc headers win over musl headers. + args+=("-idirafter" "\${arg#-I}") + continue + ;; -Wp,-U_FORTIFY_SOURCE) # aws-lc-sys emits this GCC forwarding form in debug builds; zig c++ # expects the define flag directly. @@ -175,3 +217,13 @@ echo "${cargo_linker_var}=${musl_linker}" >> "$GITHUB_ENV" echo "CMAKE_C_COMPILER=${cc}" >> "$GITHUB_ENV" echo "CMAKE_CXX_COMPILER=${cxx}" >> "$GITHUB_ENV" echo "CMAKE_ARGS=-DCMAKE_HAVE_THREADS_LIBRARY=1 -DCMAKE_USE_PTHREADS_INIT=1 -DCMAKE_THREAD_LIBS_INIT=-pthread -DTHREADS_PREFER_PTHREAD_FLAG=ON" >> "$GITHUB_ENV" + +# Allow pkg-config resolution during cross-compilation. +echo "PKG_CONFIG_ALLOW_CROSS=1" >> "$GITHUB_ENV" + +if [[ -n "${sysroot}" && "${sysroot}" != "/" ]]; then + echo "PKG_CONFIG_SYSROOT_DIR=${sysroot}" >> "$GITHUB_ENV" + pkg_config_sysroot_var="PKG_CONFIG_SYSROOT_DIR_${TARGET}" + pkg_config_sysroot_var="${pkg_config_sysroot_var//-/_}" + echo "${pkg_config_sysroot_var}=${sysroot}" >> "$GITHUB_ENV" +fi diff --git a/.github/workflows/bazel.yml b/.github/workflows/bazel.yml index 65b82c199..292e549b4 100644 --- a/.github/workflows/bazel.yml +++ b/.github/workflows/bazel.yml @@ -100,7 +100,6 @@ jobs: - name: bazel test //... env: BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }} - CODEX_BWRAP_ENABLE_FFI: ${{ contains(matrix.target, 'unknown-linux') && '1' || '0' }} shell: bash run: | bazel_args=( diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index 72ce00522..436759cea 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -99,9 +99,6 @@ jobs: USE_SCCACHE: ${{ startsWith(matrix.runner, 'windows') && 'false' || 'true' }} CARGO_INCREMENTAL: "0" SCCACHE_CACHE_SIZE: 10G - # Keep cargo-based CI independent of system bwrap build deps. - # The bwrap FFI path is validated in Bazel workflows. - CODEX_BWRAP_ENABLE_FFI: "0" strategy: fail-fast: false @@ -178,14 +175,18 @@ jobs: steps: - uses: actions/checkout@v6 - - name: Install UBSan runtime (musl) - if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl' }} + - name: Install Linux build dependencies + if: ${{ runner.os == 'Linux' }} shell: bash run: | set -euo pipefail if command -v apt-get >/dev/null 2>&1; then sudo apt-get update -y - sudo DEBIAN_FRONTEND=noninteractive apt-get install -y libubsan1 + packages=(pkg-config libcap-dev) + if [[ "${{ matrix.target }}" == 'x86_64-unknown-linux-musl' || "${{ matrix.target }}" == 'aarch64-unknown-linux-musl' ]]; then + packages+=(libubsan1) + fi + sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends "${packages[@]}" fi - uses: dtolnay/rust-toolchain@1.93 with: @@ -447,9 +448,6 @@ jobs: USE_SCCACHE: ${{ startsWith(matrix.runner, 'windows') && 'false' || 'true' }} CARGO_INCREMENTAL: "0" SCCACHE_CACHE_SIZE: 10G - # Keep cargo-based CI independent of system bwrap build deps. - # The bwrap FFI path is validated in Bazel workflows. - CODEX_BWRAP_ENABLE_FFI: "0" strategy: fail-fast: false @@ -485,6 +483,15 @@ jobs: steps: - uses: actions/checkout@v6 + - name: Install Linux build dependencies + if: ${{ runner.os == 'Linux' }} + shell: bash + run: | + set -euo pipefail + if command -v apt-get >/dev/null 2>&1; then + sudo apt-get update -y + sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends pkg-config libcap-dev + fi # Some integration tests rely on DotSlash being installed. # See https://github.com/openai/codex/pull/7617. - name: Install DotSlash diff --git a/.github/workflows/rust-release.yml b/.github/workflows/rust-release.yml index 5e0d7f9ac..34870f303 100644 --- a/.github/workflows/rust-release.yml +++ b/.github/workflows/rust-release.yml @@ -57,7 +57,6 @@ jobs: run: working-directory: codex-rs env: - CODEX_BWRAP_ENABLE_FFI: ${{ contains(matrix.target, 'unknown-linux') && '1' || '0' }} CARGO_PROFILE_RELEASE_LTO: ${{ contains(github.ref_name, '-alpha') && 'thin' || 'fat' }} strategy: diff --git a/.github/workflows/sdk.yml b/.github/workflows/sdk.yml index 60c14561a..ec67cd187 100644 --- a/.github/workflows/sdk.yml +++ b/.github/workflows/sdk.yml @@ -13,6 +13,13 @@ jobs: - name: Checkout repository uses: actions/checkout@v6 + - name: Install Linux bwrap build dependencies + shell: bash + run: | + set -euo pipefail + sudo apt-get update -y + sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends pkg-config libcap-dev + - name: Setup pnpm uses: pnpm/action-setup@v4 with: diff --git a/MODULE.bazel b/MODULE.bazel index f12eeb888..35dd57500 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -96,6 +96,7 @@ crate.annotation( inject_repo(crate, "zstd") bazel_dep(name = "bzip2", version = "1.0.8.bcr.3") +bazel_dep(name = "libcap", version = "2.27.bcr.1") crate.annotation( crate = "bzip2-sys", diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 549c2817a..98efd3ced 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -82,6 +82,8 @@ "https://bcr.bazel.build/modules/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075", "https://bcr.bazel.build/modules/jsoncpp/1.9.6/MODULE.bazel": "2f8d20d3b7d54143213c4dfc3d98225c42de7d666011528dc8fe91591e2e17b0", "https://bcr.bazel.build/modules/jsoncpp/1.9.6/source.json": "a04756d367a2126c3541682864ecec52f92cdee80a35735a3cb249ce015ca000", + "https://bcr.bazel.build/modules/libcap/2.27.bcr.1/MODULE.bazel": "7c034d7a4d92b2293294934377f5d1cbc88119710a11079fa8142120f6f08768", + "https://bcr.bazel.build/modules/libcap/2.27.bcr.1/source.json": "3b116cbdbd25a68ffb587b672205f6d353a4c19a35452e480d58fc89531e0a10", "https://bcr.bazel.build/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902", "https://bcr.bazel.build/modules/nlohmann_json/3.6.1/MODULE.bazel": "6f7b417dcc794d9add9e556673ad25cb3ba835224290f4f848f8e2db1e1fca74", "https://bcr.bazel.build/modules/nlohmann_json/3.6.1/source.json": "f448c6e8963fdfa7eb831457df83ad63d3d6355018f6574fb017e8169deb43a9", @@ -204,6 +206,8 @@ "https://bcr.bazel.build/modules/rules_swift/2.4.0/MODULE.bazel": "1639617eb1ede28d774d967a738b4a68b0accb40650beadb57c21846beab5efd", "https://bcr.bazel.build/modules/rules_swift/3.1.2/MODULE.bazel": "72c8f5cf9d26427cee6c76c8e3853eb46ce6b0412a081b2b6db6e8ad56267400", "https://bcr.bazel.build/modules/rules_swift/3.1.2/source.json": "e85761f3098a6faf40b8187695e3de6d97944e98abd0d8ce579cb2daf6319a66", + "https://bcr.bazel.build/modules/sed/4.9.bcr.3/MODULE.bazel": "3aca45895b85b6ef65366cc12a45217ba6870f8931d2d62e09c99c772d9736ab", + "https://bcr.bazel.build/modules/sed/4.9.bcr.3/source.json": "31c0cf4c135ed3fa58298cd7bcfd4301c54ea4cf59d7c4e2ea0a180ce68eb34f", "https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8", "https://bcr.bazel.build/modules/stardoc/0.5.3/MODULE.bazel": "c7f6948dae6999bf0db32c1858ae345f112cacf98f174c7a8bb707e41b974f1c", "https://bcr.bazel.build/modules/stardoc/0.6.2/MODULE.bazel": "7060193196395f5dd668eda046ccbeacebfd98efc77fed418dbe2b82ffaa39fd", diff --git a/codex-rs/linux-sandbox/BUILD.bazel b/codex-rs/linux-sandbox/BUILD.bazel index aeef302ab..87ca8ba06 100644 --- a/codex-rs/linux-sandbox/BUILD.bazel +++ b/codex-rs/linux-sandbox/BUILD.bazel @@ -1,6 +1,36 @@ +load("@rules_cc//cc:defs.bzl", "cc_library") load("//:defs.bzl", "codex_rust_crate") codex_rust_crate( name = "linux-sandbox", crate_name = "codex_linux_sandbox", + # Bazel wires vendored bubblewrap + libcap via :vendored-bwrap-ffi below + # and sets vendored_bwrap_available explicitly, so we skip Cargo's + # build.rs in Bazel builds. + build_script_enabled = False, + deps_extra = select({ + "@platforms//os:linux": [":vendored-bwrap-ffi"], + "//conditions:default": [], + }), + rustc_flags_extra = select({ + "@platforms//os:linux": ["--cfg=vendored_bwrap_available"], + "//conditions:default": [], + }), +) + +cc_library( + name = "vendored-bwrap-ffi", + srcs = ["//codex-rs/vendor:bubblewrap_c_sources"], + hdrs = [ + "config.h", + "//codex-rs/vendor:bubblewrap_headers", + ], + copts = [ + "-D_GNU_SOURCE", + "-Dmain=bwrap_main", + ], + includes = ["."], + deps = ["@libcap//:libcap"], + target_compatible_with = ["@platforms//os:linux"], + visibility = ["//visibility:private"], ) diff --git a/codex-rs/linux-sandbox/build.rs b/codex-rs/linux-sandbox/build.rs index 6b73e0e21..700aade40 100644 --- a/codex-rs/linux-sandbox/build.rs +++ b/codex-rs/linux-sandbox/build.rs @@ -5,8 +5,10 @@ use std::path::PathBuf; fn main() { // Tell rustc/clippy that this is an expected cfg value. println!("cargo:rustc-check-cfg=cfg(vendored_bwrap_available)"); - println!("cargo:rerun-if-env-changed=CODEX_BWRAP_ENABLE_FFI"); 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"); // Rebuild if the vendored bwrap sources change. let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap_or_default()); @@ -33,15 +35,8 @@ fn main() { return; } - // Opt-in: do not attempt to fetch/compile bwrap unless explicitly enabled. - let enable_ffi = matches!(env::var("CODEX_BWRAP_ENABLE_FFI"), Ok(value) if value == "1"); - if !enable_ffi { - return; - } - if let Err(err) = try_build_vendored_bwrap() { - // Keep normal builds working even if the experiment fails. - println!("cargo:warning=build-time bubblewrap disabled: {err}"); + panic!("failed to compile vendored bubblewrap for Linux target: {err}"); } } @@ -50,7 +45,6 @@ fn try_build_vendored_bwrap() -> Result<(), String> { 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() .probe("libcap") .map_err(|err| format!("libcap not available via pkg-config: {err}"))?; @@ -75,9 +69,10 @@ fn try_build_vendored_bwrap() -> Result<(), String> { .define("_GNU_SOURCE", None) // Rename `main` so we can call it via FFI. .define("main", Some("bwrap_main")); - for include_path in libcap.include_paths { - build.include(include_path); + // 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("build_time_bwrap"); diff --git a/codex-rs/linux-sandbox/config.h b/codex-rs/linux-sandbox/config.h new file mode 100644 index 000000000..f08aa6fce --- /dev/null +++ b/codex-rs/linux-sandbox/config.h @@ -0,0 +1,3 @@ +#pragma once + +#define PACKAGE_STRING "bubblewrap built at codex build-time" diff --git a/codex-rs/linux-sandbox/src/vendored_bwrap.rs b/codex-rs/linux-sandbox/src/vendored_bwrap.rs index c2816061e..3150a1d86 100644 --- a/codex-rs/linux-sandbox/src/vendored_bwrap.rs +++ b/codex-rs/linux-sandbox/src/vendored_bwrap.rs @@ -1,8 +1,7 @@ //! Build-time bubblewrap entrypoint. //! -//! This module is intentionally behind a build-time opt-in. When enabled, the -//! build script compiles bubblewrap's C sources and exposes a `bwrap_main` -//! symbol that we can call via FFI. +//! On Linux targets, the build script compiles bubblewrap's C sources and +//! exposes a `bwrap_main` symbol that we can call via FFI. #[cfg(vendored_bwrap_available)] mod imp { @@ -51,15 +50,12 @@ mod imp { /// Panics with a clear error when the build-time bwrap path is not enabled. pub(crate) fn run_vendored_bwrap_main(_argv: &[String]) -> libc::c_int { panic!( - "build-time bubblewrap is not available in this build.\n\ -Rebuild codex-linux-sandbox on Linux with CODEX_BWRAP_ENABLE_FFI=1.\n\ -Example:\n\ -- cd codex-rs && CODEX_BWRAP_ENABLE_FFI=1 cargo build -p codex-linux-sandbox\n\ -If this crate was already built without it, run:\n\ -- cargo clean -p codex-linux-sandbox\n\ -Notes:\n\ -- libcap headers must be available via pkg-config\n\ -- bubblewrap sources expected at codex-rs/vendor/bubblewrap (default)" + r#"build-time bubblewrap is not available in this build. +codex-linux-sandbox should always compile vendored bubblewrap on Linux targets. +Notes: +- ensure the target OS is Linux +- libcap headers must be available via pkg-config +- bubblewrap sources expected at codex-rs/vendor/bubblewrap (default)"# ); } diff --git a/codex-rs/vendor/BUILD.bazel b/codex-rs/vendor/BUILD.bazel new file mode 100644 index 000000000..a36bfbad0 --- /dev/null +++ b/codex-rs/vendor/BUILD.bazel @@ -0,0 +1,17 @@ +filegroup( + name = "bubblewrap_c_sources", + srcs = glob(["bubblewrap/*.c"]), + visibility = ["//visibility:public"], +) + +filegroup( + name = "bubblewrap_headers", + srcs = glob(["bubblewrap/*.h"]), + visibility = ["//visibility:public"], +) + +filegroup( + name = "bubblewrap_sources", + srcs = [":bubblewrap_c_sources", ":bubblewrap_headers"], + visibility = ["//visibility:public"], +) diff --git a/defs.bzl b/defs.bzl index 5800d996a..db09ef013 100644 --- a/defs.bzl +++ b/defs.bzl @@ -35,9 +35,11 @@ def codex_rust_crate( crate_srcs = None, crate_edition = None, proc_macro = False, + build_script_enabled = True, build_script_data = [], compile_data = [], lib_data_extra = [], + rustc_flags_extra = [], rustc_env = {}, deps_extra = [], integration_deps_extra = [], @@ -97,7 +99,7 @@ def codex_rust_crate( lib_srcs = crate_srcs or native.glob(["src/**/*.rs"], exclude = binaries.values(), allow_empty = True) - if native.glob(["build.rs"], allow_empty = True): + if build_script_enabled and native.glob(["build.rs"], allow_empty = True): cargo_build_script( name = name + "-build-script", srcs = ["build.rs"], @@ -122,6 +124,7 @@ def codex_rust_crate( data = lib_data_extra, srcs = lib_srcs, edition = crate_edition, + rustc_flags = rustc_flags_extra, rustc_env = rustc_env, visibility = ["//visibility:public"], ) @@ -132,6 +135,7 @@ def codex_rust_crate( env = test_env, deps = deps + dev_deps, proc_macro_deps = proc_macro_deps + proc_macro_dev_deps, + rustc_flags = rustc_flags_extra, rustc_env = rustc_env, data = test_data_extra, tags = test_tags, @@ -155,6 +159,7 @@ def codex_rust_crate( deps = maybe_lib + deps, proc_macro_deps = proc_macro_deps, edition = crate_edition, + rustc_flags = rustc_flags_extra, srcs = native.glob(["src/**/*.rs"]), visibility = ["//visibility:public"], ) @@ -177,6 +182,7 @@ def codex_rust_crate( 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_flags = rustc_flags_extra, rustc_env = rustc_env, env = test_env | cargo_env, tags = test_tags,