Files
bun.sh/src/bun.js/node/node_process.zig
robobun e329316d44 Generate dependency versions header from CMake (#22561)
## Summary

This PR introduces a CMake-generated header file containing all
dependency versions, eliminating the need for C++ code to depend on
Zig-exported version constants.

## Changes

- **New CMake script**: `cmake/tools/GenerateDependencyVersions.cmake`
that:
  - Reads versions from the existing `generated_versions_list.zig` file
- Extracts semantic versions from header files where available
(libdeflate, zlib)
- Generates `bun_dependency_versions.h` with all dependency versions as
compile-time constants
  
- **Updated BunProcess.cpp**:
  - Now includes the CMake-generated `bun_dependency_versions.h`
  - Uses `BUN_VERSION_*` constants instead of `Bun__versions_*` 
  - Removes dependency on Zig-exported version constants

- **Build system updates**:
  - Added `GenerateDependencyVersions` to main CMakeLists.txt
  - Added build directory to include paths in BuildBun.cmake

## Benefits

 Single source of truth for dependency versions
 Versions accessible from C++ without Zig exports
 Automatic regeneration during CMake configuration
 Semantic versions shown where available (e.g., zlib 1.2.8 instead of
commit hash)
 Debug output file for verification

## Test Results

Verified that `process.versions` correctly shows all dependency
versions:

```javascript
$ bun -e "console.log(JSON.stringify(process.versions, null, 2))"
{
  "node": "24.3.0",
  "bun": "1.2.22-debug",
  "boringssl": "29a2cd359458c9384694b75456026e4b57e3e567",
  "libarchive": "898dc8319355b7e985f68a9819f182aaed61b53a",
  "mimalloc": "4c283af60cdae205df5a872530c77e2a6a307d43",
  "webkit": "0ddf6f47af0a9782a354f61e06d7f83d097d9f84",
  "zlib": "1.2.8",
  "libdeflate": "1.24",
  // ... all versions present and correct
}
```

## Generated Files

- `build/debug/bun_dependency_versions.h` - Header file with version
constants
- `build/debug/bun_dependency_versions_debug.txt` - Human-readable
version list

🤖 Generated with [Claude Code](https://claude.ai/code)

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-09-11 19:24:43 -07:00

366 lines
14 KiB
Zig

//! Process information and control APIs (`globalThis.process` / `node:process`)
comptime {
@export(&getTitle, .{ .name = "Bun__Process__getTitle" });
@export(&setTitle, .{ .name = "Bun__Process__setTitle" });
@export(&createArgv, .{ .name = "Bun__Process__createArgv" });
@export(&getCwd, .{ .name = "Bun__Process__getCwd" });
@export(&setCwd, .{ .name = "Bun__Process__setCwd" });
@export(&exit, .{ .name = "Bun__Process__exit" });
@export(&createArgv0, .{ .name = "Bun__Process__createArgv0" });
@export(&getExecPath, .{ .name = "Bun__Process__getExecPath" });
@export(&bun.jsc.host_fn.wrap1(createExecArgv), .{ .name = "Bun__Process__createExecArgv" });
@export(&getEval, .{ .name = "Bun__Process__getEval" });
}
var title_mutex = bun.Mutex{};
pub fn getTitle(_: *JSGlobalObject, title: *ZigString) callconv(.C) void {
title_mutex.lock();
defer title_mutex.unlock();
const str = bun.cli.Bun__Node__ProcessTitle;
title.* = ZigString.init(str orelse "bun");
}
// TODO: https://github.com/nodejs/node/blob/master/deps/uv/src/unix/darwin-proctitle.c
pub fn setTitle(globalObject: *JSGlobalObject, newvalue: *ZigString) callconv(.C) JSValue {
title_mutex.lock();
defer title_mutex.unlock();
if (bun.cli.Bun__Node__ProcessTitle) |_| bun.default_allocator.free(bun.cli.Bun__Node__ProcessTitle.?);
bun.cli.Bun__Node__ProcessTitle = bun.handleOom(newvalue.dupe(bun.default_allocator));
return newvalue.toJS(globalObject);
}
pub fn createArgv0(globalObject: *jsc.JSGlobalObject) callconv(.C) jsc.JSValue {
return jsc.ZigString.fromUTF8(bun.argv[0]).toJS(globalObject);
}
pub fn getExecPath(globalObject: *jsc.JSGlobalObject) callconv(.C) jsc.JSValue {
const out = bun.selfExePath() catch {
// if for any reason we are unable to get the executable path, we just return argv[0]
return createArgv0(globalObject);
};
return jsc.ZigString.fromUTF8(out).toJS(globalObject);
}
fn createExecArgv(globalObject: *jsc.JSGlobalObject) bun.JSError!jsc.JSValue {
var sfb = std.heap.stackFallback(4096, globalObject.allocator());
const temp_alloc = sfb.get();
const vm = globalObject.bunVM();
if (vm.worker) |worker| {
// was explicitly overridden for the worker?
if (worker.execArgv) |execArgv| {
const array = try jsc.JSValue.createEmptyArray(globalObject, execArgv.len);
for (0..execArgv.len) |i| {
try array.putIndex(globalObject, @intCast(i), bun.String.init(execArgv[i]).toJS(globalObject));
}
return array;
}
}
// For compiled/standalone executables, execArgv should contain compile_exec_argv
if (vm.standalone_module_graph) |graph| {
if (graph.compile_exec_argv.len > 0) {
// Use tokenize to split the compile_exec_argv string by whitespace
var args = std.ArrayList(bun.String).init(temp_alloc);
defer args.deinit();
defer for (args.items) |*arg| arg.deref();
var tokenizer = std.mem.tokenizeAny(u8, graph.compile_exec_argv, " \t\n\r");
while (tokenizer.next()) |token| {
try args.append(bun.String.cloneUTF8(token));
}
const array = try jsc.JSValue.createEmptyArray(globalObject, args.items.len);
for (0..args.items.len) |idx| {
try array.putIndex(globalObject, @intCast(idx), args.items[idx].toJS(globalObject));
}
return array;
}
return try jsc.JSValue.createEmptyArray(globalObject, 0);
}
var args = try std.ArrayList(bun.String).initCapacity(temp_alloc, bun.argv.len - 1);
defer args.deinit();
defer for (args.items) |*arg| arg.deref();
var seen_run = false;
var prev: ?[]const u8 = null;
// we re-parse the process argv to extract execArgv, since this is a very uncommon operation
// it isn't worth doing this as a part of the CLI
for (bun.argv[@min(1, bun.argv.len)..]) |arg| {
defer prev = arg;
if (arg.len >= 1 and arg[0] == '-') {
try args.append(bun.String.cloneUTF8(arg));
continue;
}
if (!seen_run and bun.strings.eqlComptime(arg, "run")) {
seen_run = true;
continue;
}
// A set of execArgv args consume an extra argument, so we do not want to
// confuse these with script names.
const map = bun.ComptimeStringMap(void, comptime brk: {
const auto_params = bun.cli.Arguments.auto_params;
const KV = struct { []const u8, void };
var entries: [auto_params.len]KV = undefined;
var i = 0;
for (auto_params) |param| {
if (param.takes_value != .none) {
if (param.names.long) |name| {
entries[i] = .{ "--" ++ name, {} };
i += 1;
}
if (param.names.short) |name| {
entries[i] = .{ &[_]u8{ '-', name }, {} };
i += 1;
}
}
}
var result: [i]KV = undefined;
@memcpy(&result, entries[0..i]);
break :brk result;
});
if (prev) |p| if (map.has(p)) {
try args.append(bun.String.cloneUTF8(arg));
continue;
};
// we hit the script name
break;
}
return bun.String.toJSArray(globalObject, args.items);
}
fn createArgv(globalObject: *jsc.JSGlobalObject) callconv(.C) jsc.JSValue {
const vm = globalObject.bunVM();
// Allocate up to 32 strings in stack
var stack_fallback_allocator = std.heap.stackFallback(
32 * @sizeOf(jsc.ZigString) + (bun.MAX_PATH_BYTES + 1) + 32,
bun.default_allocator,
);
const allocator = stack_fallback_allocator.get();
var args_count: usize = vm.argv.len;
if (vm.worker) |worker| {
args_count = worker.argv.len;
}
const args = allocator.alloc(
bun.String,
// argv omits "bun" because it could be "bun run" or "bun" and it's kind of ambiguous
// argv also omits the script name
args_count + 2,
) catch |err| bun.handleOom(err);
defer allocator.free(args);
var args_list: std.ArrayListUnmanaged(bun.String) = .initBuffer(args);
if (vm.standalone_module_graph != null) {
// Don't break user's code because they did process.argv.slice(2)
// Even if they didn't type "bun", we still want to add it as argv[0]
args_list.appendAssumeCapacity(
bun.String.static("bun"),
);
} else {
const exe_path = bun.selfExePath() catch null;
args_list.appendAssumeCapacity(
if (exe_path) |str| bun.String.borrowUTF8(str) else bun.String.static("bun"),
);
}
if (vm.main.len > 0 and
!strings.endsWithComptime(vm.main, bun.pathLiteral("/[eval]")) and
!strings.endsWithComptime(vm.main, bun.pathLiteral("/[stdin]")))
{
if (vm.worker != null and vm.worker.?.eval_mode) {
args_list.appendAssumeCapacity(bun.String.static("[worker eval]"));
} else {
args_list.appendAssumeCapacity(bun.String.borrowUTF8(vm.main));
}
}
defer allocator.free(args);
if (vm.worker) |worker| {
for (worker.argv) |arg| {
args_list.appendAssumeCapacity(bun.String.init(arg));
}
} else {
for (vm.argv) |arg| {
const str = bun.String.borrowUTF8(arg);
// https://github.com/yargs/yargs/blob/adb0d11e02c613af3d9427b3028cc192703a3869/lib/utils/process-argv.ts#L1
args_list.appendAssumeCapacity(str);
}
}
return bun.String.toJSArray(globalObject, args_list.items) catch .zero;
}
extern fn Bun__Process__getArgv(global: *JSGlobalObject) JSValue;
pub fn getArgv(global: *JSGlobalObject) callconv(.c) JSValue {
return Bun__Process__getArgv(global);
}
extern fn Bun__Process__getExecArgv(global: *JSGlobalObject) JSValue;
pub fn getExecArgv(global: *JSGlobalObject) callconv(.c) JSValue {
return Bun__Process__getExecArgv(global);
}
pub fn getEval(globalObject: *jsc.JSGlobalObject) callconv(.C) jsc.JSValue {
const vm = globalObject.bunVM();
if (vm.module_loader.eval_source) |source| {
return jsc.ZigString.init(source.contents).toJS(globalObject);
}
return .js_undefined;
}
pub const getCwd = jsc.host_fn.wrap1(getCwd_);
fn getCwd_(globalObject: *jsc.JSGlobalObject) bun.JSError!jsc.JSValue {
var buf: bun.PathBuffer = undefined;
switch (bun.api.node.path.getCwd(&buf)) {
.result => |r| return jsc.ZigString.init(r).withEncoding().toJS(globalObject),
.err => |e| {
return globalObject.throwValue(e.toJS(globalObject));
},
}
}
pub const setCwd = jsc.host_fn.wrap2(setCwd_);
fn setCwd_(globalObject: *jsc.JSGlobalObject, to: *jsc.ZigString) bun.JSError!jsc.JSValue {
if (to.len == 0) {
return globalObject.throwInvalidArguments("Expected path to be a non-empty string", .{});
}
const vm = globalObject.bunVM();
const fs = vm.transpiler.fs;
var buf: bun.PathBuffer = undefined;
const slice = to.sliceZBuf(&buf) catch return globalObject.throw("Invalid path", .{});
switch (Syscall.chdir(fs.top_level_dir, slice)) {
.result => {
// When we update the cwd from JS, we have to update the bundler's version as well
// However, this might be called many times in a row, so we use a pre-allocated buffer
// that way we don't have to worry about garbage collector
const into_cwd_buf = switch (bun.sys.getcwd(&buf)) {
.result => |r| r,
.err => |err| {
_ = Syscall.chdir(fs.top_level_dir, fs.top_level_dir);
return globalObject.throwValue(err.toJS(globalObject));
},
};
@memcpy(fs.top_level_dir_buf[0..into_cwd_buf.len], into_cwd_buf);
fs.top_level_dir_buf[into_cwd_buf.len] = 0;
fs.top_level_dir = fs.top_level_dir_buf[0..into_cwd_buf.len :0];
const len = fs.top_level_dir.len;
// Ensure the path ends with a slash
if (fs.top_level_dir_buf[len - 1] != std.fs.path.sep) {
fs.top_level_dir_buf[len] = std.fs.path.sep;
fs.top_level_dir_buf[len + 1] = 0;
fs.top_level_dir = fs.top_level_dir_buf[0 .. len + 1 :0];
}
const withoutTrailingSlash = if (Environment.isWindows) strings.withoutTrailingSlashWindowsPath else strings.withoutTrailingSlash;
var str = bun.String.cloneUTF8(withoutTrailingSlash(fs.top_level_dir));
return str.transferToJS(globalObject);
},
.err => |e| {
return globalObject.throwValue(e.toJS(globalObject));
},
}
}
// TODO(@190n) this may need to be noreturn
pub fn exit(globalObject: *jsc.JSGlobalObject, code: u8) callconv(.c) void {
var vm = globalObject.bunVM();
vm.exit_handler.exit_code = code;
if (vm.worker) |worker| {
// TODO(@190n) we may need to use requestTerminate or throwTerminationException
// instead to terminate the worker sooner
worker.exit();
} else {
vm.onExit();
vm.globalExit();
}
}
// TODO: switch this to using *bun.wtf.String when it is added
pub fn Bun__Process__editWindowsEnvVar(k: bun.String, v: bun.String) callconv(.C) void {
comptime bun.assert(bun.Environment.isWindows);
if (k.tag == .Empty) return;
const wtf1 = k.value.WTFStringImpl;
var fixed_stack_allocator = std.heap.stackFallback(1025, bun.default_allocator);
const allocator = fixed_stack_allocator.get();
var buf1 = bun.handleOom(allocator.alloc(u16, k.utf16ByteLength() + 1));
defer allocator.free(buf1);
var buf2 = bun.handleOom(allocator.alloc(u16, v.utf16ByteLength() + 1));
defer allocator.free(buf2);
const len1: usize = switch (wtf1.is8Bit()) {
true => bun.strings.copyLatin1IntoUTF16([]u16, buf1, []const u8, wtf1.latin1Slice()).written,
false => b: {
@memcpy(buf1[0..wtf1.length()], wtf1.utf16Slice());
break :b wtf1.length();
},
};
buf1[len1] = 0;
const str2: ?[*:0]const u16 = if (v.tag != .Dead) str: {
if (v.tag == .Empty) break :str (&[_]u16{0})[0..0 :0];
const wtf2 = v.value.WTFStringImpl;
const len2: usize = switch (wtf2.is8Bit()) {
true => bun.strings.copyLatin1IntoUTF16([]u16, buf2, []const u8, wtf2.latin1Slice()).written,
false => b: {
@memcpy(buf2[0..wtf2.length()], wtf2.utf16Slice());
break :b wtf2.length();
},
};
buf2[len2] = 0;
break :str buf2[0..len2 :0].ptr;
} else null;
_ = bun.c.SetEnvironmentVariableW(buf1[0..len1 :0].ptr, str2);
}
comptime {
if (Environment.export_cpp_apis and Environment.isWindows) {
@export(&Bun__Process__editWindowsEnvVar, .{ .name = "Bun__Process__editWindowsEnvVar" });
}
}
pub export fn Bun__NODE_NO_WARNINGS() bool {
return bun.getRuntimeFeatureFlag(.NODE_NO_WARNINGS);
}
pub export fn Bun__suppressCrashOnProcessKillSelfIfDesired() void {
if (bun.getRuntimeFeatureFlag(.BUN_INTERNAL_SUPPRESS_CRASH_ON_PROCESS_KILL_SELF)) {
bun.crash_handler.suppressReporting();
}
}
pub export const Bun__version: [*:0]const u8 = "v" ++ bun.Global.package_json_version;
pub export const Bun__version_with_sha: [*:0]const u8 = "v" ++ bun.Global.package_json_version_with_sha;
// Version exports removed - now handled by CMake-generated header (bun_dependency_versions.h)
// The C++ code in BunProcess.cpp uses the generated header directly
pub export const Bun__versions_uws: [*:0]const u8 = bun.Environment.git_sha;
pub export const Bun__versions_usockets: [*:0]const u8 = bun.Environment.git_sha;
pub export const Bun__version_sha: [*:0]const u8 = bun.Environment.git_sha;
const std = @import("std");
const bun = @import("bun");
const Environment = bun.Environment;
const Syscall = bun.sys;
const strings = bun.strings;
const jsc = bun.jsc;
const JSGlobalObject = jsc.JSGlobalObject;
const JSValue = jsc.JSValue;
const ZigString = jsc.ZigString;