mirror of
https://github.com/oven-sh/bun
synced 2026-02-06 08:58:52 +00:00
Compare commits
3 Commits
dylan/pyth
...
claude/add
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f299efe096 | ||
|
|
899e327047 | ||
|
|
9444d24104 |
@@ -16,6 +16,27 @@ const macho = bun.macho;
|
||||
const pe = bun.pe;
|
||||
const w = std.os.windows;
|
||||
|
||||
const StandaloneError = error{
|
||||
TempFileFailed,
|
||||
CopyFailed,
|
||||
OpenFailed,
|
||||
ReadFailed,
|
||||
WriteFailed,
|
||||
SeekFailed,
|
||||
MachoInitFailed,
|
||||
MachoWriteFailed,
|
||||
PEInitFailed,
|
||||
PEWriteFailed,
|
||||
DownloadFailed,
|
||||
GetSelfExePathFailed,
|
||||
MoveFailed,
|
||||
DisableConsoleFailed,
|
||||
OutOfMemory,
|
||||
InvalidSourceMap,
|
||||
FileNotFound,
|
||||
@"Corrupted module graph: entry point ID is greater than module list count",
|
||||
};
|
||||
|
||||
pub const StandaloneModuleGraph = struct {
|
||||
bytes: []const u8 = "",
|
||||
files: bun.StringArrayHashMap(File),
|
||||
@@ -359,7 +380,9 @@ pub const StandaloneModuleGraph = struct {
|
||||
var entry_point_id: ?usize = null;
|
||||
var string_builder = bun.StringBuilder{};
|
||||
var module_count: usize = 0;
|
||||
for (output_files) |output_file| {
|
||||
std.debug.print("[DEBUG] toBytes - processing {d} output files\n", .{output_files.len});
|
||||
for (output_files, 0..) |output_file, i| {
|
||||
std.debug.print("[DEBUG] toBytes - file {d}: dest_path={s}, output_kind={}, side={?}, value={}\n", .{ i, output_file.dest_path, output_file.output_kind, output_file.side, output_file.value });
|
||||
string_builder.countZ(output_file.dest_path);
|
||||
string_builder.countZ(prefix);
|
||||
if (output_file.value == .buffer) {
|
||||
@@ -374,10 +397,15 @@ pub const StandaloneModuleGraph = struct {
|
||||
string_builder.cap += (output_file.value.buffer.bytes.len + 255) / 256 * 256 + 256;
|
||||
} else {
|
||||
if (entry_point_id == null) {
|
||||
if (output_file.side == null or output_file.side.? == .server) {
|
||||
std.debug.print("[DEBUG] toBytes - checking entry-point: side={?}, output_kind={}\n", .{ output_file.side, output_file.output_kind });
|
||||
// For standalone executables, accept client-side entry points as well as server-side
|
||||
if (output_file.side == null or output_file.side.? == .server or output_file.side.? == .client) {
|
||||
if (output_file.output_kind == .@"entry-point") {
|
||||
std.debug.print("[DEBUG] toBytes - setting entry_point_id = {d}\n", .{module_count});
|
||||
entry_point_id = module_count;
|
||||
}
|
||||
} else {
|
||||
std.debug.print("[DEBUG] toBytes - skipping entry-point due to side: {?}\n", .{output_file.side});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,7 +415,11 @@ pub const StandaloneModuleGraph = struct {
|
||||
}
|
||||
}
|
||||
|
||||
if (module_count == 0 or entry_point_id == null) return &[_]u8{};
|
||||
std.debug.print("[DEBUG] toBytes - module_count: {d}, entry_point_id: {?}\n", .{ module_count, entry_point_id });
|
||||
if (module_count == 0 or entry_point_id == null) {
|
||||
std.debug.print("[DEBUG] toBytes - returning empty array because module_count={d} or entry_point_id={?}\n", .{ module_count, entry_point_id });
|
||||
return &[_]u8{};
|
||||
}
|
||||
|
||||
string_builder.cap += @sizeOf(CompiledModuleGraphFile) * output_files.len;
|
||||
string_builder.cap += trailer.len;
|
||||
@@ -505,12 +537,9 @@ pub const StandaloneModuleGraph = struct {
|
||||
windows_hide_console: bool = false,
|
||||
};
|
||||
|
||||
pub fn inject(bytes: []const u8, self_exe: [:0]const u8, inject_options: InjectOptions, target: *const CompileTarget) bun.FileDescriptor {
|
||||
pub fn inject(bytes: []const u8, self_exe: [:0]const u8, inject_options: InjectOptions, target: *const CompileTarget) anyerror!bun.FileDescriptor {
|
||||
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);
|
||||
});
|
||||
var zname: [:0]const u8 = bun.span(try bun.fs.FileSystem.instance.tmpname("bun-build", &buf, @as(u64, @bitCast(std.time.milliTimestamp()))));
|
||||
|
||||
const cleanup = struct {
|
||||
pub fn toClean(name: [:0]const u8, fd: bun.FileDescriptor) void {
|
||||
@@ -537,10 +566,7 @@ pub const StandaloneModuleGraph = struct {
|
||||
out_buf[zname.len] = 0;
|
||||
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);
|
||||
};
|
||||
try bun.copyFile(in, out).unwrap();
|
||||
const file = bun.sys.openFileAtWindows(
|
||||
bun.invalid_fd,
|
||||
out,
|
||||
@@ -549,10 +575,7 @@ pub const StandaloneModuleGraph = struct {
|
||||
.disposition = w.FILE_OPEN,
|
||||
.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);
|
||||
};
|
||||
).unwrap() catch |err| return err;
|
||||
|
||||
break :brk file;
|
||||
}
|
||||
@@ -604,8 +627,7 @@ 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);
|
||||
return err.toZigErr();
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -625,9 +647,8 @@ 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});
|
||||
cleanup(zname, fd);
|
||||
Global.exit(1);
|
||||
return err.toZigErr();
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -637,9 +658,8 @@ pub const StandaloneModuleGraph = struct {
|
||||
defer self_fd.close();
|
||||
|
||||
bun.copyFile(self_fd, fd).unwrap() catch |err| {
|
||||
Output.prettyErrorln("<r><red>error<r><d>:<r> failed to copy bun executable into temporary file: {s}", .{@errorName(err)});
|
||||
cleanup(zname, fd);
|
||||
Global.exit(1);
|
||||
return err;
|
||||
};
|
||||
break :brk fd;
|
||||
};
|
||||
@@ -843,26 +863,23 @@ pub const StandaloneModuleGraph = struct {
|
||||
output_format: bun.options.Format,
|
||||
windows_hide_console: bool,
|
||||
windows_icon: ?[]const u8,
|
||||
) !void {
|
||||
) anyerror!void {
|
||||
std.debug.print("[DEBUG] StandaloneModuleGraph.toExecutable entry - outfile: {s}\n", .{outfile});
|
||||
const bytes = try toBytes(allocator, module_prefix, output_files, output_format);
|
||||
std.debug.print("[DEBUG] toBytes returned {d} bytes\n", .{bytes.len});
|
||||
if (bytes.len == 0) return;
|
||||
|
||||
const fd = inject(
|
||||
const fd = try inject(
|
||||
bytes,
|
||||
if (target.isDefault())
|
||||
bun.selfExePath() catch |err| {
|
||||
Output.err(err, "failed to get self executable path", .{});
|
||||
Global.exit(1);
|
||||
}
|
||||
try bun.selfExePath()
|
||||
else
|
||||
download(allocator, target, env) catch |err| {
|
||||
Output.err(err, "failed to download cross-compiled bun executable", .{});
|
||||
Global.exit(1);
|
||||
},
|
||||
try download(allocator, target, env),
|
||||
.{ .windows_hide_console = windows_hide_console },
|
||||
target,
|
||||
);
|
||||
bun.debugAssert(fd.kind == .system);
|
||||
std.debug.print("[DEBUG] After inject, about to check Environment.isWindows: {}\n", .{Environment.isWindows});
|
||||
|
||||
if (Environment.isWindows) {
|
||||
var outfile_buf: bun.OSPathBuffer = undefined;
|
||||
@@ -883,7 +900,7 @@ pub const StandaloneModuleGraph = struct {
|
||||
|
||||
_ = bun.windows.deleteOpenedFile(fd);
|
||||
|
||||
Global.exit(1);
|
||||
return err;
|
||||
};
|
||||
fd.close();
|
||||
|
||||
@@ -898,18 +915,28 @@ pub const StandaloneModuleGraph = struct {
|
||||
}
|
||||
|
||||
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);
|
||||
};
|
||||
const temp_location = bun.getFdPath(fd, &buf) catch |err| return err;
|
||||
|
||||
const dest_basename = std.fs.path.basename(outfile);
|
||||
std.debug.print("[DEBUG] toExecutable - temp_location: {s}\n", .{temp_location});
|
||||
std.debug.print("[DEBUG] toExecutable - outfile: {s}\n", .{outfile});
|
||||
std.debug.print("[DEBUG] toExecutable - dest_basename: {s}\n", .{dest_basename});
|
||||
|
||||
// Check the size of the temporary file before moving
|
||||
if (std.fs.cwd().statFile(temp_location)) |temp_stat| {
|
||||
std.debug.print("[DEBUG] toExecutable - temp file size: {d} bytes\n", .{temp_stat.size});
|
||||
} else |err| {
|
||||
std.debug.print("[DEBUG] toExecutable - failed to stat temp file: {}\n", .{err});
|
||||
}
|
||||
|
||||
bun.sys.moveFileZWithHandle(
|
||||
fd,
|
||||
bun.FD.cwd(),
|
||||
bun.sliceTo(&(try std.posix.toPosixPath(temp_location)), 0),
|
||||
.fromStdDir(root_dir),
|
||||
bun.sliceTo(&(try std.posix.toPosixPath(std.fs.path.basename(outfile))), 0),
|
||||
bun.sliceTo(&(try std.posix.toPosixPath(dest_basename)), 0),
|
||||
) catch |err| {
|
||||
std.debug.print("[DEBUG] toExecutable - moveFileZWithHandle failed: {}\n", .{err});
|
||||
if (err == error.IsDir or err == error.EISDIR) {
|
||||
Output.prettyErrorln("<r><red>error<r><d>:<r> {} is a directory. Please choose a different --outfile or delete the directory", .{bun.fmt.quote(outfile)});
|
||||
} else {
|
||||
@@ -919,7 +946,7 @@ pub const StandaloneModuleGraph = struct {
|
||||
&(try std.posix.toPosixPath(temp_location)),
|
||||
);
|
||||
|
||||
Global.exit(1);
|
||||
return StandaloneError.MoveFailed;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ const logger = bun.logger;
|
||||
const Loader = options.Loader;
|
||||
const Target = options.Target;
|
||||
const Index = @import("../../ast/base.zig").Index;
|
||||
const CompileTarget = @import("../../compile_target.zig");
|
||||
|
||||
const debug = bun.Output.scoped(.Transpiler, false);
|
||||
|
||||
@@ -59,6 +60,8 @@ pub const JSBundler = struct {
|
||||
env_behavior: Api.DotEnvBehavior = .disable,
|
||||
env_prefix: OwnedString = OwnedString.initEmpty(bun.default_allocator),
|
||||
tsconfig_override: OwnedString = OwnedString.initEmpty(bun.default_allocator),
|
||||
compile: bool = false,
|
||||
compile_target: ?*CompileTarget = null,
|
||||
|
||||
pub const List = bun.StringArrayHashMapUnmanaged(Config);
|
||||
|
||||
@@ -80,7 +83,24 @@ pub const JSBundler = struct {
|
||||
errdefer if (plugins.*) |plugin| plugin.deinit();
|
||||
|
||||
var did_set_target = false;
|
||||
if (try config.getOptionalEnum(globalThis, "target", options.Target)) |target| {
|
||||
if (try config.getOptional(globalThis, "target", ZigString.Slice)) |target_slice| {
|
||||
defer target_slice.deinit();
|
||||
const target_str = target_slice.slice();
|
||||
|
||||
if (bun.strings.hasPrefixComptime(target_str, "bun-")) {
|
||||
this.compile_target = try allocator.create(CompileTarget);
|
||||
this.compile_target.?.* = CompileTarget.from(target_str);
|
||||
// Set the build target based on the compile target's OS
|
||||
this.target = .bun;
|
||||
did_set_target = true;
|
||||
} else {
|
||||
// Try to parse as enum target first
|
||||
if (try config.getOptionalEnum(globalThis, "target", options.Target)) |target| {
|
||||
this.target = target;
|
||||
did_set_target = true;
|
||||
}
|
||||
}
|
||||
} else if (try config.getOptionalEnum(globalThis, "target", options.Target)) |target| {
|
||||
this.target = target;
|
||||
did_set_target = true;
|
||||
}
|
||||
@@ -286,6 +306,10 @@ pub const JSBundler = struct {
|
||||
this.ignore_dce_annotations = flag;
|
||||
}
|
||||
|
||||
if (try config.getBooleanLoose(globalThis, "compile")) |compile_flag| {
|
||||
this.compile = compile_flag;
|
||||
}
|
||||
|
||||
if (try config.getTruthy(globalThis, "conditions")) |conditions_value| {
|
||||
if (conditions_value.isString()) {
|
||||
var slice = try conditions_value.toSliceOrNull(globalThis);
|
||||
@@ -531,6 +555,9 @@ pub const JSBundler = struct {
|
||||
self.env_prefix.deinit();
|
||||
self.footer.deinit();
|
||||
self.tsconfig_override.deinit();
|
||||
if (self.compile_target) |target| {
|
||||
allocator.destroy(target);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1604,8 +1604,13 @@ pub const BundleV2 = struct {
|
||||
.plugins = plugins,
|
||||
.log = Logger.Log.init(bun.default_allocator),
|
||||
.task = undefined,
|
||||
.compile_target = config.compile_target,
|
||||
});
|
||||
completion.task = JSBundleCompletionTask.TaskCompletion.init(completion);
|
||||
|
||||
// Important: Null out the pointer in the config so it's not double-freed
|
||||
var mutable_config = &completion.config;
|
||||
mutable_config.compile_target = null;
|
||||
|
||||
if (plugins) |plugin| {
|
||||
plugin.setConfig(completion);
|
||||
@@ -1685,6 +1690,7 @@ pub const BundleV2 = struct {
|
||||
transpiler: *BundleV2 = undefined,
|
||||
plugins: ?*bun.JSC.API.JSBundler.Plugin = null,
|
||||
started_at_ns: u64 = 0,
|
||||
compile_target: ?*CompileTarget = null,
|
||||
|
||||
pub fn configureBundler(
|
||||
completion: *JSBundleCompletionTask,
|
||||
@@ -1748,6 +1754,7 @@ pub const BundleV2 = struct {
|
||||
transpiler.options.css_chunking = config.css_chunking;
|
||||
transpiler.options.banner = config.banner.slice();
|
||||
transpiler.options.footer = config.footer.slice();
|
||||
transpiler.options.compile = config.compile;
|
||||
|
||||
transpiler.configureLinker();
|
||||
try transpiler.configureDefines();
|
||||
@@ -2283,7 +2290,128 @@ pub const BundleV2 = struct {
|
||||
return error.BuildFailed;
|
||||
}
|
||||
|
||||
return try this.linker.generateChunksInParallel(chunks, false);
|
||||
const output_files_list = try this.linker.generateChunksInParallel(chunks, false);
|
||||
|
||||
// Handle compile: true option
|
||||
if (this.transpiler.options.compile) {
|
||||
std.debug.print("[DEBUG] Entering compile mode\n", .{});
|
||||
// Extract the compile target from the completion task
|
||||
const compile_target_from_js = if (this.completion) |completion| completion.compile_target else null;
|
||||
var default_compile_target: CompileTarget = .{};
|
||||
const target = compile_target_from_js orelse &default_compile_target;
|
||||
|
||||
// Determine output file name - use proper basename logic
|
||||
const entry_point = this.transpiler.options.entry_points[0];
|
||||
const basename = std.fs.path.basename(entry_point);
|
||||
const name_without_ext = if (std.mem.lastIndexOfScalar(u8, basename, '.')) |dot_index|
|
||||
basename[0..dot_index]
|
||||
else
|
||||
basename;
|
||||
|
||||
// Create output path in the specified output directory
|
||||
var outfile_buf: bun.PathBuffer = undefined;
|
||||
const outfile = if (this.transpiler.options.output_dir.len > 0) blk: {
|
||||
break :blk std.fmt.bufPrint(&outfile_buf, "{s}{c}{s}", .{
|
||||
this.transpiler.options.output_dir,
|
||||
std.fs.path.sep,
|
||||
name_without_ext,
|
||||
}) catch "a.out";
|
||||
} else name_without_ext;
|
||||
|
||||
std.debug.print("[DEBUG] Output file path: {s}\n", .{outfile});
|
||||
|
||||
// For compile mode, we need to load saved files into buffers for embedding
|
||||
var output_files_for_executable = std.ArrayList(options.OutputFile).init(this.graph.allocator);
|
||||
defer output_files_for_executable.deinit();
|
||||
|
||||
for (output_files_list.items) |*output_file| {
|
||||
if (output_file.value == .saved) {
|
||||
// Read the saved file content into a buffer
|
||||
// Check if dest_path is absolute or relative to output_dir
|
||||
const file_path = if (std.fs.path.isAbsolute(output_file.dest_path))
|
||||
output_file.dest_path
|
||||
else if (this.transpiler.options.output_dir.len > 0) blk: {
|
||||
var path_buf: bun.PathBuffer = undefined;
|
||||
break :blk std.fmt.bufPrint(&path_buf, "{s}{c}{s}", .{
|
||||
this.transpiler.options.output_dir,
|
||||
std.fs.path.sep,
|
||||
output_file.dest_path,
|
||||
}) catch output_file.dest_path;
|
||||
} else output_file.dest_path;
|
||||
|
||||
std.debug.print("[DEBUG] Attempting to read file: {s}\n", .{file_path});
|
||||
const file_content = std.fs.cwd().readFileAlloc(this.graph.allocator, file_path, 16 * 1024 * 1024) catch |err| {
|
||||
std.debug.print("[DEBUG] Failed to read saved file {s}: {}\n", .{ file_path, err });
|
||||
continue;
|
||||
};
|
||||
|
||||
// Create a new output file with buffer content
|
||||
var new_output_file = output_file.*;
|
||||
new_output_file.value = .{ .buffer = .{ .allocator = this.graph.allocator, .bytes = file_content } };
|
||||
try output_files_for_executable.append(new_output_file);
|
||||
std.debug.print("[DEBUG] Loaded saved file into buffer: {s} ({d} bytes)\n", .{ file_path, file_content.len });
|
||||
} else {
|
||||
try output_files_for_executable.append(output_file.*);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate standalone executable from bundled output
|
||||
std.debug.print("[DEBUG] Calling toExecutable with {} output files\n", .{output_files_for_executable.items.len});
|
||||
|
||||
// For toExecutable to work correctly, we need to pass the directory and filename separately
|
||||
var output_dir_owned: ?std.fs.Dir = null;
|
||||
const output_dir = if (std.fs.path.dirname(outfile)) |dirname| blk: {
|
||||
const dir = std.fs.cwd().openDir(dirname, .{}) catch |err| {
|
||||
std.debug.print("[DEBUG] Failed to open output directory {s}: {}\n", .{ dirname, err });
|
||||
return err;
|
||||
};
|
||||
output_dir_owned = dir;
|
||||
break :blk dir;
|
||||
} else std.fs.cwd();
|
||||
defer if (output_dir_owned) |*dir| dir.close();
|
||||
|
||||
const output_filename = std.fs.path.basename(outfile);
|
||||
std.debug.print("[DEBUG] Output directory: {s}, filename: {s}\n", .{
|
||||
if (std.fs.path.dirname(outfile)) |dir| dir else ".",
|
||||
output_filename
|
||||
});
|
||||
|
||||
bun.StandaloneModuleGraph.toExecutable(
|
||||
target,
|
||||
this.graph.allocator,
|
||||
output_files_for_executable.items,
|
||||
output_dir,
|
||||
"", // module_prefix
|
||||
output_filename,
|
||||
this.transpiler.env,
|
||||
this.transpiler.options.output_format,
|
||||
false, // windows_hide_console, TODO: expose this option
|
||||
null, // windows_icon, TODO: expose this option
|
||||
) catch |err| {
|
||||
std.debug.print("[DEBUG] toExecutable failed with error: {}\n", .{err});
|
||||
this.transpiler.log.addError(null, .{}, @errorName(err)) catch {};
|
||||
return err;
|
||||
};
|
||||
std.debug.print("[DEBUG] toExecutable completed successfully\n", .{});
|
||||
|
||||
// Debug: Check if the executable was actually created
|
||||
if (std.fs.cwd().access(outfile, .{})) {
|
||||
std.debug.print("[DEBUG] Executable file created successfully at: {s}\n", .{outfile});
|
||||
} else |err| {
|
||||
std.debug.print("[DEBUG] Executable file NOT found after toExecutable: {} at path: {s}\n", .{ err, outfile });
|
||||
}
|
||||
|
||||
// Clean up the intermediate output files since we've created an executable
|
||||
for (output_files_list.items) |*file| {
|
||||
file.deinit();
|
||||
}
|
||||
output_files_list.deinit();
|
||||
|
||||
// Return empty output list for compile mode (executable was written to disk)
|
||||
return std.ArrayList(options.OutputFile).init(this.graph.allocator);
|
||||
}
|
||||
|
||||
return output_files_list;
|
||||
}
|
||||
|
||||
fn shouldAddWatcherPlugin(bv2: *BundleV2, namespace: []const u8, path: []const u8) bool {
|
||||
@@ -4154,6 +4282,7 @@ pub const URL = @import("../url.zig").URL;
|
||||
pub const Resolver = _resolver.Resolver;
|
||||
pub const TOML = @import("../toml/toml_parser.zig").TOML;
|
||||
pub const Dependency = js_ast.Dependency;
|
||||
pub const CompileTarget = @import("../compile_target.zig");
|
||||
pub const JSAst = js_ast.BundledAst;
|
||||
pub const Loader = options.Loader;
|
||||
pub const Index = @import("../ast/base.zig").Index;
|
||||
|
||||
30
src/sys.zig
30
src/sys.zig
@@ -4942,6 +4942,7 @@ pub fn moveFileZWithHandle(from_handle: bun.FileDescriptor, from_dir: bun.FileDe
|
||||
if (err.getErrno() == .XDEV) {
|
||||
try copyFileZSlowWithHandle(from_handle, to_dir, destination).unwrap();
|
||||
_ = unlinkat(from_dir, filename);
|
||||
return;
|
||||
}
|
||||
|
||||
return bun.errnoToZigErr(err.errno);
|
||||
@@ -5008,9 +5009,34 @@ pub fn copyFileZSlowWithHandle(in_handle: bun.FileDescriptor, to_dir: bun.FileDe
|
||||
_ = std.os.linux.fallocate(out_handle.cast(), 0, 0, @intCast(stat_.size));
|
||||
}
|
||||
|
||||
// Seek to the beginning of the input file
|
||||
switch (lseek(in_handle, 0, std.posix.SEEK.SET)) {
|
||||
.result => |pos| {
|
||||
std.debug.print("[DEBUG] copyFileZSlowWithHandle - seeked to position: {d}\n", .{pos});
|
||||
},
|
||||
.err => |err| {
|
||||
std.debug.print("[DEBUG] copyFileZSlowWithHandle - failed to seek input file: {}\n", .{err});
|
||||
},
|
||||
}
|
||||
|
||||
std.debug.print("[DEBUG] copyFileZSlowWithHandle - copying from fd {d} to fd {d}, source size: {d}\n", .{ in_handle.cast(), out_handle.cast(), stat_.size });
|
||||
switch (bun.copyFile(in_handle, out_handle)) {
|
||||
.err => |e| return .{ .err = e },
|
||||
.result => {},
|
||||
.err => |e| {
|
||||
std.debug.print("[DEBUG] copyFileZSlowWithHandle - copyFile failed: {}\n", .{e});
|
||||
return .{ .err = e };
|
||||
},
|
||||
.result => {
|
||||
std.debug.print("[DEBUG] copyFileZSlowWithHandle - copyFile succeeded\n", .{});
|
||||
// Check the size of the output file after copying
|
||||
switch (fstat(out_handle)) {
|
||||
.result => |out_stat| {
|
||||
std.debug.print("[DEBUG] copyFileZSlowWithHandle - output file size after copy: {d}\n", .{out_stat.size});
|
||||
},
|
||||
.err => |err| {
|
||||
std.debug.print("[DEBUG] copyFileZSlowWithHandle - failed to stat output file: {}\n", .{err});
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
if (comptime Environment.isPosix) {
|
||||
|
||||
@@ -957,3 +957,313 @@ export { greeting };`,
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("Bun.build compile: true", () => {
|
||||
test("basic compile functionality", async () => {
|
||||
const dir = tempDirWithFiles("bun-build-compile-basic", {
|
||||
"index.ts": `
|
||||
console.log("Hello from compiled executable!");
|
||||
process.exit(0);
|
||||
`,
|
||||
});
|
||||
|
||||
const build = await Bun.build({
|
||||
entrypoints: [join(dir, "index.ts")],
|
||||
compile: true,
|
||||
outdir: dir,
|
||||
});
|
||||
|
||||
expect(build.success).toBe(true);
|
||||
expect(build.outputs).toHaveLength(0); // Compile returns empty outputs
|
||||
expect(build.logs).toHaveLength(0);
|
||||
|
||||
// Check that an executable was created
|
||||
const executableName = process.platform === "win32" ? "index.exe" : "index";
|
||||
const executablePath = join(dir, executableName);
|
||||
expect(Bun.file(executablePath).size).toBeGreaterThan(0);
|
||||
|
||||
// Test running the executable
|
||||
const { exitCode, stdout } = Bun.spawnSync({
|
||||
cmd: [executablePath],
|
||||
cwd: dir,
|
||||
stdout: "pipe",
|
||||
});
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout.toString().trim()).toBe("Hello from compiled executable!");
|
||||
});
|
||||
|
||||
test("compile with cross-compilation target", async () => {
|
||||
const dir = tempDirWithFiles("bun-build-compile-target", {
|
||||
"index.ts": `
|
||||
console.log("Cross-compiled executable!");
|
||||
console.log("Platform:", process.platform);
|
||||
console.log("Arch:", process.arch);
|
||||
`,
|
||||
});
|
||||
|
||||
// Test cross-compilation to a different target
|
||||
const targetPlatform = process.platform === "linux" ? "darwin" : "linux";
|
||||
const targetArch = "x64";
|
||||
const targetString = `bun-${targetPlatform}-${targetArch}`;
|
||||
|
||||
const build = await Bun.build({
|
||||
entrypoints: [join(dir, "index.ts")],
|
||||
compile: true,
|
||||
target: targetString,
|
||||
outdir: dir,
|
||||
});
|
||||
|
||||
expect(build.success).toBe(true);
|
||||
expect(build.outputs).toHaveLength(0);
|
||||
|
||||
// Check that an executable was created with appropriate extension
|
||||
const executableName = targetPlatform === "win32" ? "index.exe" : "index";
|
||||
const executablePath = join(dir, executableName);
|
||||
expect(Bun.file(executablePath).size).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test("compile with outfile option", async () => {
|
||||
const dir = tempDirWithFiles("bun-build-compile-outfile", {
|
||||
"app.ts": `
|
||||
console.log("Custom executable name!");
|
||||
`,
|
||||
});
|
||||
|
||||
const customName = process.platform === "win32" ? "myapp.exe" : "myapp";
|
||||
|
||||
const build = await Bun.build({
|
||||
entrypoints: [join(dir, "app.ts")],
|
||||
compile: true,
|
||||
outfile: join(dir, customName),
|
||||
});
|
||||
|
||||
expect(build.success).toBe(true);
|
||||
expect(build.outputs).toHaveLength(0);
|
||||
|
||||
// Check that the custom-named executable was created
|
||||
const executablePath = join(dir, customName);
|
||||
expect(Bun.file(executablePath).size).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test("compile with bundling optimizations", async () => {
|
||||
const dir = tempDirWithFiles("bun-build-compile-optimized", {
|
||||
"index.ts": `
|
||||
import { helper } from "./helper";
|
||||
console.log(helper("World"));
|
||||
`,
|
||||
"helper.ts": `
|
||||
export function helper(name: string): string {
|
||||
return \`Hello, \${name}!\`;
|
||||
}
|
||||
`,
|
||||
});
|
||||
|
||||
const build = await Bun.build({
|
||||
entrypoints: [join(dir, "index.ts")],
|
||||
compile: true,
|
||||
minify: true,
|
||||
target: "bun",
|
||||
outdir: dir,
|
||||
});
|
||||
|
||||
expect(build.success).toBe(true);
|
||||
expect(build.outputs).toHaveLength(0);
|
||||
|
||||
const executableName = process.platform === "win32" ? "index.exe" : "index";
|
||||
const executablePath = join(dir, executableName);
|
||||
expect(Bun.file(executablePath).size).toBeGreaterThan(0);
|
||||
|
||||
// Test that the executable runs correctly with bundled code
|
||||
const { exitCode, stdout } = Bun.spawnSync({
|
||||
cmd: [executablePath],
|
||||
cwd: dir,
|
||||
stdout: "pipe",
|
||||
});
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout.toString().trim()).toBe("Hello, World!");
|
||||
});
|
||||
|
||||
test("compile error handling for missing files", async () => {
|
||||
const dir = tempDirWithFiles("bun-build-compile-error", {});
|
||||
|
||||
try {
|
||||
await Bun.build({
|
||||
entrypoints: [join(dir, "nonexistent.ts")],
|
||||
compile: true,
|
||||
outdir: dir,
|
||||
});
|
||||
expect.unreachable("Should have thrown an error");
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(AggregateError);
|
||||
expect(error.errors).toHaveLength(1);
|
||||
expect(error.errors[0].message).toMatch(/ModuleNotFound/);
|
||||
}
|
||||
});
|
||||
|
||||
test("compile with external dependencies", async () => {
|
||||
const dir = tempDirWithFiles("bun-build-compile-external", {
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-app",
|
||||
dependencies: {
|
||||
"lodash": "^4.0.0"
|
||||
}
|
||||
}),
|
||||
"index.ts": `
|
||||
console.log("Testing external dependencies");
|
||||
// Only log a message to avoid requiring actual lodash installation
|
||||
console.log("App started successfully");
|
||||
`,
|
||||
});
|
||||
|
||||
const build = await Bun.build({
|
||||
entrypoints: [join(dir, "index.ts")],
|
||||
compile: true,
|
||||
target: "bun",
|
||||
outdir: dir,
|
||||
});
|
||||
|
||||
expect(build.success).toBe(true);
|
||||
expect(build.outputs).toHaveLength(0);
|
||||
|
||||
const executableName = process.platform === "win32" ? "index.exe" : "index";
|
||||
const executablePath = join(dir, executableName);
|
||||
expect(Bun.file(executablePath).size).toBeGreaterThan(0);
|
||||
|
||||
const { exitCode, stdout } = Bun.spawnSync({
|
||||
cmd: [executablePath],
|
||||
cwd: dir,
|
||||
stdout: "pipe",
|
||||
});
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout.toString()).toContain("App started successfully");
|
||||
});
|
||||
|
||||
test("compile with TypeScript configuration", async () => {
|
||||
const dir = tempDirWithFiles("bun-build-compile-typescript", {
|
||||
"tsconfig.json": JSON.stringify({
|
||||
compilerOptions: {
|
||||
target: "ES2020",
|
||||
module: "ESNext",
|
||||
strict: true,
|
||||
paths: {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}),
|
||||
"src/utils.ts": `
|
||||
export const getMessage = (): string => {
|
||||
return "TypeScript compiled successfully!";
|
||||
};
|
||||
`,
|
||||
"index.ts": `
|
||||
import { getMessage } from "@/utils";
|
||||
console.log(getMessage());
|
||||
`,
|
||||
});
|
||||
|
||||
const build = await Bun.build({
|
||||
entrypoints: [join(dir, "index.ts")],
|
||||
compile: true,
|
||||
tsconfig: join(dir, "tsconfig.json"),
|
||||
outdir: dir,
|
||||
});
|
||||
|
||||
expect(build.success).toBe(true);
|
||||
expect(build.outputs).toHaveLength(0);
|
||||
|
||||
const executableName = process.platform === "win32" ? "index.exe" : "index";
|
||||
const executablePath = join(dir, executableName);
|
||||
expect(Bun.file(executablePath).size).toBeGreaterThan(0);
|
||||
|
||||
const { exitCode, stdout } = Bun.spawnSync({
|
||||
cmd: [executablePath],
|
||||
cwd: dir,
|
||||
stdout: "pipe",
|
||||
});
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout.toString().trim()).toBe("TypeScript compiled successfully!");
|
||||
});
|
||||
|
||||
test("compile: false should not create executable", async () => {
|
||||
const dir = tempDirWithFiles("bun-build-no-compile", {
|
||||
"index.ts": `
|
||||
console.log("Not compiled");
|
||||
`,
|
||||
});
|
||||
|
||||
const build = await Bun.build({
|
||||
entrypoints: [join(dir, "index.ts")],
|
||||
compile: false,
|
||||
outdir: dir,
|
||||
});
|
||||
|
||||
expect(build.success).toBe(true);
|
||||
expect(build.outputs).toHaveLength(1); // Should have normal JS output
|
||||
expect(build.outputs[0].kind).toBe("entry-point");
|
||||
|
||||
// Check that no executable was created
|
||||
const executableName = process.platform === "win32" ? "index.exe" : "index";
|
||||
const executablePath = join(dir, executableName);
|
||||
expect(() => Bun.file(executablePath).size).toThrow(); // File should not exist
|
||||
});
|
||||
|
||||
test("compile with invalid target throws error", async () => {
|
||||
const dir = tempDirWithFiles("bun-build-compile-invalid-target", {
|
||||
"index.ts": `console.log("test");`,
|
||||
});
|
||||
|
||||
expect(() => {
|
||||
Bun.build({
|
||||
entrypoints: [join(dir, "index.ts")],
|
||||
compile: true,
|
||||
target: "invalid-target-name",
|
||||
outdir: dir,
|
||||
});
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
test("compile works with plugins", async () => {
|
||||
const dir = tempDirWithFiles("bun-build-compile-plugins", {
|
||||
"index.ts": `
|
||||
import text from "./data.txt";
|
||||
console.log("Loaded text:", text);
|
||||
`,
|
||||
"data.txt": "Hello from text file!",
|
||||
});
|
||||
|
||||
const build = await Bun.build({
|
||||
entrypoints: [join(dir, "index.ts")],
|
||||
compile: true,
|
||||
outdir: dir,
|
||||
plugins: [
|
||||
{
|
||||
name: "text-loader",
|
||||
setup(build) {
|
||||
build.onLoad({ filter: /\.txt$/ }, async (args) => {
|
||||
const text = await Bun.file(args.path).text();
|
||||
return {
|
||||
contents: `export default ${JSON.stringify(text.trim())};`,
|
||||
loader: "js",
|
||||
};
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(build.success).toBe(true);
|
||||
expect(build.outputs).toHaveLength(0);
|
||||
|
||||
const executableName = process.platform === "win32" ? "index.exe" : "index";
|
||||
const executablePath = join(dir, executableName);
|
||||
expect(Bun.file(executablePath).size).toBeGreaterThan(0);
|
||||
|
||||
const { exitCode, stdout } = Bun.spawnSync({
|
||||
cmd: [executablePath],
|
||||
cwd: dir,
|
||||
stdout: "pipe",
|
||||
});
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout.toString()).toContain("Hello from text file!");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user