feat(build): add --compile-executable-path CLI flag - #25934
Conversation
Adds a new CLI flag --compile-executable-path that allows specifying
a custom Bun executable path for cross-compilation instead of
downloading from the npm registry.
This mirrors the executablePath option already available in the
JavaScript Bun.build() API.
Usage:
bun build --compile --target=bun-linux-x64 \
--compile-executable-path=/path/to/bun-linux-x64 app.ts
|
Updated 5:18 PM PT - Jan 9th, 2026
❌ @autofix-ci[bot], your commit b7b3db7 has 6 failures in
🧪 To try this PR locally: bunx bun-pr 25934That installs a local version of the PR into your bun-25934 --bun |
WalkthroughAdds a public BundlerOptions field and CLI flag to accept a Bun executable path for cross-compilation, threads that path into standalone executable generation, and changes Mach-O __BUN segment offset/size calculations to use the containing load command's fileoff/filesize. Changes
Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 2✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In @src/cli/Arguments.zig:
- Around line 1110-1116: The new flag handler for --compile-executable-path
currently only assigns the string to
ctx.bundler_options.compile_executable_path; add runtime validation before
assignment: when args.option("--compile-executable-path") returns a path, check
that ctx.bundler_options.compile is true (existing), then use the filesystem API
to resolve the path to an absolute/normalized path, verify the file exists and
is executable (check file type and execute permission bits on POSIX, or an
acceptable extension on Windows), and if validation fails call Output.errGeneric
with a clear message and Global.crash(); only assign the normalized absolute
path to ctx.bundler_options.compile_executable_path after successful validation
(leave architecture validation to toExecutable()).
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
src/cli.zigsrc/cli/Arguments.zigsrc/cli/build_command.zig
🧰 Additional context used
📓 Path-based instructions (2)
**/*.zig
📄 CodeRabbit inference engine (CLAUDE.md)
In Zig code, be careful with allocators and use defer for cleanup
Files:
src/cli.zigsrc/cli/build_command.zigsrc/cli/Arguments.zig
src/**/*.zig
📄 CodeRabbit inference engine (src/CLAUDE.md)
src/**/*.zig: Use the#prefix for private fields in Zig structs, e.g.,struct { #foo: u32 };
Use Decl literals in Zig, e.g.,const decl: Decl = .{ .binding = 0, .value = 0 };
Place@importstatements at the bottom of the file in Zig (auto formatter will handle positioning)
Never use@import()inline inside functions in Zig; always place imports at the bottom of the file or containing struct
Files:
src/cli.zigsrc/cli/build_command.zigsrc/cli/Arguments.zig
🧠 Learnings (11)
📓 Common learnings
Learnt from: RiskyMH
Repo: oven-sh/bun PR: 24719
File: docs/bundler/executables.mdx:527-560
Timestamp: 2025-11-14T16:07:01.064Z
Learning: In the Bun repository, certain bundler features like compile with code splitting (--compile --splitting) are CLI-only and not supported in the Bun.build() JavaScript API. Tests for CLI-only features use backend: "cli" flag (e.g., test/bundler/bundler_compile_splitting.test.ts). The CompileBuildConfig interface correctly restricts these with splitting?: never;. When documenting CLI-only bundler features, add a note clarifying they're not available via the programmatic API.
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 25462
File: src/ast/visitExpr.zig:1644-1695
Timestamp: 2025-12-11T02:11:47.024Z
Learning: In Bun's bundler feature flag implementation (src/ast/visitExpr.zig), the validation for feature() flag names intentionally only rejects UTF-16 strings (checking `is_utf16`) while allowing UTF-8 strings, even though the error message says "must be an ASCII string". This is the intended behavior and should not be changed to enforce strict ASCII validation.
📚 Learning: 2025-11-14T16:07:01.064Z
Learnt from: RiskyMH
Repo: oven-sh/bun PR: 24719
File: docs/bundler/executables.mdx:527-560
Timestamp: 2025-11-14T16:07:01.064Z
Learning: In the Bun repository, certain bundler features like compile with code splitting (--compile --splitting) are CLI-only and not supported in the Bun.build() JavaScript API. Tests for CLI-only features use backend: "cli" flag (e.g., test/bundler/bundler_compile_splitting.test.ts). The CompileBuildConfig interface correctly restricts these with splitting?: never;. When documenting CLI-only bundler features, add a note clarifying they're not available via the programmatic API.
Applied to files:
src/cli.zigsrc/cli/build_command.zigsrc/cli/Arguments.zig
📚 Learning: 2026-01-05T16:32:07.551Z
Learnt from: alii
Repo: oven-sh/bun PR: 25474
File: src/bun.js/event_loop/Sigusr1Handler.zig:0-0
Timestamp: 2026-01-05T16:32:07.551Z
Learning: In Zig codebases (e.g., Bun), treat std.posix.sigaction as returning void and do not perform runtime error handling for its failure. The Zig standard library views sigaction failures as programmer errors (unreachable) because they only occur with invalid signals like SIGKILL/SIGSTOP. Apply this pattern across Zig files that call sigaction (e.g., crash_handler.zig, main.zig, filter_run.zig, process.zig) and ensure failures are not handled as recoverable errors; prefer reaching an explicit unreachable/compile-time assumption when such failures are detected.
Applied to files:
src/cli.zigsrc/cli/build_command.zigsrc/cli/Arguments.zig
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Code generation happens automatically during the build process - bundled modules can be reloaded without rebuilding Zig by running `bun run build`
Applied to files:
src/cli/build_command.zigsrc/cli/Arguments.zig
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Run `bun run zig:check-all` to compile Zig code on all platforms when making platform-specific changes
Applied to files:
src/cli/build_command.zigsrc/cli/Arguments.zig
📚 Learning: 2025-10-16T02:17:35.237Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 23710
File: src/analytics.zig:15-21
Timestamp: 2025-10-16T02:17:35.237Z
Learning: In src/analytics.zig and similar files using bun.EnvVar boolean environment variables: the new EnvVar API for boolean flags (e.g., bun.EnvVar.do_not_track.get(), bun.EnvVar.ci.get()) is designed to parse and return boolean values from environment variables, not just check for their presence. This is an intentional design change from the previous presence-based checks using bun.getenvZ().
Applied to files:
src/cli/Arguments.zig
📚 Learning: 2025-09-12T22:27:19.572Z
Learnt from: RiskyMH
Repo: oven-sh/bun PR: 22613
File: src/cli/package_manager_command.zig:0-0
Timestamp: 2025-09-12T22:27:19.572Z
Learning: In Bun's CLI architecture, commands that go through the standard Command.Context flow should use `ctx.positionals` (which excludes flags) rather than manually parsing `bun.argv`. Manual `bun.argv` parsing is only used for standalone commands like `bun info` that bypass the normal command routing.
Applied to files:
src/cli/Arguments.zig
📚 Learning: 2025-10-16T17:32:03.074Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 23710
File: src/install/PackageManager/PackageManagerOptions.zig:187-193
Timestamp: 2025-10-16T17:32:03.074Z
Learning: In Bun's codebase (particularly in files like src/install/PackageManager/PackageManagerOptions.zig), mixing bun.EnvVar.*.get() and bun.EnvVar.*.platformGet() for environment variable lookups is intentional and safe. The code is protected by compile-time platform checks (Environment.isWindows, etc.), and compilation will fail if the wrong function is used on the wrong platform. This pattern should not be flagged as a consistency issue.
Applied to files:
src/cli/Arguments.zig
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/src/napi/napi.zig : For each new V8 C++ method, add both GCC/Clang and MSVC mangled symbol names to the V8API struct in src/napi/napi.zig using extern fn declarations
Applied to files:
src/cli/Arguments.zig
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Use `bun bd test <...test file>` to run tests with compiled code changes. Do not use `bun test` as it will not include your changes.
Applied to files:
src/cli/Arguments.zig
📚 Learning: 2025-09-12T22:30:48.490Z
Learnt from: RiskyMH
Repo: oven-sh/bun PR: 22613
File: src/cli/package_manager_command.zig:0-0
Timestamp: 2025-09-12T22:30:48.490Z
Learning: Commands like `bun info` and `bun whoami` that are promoted from pm-only subcommands to top-level commands use manual `bun.argv` parsing to detect direct invocation vs. pm invocation. This is different from regular commands that should use `ctx.positionals`.
Applied to files:
src/cli/Arguments.zig
🔇 Additional comments (3)
src/cli.zig (1)
467-467: LGTM!The new
compile_executable_pathfield is correctly defined as an optional string with a null default, following Zig conventions. Its placement among other compile-related options is logical.src/cli/build_command.zig (1)
460-478: LGTM!The change correctly passes
ctx.bundler_options.compile_executable_pathtobun.StandaloneModuleGraph.toExecutable()as the 11th argument, replacing the previousnullvalue. This allows users to specify a custom Bun executable path for cross-compilation.src/cli/Arguments.zig (1)
161-161: LGTM!The CLI parameter definition is clear and well-placed among other compile-related options. The description accurately explains the flag's purpose.
| if (args.option("--compile-executable-path")) |path| { | ||
| if (!ctx.bundler_options.compile) { | ||
| Output.errGeneric("--compile-executable-path requires --compile", .{}); | ||
| Global.crash(); | ||
| } | ||
| ctx.bundler_options.compile_executable_path = path; | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Consider adding path validation.
The implementation correctly enforces that --compile must be set and follows the existing patterns for compile options. However, consider validating the provided path to improve error messages:
- Verify the path exists and is executable
- Normalize relative paths to absolute paths
- Optionally validate that the executable architecture matches the
--target(though this might be better handled intoExecutable())
This would provide earlier, clearer feedback to users when they specify an invalid path.
💡 Suggested validation
if (args.option("--compile-executable-path")) |path| {
if (!ctx.bundler_options.compile) {
Output.errGeneric("--compile-executable-path requires --compile", .{});
Global.crash();
}
+
+ // Validate that the path exists and is executable
+ var path_buf: bun.PathBuffer = undefined;
+ const absolute_path = if (std.fs.path.isAbsolute(path))
+ path
+ else
+ resolve_path.joinAbsStringBuf(cwd, &path_buf, &[_]string{path}, .auto);
+
+ std.fs.accessAbsolute(absolute_path, .{ .mode = .read_only }) catch |err| {
+ Output.err(err, "Cannot access executable at path {f}", .{bun.fmt.quote(path)});
+ Global.crash();
+ };
+
ctx.bundler_options.compile_executable_path = path;
}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In @src/cli/Arguments.zig around lines 1110 - 1116, The new flag handler for
--compile-executable-path currently only assigns the string to
ctx.bundler_options.compile_executable_path; add runtime validation before
assignment: when args.option("--compile-executable-path") returns a path, check
that ctx.bundler_options.compile is true (existing), then use the filesystem API
to resolve the path to an absolute/normalized path, verify the file exists and
is executable (check file type and execute permission bits on POSIX, or an
acceptable extension on Windows), and if validation fails call Output.errGeneric
with a clear message and Global.crash(); only assign the normalized absolute
path to ctx.bundler_options.compile_executable_path after successful validation
(leave architecture validation to toExecutable()).
When using a standalone executable as the base for another --compile, the __BUN segment's actual filesize must be used instead of the hardcoded 16KB blob_alignment. This fixes: 1. @divExact panic when size_diff wasn't page-aligned 2. Binary corruption from incorrect memmove destination offset This enables creating standalone executables from other standalone executables (e.g., using BUN_BE_BUN=1 with a compiled binary).
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/macho.zig (1)
69-92: Add validation and bounds checks for segment/section offset invariants and size_diff signedness.The code mixes
sect.offset(line 78) andcommand.filesize(line 81) without validating they form a consistent layout. Line 157–164 assumessect.offset == command.fileoff; if not, the slicing and memmove will access wrong offsets and corrupt the binary.Additionally,
size_diff(line 89) can be negative if the newaligned_sizeis smaller than the current segment size, but the code assumes growth:@divExact(size_diff, PAGE_SIZE)(line 92) will panic on negative divisor, and casting negativesize_difftousize(lines 139, 158, 163) is undefined behavior.At minimum:
- Validate
sect.offset == command.fileoffbefore using them together (fail fast if not)- Guard
size_diffagainst negative values or implement shrink support- Add overflow and bounds checks on
command.fileoff + command.filesizeagainstself.data.items.len
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
src/macho.zig
🧰 Additional context used
📓 Path-based instructions (2)
**/*.zig
📄 CodeRabbit inference engine (CLAUDE.md)
In Zig code, be careful with allocators and use defer for cleanup
Files:
src/macho.zig
src/**/*.zig
📄 CodeRabbit inference engine (src/CLAUDE.md)
src/**/*.zig: Use the#prefix for private fields in Zig structs, e.g.,struct { #foo: u32 };
Use Decl literals in Zig, e.g.,const decl: Decl = .{ .binding = 0, .value = 0 };
Place@importstatements at the bottom of the file in Zig (auto formatter will handle positioning)
Never use@import()inline inside functions in Zig; always place imports at the bottom of the file or containing struct
Files:
src/macho.zig
🧠 Learnings (2)
📓 Common learnings
Learnt from: RiskyMH
Repo: oven-sh/bun PR: 24719
File: docs/bundler/executables.mdx:527-560
Timestamp: 2025-11-14T16:07:01.064Z
Learning: In the Bun repository, certain bundler features like compile with code splitting (--compile --splitting) are CLI-only and not supported in the Bun.build() JavaScript API. Tests for CLI-only features use backend: "cli" flag (e.g., test/bundler/bundler_compile_splitting.test.ts). The CompileBuildConfig interface correctly restricts these with splitting?: never;. When documenting CLI-only bundler features, add a note clarifying they're not available via the programmatic API.
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 25462
File: src/ast/visitExpr.zig:1644-1695
Timestamp: 2025-12-11T02:11:47.024Z
Learning: In Bun's bundler feature flag implementation (src/ast/visitExpr.zig), the validation for feature() flag names intentionally only rejects UTF-16 strings (checking `is_utf16`) while allowing UTF-8 strings, even though the error message says "must be an ASCII string". This is the intended behavior and should not be changed to enforce strict ASCII validation.
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Run `bun run zig:check-all` to compile Zig code on all platforms when making platform-specific changes
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: Use `-e` flag for single-file tests when spawning Bun processes with `Bun.spawn()`.
Learnt from: RiskyMH
Repo: oven-sh/bun PR: 22606
File: src/glob/GlobWalker.zig:449-452
Timestamp: 2025-09-12T18:16:50.754Z
Learning: For Bun codebase: prefer using `std.fs.path.sep` over manual platform separator detection, and use `bun.strings.lastIndexOfChar` instead of `std.mem.lastIndexOfScalar` for string operations.
📚 Learning: 2026-01-05T16:32:07.551Z
Learnt from: alii
Repo: oven-sh/bun PR: 25474
File: src/bun.js/event_loop/Sigusr1Handler.zig:0-0
Timestamp: 2026-01-05T16:32:07.551Z
Learning: In Zig codebases (e.g., Bun), treat std.posix.sigaction as returning void and do not perform runtime error handling for its failure. The Zig standard library views sigaction failures as programmer errors (unreachable) because they only occur with invalid signals like SIGKILL/SIGSTOP. Apply this pattern across Zig files that call sigaction (e.g., crash_handler.zig, main.zig, filter_run.zig, process.zig) and ensure failures are not handled as recoverable errors; prefer reaching an explicit unreachable/compile-time assumption when such failures are detected.
Applied to files:
src/macho.zig
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In @docs/runtime/ffi.mdx:
- Line 361: The emphasis on the critical negation was weakened—restore stronger
emphasis for "not" so the warning about HANDLE vs ptr is clearer; update the MDX
sentence that mentions HANDLE, ptr, and u64 to use combined bold+italics (e.g.,
**_not_**) around "not" so it reads “will **_not_** work as expected,” keeping
HANDLE, ptr and u64 wording unchanged.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
docs/runtime/ffi.mdx
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: RiskyMH
Repo: oven-sh/bun PR: 24719
File: docs/bundler/executables.mdx:527-560
Timestamp: 2025-11-14T16:07:01.064Z
Learning: In the Bun repository, certain bundler features like compile with code splitting (--compile --splitting) are CLI-only and not supported in the Bun.build() JavaScript API. Tests for CLI-only features use backend: "cli" flag (e.g., test/bundler/bundler_compile_splitting.test.ts). The CompileBuildConfig interface correctly restricts these with splitting?: never;. When documenting CLI-only bundler features, add a note clarifying they're not available via the programmatic API.
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Run `bun run zig:check-all` to compile Zig code on all platforms when making platform-specific changes
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 25462
File: src/ast/visitExpr.zig:1644-1695
Timestamp: 2025-12-11T02:11:47.024Z
Learning: In Bun's bundler feature flag implementation (src/ast/visitExpr.zig), the validation for feature() flag names intentionally only rejects UTF-16 strings (checking `is_utf16`) while allowing UTF-8 strings, even though the error message says "must be an ASCII string". This is the intended behavior and should not be changed to enforce strict ASCII validation.
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: Use `-e` flag for single-file tests when spawning Bun processes with `Bun.spawn()`.
Learnt from: RiskyMH
Repo: oven-sh/bun PR: 22606
File: src/glob/GlobWalker.zig:449-452
Timestamp: 2025-09-12T18:16:50.754Z
Learning: For Bun codebase: prefer using `std.fs.path.sep` over manual platform separator detection, and use `bun.strings.lastIndexOfChar` instead of `std.mem.lastIndexOfScalar` for string operations.
📚 Learning: 2025-10-18T20:59:45.579Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 23798
File: src/bun.js/telemetry.zig:458-475
Timestamp: 2025-10-18T20:59:45.579Z
Learning: In src/bun.js/telemetry.zig, the RequestId (u64) to JavaScript number (f64) conversion in jsRequestId() is intentionally allowed to lose precision beyond 2^53-1. This is acceptable because: (1) at 1M requests/sec it takes ~285 years to overflow, (2) the counter resets per-process, and (3) these are observability IDs, not critical distributed IDs. Precision loss is an acceptable trade-off for this use case.
Applied to files:
docs/runtime/ffi.mdx
🔇 Additional comments (1)
docs/runtime/ffi.mdx (1)
361-361: Scope mismatch: FFI documentation change in compilation feature PR.This file change appears unrelated to the PR objective (adding
--compile-executable-pathCLI flag for cross-compilation). The PR summary indicates changes tosrc/cli/Arguments.zig,src/cli.zig, andsrc/cli/build_command.zig, but no FFI documentation updates are mentioned.Verify whether this documentation change is intentional or was accidentally included in this PR. If it is intentional, clarify the rationale in the PR description.
| **Why not `BigInt`?** `BigInt` is slower. JavaScript engines allocate a separate `BigInt` which means they can't fit into a regular JavaScript value. If you pass a `BigInt` to a function, it will be converted to a `number` | ||
|
|
||
| **Windows Note**: The Windows API type HANDLE does not represent a virtual address, and using `ptr` for it will *not* work as expected. Use `u64` to safely represent HANDLE values. | ||
| **Windows Note**: The Windows API type HANDLE does not represent a virtual address, and using `ptr` for it will _not_ work as expected. Use `u64` to safely represent HANDLE values. |
There was a problem hiding this comment.
Formatting change: Reduced emphasis emphasis on "not".
Line 361 changes the formatting emphasis from bold/italics (**not** or similar) to italics-only (_not_). This reduces the visual weight of "not" in the warning. While this change is subtle, ensure it aligns with the documentation style guide and editorial intent—warnings should maintain clear emphasis on critical negations like "will not work as expected."
🤖 Prompt for AI Agents
In @docs/runtime/ffi.mdx at line 361, The emphasis on the critical negation was
weakened—restore stronger emphasis for "not" so the warning about HANDLE vs ptr
is clearer; update the MDX sentence that mentions HANDLE, ptr, and u64 to use
combined bold+italics (e.g., **_not_**) around "not" so it reads “will **_not_**
work as expected,” keeping HANDLE, ptr and u64 wording unchanged.
Summary
Adds a new CLI flag
--compile-executable-paththat allows specifying a custom Bun executable path for cross-compilation instead of downloading from the npm registry.Usage
Motivation
The
executablePathoption was already available in the JavaScriptBun.build()API. This exposes the same functionality from the CLI.Changes
--compile-executable-path <STR>CLI parameter insrc/cli/Arguments.zigcompile_executable_pathfield toBundlerOptionsinsrc/cli.zigStandaloneModuleGraph.toExecutable()insrc/cli/build_command.zig