diff --git a/.github/workflows/rust-release.yml b/.github/workflows/rust-release.yml index 0044b864c7..19a549bcca 100644 --- a/.github/workflows/rust-release.yml +++ b/.github/workflows/rust-release.yml @@ -95,7 +95,7 @@ jobs: sudo apt install -y musl-tools pkg-config - name: Cargo build - run: cargo build --target ${{ matrix.target }} --release --bin codex + run: cargo build --target ${{ matrix.target }} --release --bin codex --bin apply-patch - name: Stage artifacts shell: bash @@ -105,8 +105,10 @@ jobs: if [[ "${{ matrix.runner }}" == windows* ]]; then cp target/${{ matrix.target }}/release/codex.exe "$dest/codex-${{ matrix.target }}.exe" + cp target/${{ matrix.target }}/release/apply-patch.exe "$dest/apply-patch-${{ matrix.target }}.exe" else cp target/${{ matrix.target }}/release/codex "$dest/codex-${{ matrix.target }}" + cp target/${{ matrix.target }}/release/apply-patch "$dest/apply-patch-${{ matrix.target }}" fi - name: Compress artifacts diff --git a/codex-cli/bin/codex.js b/codex-cli/bin/codex.js index b22c5180c0..81b59e48ea 100755 --- a/codex-cli/bin/codex.js +++ b/codex-cli/bin/codex.js @@ -2,6 +2,9 @@ // Unified entry point for the Codex CLI. import path from "path"; +import os from "os"; +import fs from "fs"; +import { createRequire } from "module"; import { fileURLToPath } from "url"; // __dirname equivalent in ESM @@ -56,7 +59,9 @@ if (!targetTriple) { throw new Error(`Unsupported platform: ${platform} (${arch})`); } -const binaryPath = path.join(__dirname, "..", "bin", `codex-${targetTriple}`); +const pkgRoot = path.join(__dirname, ".."); +const pkgBinDir = path.join(pkgRoot, "bin"); +const binaryPath = path.join(pkgBinDir, `codex-${targetTriple}`); // Use an asynchronous spawn instead of spawnSync so that Node is able to // respond to signals (e.g. Ctrl-C / SIGINT) while the native binary is @@ -93,10 +98,35 @@ function getUpdatedPath(newDirs) { } const additionalDirs = []; +// 1) Make packaged bin directory available on PATH for any helper binaries. +additionalDirs.push(pkgBinDir); const rgDir = await resolveRgDir(); if (rgDir) { additionalDirs.push(rgDir); } +// 2) Ensure an `apply_patch` helper exists in $CODEX_HOME// and add that directory to PATH. +try { + const require = createRequire(import.meta.url); + // Load package.json to read the version string. + const { version } = require("../package.json"); + const codexHome = process.env.CODEX_HOME || path.join(os.homedir(), ".codex"); + const versionDir = path.join(codexHome, version); + fs.mkdirSync(versionDir, { recursive: true }); + const isWindows = platform === "win32"; + const destName = isWindows ? "apply_patch.exe" : "apply_patch"; + const destPath = path.join(versionDir, destName); + const srcPath = path.join(pkgBinDir, `apply-patch-${targetTriple}`); + // Only copy if missing; keep it simple and fast. + if (!fs.existsSync(destPath)) { + fs.copyFileSync(srcPath, destPath); + if (!isWindows) { + fs.chmodSync(destPath, 0o755); + } + } + additionalDirs.push(versionDir); +} catch { + // Best-effort: if anything fails, continue without the helper. +} const updatedPath = getUpdatedPath(additionalDirs); const child = spawn(binaryPath, process.argv.slice(2), { diff --git a/codex-cli/scripts/install_native_deps.sh b/codex-cli/scripts/install_native_deps.sh index 6cf2faafc8..5463b6606b 100755 --- a/codex-cli/scripts/install_native_deps.sh +++ b/codex-cli/scripts/install_native_deps.sh @@ -75,17 +75,27 @@ gh run download --dir "$ARTIFACTS_DIR" --repo openai/codex "$WORKFLOW_ID" # x64 Linux zstd -d "$ARTIFACTS_DIR/x86_64-unknown-linux-musl/codex-x86_64-unknown-linux-musl.zst" \ -o "$BIN_DIR/codex-x86_64-unknown-linux-musl" +zstd -d "$ARTIFACTS_DIR/x86_64-unknown-linux-musl/apply-patch-x86_64-unknown-linux-musl.zst" \ + -o "$BIN_DIR/apply-patch-x86_64-unknown-linux-musl" # ARM64 Linux zstd -d "$ARTIFACTS_DIR/aarch64-unknown-linux-musl/codex-aarch64-unknown-linux-musl.zst" \ -o "$BIN_DIR/codex-aarch64-unknown-linux-musl" +zstd -d "$ARTIFACTS_DIR/aarch64-unknown-linux-musl/apply-patch-aarch64-unknown-linux-musl.zst" \ + -o "$BIN_DIR/apply-patch-aarch64-unknown-linux-musl" # x64 macOS zstd -d "$ARTIFACTS_DIR/x86_64-apple-darwin/codex-x86_64-apple-darwin.zst" \ -o "$BIN_DIR/codex-x86_64-apple-darwin" +zstd -d "$ARTIFACTS_DIR/x86_64-apple-darwin/apply-patch-x86_64-apple-darwin.zst" \ + -o "$BIN_DIR/apply-patch-x86_64-apple-darwin" # ARM64 macOS zstd -d "$ARTIFACTS_DIR/aarch64-apple-darwin/codex-aarch64-apple-darwin.zst" \ -o "$BIN_DIR/codex-aarch64-apple-darwin" +zstd -d "$ARTIFACTS_DIR/aarch64-apple-darwin/apply-patch-aarch64-apple-darwin.zst" \ + -o "$BIN_DIR/apply-patch-aarch64-apple-darwin" # x64 Windows zstd -d "$ARTIFACTS_DIR/x86_64-pc-windows-msvc/codex-x86_64-pc-windows-msvc.exe.zst" \ -o "$BIN_DIR/codex-x86_64-pc-windows-msvc.exe" +zstd -d "$ARTIFACTS_DIR/x86_64-pc-windows-msvc/apply-patch-x86_64-pc-windows-msvc.exe.zst" \ + -o "$BIN_DIR/apply-patch-x86_64-pc-windows-msvc.exe" echo "Installed native dependencies into $BIN_DIR" diff --git a/codex-rs/apply-patch/Cargo.toml b/codex-rs/apply-patch/Cargo.toml index 622f53ce71..49f250a02d 100644 --- a/codex-rs/apply-patch/Cargo.toml +++ b/codex-rs/apply-patch/Cargo.toml @@ -6,6 +6,10 @@ version = { workspace = true } [lib] name = "codex_apply_patch" path = "src/lib.rs" + +[[bin]] +name = "apply-patch" +path = "src/main.rs" [lints] workspace = true diff --git a/codex-rs/apply-patch/src/main.rs b/codex-rs/apply-patch/src/main.rs new file mode 100644 index 0000000000..ca71e97aaa --- /dev/null +++ b/codex-rs/apply-patch/src/main.rs @@ -0,0 +1,40 @@ +use std::io::Write; +use std::process::ExitCode; + +fn main() -> ExitCode { + // Expect exactly one argument: the full apply_patch payload. + let mut args = std::env::args_os(); + // argv[0] + let _argv0 = args.next(); + + let patch_arg = match args.next() { + Some(arg) => match arg.into_string() { + Ok(s) => s, + Err(_) => { + eprintln!("Error: apply-patch requires a UTF-8 PATCH argument."); + return ExitCode::from(1); + } + }, + None => { + eprintln!("Usage: apply-patch ''"); + return ExitCode::from(2); + } + }; + + // Refuse extra args to avoid ambiguity. + if args.next().is_some() { + eprintln!("Error: apply-patch accepts exactly one argument."); + return ExitCode::from(2); + } + + let mut stdout = std::io::stdout(); + let mut stderr = std::io::stderr(); + match codex_apply_patch::apply_patch(&patch_arg, &mut stdout, &mut stderr) { + Ok(()) => { + // Flush to ensure output ordering when used in pipelines. + let _ = stdout.flush(); + ExitCode::from(0) + } + Err(_) => ExitCode::from(1), + } +}