mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
# bun test Fixes #8768, Fixes #14624, Fixes #20100, Fixes #19875, Fixes #14135, Fixes #20980, Fixes #21830, Fixes #5738, Fixes #19758, Fixes #12782, Fixes #5585, Fixes #9548, Might fix 5996 # New features: ## Concurrent tests Concurrent tests allow running multiple async tests at the same time. ```ts // concurrent.test.ts test.concurrent("this takes a while 1", async () => { await Bun.sleep(1000); }); test.concurrent("this takes a while 2", async () => { await Bun.sleep(1000); }); test.concurrent("this takes a while 3", async () => { await Bun.sleep(1000); }); ``` Without `.concurrent`, this test file takes 3 seconds to run because each one has to wait for the one before it to finish before it can start. With `.concurrent`, this file takes 1 second because all three sleeps can run at once. ``` $> bun-after test concurrent concurrent.test.js: ✓ this takes a while 1 [1005.36ms] ✓ this takes a while 2 [1012.51ms] ✓ this takes a while 3 [1013.15ms] 3 pass 0 fail Ran 3 tests across 1 file. [1081.00ms] ``` To run all tests as concurrent, pass the `--concurrent` flag when running tests. Limitations: - concurrent tests cannot attribute `expect()` call counts to the test, meaning `expect.assertions()` does not function - concurrent tests cannot use `toMatchSnapshot`. `toMatchInlineSnapshot` is still supported. - `beforeAll`/`afterAll` will never be executed concurrently. `beforeEach`/`afterEach` will. ## Chaining Chaining multiple describe/test qualifiers is now allowed. Previously, it would fail. ```ts // chaining-test-qualifiers.test.ts test.failing.each([1, 2, 3])("each %i", async i => { throw new Error(i); }); ``` ``` $> bun-after test chaining-test-qualifiers a.test.js: ✓ each 1 ✓ each 2 ✓ each 3 ``` # Breaking changes: ## Describe ordering Previously, describe callbacks were called immediately. Now, they are deferred until the outer callback has finished running. The previous order matched Jest. The new order is similar to Vitest, but does not match exactly. ```ts // describe-ordering.test.ts describe("outer", () => { console.log("outer before"); describe("inner", () => { console.log("inner"); }); console.log("outer after"); }); ``` Before, this would print ``` $> bun-before test describe-ordering outer before inner outer after ``` Now, this will print ``` $> bun-after test describe-ordering outer before outer after inner ``` ## Test ordering Describes are no longer always called before tests. They are now in order. ```ts // test-ordering.test.ts test("one", () => {}); describe("scope", () => { test("two", () => {}); }); test("three", () => {}); ``` Before, this would print ``` $> bun-before test test-ordering ✓ scope > two ✓ one ✓ three ``` Now, this will print ``` $> bun-after test test-ordering ✓ one ✓ scope > two ✓ three ``` ## Preload hooks Previously, beforeAll in a preload ran before the first file and afterAll ran after the last file. Now, beforeAll will run at the start of each file and afterAll will run at the end of each file. This behaviour matches Jest and Vitest. ```ts // preload.ts beforeAll(() => console.log("preload: beforeAll")); afterAll(() => console.log("preload: afterAll")); ``` ```ts // preload-ordering-1.test.ts test("demonstration file 1", () => {}); ``` ```ts // preload-ordering-2.test.ts test("demonstration file 2", () => {}); ``` ``` $> bun-before test --preload=./preload preload-ordering preload-ordering-1.test.ts: preload: beforeAll ✓ demonstration file 1 preload-ordering-2.test.ts: ✓ demonstration file 2 preload: afterAll ``` ``` $> bun-after test --preload=./preload preload-ordering preload-ordering-1.test.ts: preload: beforeAll ✓ demonstration file 1 preload: afterAll preload-ordering-2.test.ts: preload: beforeAll ✓ demonstration file 2 preload: afterAll ``` ## Describe failures Current behaviour is that when an error is thrown inside a describe callback, none of the tests declared there will run. Now, describes declared inside will also not run. The new behaviour matches the behaviour of Jest and Vitest. ```ts // describe-failures.test.ts describe("erroring describe", () => { test("this test does not run because its describe failed", () => { expect(true).toBe(true); }); describe("inner describe", () => { console.log("does the inner describe callback get called?"); test("does the inner test run?", () => { expect(true).toBe(true); }); }); throw new Error("uh oh!"); }); ``` Before, the inner describe callback would be called and the inner test would run, although the outer test would not: ``` $> bun-before test describe-failures describe-failures.test.ts: does the inner describe callback get called? # Unhandled error between tests ------------------------------- 11 | throw new Error("uh oh!"); ^ error: uh oh! ------------------------------- ✓ erroring describe > inner describe > does the inner test run? 1 pass 0 fail 1 error 1 expect() calls Ran 1 test across 1 file. Exited with code [1] ``` Now, the inner describe callback is not called at all. ``` $> bun-after test describe-failures describe-failures.test.ts: # Unhandled error between tests ------------------------------- 11 | throw new Error("uh oh!"); ^ error: uh oh! ------------------------------- 0 pass 0 fail 1 error Ran 0 tests across 1 file. Exited with code [1] ``` ## Hook failures Previously, a beforeAll failure would skip subsequent beforeAll()s, the test, and the afterAll. Now, a beforeAll failure skips any subsequent beforeAll()s and the test, but not the afterAll. ```js beforeAll(() => { throw new Error("before all: uh oh!"); }); test("my test", () => { console.log("my test"); }); afterAll(() => console.log("after all")); ``` ``` $> bun-before test hook-failures Error: before all: uh oh! $> bun-after test hook-failures Error: before all: uh oh! after all ``` Previously, an async beforeEach failure would still allow the test to run. Now, an async beforeEach failure will prevent the test from running ```js beforeEach(() => { await 0; throw "uh oh!"; }); it("the test", async () => { console.log("does the test run?"); }); ``` ``` $> bun-before test async-beforeeach-failure does the test run? error: uh oh! uh oh! ✗ the test $> bun-after test async-beforeeach-failure error: uh oh! uh oh! ✗ the test ``` ## Hook timeouts Hooks will now time out, and can have their timeout configured in an options parameter ```js beforeAll(async () => { await Bun.sleep(1000); }, 500); test("my test", () => { console.log("ran my test"); }); ``` ``` $> bun-before test hook-timeouts ran my test Ran 1 test across 1 file. [1011.00ms] $> bun-after test hook-timeouts ✗ my test [501.15ms] ^ a beforeEach/afterEach hook timed out for this test. ``` ## Hook execution order beforeAll will now execute before the tests in the scope, rather than immediately when it is called. ```ts describe("d1", () => { beforeAll(() => { console.log("<d1>"); }); test("test", () => { console.log(" test"); }); afterAll(() => { console.log("</d1>"); }); }); describe("d2", () => { beforeAll(() => { console.log("<d2>"); }); test("test", () => { console.log(" test"); }); afterAll(() => { console.log("</d2>"); }); }); ``` ``` $> bun-before test ./beforeall-ordering.test.ts <d1> <d2> test </d1> test </d2> $> bun-after test ./beforeall-ordering.test.ts <d1> test </d1> <d2> test </d2> ``` ## test inside test test() inside test() now errors rather than silently failing. Support for this may be added in the future. ```ts test("outer", () => { console.log("outer"); test("inner", () => { console.log("inner"); }); }); ``` ``` $> bun-before test outer ✓ outer [0.06ms] 1 pass 0 fail Ran 1 test across 1 file. [8.00ms] $> bun-after test outer 1 | test("outer", () => { 2 | console.log("outer"); 3 | test("inner", () => { ^ error: Cannot call test() inside a test. Call it inside describe() instead. ✗ outer [0.71ms] 0 pass 1 fail ``` ## afterAll inside test afterAll inside a test is no longer allowed ```ts test("test 1", () => { afterAll(() => console.log("afterAll")); console.log("test 1"); }); test("test 2", () => { console.log("test 2"); }); ``` ``` $> bun-before test 1 ✓ test 1 [0.05ms] test 2 ✓ test 2 afterAll $> bun-after error: Cannot call afterAll() inside a test. Call it inside describe() instead. ✗ test 1 [1.00ms] test 2 ✓ test 2 [0.20ms] ``` # Only inside only Previously, an outer 'describe.only' would run all tests inside it even if there was an inner 'test.only'. Now, only the innermost only tests are executed. ```ts describe.only("outer", () => { test("one", () => console.log("should not run")); test.only("two", () => console.log("should run")); }); ``` ``` $> bun-before test should not run should run $> bun-after test should run ``` With no inner only, the outer only will still run all tests: ```ts describe.only("outer", () => { test("test 1", () => console.log("test 1 runs")); test("test 2", () => console.log("test 2 runs")); }); ``` # Potential follow-up work - [ ] for concurrent tests, display headers before console.log messages saying which test it is for - this will need async context or similar - refActiveExecutionEntry should also be able to know the current test even in test.concurrent - [ ] `test("rerun me", () => { console.log("run one time!"); });` `--rerun-each=3` <- this runs the first and third time but not the second time. fix. - [ ] should to cache the JSValue created from DoneCallback.callAsFunction - [ ] implement retry and rerun params for tests. - [ ] Remove finalizer on ScopeFunctions.zig by storing the data in 3 jsvalues passed in bind rather than using a custom class. We should also migrate off of the ClassGenerator for ScopeFunctions - [ ] support concurrent limit, how many concurrent tests are allowed to run at a time. ie `--concurrent-limit=25` - [ ] flag to run tests in random order - [ ] `test.failing` should have its own style in the same way `test.todo` passing marks as 'todo' insetead of 'passing'. right now it's `✓` which is confusing. - [ ] remove all instances of bun.jsc.Jest.Jest.current - [ ] test options should be in BunTestRoot - [ ] we will need one global still, stored in the globalobject/vm/?. but it should not be a Jest instance. - [ ] consider allowing test() inside test(), as well as afterEach and afterAll. could even allow describe() too. to do this we would switch from indices to pointers and they would be in a linked list. they would be allocated in memorypools for perf/locality. some special consideration is needed for making sure repeated tests lose their temporary items. this could also improve memory usage soomewhat. - [ ] consider using a jsc Bound Function rather than CallbackWithArgs. bound functions allow adding arguments and they are only one value for GC instead of many. and this removes our unnecessary three copies. - [ ] eliminate Strong.Safe. we should be using a C++ class instead. - [ ] consider modifying the junit reporter to print the whole describe tree at the end instead of trying to output as test results come in. and move it into its own file. - [ ] expect_call_count/expect_assertions is confusing. rename to `expect_calls`, `assert_expect_calls`. or something. - [ ] Should make line_no be an enum with a none option and a function to get if line nombers are enabled - [ ] looks like we don't need to use file_id anymore (remove `bun.jsc.Jest.Jest.runner.?.getOrPutFile(file_path).file_id;`, store the file path directly) - [ ] 'dot' test reporter like vitest? - [ ] `test.failing.if(false)` errors because it can't replace mode 'failing' with mode 'skip'. this should probably be allowed instead. - [ ] trigger timeout termination exception for `while(true) {}` - [ ] clean up unused callbacks. as soon as we advance to the next execution group, we can fully clean out the previous one. sometimes within an execution sequence we can do the same. - clean by swapping held values with undefined - [ ] structure cache for performance for donecallback/scopefunctions - [ ] consider migrating CallbackWithArgs to be a bound function. the length of the bound function can exclude the specified args. - [ ] setting both result and maybe_skip is not ideal, maybe there should be a function to do both at once? - [ ] try using a linked list rather than arraylist for describe/test children, see how it affects performance - [ ] consider a memory pool for describescope/executionentry. test if it improves performance. - [ ] consider making RefDataValue methods return the reason for failure rather than ?value. that way we can improve error messages. the reason could be a string or it could be a defined error set - [ ] instead of 'description orelse (unnamed)', let's have description default to 'unnamed' and not free it if it === the global that defines that - [ ] Add a phase before ordering results that inherits properties to the parents. (eg inherit only from the child and inherit has_callback from the child. and has_callback can be on describe/test individually rather than on base). then we won't have that happening in an init() function (terrible!) - [ ] this test was incidentally passing because resolves.pass() wasn't waiting for promise ``` test("fetching with Request object - issue #1527", async () => { const server = createServer((req, res) => { res.end(); }).listen(0); try { await once(server, "listening"); const body = JSON.stringify({ foo: "bar" }); const request = new Request(`http://localhost:${server.address().port}`, { method: "POST", body, }); expect(fetch(request)).resolves.pass(); } finally { server.closeAllConnections(); } }); ``` - [ ] the error "expect.assertions() is not supported in the describe phase, in concurrent tests, between tests, or after test execution has completed" is not very good. we should be able to identify which of those it is and print the right error for the context - [ ] consider: instead of storing weak pointers to BunTest, we can instead give the instance an id and check that it is correct when getting the current bun test instance from the ref - [ ] auto_killer: add three layers of auto_killer: - preload (includes file & test) - file (includes test) - test - that way at the end of the test, we kill the test processes. at the end of the file, we kill the file processes. at the end of all, we kill anything remaining. AsyncLocalStorage - store active_id & refdatavalue. active_id is a replacement for the above weak pointers thing. refdatavalue is for determining which test it is. this probably fits in 2×u64 - use for auto_killer so timeouts can kill even in concurrent tests --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
865 lines
34 KiB
Zig
865 lines
34 KiB
Zig
const std = @import("std");
|
|
const builtin = @import("builtin");
|
|
|
|
const Build = std.Build;
|
|
const Step = Build.Step;
|
|
const Compile = Step.Compile;
|
|
const LazyPath = Build.LazyPath;
|
|
const Target = std.Target;
|
|
const ResolvedTarget = std.Build.ResolvedTarget;
|
|
const CrossTarget = std.zig.CrossTarget;
|
|
const OptimizeMode = std.builtin.OptimizeMode;
|
|
const Module = Build.Module;
|
|
const fs = std.fs;
|
|
const Version = std.SemanticVersion;
|
|
const Arch = std.Target.Cpu.Arch;
|
|
|
|
const OperatingSystem = @import("src/env.zig").OperatingSystem;
|
|
|
|
const pathRel = fs.path.relative;
|
|
|
|
/// When updating this, make sure to adjust SetupZig.cmake
|
|
const recommended_zig_version = "0.14.0";
|
|
|
|
// comptime {
|
|
// if (!std.mem.eql(u8, builtin.zig_version_string, recommended_zig_version)) {
|
|
// @compileError(
|
|
// "" ++
|
|
// "Bun requires Zig version " ++ recommended_zig_version ++ ", but you have " ++
|
|
// builtin.zig_version_string ++ ". This is automatically configured via Bun's " ++
|
|
// "CMake setup. You likely meant to run `bun run build`. If you are trying to " ++
|
|
// "upgrade the Zig compiler, edit ZIG_COMMIT in cmake/tools/SetupZig.cmake or " ++
|
|
// "comment this error out.",
|
|
// );
|
|
// }
|
|
// }
|
|
|
|
const zero_sha = "0000000000000000000000000000000000000000";
|
|
|
|
const BunBuildOptions = struct {
|
|
target: ResolvedTarget,
|
|
optimize: OptimizeMode,
|
|
os: OperatingSystem,
|
|
arch: Arch,
|
|
|
|
version: Version,
|
|
canary_revision: ?u32,
|
|
sha: []const u8,
|
|
/// enable debug logs in release builds
|
|
enable_logs: bool = false,
|
|
enable_asan: bool,
|
|
tracy_callstack_depth: u16,
|
|
reported_nodejs_version: Version,
|
|
/// To make iterating on some '@embedFile's faster, we load them at runtime
|
|
/// instead of at compile time. This is disabled in release or if this flag
|
|
/// is set (to allow CI to build a portable executable). Affected files:
|
|
///
|
|
/// - src/bake/runtime.ts (bundled)
|
|
/// - src/bun.js/api/FFI.h
|
|
///
|
|
/// A similar technique is used in C++ code for JavaScript builtins
|
|
codegen_embed: bool = false,
|
|
|
|
/// `./build/codegen` or equivalent
|
|
codegen_path: []const u8,
|
|
no_llvm: bool,
|
|
override_no_export_cpp_apis: bool,
|
|
|
|
cached_options_module: ?*Module = null,
|
|
windows_shim: ?WindowsShim = null,
|
|
|
|
pub fn isBaseline(this: *const BunBuildOptions) bool {
|
|
return this.arch.isX86() and
|
|
!Target.x86.featureSetHas(this.target.result.cpu.features, .avx2);
|
|
}
|
|
|
|
pub fn shouldEmbedCode(opts: *const BunBuildOptions) bool {
|
|
return opts.optimize != .Debug or opts.codegen_embed;
|
|
}
|
|
|
|
pub fn buildOptionsModule(this: *BunBuildOptions, b: *Build) *Module {
|
|
if (this.cached_options_module) |mod| {
|
|
return mod;
|
|
}
|
|
|
|
var opts = b.addOptions();
|
|
opts.addOption([]const u8, "base_path", b.pathFromRoot("."));
|
|
opts.addOption([]const u8, "codegen_path", std.fs.path.resolve(b.graph.arena, &.{ b.build_root.path.?, this.codegen_path }) catch @panic("OOM"));
|
|
|
|
opts.addOption(bool, "codegen_embed", this.shouldEmbedCode());
|
|
opts.addOption(u32, "canary_revision", this.canary_revision orelse 0);
|
|
opts.addOption(bool, "is_canary", this.canary_revision != null);
|
|
opts.addOption(Version, "version", this.version);
|
|
opts.addOption([:0]const u8, "sha", b.allocator.dupeZ(u8, this.sha) catch @panic("OOM"));
|
|
opts.addOption(bool, "baseline", this.isBaseline());
|
|
opts.addOption(bool, "enable_logs", this.enable_logs);
|
|
opts.addOption(bool, "enable_asan", this.enable_asan);
|
|
opts.addOption([]const u8, "reported_nodejs_version", b.fmt("{}", .{this.reported_nodejs_version}));
|
|
opts.addOption(bool, "zig_self_hosted_backend", this.no_llvm);
|
|
opts.addOption(bool, "override_no_export_cpp_apis", this.override_no_export_cpp_apis);
|
|
|
|
const mod = opts.createModule();
|
|
this.cached_options_module = mod;
|
|
return mod;
|
|
}
|
|
|
|
pub fn windowsShim(this: *BunBuildOptions, b: *Build) WindowsShim {
|
|
return this.windows_shim orelse {
|
|
this.windows_shim = WindowsShim.create(b);
|
|
return this.windows_shim.?;
|
|
};
|
|
}
|
|
};
|
|
|
|
pub fn getOSVersionMin(os: OperatingSystem) ?Target.Query.OsVersion {
|
|
return switch (os) {
|
|
.mac => .{
|
|
.semver = .{ .major = 13, .minor = 0, .patch = 0 },
|
|
},
|
|
|
|
// Windows 10 1809 is the minimum supported version
|
|
// One case where this is specifically required is in `deleteOpenedFile`
|
|
.windows => .{
|
|
.windows = .win10_rs5,
|
|
},
|
|
|
|
else => null,
|
|
};
|
|
}
|
|
|
|
pub fn getOSGlibCVersion(os: OperatingSystem) ?Version {
|
|
return switch (os) {
|
|
// Compiling with a newer glibc than this will break certain cloud environments.
|
|
.linux => .{ .major = 2, .minor = 27, .patch = 0 },
|
|
|
|
else => null,
|
|
};
|
|
}
|
|
|
|
pub fn getCpuModel(os: OperatingSystem, arch: Arch) ?Target.Query.CpuModel {
|
|
// https://github.com/oven-sh/bun/issues/12076
|
|
if (os == .linux and arch == .aarch64) {
|
|
return .{ .explicit = &Target.aarch64.cpu.cortex_a35 };
|
|
}
|
|
|
|
// Be explicit and ensure we do not accidentally target a newer M-series chip
|
|
if (os == .mac and arch == .aarch64) {
|
|
return .{ .explicit = &Target.aarch64.cpu.apple_m1 };
|
|
}
|
|
|
|
// note: x86_64 is dealt with in the CMake config and passed in.
|
|
// the reason for the explicit handling on aarch64 is due to troubles
|
|
// passing the exact target in via flags.
|
|
return null;
|
|
}
|
|
|
|
pub fn build(b: *Build) !void {
|
|
std.log.info("zig compiler v{s}", .{builtin.zig_version_string});
|
|
checked_file_exists = std.AutoHashMap(u64, void).init(b.allocator);
|
|
|
|
var target_query = b.standardTargetOptionsQueryOnly(.{});
|
|
const optimize = b.standardOptimizeOption(.{});
|
|
|
|
const os, const arch, const abi = brk: {
|
|
// resolve the target query to pick up what operating system and cpu
|
|
// architecture that is desired. this information is used to slightly
|
|
// refine the query.
|
|
const temp_resolved = b.resolveTargetQuery(target_query);
|
|
const arch = temp_resolved.result.cpu.arch;
|
|
const os: OperatingSystem = if (arch.isWasm())
|
|
.wasm
|
|
else switch (temp_resolved.result.os.tag) {
|
|
.macos => .mac,
|
|
.linux => .linux,
|
|
.windows => .windows,
|
|
else => |t| std.debug.panic("Unsupported OS tag {}", .{t}),
|
|
};
|
|
const abi = temp_resolved.result.abi;
|
|
break :brk .{ os, arch, abi };
|
|
};
|
|
|
|
// target must be refined to support older but very popular devices on
|
|
// aarch64, this means moving the minimum supported CPU to support certain
|
|
// raspberry PIs. there are also a number of cloud hosts that use virtual
|
|
// machines with surprisingly out of date versions of glibc.
|
|
if (getCpuModel(os, arch)) |cpu_model| {
|
|
target_query.cpu_model = cpu_model;
|
|
}
|
|
|
|
target_query.os_version_min = getOSVersionMin(os);
|
|
target_query.glibc_version = if (abi.isGnu()) getOSGlibCVersion(os) else null;
|
|
|
|
const target = b.resolveTargetQuery(target_query);
|
|
|
|
const codegen_path = b.pathFromRoot(
|
|
b.option([]const u8, "codegen_path", "Set the generated code directory") orelse
|
|
"build/debug/codegen",
|
|
);
|
|
const codegen_embed = b.option(bool, "codegen_embed", "If codegen files should be embedded in the binary") orelse switch (b.release_mode) {
|
|
.off => false,
|
|
else => true,
|
|
};
|
|
|
|
const bun_version = b.option([]const u8, "version", "Value of `Bun.version`") orelse "0.0.0";
|
|
|
|
// Lower the default reference trace for incremental
|
|
b.reference_trace = b.reference_trace orelse if (b.graph.incremental == true) 8 else 16;
|
|
|
|
const obj_format = b.option(ObjectFormat, "obj_format", "Output file for object files") orelse .obj;
|
|
|
|
const no_llvm = b.option(bool, "no_llvm", "Experiment with Zig self hosted backends. No stability guaranteed") orelse false;
|
|
const override_no_export_cpp_apis = b.option(bool, "override-no-export-cpp-apis", "Override the default export_cpp_apis logic to disable exports") orelse false;
|
|
|
|
var build_options = BunBuildOptions{
|
|
.target = target,
|
|
.optimize = optimize,
|
|
|
|
.os = os,
|
|
.arch = arch,
|
|
|
|
.codegen_path = codegen_path,
|
|
.codegen_embed = codegen_embed,
|
|
.no_llvm = no_llvm,
|
|
.override_no_export_cpp_apis = override_no_export_cpp_apis,
|
|
|
|
.version = try Version.parse(bun_version),
|
|
.canary_revision = canary: {
|
|
const rev = b.option(u32, "canary", "Treat this as a canary build") orelse 0;
|
|
break :canary if (rev == 0) null else rev;
|
|
},
|
|
|
|
.reported_nodejs_version = try Version.parse(
|
|
b.option([]const u8, "reported_nodejs_version", "Reported Node.js version") orelse
|
|
"0.0.0-unset",
|
|
),
|
|
|
|
.sha = sha: {
|
|
const sha_buildoption = b.option([]const u8, "sha", "Force the git sha");
|
|
const sha_github = b.graph.env_map.get("GITHUB_SHA");
|
|
const sha_env = b.graph.env_map.get("GIT_SHA");
|
|
const sha = sha_buildoption orelse sha_github orelse sha_env orelse fetch_sha: {
|
|
const result = std.process.Child.run(.{
|
|
.allocator = b.allocator,
|
|
.argv = &.{
|
|
"git",
|
|
"rev-parse",
|
|
"HEAD",
|
|
},
|
|
.cwd = b.pathFromRoot("."),
|
|
.expand_arg0 = .expand,
|
|
}) catch |err| {
|
|
std.log.warn("Failed to execute 'git rev-parse HEAD': {s}", .{@errorName(err)});
|
|
std.log.warn("Falling back to zero sha", .{});
|
|
break :sha zero_sha;
|
|
};
|
|
|
|
break :fetch_sha b.dupe(std.mem.trim(u8, result.stdout, "\n \t"));
|
|
};
|
|
|
|
if (sha.len == 0) {
|
|
std.log.warn("No git sha found, falling back to zero sha", .{});
|
|
break :sha zero_sha;
|
|
}
|
|
if (sha.len != 40) {
|
|
std.log.warn("Invalid git sha: {s}", .{sha});
|
|
std.log.warn("Falling back to zero sha", .{});
|
|
break :sha zero_sha;
|
|
}
|
|
|
|
break :sha sha;
|
|
},
|
|
|
|
.tracy_callstack_depth = b.option(u16, "tracy_callstack_depth", "") orelse 10,
|
|
.enable_logs = b.option(bool, "enable_logs", "Enable logs in release") orelse false,
|
|
.enable_asan = b.option(bool, "enable_asan", "Enable asan") orelse false,
|
|
};
|
|
|
|
// zig build obj
|
|
{
|
|
var step = b.step("obj", "Build Bun's Zig code as a .o file");
|
|
var bun_obj = addBunObject(b, &build_options);
|
|
step.dependOn(&bun_obj.step);
|
|
step.dependOn(addInstallObjectFile(b, bun_obj, "bun-zig", obj_format));
|
|
}
|
|
|
|
// zig build test
|
|
{
|
|
var step = b.step("test", "Build Bun's unit test suite");
|
|
var o = build_options;
|
|
var unit_tests = b.addTest(.{
|
|
.name = "bun-test",
|
|
.optimize = build_options.optimize,
|
|
.root_source_file = b.path("src/unit_test.zig"),
|
|
.test_runner = .{ .path = b.path("src/main_test.zig"), .mode = .simple },
|
|
.target = build_options.target,
|
|
.use_llvm = !build_options.no_llvm,
|
|
.use_lld = if (build_options.os == .mac) false else !build_options.no_llvm,
|
|
.omit_frame_pointer = false,
|
|
.strip = false,
|
|
});
|
|
configureObj(b, &o, unit_tests);
|
|
// Setting `linker_allow_shlib_undefined` causes the linker to ignore
|
|
// all undefined symbols. We want this because all we care about is the
|
|
// object file Zig creates; we perform our own linking later. There is
|
|
// currently no way to make a test build that only creates an object
|
|
// file w/o creating an executable.
|
|
//
|
|
// See: https://github.com/ziglang/zig/issues/23374
|
|
unit_tests.linker_allow_shlib_undefined = true;
|
|
unit_tests.link_function_sections = true;
|
|
unit_tests.link_data_sections = true;
|
|
unit_tests.bundle_ubsan_rt = false;
|
|
|
|
const bin = unit_tests.getEmittedBin();
|
|
const obj = bin.dirname().path(b, "bun-test.o");
|
|
const cpy_obj = b.addInstallFile(obj, "bun-test.o");
|
|
step.dependOn(&cpy_obj.step);
|
|
}
|
|
|
|
// zig build windows-shim
|
|
{
|
|
var step = b.step("windows-shim", "Build the Windows shim (bun_shim_impl.exe + bun_shim_debug.exe)");
|
|
var windows_shim = build_options.windowsShim(b);
|
|
step.dependOn(&b.addInstallFile(windows_shim.exe.getEmittedBin(), "bun_shim_impl.exe").step);
|
|
step.dependOn(&b.addInstallFile(windows_shim.dbg.getEmittedBin(), "bun_shim_debug.exe").step);
|
|
}
|
|
|
|
// zig build check
|
|
{
|
|
var step = b.step("check", "Check for semantic analysis errors");
|
|
var bun_check_obj = addBunObject(b, &build_options);
|
|
bun_check_obj.generated_bin = null;
|
|
step.dependOn(&bun_check_obj.step);
|
|
|
|
// The default install step will run zig build check. This is so ZLS
|
|
// identifies the codebase, as well as performs checking if build on
|
|
// save is enabled.
|
|
|
|
// For building Bun itself, one should run `bun setup`
|
|
b.default_step.dependOn(step);
|
|
}
|
|
|
|
// zig build watch
|
|
// const enable_watch_step = b.option(bool, "watch_step", "Enable the watch step. This reads more files so it is off by default") orelse false;
|
|
// if (no_llvm or enable_watch_step) {
|
|
// self_hosted_watch.selfHostedExeBuild(b, &build_options) catch @panic("OOM");
|
|
// }
|
|
|
|
// zig build check-debug
|
|
{
|
|
const step = b.step("check-debug", "Check for semantic analysis errors on some platforms");
|
|
addMultiCheck(b, step, build_options, &.{
|
|
.{ .os = .windows, .arch = .x86_64 },
|
|
.{ .os = .mac, .arch = .aarch64 },
|
|
.{ .os = .linux, .arch = .x86_64 },
|
|
}, &.{.Debug});
|
|
}
|
|
|
|
// zig build check-all
|
|
{
|
|
const step = b.step("check-all", "Check for semantic analysis errors on all supported platforms");
|
|
addMultiCheck(b, step, build_options, &.{
|
|
.{ .os = .windows, .arch = .x86_64 },
|
|
.{ .os = .mac, .arch = .x86_64 },
|
|
.{ .os = .mac, .arch = .aarch64 },
|
|
.{ .os = .linux, .arch = .x86_64 },
|
|
.{ .os = .linux, .arch = .aarch64 },
|
|
.{ .os = .linux, .arch = .x86_64, .musl = true },
|
|
.{ .os = .linux, .arch = .aarch64, .musl = true },
|
|
}, &.{ .Debug, .ReleaseFast });
|
|
}
|
|
|
|
// zig build check-all-debug
|
|
{
|
|
const step = b.step("check-all-debug", "Check for semantic analysis errors on all supported platforms in debug mode");
|
|
addMultiCheck(b, step, build_options, &.{
|
|
.{ .os = .windows, .arch = .x86_64 },
|
|
.{ .os = .mac, .arch = .x86_64 },
|
|
.{ .os = .mac, .arch = .aarch64 },
|
|
.{ .os = .linux, .arch = .x86_64 },
|
|
.{ .os = .linux, .arch = .aarch64 },
|
|
.{ .os = .linux, .arch = .x86_64, .musl = true },
|
|
.{ .os = .linux, .arch = .aarch64, .musl = true },
|
|
}, &.{.Debug});
|
|
}
|
|
|
|
// zig build check-windows
|
|
{
|
|
const step = b.step("check-windows", "Check for semantic analysis errors on Windows");
|
|
addMultiCheck(b, step, build_options, &.{
|
|
.{ .os = .windows, .arch = .x86_64 },
|
|
}, &.{ .Debug, .ReleaseFast });
|
|
}
|
|
{
|
|
const step = b.step("check-windows-debug", "Check for semantic analysis errors on Windows");
|
|
addMultiCheck(b, step, build_options, &.{
|
|
.{ .os = .windows, .arch = .x86_64 },
|
|
}, &.{.Debug});
|
|
}
|
|
{
|
|
const step = b.step("check-macos", "Check for semantic analysis errors on Windows");
|
|
addMultiCheck(b, step, build_options, &.{
|
|
.{ .os = .mac, .arch = .x86_64 },
|
|
.{ .os = .mac, .arch = .aarch64 },
|
|
}, &.{ .Debug, .ReleaseFast });
|
|
}
|
|
{
|
|
const step = b.step("check-macos-debug", "Check for semantic analysis errors on Windows");
|
|
addMultiCheck(b, step, build_options, &.{
|
|
.{ .os = .mac, .arch = .x86_64 },
|
|
.{ .os = .mac, .arch = .aarch64 },
|
|
}, &.{.Debug});
|
|
}
|
|
{
|
|
const step = b.step("check-linux", "Check for semantic analysis errors on Windows");
|
|
addMultiCheck(b, step, build_options, &.{
|
|
.{ .os = .linux, .arch = .x86_64 },
|
|
.{ .os = .linux, .arch = .aarch64 },
|
|
}, &.{ .Debug, .ReleaseFast });
|
|
}
|
|
{
|
|
const step = b.step("check-linux-debug", "Check for semantic analysis errors on Windows");
|
|
addMultiCheck(b, step, build_options, &.{
|
|
.{ .os = .linux, .arch = .x86_64 },
|
|
.{ .os = .linux, .arch = .aarch64 },
|
|
}, &.{.Debug});
|
|
}
|
|
|
|
// zig build translate-c-headers
|
|
{
|
|
const step = b.step("translate-c", "Copy generated translated-c-headers.zig to zig-out");
|
|
for ([_]TargetDescription{
|
|
.{ .os = .windows, .arch = .x86_64 },
|
|
.{ .os = .mac, .arch = .x86_64 },
|
|
.{ .os = .mac, .arch = .aarch64 },
|
|
.{ .os = .linux, .arch = .x86_64 },
|
|
.{ .os = .linux, .arch = .aarch64 },
|
|
.{ .os = .linux, .arch = .x86_64, .musl = true },
|
|
.{ .os = .linux, .arch = .aarch64, .musl = true },
|
|
}) |t| {
|
|
const resolved = t.resolveTarget(b);
|
|
step.dependOn(
|
|
&b.addInstallFile(getTranslateC(b, resolved, .Debug), b.fmt("translated-c-headers/{s}.zig", .{
|
|
resolved.result.zigTriple(b.allocator) catch @panic("OOM"),
|
|
})).step,
|
|
);
|
|
}
|
|
}
|
|
|
|
// zig build enum-extractor
|
|
{
|
|
// const step = b.step("enum-extractor", "Extract enum definitions (invoked by a code generator)");
|
|
// const exe = b.addExecutable(.{
|
|
// .name = "enum_extractor",
|
|
// .root_source_file = b.path("./src/generated_enum_extractor.zig"),
|
|
// .target = b.graph.host,
|
|
// .optimize = .Debug,
|
|
// });
|
|
// const run = b.addRunArtifact(exe);
|
|
// step.dependOn(&run.step);
|
|
}
|
|
}
|
|
|
|
const TargetDescription = struct {
|
|
os: OperatingSystem,
|
|
arch: Arch,
|
|
musl: bool = false,
|
|
|
|
fn resolveTarget(desc: TargetDescription, b: *Build) std.Build.ResolvedTarget {
|
|
return b.resolveTargetQuery(.{
|
|
.os_tag = OperatingSystem.stdOSTag(desc.os),
|
|
.cpu_arch = desc.arch,
|
|
.cpu_model = getCpuModel(desc.os, desc.arch) orelse .determined_by_arch_os,
|
|
.os_version_min = getOSVersionMin(desc.os),
|
|
.glibc_version = if (desc.musl) null else getOSGlibCVersion(desc.os),
|
|
});
|
|
}
|
|
};
|
|
|
|
fn addMultiCheck(
|
|
b: *Build,
|
|
parent_step: *Step,
|
|
root_build_options: BunBuildOptions,
|
|
to_check: []const TargetDescription,
|
|
optimize: []const std.builtin.OptimizeMode,
|
|
) void {
|
|
for (to_check) |check| {
|
|
for (optimize) |mode| {
|
|
const check_target = check.resolveTarget(b);
|
|
var options: BunBuildOptions = .{
|
|
.target = check_target,
|
|
.os = check.os,
|
|
.arch = check_target.result.cpu.arch,
|
|
.optimize = mode,
|
|
|
|
.canary_revision = root_build_options.canary_revision,
|
|
.sha = root_build_options.sha,
|
|
.tracy_callstack_depth = root_build_options.tracy_callstack_depth,
|
|
.version = root_build_options.version,
|
|
.reported_nodejs_version = root_build_options.reported_nodejs_version,
|
|
.codegen_path = root_build_options.codegen_path,
|
|
.no_llvm = root_build_options.no_llvm,
|
|
.enable_asan = root_build_options.enable_asan,
|
|
.override_no_export_cpp_apis = root_build_options.override_no_export_cpp_apis,
|
|
};
|
|
|
|
var obj = addBunObject(b, &options);
|
|
obj.generated_bin = null;
|
|
parent_step.dependOn(&obj.step);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn getTranslateC(b: *Build, initial_target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) LazyPath {
|
|
const target = b.resolveTargetQuery(q: {
|
|
var query = initial_target.query;
|
|
if (query.os_tag == .windows)
|
|
query.abi = .gnu;
|
|
break :q query;
|
|
});
|
|
const translate_c = b.addTranslateC(.{
|
|
.root_source_file = b.path("src/c-headers-for-zig.h"),
|
|
.target = target,
|
|
.optimize = optimize,
|
|
.link_libc = true,
|
|
});
|
|
inline for ([_](struct { []const u8, bool }){
|
|
.{ "WINDOWS", translate_c.target.result.os.tag == .windows },
|
|
.{ "POSIX", translate_c.target.result.os.tag != .windows },
|
|
.{ "LINUX", translate_c.target.result.os.tag == .linux },
|
|
.{ "DARWIN", translate_c.target.result.os.tag.isDarwin() },
|
|
}) |entry| {
|
|
const str, const value = entry;
|
|
translate_c.defineCMacroRaw(b.fmt("{s}={d}", .{ str, @intFromBool(value) }));
|
|
}
|
|
|
|
translate_c.addIncludePath(b.path("vendor/zstd/lib"));
|
|
|
|
if (target.result.os.tag == .windows) {
|
|
// translate-c is unable to translate the unsuffixed windows functions
|
|
// like `SetCurrentDirectory` since they are defined with an odd macro
|
|
// that translate-c doesn't handle.
|
|
//
|
|
// #define SetCurrentDirectory __MINGW_NAME_AW(SetCurrentDirectory)
|
|
//
|
|
// In these cases, it's better to just reference the underlying function
|
|
// directly: SetCurrentDirectoryW. To make the error better, a post
|
|
// processing step is applied to the translate-c file.
|
|
//
|
|
// Additionally, this step makes it so that decls like NTSTATUS and
|
|
// HANDLE point to the standard library structures.
|
|
const helper_exe = b.addExecutable(.{
|
|
.name = "process_windows_translate_c",
|
|
.root_module = b.createModule(.{
|
|
.root_source_file = b.path("src/codegen/process_windows_translate_c.zig"),
|
|
.target = b.graph.host,
|
|
.optimize = .Debug,
|
|
}),
|
|
});
|
|
const in = translate_c.getOutput();
|
|
const run = b.addRunArtifact(helper_exe);
|
|
run.addFileArg(in);
|
|
const out = run.addOutputFileArg("c-headers-for-zig.zig");
|
|
return out;
|
|
}
|
|
return translate_c.getOutput();
|
|
}
|
|
|
|
pub fn addBunObject(b: *Build, opts: *BunBuildOptions) *Compile {
|
|
// Create `@import("bun")`, containing most of Bun's code.
|
|
const bun = b.createModule(.{
|
|
.root_source_file = b.path("src/bun.zig"),
|
|
});
|
|
bun.addImport("bun", bun); // allow circular "bun" import
|
|
addInternalImports(b, bun, opts);
|
|
|
|
const root = b.createModule(.{
|
|
.root_source_file = b.path("src/main.zig"),
|
|
|
|
// Root module gets compilation flags. Forwarded as default to dependencies.
|
|
.target = opts.target,
|
|
.optimize = opts.optimize,
|
|
});
|
|
root.addImport("bun", bun);
|
|
|
|
const obj = b.addObject(.{
|
|
.name = if (opts.optimize == .Debug) "bun-debug" else "bun",
|
|
.root_module = root,
|
|
});
|
|
configureObj(b, opts, obj);
|
|
if (enableFastBuild(b)) obj.root_module.strip = true;
|
|
return obj;
|
|
}
|
|
|
|
fn enableFastBuild(b: *Build) bool {
|
|
const val = b.graph.env_map.get("BUN_BUILD_FAST") orelse return false;
|
|
return std.mem.eql(u8, val, "1");
|
|
}
|
|
|
|
fn configureObj(b: *Build, opts: *BunBuildOptions, obj: *Compile) void {
|
|
// Flags on root module get used for the compilation
|
|
obj.root_module.omit_frame_pointer = false;
|
|
obj.root_module.strip = false; // stripped at the end
|
|
// https://github.com/ziglang/zig/issues/17430
|
|
obj.root_module.pic = true;
|
|
|
|
// Object options
|
|
obj.use_llvm = !opts.no_llvm;
|
|
obj.use_lld = if (opts.os == .mac) false else !opts.no_llvm;
|
|
if (opts.enable_asan and !enableFastBuild(b)) {
|
|
if (@hasField(Build.Module, "sanitize_address")) {
|
|
obj.root_module.sanitize_address = true;
|
|
} else {
|
|
const fail_step = b.addFail("asan is not supported on this platform");
|
|
obj.step.dependOn(&fail_step.step);
|
|
}
|
|
}
|
|
obj.bundle_compiler_rt = false;
|
|
obj.bundle_ubsan_rt = false;
|
|
|
|
// Link libc
|
|
if (opts.os != .wasm) {
|
|
obj.linkLibC();
|
|
obj.linkLibCpp();
|
|
}
|
|
|
|
// Disable stack probing on x86 so we don't need to include compiler_rt
|
|
if (opts.arch.isX86()) {
|
|
// TODO: enable on debug please.
|
|
obj.root_module.stack_check = false;
|
|
obj.root_module.stack_protector = false;
|
|
}
|
|
|
|
if (opts.os == .linux) {
|
|
obj.link_emit_relocs = false;
|
|
obj.link_eh_frame_hdr = false;
|
|
obj.link_function_sections = true;
|
|
obj.link_data_sections = true;
|
|
|
|
if (opts.optimize == .Debug) {
|
|
obj.root_module.valgrind = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
const ObjectFormat = enum {
|
|
/// Emitting LLVM bc files could allow a stronger LTO pass, however it
|
|
/// doesn't yet work. It is left accessible with `-Dobj_format=bc` or in
|
|
/// CMake with `-DZIG_OBJECT_FORMAT=bc`.
|
|
///
|
|
/// To use LLVM bitcode from Zig, more work needs to be done. Currently, an install of
|
|
/// LLVM 18.1.7 does not compatible with what bitcode Zig 0.13 outputs (has LLVM 18.1.7)
|
|
/// Change to "bc" to experiment, "Invalid record" means it is not valid output.
|
|
bc,
|
|
/// Emit a .o / .obj file for the bun-zig object.
|
|
obj,
|
|
};
|
|
|
|
pub fn addInstallObjectFile(
|
|
b: *Build,
|
|
compile: *Compile,
|
|
name: []const u8,
|
|
out_mode: ObjectFormat,
|
|
) *Step {
|
|
// bin always needed to be computed or else the compilation will do nothing. zig build system bug?
|
|
const bin = compile.getEmittedBin();
|
|
return &b.addInstallFile(switch (out_mode) {
|
|
.obj => bin,
|
|
.bc => compile.getEmittedLlvmBc(),
|
|
}, b.fmt("{s}.o", .{name})).step;
|
|
}
|
|
|
|
var checked_file_exists: std.AutoHashMap(u64, void) = undefined;
|
|
fn exists(path: []const u8) bool {
|
|
const entry = checked_file_exists.getOrPut(std.hash.Wyhash.hash(0, path)) catch unreachable;
|
|
if (entry.found_existing) {
|
|
// It would've panicked.
|
|
return true;
|
|
}
|
|
|
|
std.fs.accessAbsolute(path, .{ .mode = .read_only }) catch return false;
|
|
return true;
|
|
}
|
|
|
|
fn addInternalImports(b: *Build, mod: *Module, opts: *BunBuildOptions) void {
|
|
const os = opts.os;
|
|
|
|
mod.addImport("build_options", opts.buildOptionsModule(b));
|
|
|
|
const translate_c = getTranslateC(b, opts.target, opts.optimize);
|
|
mod.addImport("translated-c-headers", b.createModule(.{ .root_source_file = translate_c }));
|
|
|
|
const zlib_internal_path = switch (os) {
|
|
.windows => "src/deps/zlib.win32.zig",
|
|
.linux, .mac => "src/deps/zlib.posix.zig",
|
|
else => null,
|
|
};
|
|
if (zlib_internal_path) |path| {
|
|
mod.addAnonymousImport("zlib-internal", .{
|
|
.root_source_file = b.path(path),
|
|
});
|
|
}
|
|
|
|
const async_path = switch (os) {
|
|
.linux, .mac => "src/async/posix_event_loop.zig",
|
|
.windows => "src/async/windows_event_loop.zig",
|
|
else => "src/async/stub_event_loop.zig",
|
|
};
|
|
mod.addAnonymousImport("async", .{
|
|
.root_source_file = b.path(async_path),
|
|
});
|
|
|
|
// Generated code exposed as individual modules.
|
|
inline for (.{
|
|
.{ .file = "ZigGeneratedClasses.zig", .import = "ZigGeneratedClasses" },
|
|
.{ .file = "ResolvedSourceTag.zig", .import = "ResolvedSourceTag" },
|
|
.{ .file = "ErrorCode.zig", .import = "ErrorCode" },
|
|
.{ .file = "runtime.out.js", .enable = opts.shouldEmbedCode() },
|
|
.{ .file = "bake.client.js", .import = "bake-codegen/bake.client.js", .enable = opts.shouldEmbedCode() },
|
|
.{ .file = "bake.error.js", .import = "bake-codegen/bake.error.js", .enable = opts.shouldEmbedCode() },
|
|
.{ .file = "bake.server.js", .import = "bake-codegen/bake.server.js", .enable = opts.shouldEmbedCode() },
|
|
.{ .file = "bun-error/index.js", .enable = opts.shouldEmbedCode() },
|
|
.{ .file = "bun-error/bun-error.css", .enable = opts.shouldEmbedCode() },
|
|
.{ .file = "fallback-decoder.js", .enable = opts.shouldEmbedCode() },
|
|
.{ .file = "node-fallbacks/react-refresh.js", .enable = opts.shouldEmbedCode() },
|
|
.{ .file = "node-fallbacks/assert.js", .enable = opts.shouldEmbedCode() },
|
|
.{ .file = "node-fallbacks/buffer.js", .enable = opts.shouldEmbedCode() },
|
|
.{ .file = "node-fallbacks/console.js", .enable = opts.shouldEmbedCode() },
|
|
.{ .file = "node-fallbacks/constants.js", .enable = opts.shouldEmbedCode() },
|
|
.{ .file = "node-fallbacks/crypto.js", .enable = opts.shouldEmbedCode() },
|
|
.{ .file = "node-fallbacks/domain.js", .enable = opts.shouldEmbedCode() },
|
|
.{ .file = "node-fallbacks/events.js", .enable = opts.shouldEmbedCode() },
|
|
.{ .file = "node-fallbacks/http.js", .enable = opts.shouldEmbedCode() },
|
|
.{ .file = "node-fallbacks/https.js", .enable = opts.shouldEmbedCode() },
|
|
.{ .file = "node-fallbacks/net.js", .enable = opts.shouldEmbedCode() },
|
|
.{ .file = "node-fallbacks/os.js", .enable = opts.shouldEmbedCode() },
|
|
.{ .file = "node-fallbacks/path.js", .enable = opts.shouldEmbedCode() },
|
|
.{ .file = "node-fallbacks/process.js", .enable = opts.shouldEmbedCode() },
|
|
.{ .file = "node-fallbacks/punycode.js", .enable = opts.shouldEmbedCode() },
|
|
.{ .file = "node-fallbacks/querystring.js", .enable = opts.shouldEmbedCode() },
|
|
.{ .file = "node-fallbacks/stream.js", .enable = opts.shouldEmbedCode() },
|
|
.{ .file = "node-fallbacks/string_decoder.js", .enable = opts.shouldEmbedCode() },
|
|
.{ .file = "node-fallbacks/sys.js", .enable = opts.shouldEmbedCode() },
|
|
.{ .file = "node-fallbacks/timers.js", .enable = opts.shouldEmbedCode() },
|
|
.{ .file = "node-fallbacks/tty.js", .enable = opts.shouldEmbedCode() },
|
|
.{ .file = "node-fallbacks/url.js", .enable = opts.shouldEmbedCode() },
|
|
.{ .file = "node-fallbacks/util.js", .enable = opts.shouldEmbedCode() },
|
|
.{ .file = "node-fallbacks/zlib.js", .enable = opts.shouldEmbedCode() },
|
|
}) |entry| {
|
|
if (!@hasField(@TypeOf(entry), "enable") or entry.enable) {
|
|
const path = b.pathJoin(&.{ opts.codegen_path, entry.file });
|
|
validateGeneratedPath(path);
|
|
const import_path = if (@hasField(@TypeOf(entry), "import"))
|
|
entry.import
|
|
else
|
|
entry.file;
|
|
mod.addAnonymousImport(import_path, .{
|
|
.root_source_file = .{ .cwd_relative = path },
|
|
});
|
|
}
|
|
}
|
|
{
|
|
const cppImport = b.createModule(.{
|
|
.root_source_file = (std.Build.LazyPath{ .cwd_relative = opts.codegen_path }).path(b, "cpp.zig"),
|
|
});
|
|
mod.addImport("cpp", cppImport);
|
|
cppImport.addImport("bun", mod);
|
|
}
|
|
inline for (.{
|
|
.{ .import = "completions-bash", .file = b.path("completions/bun.bash") },
|
|
.{ .import = "completions-zsh", .file = b.path("completions/bun.zsh") },
|
|
.{ .import = "completions-fish", .file = b.path("completions/bun.fish") },
|
|
}) |entry| {
|
|
mod.addAnonymousImport(entry.import, .{
|
|
.root_source_file = entry.file,
|
|
});
|
|
}
|
|
|
|
if (os == .windows) {
|
|
mod.addAnonymousImport("bun_shim_impl.exe", .{
|
|
.root_source_file = opts.windowsShim(b).exe.getEmittedBin(),
|
|
});
|
|
}
|
|
|
|
// Finally, make it so all modules share the same import table.
|
|
propagateImports(mod) catch @panic("OOM");
|
|
}
|
|
|
|
/// Makes all imports of `source_mod` visible to all of its dependencies.
|
|
/// Does not replace existing imports.
|
|
fn propagateImports(source_mod: *Module) !void {
|
|
var seen = std.AutoHashMap(*Module, void).init(source_mod.owner.graph.arena);
|
|
defer seen.deinit();
|
|
var queue = std.ArrayList(*Module).init(source_mod.owner.graph.arena);
|
|
defer queue.deinit();
|
|
try queue.appendSlice(source_mod.import_table.values());
|
|
while (queue.pop()) |mod| {
|
|
if ((try seen.getOrPut(mod)).found_existing) continue;
|
|
try queue.appendSlice(mod.import_table.values());
|
|
|
|
for (source_mod.import_table.keys(), source_mod.import_table.values()) |k, v|
|
|
if (mod.import_table.get(k) == null)
|
|
mod.addImport(k, v);
|
|
}
|
|
}
|
|
|
|
fn validateGeneratedPath(path: []const u8) void {
|
|
if (!exists(path)) {
|
|
std.debug.panic(
|
|
\\Generated file '{s}' is missing!
|
|
\\
|
|
\\Make sure to use CMake and Ninja, or pass a manual codegen folder with '-Dgenerated-code=...'
|
|
, .{path});
|
|
}
|
|
}
|
|
|
|
const WindowsShim = struct {
|
|
exe: *Compile,
|
|
dbg: *Compile,
|
|
|
|
fn create(b: *Build) WindowsShim {
|
|
const target = b.resolveTargetQuery(.{
|
|
.cpu_model = .{ .explicit = &std.Target.x86.cpu.nehalem },
|
|
.cpu_arch = .x86_64,
|
|
.os_tag = .windows,
|
|
.os_version_min = getOSVersionMin(.windows),
|
|
});
|
|
|
|
const path = b.path("src/install/windows-shim/bun_shim_impl.zig");
|
|
|
|
const exe = b.addExecutable(.{
|
|
.name = "bun_shim_impl",
|
|
.root_module = b.createModule(.{
|
|
.root_source_file = path,
|
|
.target = target,
|
|
.optimize = .ReleaseFast,
|
|
.unwind_tables = .none,
|
|
.omit_frame_pointer = true,
|
|
.strip = true,
|
|
.sanitize_thread = false,
|
|
.single_threaded = true,
|
|
.link_libc = false,
|
|
}),
|
|
.linkage = .static,
|
|
.use_llvm = true,
|
|
.use_lld = true,
|
|
});
|
|
|
|
const dbg = b.addExecutable(.{
|
|
.name = "bun_shim_debug",
|
|
.root_module = b.createModule(.{
|
|
.root_source_file = path,
|
|
.target = target,
|
|
.optimize = .Debug,
|
|
.single_threaded = true,
|
|
.link_libc = false,
|
|
}),
|
|
.linkage = .static,
|
|
.use_llvm = true,
|
|
.use_lld = true,
|
|
});
|
|
|
|
return .{ .exe = exe, .dbg = dbg };
|
|
}
|
|
};
|