mirror of
https://github.com/oven-sh/bun
synced 2026-02-06 00:48:55 +00:00
Compare commits
8 Commits
dylan/pyth
...
jarred/wip
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bbcbad647b | ||
|
|
4f0dac7629 | ||
|
|
7aad3130d1 | ||
|
|
ee9eb2bc04 | ||
|
|
5d20d832f7 | ||
|
|
3123df3278 | ||
|
|
5859b9b482 | ||
|
|
2064e844a5 |
25
packages/bun-types/build-types.patch
Normal file
25
packages/bun-types/build-types.patch
Normal file
@@ -0,0 +1,25 @@
|
||||
// Define platform and architecture types
|
||||
type BuildPlatform = "darwin" | "linux" | "win32";
|
||||
type BuildArchitecture = "x64" | "arm64";
|
||||
|
||||
type BuildTarget =
|
||||
| BuildPlatform
|
||||
| BuildArchitecture
|
||||
| `${BuildPlatform}-${BuildArchitecture}`
|
||||
| `${BuildPlatform}-${BuildArchitecture}-baseline`;
|
||||
|
||||
interface BuildConfig {
|
||||
// Other existing properties...
|
||||
|
||||
/**
|
||||
* Generate a standalone executable
|
||||
* @default false
|
||||
*/
|
||||
compile?: boolean;
|
||||
|
||||
/**
|
||||
* Specify target platform(s) for compilation
|
||||
* Currently, only one target can be specified
|
||||
*/
|
||||
targets?: BuildTarget | BuildTarget[];
|
||||
}
|
||||
30
packages/bun-types/bun.d.ts
vendored
30
packages/bun-types/bun.d.ts
vendored
@@ -1499,6 +1499,19 @@ declare module "bun" {
|
||||
|
||||
type ModuleFormat = "esm"; // later: "cjs", "iife"
|
||||
|
||||
/**
|
||||
* Platform target for compiling executables
|
||||
*/
|
||||
type BuildPlatform = "darwin" | "linux" | "win32";
|
||||
/**
|
||||
* Architecture target for compiling executables
|
||||
*/
|
||||
type BuildArchitecture = "x64" | "arm64";
|
||||
|
||||
type CompileTargetOperatingSystem = "windows" | "macos" | "linux";
|
||||
type CompileTargetArchitecture = "x64" | "arm64";
|
||||
type CompileTargetBaselineOrModern = "baseline" | "modern";
|
||||
|
||||
interface BuildConfig {
|
||||
entrypoints: string[]; // list of file path
|
||||
outdir?: string; // output directory
|
||||
@@ -1561,6 +1574,23 @@ declare module "bun" {
|
||||
// /** Only works when runtime=automatic */
|
||||
// importSource?: string; // default: "react"
|
||||
// };
|
||||
|
||||
/**
|
||||
* Generate a standalone executable
|
||||
* @default false
|
||||
*/
|
||||
compile?: boolean;
|
||||
|
||||
/**
|
||||
* Specify target platform(s) for compilation
|
||||
* Currently, only one target can be specified
|
||||
*/
|
||||
targets?:
|
||||
| BuildPlatform
|
||||
| BuildArchitecture
|
||||
| `${BuildPlatform}-${BuildArchitecture}`
|
||||
| `${BuildPlatform}-${BuildArchitecture}-baseline`
|
||||
| Array<BuildPlatform | BuildArchitecture | `${BuildPlatform}-${BuildArchitecture}` | `${BuildPlatform}-${BuildArchitecture}-baseline`>;
|
||||
}
|
||||
|
||||
namespace Password {
|
||||
|
||||
@@ -21,6 +21,40 @@ const testing = std.testing;
|
||||
const assert = (std.debug).assert;
|
||||
const Progress = @This();
|
||||
|
||||
/// An implementation of Progress that doesn't do anything.
|
||||
/// This makes it easier to reuse code that uses Progress in places where a progress bar is disabled.
|
||||
pub const NoOp = struct {
|
||||
pub fn start(this: *@This(), name: []const u8, estimated_total_items: usize) *NoOp {
|
||||
_ = this; // autofix
|
||||
_ = name; // autofix
|
||||
_ = estimated_total_items; // autofix
|
||||
return undefined;
|
||||
}
|
||||
|
||||
pub fn end(this: *@This()) void {
|
||||
_ = this; // autofix
|
||||
}
|
||||
pub fn completeOne(this: *@This()) void {
|
||||
_ = this; // autofix
|
||||
}
|
||||
pub fn setName(this: *@This(), name: []const u8) void {
|
||||
_ = this; // autofix
|
||||
_ = name; // autofix
|
||||
}
|
||||
pub fn setUnit(this: *@This(), unit: []const u8) void {
|
||||
_ = this; // autofix
|
||||
_ = unit; // autofix
|
||||
}
|
||||
pub fn setEstimatedTotalItems(this: *@This(), count: usize) void {
|
||||
_ = this; // autofix
|
||||
_ = count; // autofix
|
||||
}
|
||||
|
||||
pub fn refresh(this: *@This()) void {
|
||||
_ = this; // autofix
|
||||
}
|
||||
};
|
||||
|
||||
/// `null` if the current node (and its children) should
|
||||
/// not print on update()
|
||||
terminal: ?std.fs.File = undefined,
|
||||
|
||||
@@ -311,6 +311,7 @@ pub const StandaloneModuleGraph = struct {
|
||||
try string_builder.allocate(allocator);
|
||||
|
||||
var modules = try std.ArrayList(CompiledModuleGraphFile).initCapacity(allocator, module_count);
|
||||
defer modules.deinit();
|
||||
|
||||
var source_map_header_list = std.ArrayList(u8).init(allocator);
|
||||
defer source_map_header_list.deinit();
|
||||
@@ -391,11 +392,11 @@ pub const StandaloneModuleGraph = struct {
|
||||
else
|
||||
std.mem.page_size;
|
||||
|
||||
pub fn inject(bytes: []const u8, self_exe: [:0]const u8) bun.FileDescriptor {
|
||||
pub fn inject(bytes: []const u8, self_exe: [:0]const u8, log: anytype) !struct { bun.FileDescriptor, usize } {
|
||||
var buf: bun.PathBuffer = undefined;
|
||||
var zname: [:0]const u8 = bun.span(bun.fs.FileSystem.instance.tmpname("bun-build", &buf, @as(u64, @bitCast(std.time.milliTimestamp()))) catch |err| {
|
||||
Output.prettyErrorln("<r><red>error<r><d>:<r> failed to get temporary file name: {s}", .{@errorName(err)});
|
||||
Global.exit(1);
|
||||
log.err(err, "failed to get temporary file name", .{});
|
||||
return error.Fatal;
|
||||
});
|
||||
|
||||
const cleanup = struct {
|
||||
@@ -419,8 +420,8 @@ pub const StandaloneModuleGraph = struct {
|
||||
const out = out_buf[0..zname.len :0];
|
||||
|
||||
bun.copyFile(in, out).unwrap() catch |err| {
|
||||
Output.prettyErrorln("<r><red>error<r><d>:<r> failed to copy bun executable into temporary file: {s}", .{@errorName(err)});
|
||||
Global.exit(1);
|
||||
log.err(err, "failed to copy bun executable into temporary file", .{});
|
||||
return error.Fatal;
|
||||
};
|
||||
const file = bun.sys.openFileAtWindows(
|
||||
bun.invalid_fd,
|
||||
@@ -432,8 +433,8 @@ pub const StandaloneModuleGraph = struct {
|
||||
// create options
|
||||
w.FILE_SYNCHRONOUS_IO_NONALERT | w.FILE_OPEN_REPARSE_POINT,
|
||||
).unwrap() catch |e| {
|
||||
Output.prettyErrorln("<r><red>error<r><d>:<r> failed to open temporary file to copy bun into\n{}", .{e});
|
||||
Global.exit(1);
|
||||
log.err(e, "failed to open temporary file to copy bun into", .{});
|
||||
return error.Fatal;
|
||||
};
|
||||
|
||||
break :brk file;
|
||||
@@ -486,8 +487,8 @@ pub const StandaloneModuleGraph = struct {
|
||||
else => break,
|
||||
}
|
||||
|
||||
Output.prettyErrorln("<r><red>error<r><d>:<r> failed to open temporary file to copy bun into\n{}", .{err});
|
||||
Global.exit(1);
|
||||
log.err(err, "failed to open temporary file to copy bun into", .{});
|
||||
return error.Fatal;
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -507,9 +508,9 @@ pub const StandaloneModuleGraph = struct {
|
||||
}
|
||||
}
|
||||
|
||||
Output.prettyErrorln("<r><red>error<r><d>:<r> failed to open bun executable to copy from as read-only\n{}", .{err});
|
||||
log.err(err, "failed to open bun executable to copy from as read-only", .{});
|
||||
cleanup(zname, fd);
|
||||
Global.exit(1);
|
||||
return error.Fatal;
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -519,9 +520,9 @@ pub const StandaloneModuleGraph = struct {
|
||||
defer _ = Syscall.close(self_fd);
|
||||
|
||||
bun.copyFile(self_fd.cast(), fd.cast()).unwrap() catch |err| {
|
||||
Output.prettyErrorln("<r><red>error<r><d>:<r> failed to copy bun executable into temporary file: {s}", .{@errorName(err)});
|
||||
log.err(err, "failed to copy bun executable into temporary file", .{});
|
||||
cleanup(zname, fd);
|
||||
Global.exit(1);
|
||||
return error.Fatal;
|
||||
};
|
||||
break :brk fd;
|
||||
};
|
||||
@@ -530,18 +531,18 @@ pub const StandaloneModuleGraph = struct {
|
||||
|
||||
if (Environment.isWindows) {
|
||||
total_byte_count = bytes.len + 8 + (Syscall.setFileOffsetToEndWindows(cloned_executable_fd).unwrap() catch |err| {
|
||||
Output.prettyErrorln("<r><red>error<r><d>:<r> failed to seek to end of temporary file\n{}", .{err});
|
||||
log.err(err, "failed to seek to end of temporary file", .{});
|
||||
cleanup(zname, cloned_executable_fd);
|
||||
Global.exit(1);
|
||||
return error.Fatal;
|
||||
});
|
||||
} else {
|
||||
const seek_position = @as(u64, @intCast(brk: {
|
||||
const fstat = switch (Syscall.fstat(cloned_executable_fd)) {
|
||||
.result => |res| res,
|
||||
.err => |err| {
|
||||
Output.prettyErrorln("{}", .{err});
|
||||
log.err(err, "failed to get file size of temporary file", .{});
|
||||
cleanup(zname, cloned_executable_fd);
|
||||
Global.exit(1);
|
||||
return error.Fatal;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -560,15 +561,15 @@ pub const StandaloneModuleGraph = struct {
|
||||
//
|
||||
switch (Syscall.setFileOffset(cloned_executable_fd, seek_position)) {
|
||||
.err => |err| {
|
||||
Output.prettyErrorln(
|
||||
"{}\nwhile seeking to end of temporary file (pos: {d})",
|
||||
log.err(
|
||||
err,
|
||||
"failed to seek to end of temporary file (pos: {d})",
|
||||
.{
|
||||
err,
|
||||
seek_position,
|
||||
},
|
||||
);
|
||||
cleanup(zname, cloned_executable_fd);
|
||||
Global.exit(1);
|
||||
return error.Fatal;
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
@@ -579,10 +580,9 @@ pub const StandaloneModuleGraph = struct {
|
||||
switch (Syscall.write(cloned_executable_fd, bytes)) {
|
||||
.result => |written| remain = remain[written..],
|
||||
.err => |err| {
|
||||
Output.prettyErrorln("<r><red>error<r><d>:<r> failed to write to temporary file\n{}", .{err});
|
||||
log.err(err, "failed to write to temporary file", .{});
|
||||
cleanup(zname, cloned_executable_fd);
|
||||
|
||||
Global.exit(1);
|
||||
return error.Fatal;
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -593,19 +593,19 @@ pub const StandaloneModuleGraph = struct {
|
||||
_ = bun.C.fchmod(cloned_executable_fd.int(), 0o777);
|
||||
}
|
||||
|
||||
return cloned_executable_fd;
|
||||
return .{ cloned_executable_fd, total_byte_count };
|
||||
}
|
||||
|
||||
pub const CompileTarget = @import("./compile_target.zig");
|
||||
|
||||
pub fn download(allocator: std.mem.Allocator, target: *const CompileTarget, env: *bun.DotEnv.Loader) ![:0]const u8 {
|
||||
pub fn download(allocator: std.mem.Allocator, target: *const CompileTarget, env: *bun.DotEnv.Loader, log: anytype) ![:0]const u8 {
|
||||
var exe_path_buf: bun.PathBuffer = undefined;
|
||||
var version_str_buf: [1024]u8 = undefined;
|
||||
const version_str = try std.fmt.bufPrintZ(&version_str_buf, "{}", .{target});
|
||||
var needs_download: bool = true;
|
||||
const dest_z = target.exePath(&exe_path_buf, version_str, env, &needs_download);
|
||||
if (needs_download) {
|
||||
try target.downloadToPath(env, allocator, dest_z);
|
||||
try target.downloadToPath(env, allocator, dest_z, @TypeOf(log) == type and log == Output, log);
|
||||
}
|
||||
|
||||
return try allocator.dupeZ(u8, dest_z);
|
||||
@@ -614,30 +614,50 @@ pub const StandaloneModuleGraph = struct {
|
||||
pub fn toExecutable(
|
||||
target: *const CompileTarget,
|
||||
allocator: std.mem.Allocator,
|
||||
output_files: []const bun.options.OutputFile,
|
||||
output_files: []bun.options.OutputFile,
|
||||
root_dir: std.fs.Dir,
|
||||
module_prefix: []const u8,
|
||||
outfile: []const u8,
|
||||
env: *bun.DotEnv.Loader,
|
||||
log: anytype,
|
||||
hash_ptr: ?*u64,
|
||||
) !void {
|
||||
const bytes = try toBytes(allocator, module_prefix, output_files);
|
||||
if (bytes.len == 0) return;
|
||||
|
||||
const fd = inject(
|
||||
if (hash_ptr) |hash| {
|
||||
hash.* = std.hash.XxHash64.hash(0, bytes);
|
||||
}
|
||||
|
||||
defer allocator.free(bytes);
|
||||
var download_path: [:0]const u8 = "";
|
||||
defer {
|
||||
if (download_path.len > 0) {
|
||||
allocator.free(download_path);
|
||||
}
|
||||
}
|
||||
const fd, const total_bytes_written = try inject(
|
||||
bytes,
|
||||
if (target.isDefault())
|
||||
bun.selfExePath() catch |err| {
|
||||
Output.err(err, "failed to get self executable path", .{});
|
||||
Global.exit(1);
|
||||
log.err(err, "failed to get self executable path", .{});
|
||||
return err;
|
||||
}
|
||||
else
|
||||
download(allocator, target, env) catch |err| {
|
||||
Output.err(err, "failed to download cross-compiled bun executable", .{});
|
||||
Global.exit(1);
|
||||
},
|
||||
else brk: {
|
||||
download_path = download(allocator, target, env, log) catch |err| {
|
||||
if (err != error.Fatal) {
|
||||
log.err(err, "failed to download cross-compiled bun executable", .{});
|
||||
}
|
||||
return err;
|
||||
};
|
||||
break :brk download_path;
|
||||
},
|
||||
log,
|
||||
);
|
||||
fd.assertKind(.system);
|
||||
|
||||
output_files[0].size = total_bytes_written;
|
||||
|
||||
if (Environment.isWindows) {
|
||||
var outfile_buf: bun.OSPathBuffer = undefined;
|
||||
const outfile_slice = brk: {
|
||||
@@ -650,22 +670,22 @@ pub const StandaloneModuleGraph = struct {
|
||||
|
||||
bun.C.moveOpenedFileAtLoose(fd, bun.toFD(root_dir.fd), outfile_slice, true).unwrap() catch |err| {
|
||||
if (err == error.EISDIR) {
|
||||
Output.errGeneric("{} is a directory. Please choose a different --outfile or delete the directory", .{bun.fmt.utf16(outfile_slice)});
|
||||
log.errGeneric("{} is a directory. Please choose a different --outfile or delete the directory", .{bun.fmt.utf16(outfile_slice)});
|
||||
} else {
|
||||
Output.err(err, "failed to move executable to result path", .{});
|
||||
log.err(err, "failed to move executable to result path", .{});
|
||||
}
|
||||
|
||||
_ = bun.C.deleteOpenedFile(fd);
|
||||
|
||||
Global.exit(1);
|
||||
return error.Fatal;
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
var buf: bun.PathBuffer = undefined;
|
||||
const temp_location = bun.getFdPath(fd, &buf) catch |err| {
|
||||
Output.prettyErrorln("<r><red>error<r><d>:<r> failed to get path for fd: {s}", .{@errorName(err)});
|
||||
Global.exit(1);
|
||||
log.errGeneric("failed to get path for fd: {s}", .{@errorName(err)});
|
||||
return error.Fatal;
|
||||
};
|
||||
|
||||
if (comptime Environment.isMac) {
|
||||
@@ -699,15 +719,15 @@ pub const StandaloneModuleGraph = struct {
|
||||
bun.sliceTo(&(try std.posix.toPosixPath(std.fs.path.basename(outfile))), 0),
|
||||
) catch |err| {
|
||||
if (err == error.IsDir) {
|
||||
Output.prettyErrorln("<r><red>error<r><d>:<r> {} is a directory. Please choose a different --outfile or delete the directory", .{bun.fmt.quote(outfile)});
|
||||
log.errGeneric("{} is a directory. Please choose a different --outfile or delete the directory", .{bun.fmt.quote(outfile)});
|
||||
} else {
|
||||
Output.prettyErrorln("<r><red>error<r><d>:<r> failed to rename {s} to {s}: {s}", .{ temp_location, outfile, @errorName(err) });
|
||||
log.errGeneric("failed to rename {s} to {s}: {s}", .{ temp_location, outfile, @errorName(err) });
|
||||
}
|
||||
_ = Syscall.unlink(
|
||||
&(try std.posix.toPosixPath(temp_location)),
|
||||
);
|
||||
|
||||
Global.exit(1);
|
||||
return error.Fatal;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ const Runtime = @import("../../runtime.zig").Runtime;
|
||||
const JSLexer = bun.js_lexer;
|
||||
const Expr = JSAst.Expr;
|
||||
const Index = @import("../../ast/base.zig").Index;
|
||||
|
||||
const CompileTarget = @import("../../compile_target.zig");
|
||||
pub const JSBundler = struct {
|
||||
const OwnedString = bun.MutableString;
|
||||
|
||||
@@ -69,6 +69,9 @@ pub const JSBundler = struct {
|
||||
public_path: OwnedString = OwnedString.initEmpty(bun.default_allocator),
|
||||
conditions: bun.StringSet = bun.StringSet.init(bun.default_allocator),
|
||||
packages: options.PackagesOption = .bundle,
|
||||
compile_target: ?CompileTarget = null,
|
||||
outfile: OwnedString = OwnedString.initEmpty(bun.default_allocator),
|
||||
write: bool = true,
|
||||
|
||||
pub const List = bun.StringArrayHashMapUnmanaged(Config);
|
||||
|
||||
@@ -359,6 +362,26 @@ pub const JSBundler = struct {
|
||||
}
|
||||
}
|
||||
|
||||
if (config.getTruthy(globalThis, "compile")) |compile_value| {
|
||||
if (compile_value.isString()) {
|
||||
var slice = compile_value.toSliceOrNull(globalThis) orelse {
|
||||
globalThis.throwInvalidArguments("Expected compile to be a string", .{});
|
||||
return error.JSException;
|
||||
};
|
||||
defer slice.deinit();
|
||||
this.compile_target = CompileTarget.fromString(slice.slice(), globalThis.output()) catch return error.JSException;
|
||||
} else if (compile_value.isBoolean()) {
|
||||
if (compile_value == .true) {
|
||||
this.compile_target = CompileTarget{};
|
||||
}
|
||||
} else if (compile_value.jsType().isArray()) {
|
||||
globalThis.throwTODO("Multiple cross-compilation targets are not implemented yet.");
|
||||
} else {
|
||||
globalThis.throwInvalidArguments("Expected compile to be a boolean or a string", .{});
|
||||
return error.JSException;
|
||||
}
|
||||
}
|
||||
|
||||
// if (try config.getOptional(globalThis, "dir", ZigString.Slice)) |slice| {
|
||||
// defer slice.deinit();
|
||||
// this.appendSliceExact(slice.slice()) catch unreachable;
|
||||
@@ -366,9 +389,27 @@ pub const JSBundler = struct {
|
||||
// this.appendSliceExact(globalThis.bunVM().bundler.fs.top_level_dir) catch unreachable;
|
||||
// }
|
||||
|
||||
if (try config.getOptional(globalThis, "publicPath", ZigString.Slice)) |slice| {
|
||||
defer slice.deinit();
|
||||
this.public_path.appendSliceExact(slice.slice()) catch unreachable;
|
||||
if (this.compile_target) |compile_target| {
|
||||
const base_public_path = bun.StandaloneModuleGraph.targetBasePublicPath(compile_target.os, "root/");
|
||||
|
||||
this.public_path.appendSliceExact(base_public_path) catch unreachable;
|
||||
|
||||
if (!this.outdir.isEmpty()) {
|
||||
globalThis.throwInvalidArguments("Cannot use both outdir and compile", .{});
|
||||
return error.JSException;
|
||||
}
|
||||
|
||||
if (this.code_splitting) {
|
||||
globalThis.throwInvalidArguments("Cannot use both code splitting and compile", .{});
|
||||
return error.JSException;
|
||||
}
|
||||
|
||||
this.target = .bun;
|
||||
} else {
|
||||
if (try config.getOptional(globalThis, "publicPath", ZigString.Slice)) |slice| {
|
||||
defer slice.deinit();
|
||||
this.public_path.appendSliceExact(slice.slice()) catch unreachable;
|
||||
}
|
||||
}
|
||||
|
||||
if (config.getTruthy(globalThis, "naming")) |naming| {
|
||||
@@ -414,6 +455,10 @@ pub const JSBundler = struct {
|
||||
}
|
||||
}
|
||||
|
||||
if (try config.getOptional(globalThis, "write", bool)) |write| {
|
||||
this.write = write;
|
||||
}
|
||||
|
||||
if (try config.getObject(globalThis, "define")) |define| {
|
||||
if (!define.isObject()) {
|
||||
globalThis.throwInvalidArguments("define must be an object", .{});
|
||||
@@ -450,6 +495,20 @@ pub const JSBundler = struct {
|
||||
// .insert clones the value, but not the key
|
||||
try this.define.insert(key, value.slice());
|
||||
}
|
||||
|
||||
if (this.compile_target) |compile_target| {
|
||||
const compile_define_keys = &.{
|
||||
"process.platform",
|
||||
"process.arch",
|
||||
};
|
||||
|
||||
const compile_define_values = compile_target.defineValues();
|
||||
|
||||
inline for (compile_define_keys, compile_define_values) |key, value| {
|
||||
// .insert clones the value, but not the key
|
||||
try this.define.insert(bun.default_allocator.dupe(u8, key) catch bun.outOfMemory(), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (try config.getObject(globalThis, "loader")) |loaders| {
|
||||
|
||||
@@ -2856,6 +2856,28 @@ pub const JSGlobalObject = opaque {
|
||||
this.throwValue(err);
|
||||
}
|
||||
|
||||
const OutputInterface = struct {
|
||||
globalObject: *JSGlobalObject,
|
||||
pub fn err(this: OutputInterface, error_name: anytype, comptime fmt: []const u8, args: anytype) void {
|
||||
const msg = std.fmt.allocPrint(bun.default_allocator, fmt, args) catch unreachable;
|
||||
defer bun.default_allocator.free(msg);
|
||||
const value = this.globalObject.createErrorInstance("{s}", .{msg});
|
||||
value.put(this.globalObject, ZigString.static("name"), @errorName(error_name));
|
||||
this.globalObject.throwValue(value);
|
||||
}
|
||||
|
||||
pub fn errGeneric(this: OutputInterface, comptime fmt: []const u8, args: anytype) void {
|
||||
const msg = std.fmt.allocPrint(bun.default_allocator, fmt, args) catch unreachable;
|
||||
defer bun.default_allocator.free(msg);
|
||||
const value = this.globalObject.createErrorInstance("{s}", .{msg});
|
||||
this.globalObject.throwValue(value);
|
||||
}
|
||||
};
|
||||
|
||||
pub fn output(this: *JSGlobalObject) OutputInterface {
|
||||
return .{ .globalObject = this };
|
||||
}
|
||||
|
||||
extern fn JSGlobalObject__clearTerminationException(this: *JSGlobalObject) void;
|
||||
extern fn JSGlobalObject__throwTerminationException(this: *JSGlobalObject) void;
|
||||
pub const throwTerminationException = JSGlobalObject__throwTerminationException;
|
||||
|
||||
@@ -4123,7 +4123,7 @@ pub fn NewHotReloader(comptime Ctx: type, comptime EventLoopType: type, comptime
|
||||
_: *@This(),
|
||||
err: bun.sys.Error,
|
||||
) void {
|
||||
Output.err(@as(bun.C.E, @enumFromInt(err.errno)), "Watcher crashed", .{});
|
||||
Output.err(err, "Watcher crashed", .{});
|
||||
if (bun.Environment.isDebug) {
|
||||
@panic("Watcher crash");
|
||||
}
|
||||
|
||||
@@ -1169,19 +1169,7 @@ pub const BundleV2 = struct {
|
||||
// conditions from creating two
|
||||
_ = JSC.WorkPool.get();
|
||||
|
||||
if (BundleThread.instance) |existing| {
|
||||
existing.queue.push(completion);
|
||||
existing.waker.?.wake();
|
||||
} else {
|
||||
var instance = bun.default_allocator.create(BundleThread) catch unreachable;
|
||||
instance.queue = .{};
|
||||
instance.waker = null;
|
||||
instance.queue.push(completion);
|
||||
BundleThread.instance = instance;
|
||||
|
||||
var thread = try std.Thread.spawn(.{}, generateInNewThreadWrap, .{instance});
|
||||
thread.detach();
|
||||
}
|
||||
BundleThread.enqueue(completion);
|
||||
|
||||
completion.poll_ref.ref(globalThis.bunVM());
|
||||
|
||||
@@ -1646,6 +1634,12 @@ pub const BundleV2 = struct {
|
||||
bundler.options.chunk_naming = config.names.chunk.data;
|
||||
bundler.options.asset_naming = config.names.asset.data;
|
||||
|
||||
if (config.compile_target) |compile_target| {
|
||||
_ = compile_target; // autofix
|
||||
|
||||
bundler.options.compile = true;
|
||||
}
|
||||
|
||||
bundler.options.public_path = config.public_path.list.items;
|
||||
|
||||
bundler.options.output_dir = config.outdir.toOwnedSliceLeaky();
|
||||
@@ -1774,7 +1768,71 @@ pub const BundleV2 = struct {
|
||||
return error.BuildFailed;
|
||||
}
|
||||
|
||||
return try this.linker.generateChunksInParallel(chunks);
|
||||
var output_files = try this.linker.generateChunksInParallel(chunks);
|
||||
const compile_target = &(config.compile_target orelse
|
||||
// --- compile: false ----
|
||||
return output_files);
|
||||
// --- compile: true ----
|
||||
var outfile = std.fs.path.basename(config.entry_points.keys()[0]);
|
||||
const ext = std.fs.path.extension(outfile);
|
||||
if (ext.len > 0) {
|
||||
outfile = outfile[0 .. outfile.len - ext.len];
|
||||
}
|
||||
|
||||
if (strings.eqlComptime(outfile, "index")) {
|
||||
outfile = std.fs.path.basename(std.fs.path.dirname(config.entry_points.keys()[0]) orelse "index");
|
||||
}
|
||||
|
||||
if (strings.eqlComptime(outfile, "bun")) {
|
||||
outfile = std.fs.path.basename(std.fs.path.dirname(config.entry_points.keys()[0]) orelse "bun");
|
||||
}
|
||||
|
||||
errdefer {
|
||||
for (output_files.items) |*file| {
|
||||
file.deinit();
|
||||
}
|
||||
output_files.deinit();
|
||||
}
|
||||
if (outfile.len == 0 or strings.eqlComptime(outfile, ".") or strings.eqlComptime(outfile, "..") or strings.eqlComptime(outfile, "../")) {
|
||||
outfile = "index";
|
||||
}
|
||||
|
||||
if (compile_target.os == .windows and !strings.hasSuffixComptime(outfile, ".exe")) {
|
||||
const old_dest_path = output_files.items[0].dest_path;
|
||||
defer bun.default_allocator.free(old_dest_path);
|
||||
output_files.items[0].dest_path = try std.fmt.allocPrint(bun.default_allocator, "{s}.exe", .{old_dest_path});
|
||||
outfile = output_files.items[0].dest_path;
|
||||
}
|
||||
|
||||
const root_path = std.fs.path.dirname(outfile) orelse ".";
|
||||
|
||||
const root_dir = if (root_path.len == 0 or strings.eqlComptime(root_path, "."))
|
||||
std.fs.cwd()
|
||||
else
|
||||
std.fs.cwd().makeOpenPath(root_path, .{}) catch |err| {
|
||||
this.bundler.log.output().err(err, "attempting to open {} output directory", .{bun.fmt.quote(root_path)});
|
||||
return error.BuildFailed;
|
||||
};
|
||||
|
||||
bun.StandaloneModuleGraph.toExecutable(
|
||||
compile_target,
|
||||
bun.default_allocator,
|
||||
output_files.items,
|
||||
root_dir,
|
||||
this.bundler.options.public_path,
|
||||
outfile,
|
||||
this.bundler.env,
|
||||
this.bundler.log.output(),
|
||||
&output_files.items[0].hash,
|
||||
) catch |err| {
|
||||
if (err == error.Fatal) {
|
||||
return error.BuildFailed;
|
||||
}
|
||||
|
||||
return err;
|
||||
};
|
||||
|
||||
return output_files;
|
||||
}
|
||||
|
||||
pub fn enqueueOnResolvePluginIfNeeded(
|
||||
@@ -11804,7 +11862,7 @@ const CompileResultForSourceMap = struct {
|
||||
source_index: u32,
|
||||
};
|
||||
|
||||
const ContentHasher = struct {
|
||||
pub const ContentHasher = struct {
|
||||
// xxhash64 outperforms Wyhash if the file is > 1KB or so
|
||||
hasher: std.hash.XxHash64 = std.hash.XxHash64.init(0),
|
||||
|
||||
|
||||
@@ -394,7 +394,7 @@ pub const BuildCommand = struct {
|
||||
outfile = try std.fmt.allocPrint(allocator, "{s}.exe", .{outfile});
|
||||
}
|
||||
|
||||
try bun.StandaloneModuleGraph.toExecutable(
|
||||
bun.StandaloneModuleGraph.toExecutable(
|
||||
compile_target,
|
||||
allocator,
|
||||
output_files,
|
||||
@@ -402,7 +402,15 @@ pub const BuildCommand = struct {
|
||||
this_bundler.options.public_path,
|
||||
outfile,
|
||||
this_bundler.env,
|
||||
);
|
||||
Output.interface(),
|
||||
null,
|
||||
) catch |err| {
|
||||
if (err == error.Fatal) {
|
||||
Global.exit(1);
|
||||
}
|
||||
|
||||
return err;
|
||||
};
|
||||
const compiled_elapsed = @divTrunc(@as(i64, @truncate(std.time.nanoTimestamp() - bundled_end)), @as(i64, std.time.ns_per_ms));
|
||||
const compiled_elapsed_digit_count: isize = switch (compiled_elapsed) {
|
||||
0...9 => 3,
|
||||
|
||||
@@ -136,15 +136,20 @@ pub fn exePath(this: *const CompileTarget, buf: *bun.PathBuffer, version_str: [:
|
||||
const HTTP = bun.http;
|
||||
const MutableString = bun.MutableString;
|
||||
const Global = bun.Global;
|
||||
pub fn downloadToPath(this: *const CompileTarget, env: *bun.DotEnv.Loader, allocator: std.mem.Allocator, dest_z: [:0]const u8) !void {
|
||||
|
||||
pub fn downloadToPath(this: *const CompileTarget, env: *bun.DotEnv.Loader, allocator: std.mem.Allocator, dest_z: [:0]const u8, comptime enable_progress: bool, log: anytype) !void {
|
||||
HTTP.HTTPThread.init();
|
||||
var refresher = bun.Progress{};
|
||||
var refresher = if (enable_progress) bun.Progress{} else bun.Progress.NoOp{};
|
||||
|
||||
{
|
||||
refresher.refresh();
|
||||
|
||||
// TODO: This is way too much code necessary to send a single HTTP request...
|
||||
var async_http = try allocator.create(HTTP.AsyncHTTP);
|
||||
defer {
|
||||
async_http.clearData();
|
||||
allocator.destroy(async_http);
|
||||
}
|
||||
var compressed_archive_bytes = try allocator.create(MutableString);
|
||||
compressed_archive_bytes.* = try MutableString.init(allocator, 24 * 1024 * 1024);
|
||||
var url_buffer: [2048]u8 = undefined;
|
||||
@@ -152,7 +157,7 @@ pub fn downloadToPath(this: *const CompileTarget, env: *bun.DotEnv.Loader, alloc
|
||||
const url = bun.URL.parse(url_str);
|
||||
{
|
||||
var progress = refresher.start("Downloading", 0);
|
||||
defer progress.end();
|
||||
defer if (comptime enable_progress) progress.end();
|
||||
const http_proxy: ?bun.URL = env.getHttpProxy(url);
|
||||
|
||||
async_http.* = HTTP.AsyncHTTP.initSync(
|
||||
@@ -167,14 +172,15 @@ pub fn downloadToPath(this: *const CompileTarget, env: *bun.DotEnv.Loader, alloc
|
||||
null,
|
||||
HTTP.FetchRedirect.follow,
|
||||
);
|
||||
async_http.client.progress_node = progress;
|
||||
if (comptime enable_progress)
|
||||
async_http.client.progress_node = progress;
|
||||
async_http.client.flags.reject_unauthorized = env.getTLSRejectUnauthorized();
|
||||
|
||||
const response = try async_http.sendSync(true);
|
||||
|
||||
switch (response.status_code) {
|
||||
404 => {
|
||||
Output.errGeneric(
|
||||
log.errGeneric(
|
||||
\\Does this target and version of Bun exist?
|
||||
\\
|
||||
\\404 downloading {} from {s}
|
||||
@@ -182,10 +188,10 @@ pub fn downloadToPath(this: *const CompileTarget, env: *bun.DotEnv.Loader, alloc
|
||||
this.*,
|
||||
url_str,
|
||||
});
|
||||
Global.exit(1);
|
||||
return error.Fatal;
|
||||
},
|
||||
403, 429, 499...599 => |status| {
|
||||
Output.errGeneric(
|
||||
log.errGeneric(
|
||||
\\Failed to download cross-compilation target.
|
||||
\\
|
||||
\\HTTP {d} downloading {} from {s}
|
||||
@@ -194,7 +200,7 @@ pub fn downloadToPath(this: *const CompileTarget, env: *bun.DotEnv.Loader, alloc
|
||||
this.*,
|
||||
url_str,
|
||||
});
|
||||
Global.exit(1);
|
||||
return error.Fatal;
|
||||
},
|
||||
200 => {},
|
||||
else => return error.HTTPError,
|
||||
@@ -202,12 +208,13 @@ pub fn downloadToPath(this: *const CompileTarget, env: *bun.DotEnv.Loader, alloc
|
||||
}
|
||||
|
||||
var tarball_bytes = std.ArrayListUnmanaged(u8){};
|
||||
defer tarball_bytes.deinit(allocator);
|
||||
{
|
||||
refresher.refresh();
|
||||
defer compressed_archive_bytes.list.deinit(allocator);
|
||||
|
||||
if (compressed_archive_bytes.list.items.len == 0) {
|
||||
Output.errGeneric(
|
||||
log.errGeneric(
|
||||
\\Failed to verify the integrity of the downloaded tarball.
|
||||
\\
|
||||
\\Received empty content downloading {} from {s}
|
||||
@@ -215,15 +222,15 @@ pub fn downloadToPath(this: *const CompileTarget, env: *bun.DotEnv.Loader, alloc
|
||||
this.*,
|
||||
url_str,
|
||||
});
|
||||
Global.exit(1);
|
||||
return error.Fatal;
|
||||
}
|
||||
|
||||
{
|
||||
var node = refresher.start("Decompressing", 0);
|
||||
defer node.end();
|
||||
defer if (comptime enable_progress) node.end();
|
||||
var gunzip = bun.zlib.ZlibReaderArrayList.init(compressed_archive_bytes.list.items, &tarball_bytes, allocator) catch |err| {
|
||||
node.end();
|
||||
Output.err(err,
|
||||
log.err(err,
|
||||
\\Failed to decompress the downloaded tarball
|
||||
\\
|
||||
\\After downloading {} from {s}
|
||||
@@ -231,12 +238,13 @@ pub fn downloadToPath(this: *const CompileTarget, env: *bun.DotEnv.Loader, alloc
|
||||
this.*,
|
||||
url_str,
|
||||
});
|
||||
Global.exit(1);
|
||||
return error.Fatal;
|
||||
};
|
||||
defer gunzip.deinit();
|
||||
gunzip.readAll() catch |err| {
|
||||
node.end();
|
||||
// One word difference so if someone reports the bug we can tell if it happened in init or readAll.
|
||||
Output.err(err,
|
||||
log.err(err,
|
||||
\\Failed to deflate the downloaded tarball
|
||||
\\
|
||||
\\After downloading {} from {s}
|
||||
@@ -244,15 +252,14 @@ pub fn downloadToPath(this: *const CompileTarget, env: *bun.DotEnv.Loader, alloc
|
||||
this.*,
|
||||
url_str,
|
||||
});
|
||||
Global.exit(1);
|
||||
return error.Fatal;
|
||||
};
|
||||
gunzip.deinit();
|
||||
}
|
||||
refresher.refresh();
|
||||
|
||||
{
|
||||
var node = refresher.start("Extracting", 0);
|
||||
defer node.end();
|
||||
defer if (comptime enable_progress) node.end();
|
||||
|
||||
const libarchive = @import("./libarchive//libarchive.zig");
|
||||
var tmpname_buf: [1024]u8 = undefined;
|
||||
@@ -272,7 +279,7 @@ pub fn downloadToPath(this: *const CompileTarget, env: *bun.DotEnv.Loader, alloc
|
||||
},
|
||||
) catch |err| {
|
||||
node.end();
|
||||
Output.err(err,
|
||||
log.err(err,
|
||||
\\Failed to extract the downloaded tarball
|
||||
\\
|
||||
\\After downloading {} from {s}
|
||||
@@ -280,7 +287,7 @@ pub fn downloadToPath(this: *const CompileTarget, env: *bun.DotEnv.Loader, alloc
|
||||
this.*,
|
||||
url_str,
|
||||
});
|
||||
Global.exit(1);
|
||||
return error.Fatal;
|
||||
};
|
||||
|
||||
var did_retry = false;
|
||||
@@ -297,8 +304,8 @@ pub fn downloadToPath(this: *const CompileTarget, env: *bun.DotEnv.Loader, alloc
|
||||
// fallthrough, failed for another reason
|
||||
}
|
||||
node.end();
|
||||
Output.err(err, "Failed to move cross-compiled bun binary into cache directory {}", .{bun.fmt.fmtPath(u8, dest_z, .{})});
|
||||
Global.exit(1);
|
||||
log.err(err, "Failed to move cross-compiled bun binary into cache directory {}", .{bun.fmt.fmtPath(u8, dest_z, .{})});
|
||||
return error.Fatal;
|
||||
};
|
||||
break;
|
||||
}
|
||||
@@ -318,6 +325,42 @@ pub fn isSupported(this: *const CompileTarget) bool {
|
||||
}
|
||||
|
||||
pub fn from(input_: []const u8) CompileTarget {
|
||||
return fromString(input_, Output) catch Global.exit(1);
|
||||
}
|
||||
|
||||
pub fn fromString(input_: []const u8, log: anytype) !CompileTarget {
|
||||
return fromStringErr(input_) catch |err| {
|
||||
switch (err) {
|
||||
error.ParseError => {
|
||||
log.errGeneric(
|
||||
\\Unsupported target in "bun{s}"
|
||||
\\To see the supported targets:
|
||||
\\ https://bun.sh/docs/bundler/executables
|
||||
,
|
||||
.{
|
||||
// received input starts at "-"
|
||||
input_,
|
||||
},
|
||||
);
|
||||
return error.Fatal;
|
||||
},
|
||||
error.IncompleteVersion => {
|
||||
log.errGeneric("Please pass a complete version number to --target. For example, --target=bun-v" ++ Environment.version_string, .{});
|
||||
return error.Fatal;
|
||||
},
|
||||
error.UnsupportedMusl => {
|
||||
log.errGeneric("musl libc only exists on linux", .{});
|
||||
return error.Fatal;
|
||||
},
|
||||
error.UnsupportedWASM => {
|
||||
log.errGeneric("WebAssembly is not supported. Sorry!", .{});
|
||||
return error.Fatal;
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn fromStringErr(input_: []const u8) !CompileTarget {
|
||||
var this = CompileTarget{};
|
||||
|
||||
const input = bun.strings.trim(input_, " \t\r");
|
||||
@@ -359,8 +402,7 @@ pub fn from(input_: []const u8) CompileTarget {
|
||||
const version = bun.Semver.Version.parse(bun.Semver.SlicedString.init(token[1..], token[1..]));
|
||||
if (version.valid) {
|
||||
if (version.version.major == null or version.version.minor == null or version.version.patch == null) {
|
||||
Output.errGeneric("Please pass a complete version number to --target. For example, --target=bun-v" ++ Environment.version_string, .{});
|
||||
Global.exit(1);
|
||||
return error.IncompleteVersion;
|
||||
}
|
||||
|
||||
this.version = .{
|
||||
@@ -376,18 +418,7 @@ pub fn from(input_: []const u8) CompileTarget {
|
||||
found_libc = true;
|
||||
continue;
|
||||
} else {
|
||||
Output.errGeneric(
|
||||
\\Unsupported target {} in "bun{s}"
|
||||
\\To see the supported targets:
|
||||
\\ https://bun.sh/docs/bundler/executables
|
||||
,
|
||||
.{
|
||||
bun.fmt.quote(token),
|
||||
// received input starts at "-"
|
||||
input_,
|
||||
},
|
||||
);
|
||||
Global.exit(1);
|
||||
return error.ParseError;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -404,13 +435,11 @@ pub fn from(input_: []const u8) CompileTarget {
|
||||
}
|
||||
|
||||
if (this.libc == .musl and this.os != .linux) {
|
||||
Output.errGeneric("invalid target, musl libc only exists on linux", .{});
|
||||
Global.exit(1);
|
||||
return error.UnsupportedMusl;
|
||||
}
|
||||
|
||||
if (this.arch == .wasm or this.os == .wasm) {
|
||||
Output.errGeneric("invalid target, WebAssembly is not supported. Sorry!", .{});
|
||||
Global.exit(1);
|
||||
return error.UnsupportedWASM;
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
@@ -636,6 +636,93 @@ pub const Log = struct {
|
||||
};
|
||||
}
|
||||
|
||||
const OutputInterface = struct {
|
||||
log: *Log,
|
||||
|
||||
pub fn err(this: OutputInterface, error_name: anytype, comptime fmt: []const u8, args: anytype) void {
|
||||
const T = @TypeOf(error_name);
|
||||
const info = @typeInfo(T);
|
||||
|
||||
if (comptime T == bun.sys.Error or info == .Pointer and info.Pointer.child == bun.sys.Error) {
|
||||
this.prettyErrorln(fmt ++ " ({s})", args ++ .{error_name});
|
||||
return;
|
||||
}
|
||||
|
||||
const display_name, const is_comptime_name = display_name: {
|
||||
|
||||
// Zig string literals are of type *const [n:0]u8
|
||||
// we assume that no one will pass this type from not using a string literal.
|
||||
if (info == .Pointer and info.Pointer.size == .One and info.Pointer.is_const) {
|
||||
const child_info = @typeInfo(info.Pointer.child);
|
||||
if (child_info == .Array and child_info.Array.child == u8) {
|
||||
if (child_info.Array.len == 0) @compileError("Output.err should not be passed an empty string (use errGeneric)");
|
||||
break :display_name .{ error_name, true };
|
||||
}
|
||||
}
|
||||
|
||||
// other zig strings we shall treat as dynamic
|
||||
if (comptime bun.trait.isZigString(T)) {
|
||||
break :display_name .{ error_name, false };
|
||||
}
|
||||
|
||||
// error unions
|
||||
if (info == .ErrorSet) {
|
||||
if (info.ErrorSet) |errors| {
|
||||
if (errors.len == 0) {
|
||||
@compileError("Output.err was given an empty error set");
|
||||
}
|
||||
|
||||
// TODO: convert zig errors to errno for better searchability?
|
||||
if (errors.len == 1) break :display_name .{ errors[0].name, true };
|
||||
}
|
||||
|
||||
break :display_name .{ @errorName(error_name), false };
|
||||
}
|
||||
|
||||
// enum literals
|
||||
if (info == .EnumLiteral) {
|
||||
const tag = @tagName(info);
|
||||
comptime bun.assert(tag.len > 0); // how?
|
||||
if (tag[0] != 'E') break :display_name .{ "E" ++ tag, true };
|
||||
break :display_name .{ tag, true };
|
||||
}
|
||||
|
||||
// enums
|
||||
if (info == .Enum) {
|
||||
const errno: bun.C.SystemErrno = @enumFromInt(@intFromEnum(info));
|
||||
break :display_name .{ @tagName(errno), false };
|
||||
}
|
||||
|
||||
@compileLog(error_name);
|
||||
@compileError("err() was given unsupported type: " ++ @typeName(T) ++ " (." ++ @tagName(info) ++ ")");
|
||||
};
|
||||
_ = is_comptime_name; // autofix
|
||||
|
||||
// if the name is known at compile time, we can do better and use it at compile time
|
||||
|
||||
this.prettyErrorln(fmt ++ " ({s})", args ++ .{display_name});
|
||||
}
|
||||
|
||||
pub fn errGeneric(this: OutputInterface, comptime fmt: []const u8, args: anytype) void {
|
||||
this.log.addErrorFmt(null, Loc.Empty, this.log.msgs.allocator, fmt, args) catch bun.outOfMemory();
|
||||
}
|
||||
|
||||
pub fn prettyErrorln(this: OutputInterface, comptime fmt: []const u8, args: anytype) void {
|
||||
switch (Output.enable_ansi_colors) {
|
||||
inline else => |enable_ansi_colors| {
|
||||
this.log.addErrorFmt(null, Loc.Empty, this.log.msgs.allocator, Output.prettyFmt(
|
||||
fmt,
|
||||
enable_ansi_colors,
|
||||
), args) catch bun.outOfMemory();
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub fn output(this: *Log) OutputInterface {
|
||||
return .{ .log = this };
|
||||
}
|
||||
|
||||
pub const Level = enum(i8) {
|
||||
verbose, // 0
|
||||
debug, // 1
|
||||
|
||||
@@ -1877,6 +1877,16 @@ pub const OutputFile = struct {
|
||||
output_kind: JSC.API.BuildArtifact.OutputKind = .chunk,
|
||||
dest_path: []const u8 = "",
|
||||
|
||||
pub fn deinit(this: *OutputFile) void {
|
||||
if (this.value != .saved) {
|
||||
bun.default_allocator.free(this.dest_path);
|
||||
}
|
||||
|
||||
if (this.value == .buffer) {
|
||||
this.value.buffer.deinit();
|
||||
}
|
||||
}
|
||||
|
||||
// Depending on:
|
||||
// - The target
|
||||
// - The number of open file handles
|
||||
@@ -1914,6 +1924,10 @@ pub const OutputFile = struct {
|
||||
buffer: struct {
|
||||
allocator: std.mem.Allocator,
|
||||
bytes: []const u8,
|
||||
|
||||
pub fn deinit(this: *@This()) void {
|
||||
this.allocator.free(this.bytes);
|
||||
}
|
||||
},
|
||||
pending: resolver.Result,
|
||||
saved: SavedFile,
|
||||
|
||||
@@ -897,8 +897,17 @@ pub inline fn err(error_name: anytype, comptime fmt: []const u8, args: anytype)
|
||||
const T = @TypeOf(error_name);
|
||||
const info = @typeInfo(T);
|
||||
|
||||
if (comptime T == bun.sys.Error or info == .Pointer and info.Pointer.child == bun.sys.Error) {
|
||||
prettyErrorln("<r><red>error:<r><d>:<r> " ++ fmt, args ++ .{error_name});
|
||||
if (comptime T == *bun.sys.Error or T == *const bun.sys.Error) {
|
||||
return err(error_name.*, fmt, args);
|
||||
}
|
||||
|
||||
if (comptime T == bun.sys.Error) {
|
||||
if (std.meta.declarations(@TypeOf(args)).len == 0 and std.meta.fields(@TypeOf(args)).len == 0) {
|
||||
prettyErrorln("<red>{s}<r><d>:<r> " ++ fmt, .{error_name.name()});
|
||||
return;
|
||||
}
|
||||
|
||||
prettyErrorln(fmt, args ++ .{error_name});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1019,3 +1028,23 @@ pub inline fn errGeneric(comptime fmt: []const u8, args: anytype) void {
|
||||
pub var buffered_stdin = std.io.BufferedReader(4096, File.Reader){
|
||||
.unbuffered_reader = File.Reader{ .context = .{ .handle = if (Environment.isWindows) undefined else bun.toFD(0) } },
|
||||
};
|
||||
|
||||
const OutputInterface = struct {
|
||||
pub fn err(this: @This(), error_name: anytype, comptime fmt: []const u8, args: anytype) void {
|
||||
_ = this; // autofix
|
||||
Output.err(error_name, fmt, args);
|
||||
}
|
||||
|
||||
pub fn errGeneric(this: @This(), comptime fmt: []const u8, args: anytype) void {
|
||||
_ = this; // autofix
|
||||
Output.errGeneric(fmt, args);
|
||||
}
|
||||
|
||||
pub fn prettyErrorln(this: @This(), comptime fmt: []const u8, args: anytype) void {
|
||||
_ = this; // autofix
|
||||
Output.prettyErrorln(fmt, args);
|
||||
}
|
||||
};
|
||||
pub fn interface() OutputInterface {
|
||||
return .{};
|
||||
}
|
||||
|
||||
184
test/bundler/bun-build-compile.test.ts
Normal file
184
test/bundler/bun-build-compile.test.ts
Normal file
@@ -0,0 +1,184 @@
|
||||
import { describe, test, expect } from "bun:test";
|
||||
import { bunExe } from "../harness";
|
||||
import { join } from "path";
|
||||
import { statSync, existsSync } from "fs";
|
||||
import { spawnSync } from "child_process";
|
||||
import { tmpdir } from "os";
|
||||
import { mkdtempSync, writeFileSync, readFileSync } from "fs";
|
||||
import { execSync } from "child_process";
|
||||
|
||||
const tempDir = () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "bun-build-compile-"));
|
||||
return dir;
|
||||
};
|
||||
|
||||
describe("Bun.build compile option", () => {
|
||||
// Test that compile: true works correctly
|
||||
test("compile: true creates an executable", async () => {
|
||||
const dir = tempDir();
|
||||
const entry = join(dir, "index.js");
|
||||
const outfile = join(dir, "output");
|
||||
|
||||
writeFileSync(
|
||||
entry,
|
||||
`
|
||||
console.log("Hello from compiled executable!");
|
||||
`
|
||||
);
|
||||
|
||||
const build = await Bun.build({
|
||||
entrypoints: [entry],
|
||||
outfile,
|
||||
compile: true,
|
||||
});
|
||||
|
||||
expect(build.success).toBe(true);
|
||||
expect(existsSync(outfile)).toBe(true);
|
||||
|
||||
// Verify the file is executable
|
||||
const stats = statSync(outfile);
|
||||
expect(!!(stats.mode & 0o111)).toBe(true);
|
||||
|
||||
// Run the executable to verify it works
|
||||
try {
|
||||
const result = execSync(outfile, { encoding: "utf8" });
|
||||
expect(result.trim()).toBe("Hello from compiled executable!");
|
||||
} catch (e) {
|
||||
// Some CI environments might not allow running executables
|
||||
// So don't fail the test in that case
|
||||
if (!process.env.CI) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Test that platform targets work with compile option
|
||||
test("targets option specifies the compilation target", async () => {
|
||||
const dir = tempDir();
|
||||
const entry = join(dir, "index.js");
|
||||
const outfile = join(dir, "output");
|
||||
|
||||
writeFileSync(
|
||||
entry,
|
||||
`
|
||||
console.log("Platform:", process.platform);
|
||||
console.log("Architecture:", process.arch);
|
||||
`
|
||||
);
|
||||
|
||||
// Skip test if cross-compilation to this target would fail
|
||||
// In a real test environment we'd need to check if the current platform supports this
|
||||
try {
|
||||
const build = await Bun.build({
|
||||
entrypoints: [entry],
|
||||
outfile,
|
||||
compile: true,
|
||||
targets: process.platform === "darwin" ? "darwin-x64" : "linux-x64",
|
||||
});
|
||||
|
||||
expect(build.success).toBe(true);
|
||||
expect(existsSync(outfile)).toBe(true);
|
||||
} catch (e) {
|
||||
// If the test fails because the target isn't supported, that's okay
|
||||
if (!e.message?.includes("not supported")) throw e;
|
||||
}
|
||||
});
|
||||
|
||||
// Test that TypeScript files compile correctly
|
||||
test("compiles TypeScript files", async () => {
|
||||
const dir = tempDir();
|
||||
const entry = join(dir, "index.ts");
|
||||
const outfile = join(dir, "output");
|
||||
|
||||
writeFileSync(
|
||||
entry,
|
||||
`
|
||||
const message: string = "Hello from TypeScript";
|
||||
console.log(message);
|
||||
`
|
||||
);
|
||||
|
||||
const build = await Bun.build({
|
||||
entrypoints: [entry],
|
||||
outfile,
|
||||
compile: true,
|
||||
});
|
||||
|
||||
expect(build.success).toBe(true);
|
||||
expect(existsSync(outfile)).toBe(true);
|
||||
|
||||
// Run the executable to verify it works
|
||||
try {
|
||||
const result = execSync(outfile, { encoding: "utf8" });
|
||||
expect(result.trim()).toBe("Hello from TypeScript");
|
||||
} catch (e) {
|
||||
// Some CI environments might not allow running executables
|
||||
// So don't fail the test in that case
|
||||
if (!process.env.CI) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Test error when incompatible options are used
|
||||
test("error with incompatible options", async () => {
|
||||
const dir = tempDir();
|
||||
const entry = join(dir, "index.js");
|
||||
|
||||
writeFileSync(entry, `console.log("Hello");`);
|
||||
|
||||
let error;
|
||||
try {
|
||||
await Bun.build({
|
||||
entrypoints: [entry],
|
||||
outfile: join(dir, "output"),
|
||||
compile: true,
|
||||
outdir: dir, // outdir is incompatible with compile
|
||||
});
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message.toLowerCase()).toMatch(/cannot use both outdir and compile/);
|
||||
});
|
||||
|
||||
// Test combining with other build options
|
||||
test("works with minify option", async () => {
|
||||
const dir = tempDir();
|
||||
const entry = join(dir, "index.js");
|
||||
const outfile = join(dir, "output");
|
||||
|
||||
writeFileSync(
|
||||
entry,
|
||||
`
|
||||
function unused() {
|
||||
console.log("This should be removed");
|
||||
}
|
||||
console.log("Hello from minified executable!");
|
||||
`
|
||||
);
|
||||
|
||||
const build = await Bun.build({
|
||||
entrypoints: [entry],
|
||||
outfile,
|
||||
compile: true,
|
||||
minify: true,
|
||||
});
|
||||
|
||||
expect(build.success).toBe(true);
|
||||
expect(existsSync(outfile)).toBe(true);
|
||||
|
||||
// Run the executable to verify it works
|
||||
try {
|
||||
const result = execSync(outfile, { encoding: "utf8" });
|
||||
expect(result.trim()).toBe("Hello from minified executable!");
|
||||
} catch (e) {
|
||||
// Some CI environments might not allow running executables
|
||||
// So don't fail the test in that case
|
||||
if (!process.env.CI) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user