Skip to content

Commit d0cf036

Browse files
authored
feat: include Windows binary of the CLI in the npm release (openai#2040)
To date, the build scripts in `codex-cli` still supported building the old TypeScript version of the Codex CLI to give Windows users something they can run, but we are just going to have them use the Rust version like everyone else, so: - updates `codex-cli/bin/codex.js` so that we run the native binary or throw if the target platform/arch is not supported (no more conditional usage based on `CODEX_RUST`, `use-native` file, etc.) - drops the `--native` flag from `codex-cli/scripts/stage_release.sh` and updates all the code paths to behave as if `--native` were passed (i.e., it is the only way to run it now) Tested this by running: ``` ./codex-cli/scripts/stage_rust_release.py --release-version 0.20.0-alpha.2 ```
1 parent 8a26ea0 commit d0cf036

File tree

4 files changed

+128
-201
lines changed

4 files changed

+128
-201
lines changed

codex-cli/bin/codex.js

Lines changed: 105 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -1,154 +1,123 @@
11
#!/usr/bin/env node
22
// Unified entry point for the Codex CLI.
3-
/*
4-
* Behavior
5-
* =========
6-
* 1. By default we import the JavaScript implementation located in
7-
* dist/cli.js.
8-
*
9-
* 2. Developers can opt-in to a pre-compiled Rust binary by setting the
10-
* environment variable CODEX_RUST to a truthy value (`1`, `true`, etc.).
11-
* When that variable is present we resolve the correct binary for the
12-
* current platform / architecture and execute it via child_process.
13-
*
14-
* If the CODEX_RUST=1 is specified and there is no native binary for the
15-
* current platform / architecture, an error is thrown.
16-
*/
173

18-
import fs from "fs";
194
import path from "path";
20-
import { fileURLToPath, pathToFileURL } from "url";
21-
22-
// Determine whether the user explicitly wants the Rust CLI.
5+
import { fileURLToPath } from "url";
236

247
// __dirname equivalent in ESM
258
const __filename = fileURLToPath(import.meta.url);
269
const __dirname = path.dirname(__filename);
2710

28-
// For the @native release of the Node module, the `use-native` file is added,
29-
// indicating we should default to the native binary. For other releases,
30-
// setting CODEX_RUST=1 will opt-in to the native binary, if included.
31-
const wantsNative = fs.existsSync(path.join(__dirname, "use-native")) ||
32-
(process.env.CODEX_RUST != null
33-
? ["1", "true", "yes"].includes(process.env.CODEX_RUST.toLowerCase())
34-
: false);
11+
const { platform, arch } = process;
12+
13+
let targetTriple = null;
14+
switch (platform) {
15+
case "linux":
16+
case "android":
17+
switch (arch) {
18+
case "x64":
19+
targetTriple = "x86_64-unknown-linux-musl";
20+
break;
21+
case "arm64":
22+
targetTriple = "aarch64-unknown-linux-musl";
23+
break;
24+
default:
25+
break;
26+
}
27+
break;
28+
case "darwin":
29+
switch (arch) {
30+
case "x64":
31+
targetTriple = "x86_64-apple-darwin";
32+
break;
33+
case "arm64":
34+
targetTriple = "aarch64-apple-darwin";
35+
break;
36+
default:
37+
break;
38+
}
39+
break;
40+
case "win32":
41+
switch (arch) {
42+
case "x64":
43+
targetTriple = "x86_64-pc-windows-msvc.exe";
44+
break;
45+
case "arm64":
46+
// We do not build this today, fall through...
47+
default:
48+
break;
49+
}
50+
break;
51+
default:
52+
break;
53+
}
3554

36-
// Try native binary if requested.
37-
if (wantsNative && process.platform !== 'win32') {
38-
const { platform, arch } = process;
55+
if (!targetTriple) {
56+
throw new Error(`Unsupported platform: ${platform} (${arch})`);
57+
}
3958

40-
let targetTriple = null;
41-
switch (platform) {
42-
case "linux":
43-
case "android":
44-
switch (arch) {
45-
case "x64":
46-
targetTriple = "x86_64-unknown-linux-musl";
47-
break;
48-
case "arm64":
49-
targetTriple = "aarch64-unknown-linux-musl";
50-
break;
51-
default:
52-
break;
53-
}
54-
break;
55-
case "darwin":
56-
switch (arch) {
57-
case "x64":
58-
targetTriple = "x86_64-apple-darwin";
59-
break;
60-
case "arm64":
61-
targetTriple = "aarch64-apple-darwin";
62-
break;
63-
default:
64-
break;
65-
}
66-
break;
67-
default:
68-
break;
59+
const binaryPath = path.join(__dirname, "..", "bin", `codex-${targetTriple}`);
60+
61+
// Use an asynchronous spawn instead of spawnSync so that Node is able to
62+
// respond to signals (e.g. Ctrl-C / SIGINT) while the native binary is
63+
// executing. This allows us to forward those signals to the child process
64+
// and guarantees that when either the child terminates or the parent
65+
// receives a fatal signal, both processes exit in a predictable manner.
66+
const { spawn } = await import("child_process");
67+
68+
const child = spawn(binaryPath, process.argv.slice(2), {
69+
stdio: "inherit",
70+
env: { ...process.env, CODEX_MANAGED_BY_NPM: "1" },
71+
});
72+
73+
child.on("error", (err) => {
74+
// Typically triggered when the binary is missing or not executable.
75+
// Re-throwing here will terminate the parent with a non-zero exit code
76+
// while still printing a helpful stack trace.
77+
// eslint-disable-next-line no-console
78+
console.error(err);
79+
process.exit(1);
80+
});
81+
82+
// Forward common termination signals to the child so that it shuts down
83+
// gracefully. In the handler we temporarily disable the default behavior of
84+
// exiting immediately; once the child has been signaled we simply wait for
85+
// its exit event which will in turn terminate the parent (see below).
86+
const forwardSignal = (signal) => {
87+
if (child.killed) {
88+
return;
6989
}
70-
71-
if (!targetTriple) {
72-
throw new Error(`Unsupported platform: ${platform} (${arch})`);
90+
try {
91+
child.kill(signal);
92+
} catch {
93+
/* ignore */
7394
}
74-
75-
const binaryPath = path.join(__dirname, "..", "bin", `codex-${targetTriple}`);
76-
77-
// Use an asynchronous spawn instead of spawnSync so that Node is able to
78-
// respond to signals (e.g. Ctrl-C / SIGINT) while the native binary is
79-
// executing. This allows us to forward those signals to the child process
80-
// and guarantees that when either the child terminates or the parent
81-
// receives a fatal signal, both processes exit in a predictable manner.
82-
const { spawn } = await import("child_process");
83-
84-
const child = spawn(binaryPath, process.argv.slice(2), {
85-
stdio: "inherit",
86-
env: { ...process.env, CODEX_MANAGED_BY_NPM: "1" },
87-
});
88-
89-
child.on("error", (err) => {
90-
// Typically triggered when the binary is missing or not executable.
91-
// Re-throwing here will terminate the parent with a non-zero exit code
92-
// while still printing a helpful stack trace.
93-
// eslint-disable-next-line no-console
94-
console.error(err);
95-
process.exit(1);
96-
});
97-
98-
// Forward common termination signals to the child so that it shuts down
99-
// gracefully. In the handler we temporarily disable the default behavior of
100-
// exiting immediately; once the child has been signaled we simply wait for
101-
// its exit event which will in turn terminate the parent (see below).
102-
const forwardSignal = (signal) => {
103-
if (child.killed) {
104-
return;
105-
}
106-
try {
107-
child.kill(signal);
108-
} catch {
109-
/* ignore */
95+
};
96+
97+
["SIGINT", "SIGTERM", "SIGHUP"].forEach((sig) => {
98+
process.on(sig, () => forwardSignal(sig));
99+
});
100+
101+
// When the child exits, mirror its termination reason in the parent so that
102+
// shell scripts and other tooling observe the correct exit status.
103+
// Wrap the lifetime of the child process in a Promise so that we can await
104+
// its termination in a structured way. The Promise resolves with an object
105+
// describing how the child exited: either via exit code or due to a signal.
106+
const childResult = await new Promise((resolve) => {
107+
child.on("exit", (code, signal) => {
108+
if (signal) {
109+
resolve({ type: "signal", signal });
110+
} else {
111+
resolve({ type: "code", exitCode: code ?? 1 });
110112
}
111-
};
112-
113-
["SIGINT", "SIGTERM", "SIGHUP"].forEach((sig) => {
114-
process.on(sig, () => forwardSignal(sig));
115-
});
116-
117-
// When the child exits, mirror its termination reason in the parent so that
118-
// shell scripts and other tooling observe the correct exit status.
119-
// Wrap the lifetime of the child process in a Promise so that we can await
120-
// its termination in a structured way. The Promise resolves with an object
121-
// describing how the child exited: either via exit code or due to a signal.
122-
const childResult = await new Promise((resolve) => {
123-
child.on("exit", (code, signal) => {
124-
if (signal) {
125-
resolve({ type: "signal", signal });
126-
} else {
127-
resolve({ type: "code", exitCode: code ?? 1 });
128-
}
129-
});
130113
});
114+
});
131115

132-
if (childResult.type === "signal") {
133-
// Re-emit the same signal so that the parent terminates with the expected
134-
// semantics (this also sets the correct exit code of 128 + n).
135-
process.kill(process.pid, childResult.signal);
136-
} else {
137-
process.exit(childResult.exitCode);
138-
}
116+
if (childResult.type === "signal") {
117+
// Re-emit the same signal so that the parent terminates with the expected
118+
// semantics (this also sets the correct exit code of 128 + n).
119+
process.kill(process.pid, childResult.signal);
139120
} else {
140-
// Fallback: execute the original JavaScript CLI.
141-
142-
// Resolve the path to the compiled CLI bundle
143-
const cliPath = path.resolve(__dirname, "../dist/cli.js");
144-
const cliUrl = pathToFileURL(cliPath).href;
145-
146-
// Load and execute the CLI
147-
try {
148-
await import(cliUrl);
149-
} catch (err) {
150-
// eslint-disable-next-line no-console
151-
console.error(err);
152-
process.exit(1);
153-
}
121+
process.exit(childResult.exitCode);
154122
}
123+

codex-cli/scripts/install_native_deps.sh

Lines changed: 17 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,8 @@
22

33
# Install native runtime dependencies for codex-cli.
44
#
5-
# By default the script copies the sandbox binaries that are required at
6-
# runtime. When called with the --full-native flag, it additionally
7-
# bundles pre-built Rust CLI binaries so that the resulting npm package can run
8-
# the native implementation when users set CODEX_RUST=1.
9-
#
105
# Usage
11-
# install_native_deps.sh [--full-native] [--workflow-url URL] [CODEX_CLI_ROOT]
6+
# install_native_deps.sh [--workflow-url URL] [CODEX_CLI_ROOT]
127
#
138
# The optional RELEASE_ROOT is the path that contains package.json. Omitting
149
# it installs the binaries into the repository's own bin/ folder to support
@@ -21,18 +16,14 @@ set -euo pipefail
2116
# ------------------
2217

2318
CODEX_CLI_ROOT=""
24-
INCLUDE_RUST=0
2519

2620
# Until we start publishing stable GitHub releases, we have to grab the binaries
2721
# from the GitHub Action that created them. Update the URL below to point to the
2822
# appropriate workflow run:
29-
WORKFLOW_URL="https://github.com/openai/codex/actions/runs/15981617627"
23+
WORKFLOW_URL="https://github.com/openai/codex/actions/runs/16840150768" # rust-v0.20.0-alpha.2
3024

3125
while [[ $# -gt 0 ]]; do
3226
case "$1" in
33-
--full-native)
34-
INCLUDE_RUST=1
35-
;;
3627
--workflow-url)
3728
shift || { echo "--workflow-url requires an argument"; exit 1; }
3829
if [ -n "$1" ]; then
@@ -81,26 +72,20 @@ trap 'rm -rf "$ARTIFACTS_DIR"' EXIT
8172
# NB: The GitHub CLI `gh` must be installed and authenticated.
8273
gh run download --dir "$ARTIFACTS_DIR" --repo openai/codex "$WORKFLOW_ID"
8374

84-
# Decompress the artifacts for Linux sandboxing.
85-
zstd -d "$ARTIFACTS_DIR/x86_64-unknown-linux-musl/codex-linux-sandbox-x86_64-unknown-linux-musl.zst" \
86-
-o "$BIN_DIR/codex-linux-sandbox-x64"
87-
88-
zstd -d "$ARTIFACTS_DIR/aarch64-unknown-linux-musl/codex-linux-sandbox-aarch64-unknown-linux-musl.zst" \
89-
-o "$BIN_DIR/codex-linux-sandbox-arm64"
90-
91-
if [[ "$INCLUDE_RUST" -eq 1 ]]; then
92-
# x64 Linux
93-
zstd -d "$ARTIFACTS_DIR/x86_64-unknown-linux-musl/codex-x86_64-unknown-linux-musl.zst" \
94-
-o "$BIN_DIR/codex-x86_64-unknown-linux-musl"
95-
# ARM64 Linux
96-
zstd -d "$ARTIFACTS_DIR/aarch64-unknown-linux-musl/codex-aarch64-unknown-linux-musl.zst" \
97-
-o "$BIN_DIR/codex-aarch64-unknown-linux-musl"
98-
# x64 macOS
99-
zstd -d "$ARTIFACTS_DIR/x86_64-apple-darwin/codex-x86_64-apple-darwin.zst" \
100-
-o "$BIN_DIR/codex-x86_64-apple-darwin"
101-
# ARM64 macOS
102-
zstd -d "$ARTIFACTS_DIR/aarch64-apple-darwin/codex-aarch64-apple-darwin.zst" \
103-
-o "$BIN_DIR/codex-aarch64-apple-darwin"
104-
fi
75+
# x64 Linux
76+
zstd -d "$ARTIFACTS_DIR/x86_64-unknown-linux-musl/codex-x86_64-unknown-linux-musl.zst" \
77+
-o "$BIN_DIR/codex-x86_64-unknown-linux-musl"
78+
# ARM64 Linux
79+
zstd -d "$ARTIFACTS_DIR/aarch64-unknown-linux-musl/codex-aarch64-unknown-linux-musl.zst" \
80+
-o "$BIN_DIR/codex-aarch64-unknown-linux-musl"
81+
# x64 macOS
82+
zstd -d "$ARTIFACTS_DIR/x86_64-apple-darwin/codex-x86_64-apple-darwin.zst" \
83+
-o "$BIN_DIR/codex-x86_64-apple-darwin"
84+
# ARM64 macOS
85+
zstd -d "$ARTIFACTS_DIR/aarch64-apple-darwin/codex-aarch64-apple-darwin.zst" \
86+
-o "$BIN_DIR/codex-aarch64-apple-darwin"
87+
# x64 Windows
88+
zstd -d "$ARTIFACTS_DIR/x86_64-pc-windows-msvc/codex-x86_64-pc-windows-msvc.exe.zst" \
89+
-o "$BIN_DIR/codex-x86_64-pc-windows-msvc.exe"
10590

10691
echo "Installed native dependencies into $BIN_DIR"

0 commit comments

Comments
 (0)