feat: introduce codex-utils-cargo-bin as an alternative to assert_cmd::Command (#8496)

This PR introduces a `codex-utils-cargo-bin` utility crate that
wraps/replaces our use of `assert_cmd::Command` and
`escargot::CargoBuild`.

As you can infer from the introduction of `buck_project_root()` in this
PR, I am attempting to make it possible to build Codex under
[Buck2](https://buck2.build) as well as `cargo`. With Buck2, I hope to
achieve faster incremental local builds (largely due to Buck2's
[dice](https://buck2.build/docs/insights_and_knowledge/modern_dice/)
build strategy, as well as benefits from its local build daemon) as well
as faster CI builds if we invest in remote execution and caching.

See
https://buck2.build/docs/getting_started/what_is_buck2/#why-use-buck2-key-advantages
for more details about the performance advantages of Buck2.

Buck2 enforces stronger requirements in terms of build and test
isolation. It discourages assumptions about absolute paths (which is key
to enabling remote execution). Because the `CARGO_BIN_EXE_*` environment
variables that Cargo provides are absolute paths (which
`assert_cmd::Command` reads), this is a problem for Buck2, which is why
we need this `codex-utils-cargo-bin` utility.

My WIP-Buck2 setup sets the `CARGO_BIN_EXE_*` environment variables
passed to a `rust_test()` build rule as relative paths.
`codex-utils-cargo-bin` will resolve these values to absolute paths,
when necessary.


---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/8496).
* #8498
* __->__ #8496
This commit is contained in:
Michael Bolin
2025-12-23 19:29:32 -08:00
committed by GitHub
parent 96a65ff0ed
commit e61bae12e3
37 changed files with 248 additions and 101 deletions

View File

@@ -56,7 +56,7 @@ tracing = { workspace = true }
tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] }
[dev-dependencies]
assert_cmd = { workspace = true }
codex-utils-cargo-bin = { workspace = true }
exec_server_test_support = { workspace = true }
maplit = { workspace = true }
pretty_assertions = { workspace = true }

View File

@@ -8,9 +8,9 @@ license.workspace = true
path = "lib.rs"
[dependencies]
assert_cmd = { workspace = true }
anyhow = { workspace = true }
codex-core = { workspace = true }
codex-utils-cargo-bin = { workspace = true }
rmcp = { workspace = true }
serde_json = { workspace = true }
tokio = { workspace = true }

View File

@@ -32,14 +32,18 @@ pub async fn create_transport<P>(
where
P: AsRef<Path>,
{
let mcp_executable = assert_cmd::Command::cargo_bin("codex-exec-mcp-server")?;
let execve_wrapper = assert_cmd::Command::cargo_bin("codex-execve-wrapper")?;
let bash = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("..")
.join("..")
.join("tests")
.join("suite")
.join("bash");
let mcp_executable = codex_utils_cargo_bin::cargo_bin("codex-exec-mcp-server")?;
let execve_wrapper = codex_utils_cargo_bin::cargo_bin("codex-execve-wrapper")?;
// `bash` requires a special lookup when running under Buck because it is a
// _resource_ rather than a binary target.
let bash = if let Some(root) = codex_utils_cargo_bin::buck_project_root()? {
root.join("codex-rs/exec-server/tests/suite/bash")
} else {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("..")
.join("suite")
.join("bash")
};
// Need to ensure the artifact associated with the bash DotSlash file is
// available before it is run in a read-only sandbox.
@@ -52,20 +56,19 @@ where
.await?;
assert!(status.success(), "dotslash fetch failed: {status:?}");
let transport =
TokioChildProcess::new(Command::new(mcp_executable.get_program()).configure(|cmd| {
cmd.arg("--bash").arg(bash);
cmd.arg("--execve").arg(execve_wrapper.get_program());
cmd.env("CODEX_HOME", codex_home.as_ref());
cmd.env("DOTSLASH_CACHE", dotslash_cache.as_ref());
let transport = TokioChildProcess::new(Command::new(&mcp_executable).configure(|cmd| {
cmd.arg("--bash").arg(bash);
cmd.arg("--execve").arg(&execve_wrapper);
cmd.env("CODEX_HOME", codex_home.as_ref());
cmd.env("DOTSLASH_CACHE", dotslash_cache.as_ref());
// Important: pipe stdio so rmcp can speak JSON-RPC over stdin/stdout
cmd.stdin(Stdio::piped());
cmd.stdout(Stdio::piped());
// Important: pipe stdio so rmcp can speak JSON-RPC over stdin/stdout
cmd.stdin(Stdio::piped());
cmd.stdout(Stdio::piped());
// Optional but very helpful while debugging:
cmd.stderr(Stdio::inherit());
}))?;
// Optional but very helpful while debugging:
cmd.stderr(Stdio::inherit());
}))?;
Ok(transport)
}

View File

@@ -142,11 +142,7 @@ prefix_rule(
}
fn ensure_codex_cli() -> Result<PathBuf> {
let codex_cli = PathBuf::from(
assert_cmd::Command::cargo_bin("codex")?
.get_program()
.to_os_string(),
);
let codex_cli = codex_utils_cargo_bin::cargo_bin("codex")?;
let metadata = codex_cli.metadata().with_context(|| {
format!(