mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
Plugins + cross-compilation + Bun.build API support for Bun.build({compile}) (#21915)
### What does this PR do? in the name ### How did you verify your code works? tests, but using ci to see if anything else broke --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
This commit is contained in:
66
packages/bun-types/bun.d.ts
vendored
66
packages/bun-types/bun.d.ts
vendored
@@ -1628,12 +1628,24 @@ declare module "bun" {
|
|||||||
kind: ImportKind;
|
kind: ImportKind;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace _BunBuildInterface {
|
||||||
|
type Architecture = "x64" | "arm64";
|
||||||
|
type Libc = "glibc" | "musl";
|
||||||
|
type SIMD = "baseline" | "modern";
|
||||||
|
type Target =
|
||||||
|
| `bun-darwin-${Architecture}`
|
||||||
|
| `bun-darwin-x64-${SIMD}`
|
||||||
|
| `bun-linux-${Architecture}`
|
||||||
|
| `bun-linux-${Architecture}-${Libc}`
|
||||||
|
| "bun-windows-x64"
|
||||||
|
| `bun-windows-x64-${SIMD}`
|
||||||
|
| `bun-linux-x64-${SIMD}-${Libc}`;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* @see [Bun.build API docs](https://bun.com/docs/bundler#api)
|
* @see [Bun.build API docs](https://bun.com/docs/bundler#api)
|
||||||
*/
|
*/
|
||||||
interface BuildConfig {
|
interface BuildConfigBase {
|
||||||
entrypoints: string[]; // list of file path
|
entrypoints: string[]; // list of file path
|
||||||
outdir?: string; // output directory
|
|
||||||
/**
|
/**
|
||||||
* @default "browser"
|
* @default "browser"
|
||||||
*/
|
*/
|
||||||
@@ -1671,7 +1683,6 @@ declare module "bun" {
|
|||||||
asset?: string;
|
asset?: string;
|
||||||
}; // | string;
|
}; // | string;
|
||||||
root?: string; // project root
|
root?: string; // project root
|
||||||
splitting?: boolean; // default true, enable code splitting
|
|
||||||
plugins?: BunPlugin[];
|
plugins?: BunPlugin[];
|
||||||
// manifest?: boolean; // whether to return manifest
|
// manifest?: boolean; // whether to return manifest
|
||||||
external?: string[];
|
external?: string[];
|
||||||
@@ -1820,8 +1831,57 @@ declare module "bun" {
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
tsconfig?: string;
|
tsconfig?: string;
|
||||||
|
|
||||||
|
outdir?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface CompileBuildOptions {
|
||||||
|
target?: _BunBuildInterface.Target;
|
||||||
|
execArgv?: string[];
|
||||||
|
executablePath?: string;
|
||||||
|
outfile?: string;
|
||||||
|
windows?: {
|
||||||
|
hideConsole?: boolean;
|
||||||
|
icon?: string;
|
||||||
|
title?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile build config - uses outfile for executable output
|
||||||
|
interface CompileBuildConfig extends BuildConfigBase {
|
||||||
|
/**
|
||||||
|
* Create a standalone executable
|
||||||
|
*
|
||||||
|
* When `true`, creates an executable for the current platform.
|
||||||
|
* When a target string, creates an executable for that platform.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* // Create executable for current platform
|
||||||
|
* await Bun.build({
|
||||||
|
* entrypoints: ['./app.js'],
|
||||||
|
* compile: {
|
||||||
|
* target: 'linux-x64',
|
||||||
|
* },
|
||||||
|
* outfile: './my-app'
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* // Cross-compile for Linux x64
|
||||||
|
* await Bun.build({
|
||||||
|
* entrypoints: ['./app.js'],
|
||||||
|
* compile: 'linux-x64',
|
||||||
|
* outfile: './my-app'
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
compile: boolean | _BunBuildInterface.Target | CompileBuildOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see [Bun.build API docs](https://bun.com/docs/bundler#api)
|
||||||
|
*/
|
||||||
|
type BuildConfig = BuildConfigBase | CompileBuildConfig;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hash and verify passwords using argon2 or bcrypt
|
* Hash and verify passwords using argon2 or bcrypt
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ pub const StandaloneModuleGraph = struct {
|
|||||||
|
|
||||||
// by normalized file path
|
// by normalized file path
|
||||||
pub fn find(this: *const StandaloneModuleGraph, name: []const u8) ?*File {
|
pub fn find(this: *const StandaloneModuleGraph, name: []const u8) ?*File {
|
||||||
if (!isBunStandaloneFilePath(base_path)) {
|
if (!isBunStandaloneFilePath(name)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -348,7 +348,7 @@ pub const StandaloneModuleGraph = struct {
|
|||||||
var entry_point_id: ?usize = null;
|
var entry_point_id: ?usize = null;
|
||||||
var string_builder = bun.StringBuilder{};
|
var string_builder = bun.StringBuilder{};
|
||||||
var module_count: usize = 0;
|
var module_count: usize = 0;
|
||||||
for (output_files) |output_file| {
|
for (output_files) |*output_file| {
|
||||||
string_builder.countZ(output_file.dest_path);
|
string_builder.countZ(output_file.dest_path);
|
||||||
string_builder.countZ(prefix);
|
string_builder.countZ(prefix);
|
||||||
if (output_file.value == .buffer) {
|
if (output_file.value == .buffer) {
|
||||||
@@ -395,7 +395,7 @@ pub const StandaloneModuleGraph = struct {
|
|||||||
var source_map_arena = bun.ArenaAllocator.init(allocator);
|
var source_map_arena = bun.ArenaAllocator.init(allocator);
|
||||||
defer source_map_arena.deinit();
|
defer source_map_arena.deinit();
|
||||||
|
|
||||||
for (output_files) |output_file| {
|
for (output_files) |*output_file| {
|
||||||
if (!output_file.output_kind.isFileInStandaloneMode()) {
|
if (!output_file.output_kind.isFileInStandaloneMode()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -496,6 +496,21 @@ pub const StandaloneModuleGraph = struct {
|
|||||||
windows_hide_console: bool = false,
|
windows_hide_console: bool = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const CompileResult = union(enum) {
|
||||||
|
success: void,
|
||||||
|
error_message: []const u8,
|
||||||
|
|
||||||
|
pub fn fail(msg: []const u8) CompileResult {
|
||||||
|
return .{ .error_message = msg };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(this: *const @This()) void {
|
||||||
|
if (this.* == .error_message) {
|
||||||
|
bun.default_allocator.free(this.error_message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
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) bun.FileDescriptor {
|
||||||
var buf: bun.PathBuffer = undefined;
|
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| {
|
var zname: [:0]const u8 = bun.span(bun.fs.FileSystem.instance.tmpname("bun-build", &buf, @as(u64, @bitCast(std.time.milliTimestamp()))) catch |err| {
|
||||||
@@ -632,6 +647,7 @@ pub const StandaloneModuleGraph = struct {
|
|||||||
cleanup(zname, fd);
|
cleanup(zname, fd);
|
||||||
Global.exit(1);
|
Global.exit(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
break :brk fd;
|
break :brk fd;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -821,7 +837,43 @@ pub const StandaloneModuleGraph = struct {
|
|||||||
var needs_download: bool = true;
|
var needs_download: bool = true;
|
||||||
const dest_z = target.exePath(&exe_path_buf, version_str, env, &needs_download);
|
const dest_z = target.exePath(&exe_path_buf, version_str, env, &needs_download);
|
||||||
if (needs_download) {
|
if (needs_download) {
|
||||||
try target.downloadToPath(env, allocator, dest_z);
|
target.downloadToPath(env, allocator, dest_z) catch |err| {
|
||||||
|
// For CLI, provide detailed error messages and exit
|
||||||
|
switch (err) {
|
||||||
|
error.TargetNotFound => {
|
||||||
|
Output.errGeneric(
|
||||||
|
\\Does this target and version of Bun exist?
|
||||||
|
\\
|
||||||
|
\\404 downloading {} from npm registry
|
||||||
|
, .{target.*});
|
||||||
|
},
|
||||||
|
error.NetworkError => {
|
||||||
|
Output.errGeneric(
|
||||||
|
\\Failed to download cross-compilation target.
|
||||||
|
\\
|
||||||
|
\\Network error downloading {} from npm registry
|
||||||
|
, .{target.*});
|
||||||
|
},
|
||||||
|
error.InvalidResponse => {
|
||||||
|
Output.errGeneric(
|
||||||
|
\\Failed to verify the integrity of the downloaded tarball.
|
||||||
|
\\
|
||||||
|
\\The downloaded content for {} appears to be corrupted
|
||||||
|
, .{target.*});
|
||||||
|
},
|
||||||
|
error.ExtractionFailed => {
|
||||||
|
Output.errGeneric(
|
||||||
|
\\Failed to extract the downloaded tarball.
|
||||||
|
\\
|
||||||
|
\\Could not extract executable for {}
|
||||||
|
, .{target.*});
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
Output.errGeneric("Failed to download {}: {s}", .{ target.*, @errorName(err) });
|
||||||
|
},
|
||||||
|
}
|
||||||
|
Global.exit(1);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return try allocator.dupeZ(u8, dest_z);
|
return try allocator.dupeZ(u8, dest_z);
|
||||||
@@ -839,27 +891,67 @@ pub const StandaloneModuleGraph = struct {
|
|||||||
windows_hide_console: bool,
|
windows_hide_console: bool,
|
||||||
windows_icon: ?[]const u8,
|
windows_icon: ?[]const u8,
|
||||||
compile_exec_argv: []const u8,
|
compile_exec_argv: []const u8,
|
||||||
) !void {
|
self_exe_path: ?[]const u8,
|
||||||
const bytes = try toBytes(allocator, module_prefix, output_files, output_format, compile_exec_argv);
|
) !CompileResult {
|
||||||
if (bytes.len == 0) return;
|
const bytes = toBytes(allocator, module_prefix, output_files, output_format, compile_exec_argv) catch |err| {
|
||||||
|
return CompileResult.fail(std.fmt.allocPrint(allocator, "failed to generate module graph bytes: {s}", .{@errorName(err)}) catch "failed to generate module graph bytes");
|
||||||
|
};
|
||||||
|
if (bytes.len == 0) return CompileResult.fail("no output files to bundle");
|
||||||
|
defer allocator.free(bytes);
|
||||||
|
|
||||||
const fd = inject(
|
var free_self_exe = false;
|
||||||
|
const self_exe = if (self_exe_path) |path| brk: {
|
||||||
|
free_self_exe = true;
|
||||||
|
break :brk allocator.dupeZ(u8, path) catch bun.outOfMemory();
|
||||||
|
} else if (target.isDefault())
|
||||||
|
bun.selfExePath() catch |err| {
|
||||||
|
return CompileResult.fail(std.fmt.allocPrint(allocator, "failed to get self executable path: {s}", .{@errorName(err)}) catch "failed to get self executable path");
|
||||||
|
}
|
||||||
|
else blk: {
|
||||||
|
var exe_path_buf: bun.PathBuffer = undefined;
|
||||||
|
var version_str_buf: [1024]u8 = undefined;
|
||||||
|
const version_str = std.fmt.bufPrintZ(&version_str_buf, "{}", .{target}) catch {
|
||||||
|
return CompileResult.fail("failed to format target version string");
|
||||||
|
};
|
||||||
|
var needs_download: bool = true;
|
||||||
|
const dest_z = target.exePath(&exe_path_buf, version_str, env, &needs_download);
|
||||||
|
|
||||||
|
if (needs_download) {
|
||||||
|
target.downloadToPath(env, allocator, dest_z) catch |err| {
|
||||||
|
const msg = switch (err) {
|
||||||
|
error.TargetNotFound => std.fmt.allocPrint(allocator, "Target platform '{}' is not available for download. Check if this version of Bun supports this target.", .{target}) catch "Target platform not available for download",
|
||||||
|
error.NetworkError => std.fmt.allocPrint(allocator, "Network error downloading executable for '{}'. Check your internet connection and proxy settings.", .{target}) catch "Network error downloading executable",
|
||||||
|
error.InvalidResponse => std.fmt.allocPrint(allocator, "Downloaded file for '{}' appears to be corrupted. Please try again.", .{target}) catch "Downloaded file is corrupted",
|
||||||
|
error.ExtractionFailed => std.fmt.allocPrint(allocator, "Failed to extract executable for '{}'. The download may be incomplete.", .{target}) catch "Failed to extract downloaded executable",
|
||||||
|
error.UnsupportedTarget => std.fmt.allocPrint(allocator, "Target '{}' is not supported", .{target}) catch "Unsupported target",
|
||||||
|
else => std.fmt.allocPrint(allocator, "Failed to download '{}': {s}", .{ target, @errorName(err) }) catch "Download failed",
|
||||||
|
};
|
||||||
|
return CompileResult.fail(msg);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
free_self_exe = true;
|
||||||
|
break :blk allocator.dupeZ(u8, dest_z) catch bun.outOfMemory();
|
||||||
|
};
|
||||||
|
|
||||||
|
defer if (free_self_exe) {
|
||||||
|
allocator.free(self_exe);
|
||||||
|
};
|
||||||
|
|
||||||
|
var fd = inject(
|
||||||
bytes,
|
bytes,
|
||||||
if (target.isDefault())
|
self_exe,
|
||||||
bun.selfExePath() catch |err| {
|
|
||||||
Output.err(err, "failed to get self executable path", .{});
|
|
||||||
Global.exit(1);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
download(allocator, target, env) catch |err| {
|
|
||||||
Output.err(err, "failed to download cross-compiled bun executable", .{});
|
|
||||||
Global.exit(1);
|
|
||||||
},
|
|
||||||
.{ .windows_hide_console = windows_hide_console },
|
.{ .windows_hide_console = windows_hide_console },
|
||||||
target,
|
target,
|
||||||
);
|
);
|
||||||
|
defer if (fd != bun.invalid_fd) fd.close();
|
||||||
bun.debugAssert(fd.kind == .system);
|
bun.debugAssert(fd.kind == .system);
|
||||||
|
|
||||||
|
if (Environment.isPosix) {
|
||||||
|
// Set executable permissions (0o755 = rwxr-xr-x) - makes it executable for owner, readable/executable for group and others
|
||||||
|
_ = Syscall.fchmod(fd, 0o755);
|
||||||
|
}
|
||||||
|
|
||||||
if (Environment.isWindows) {
|
if (Environment.isWindows) {
|
||||||
var outfile_buf: bun.OSPathBuffer = undefined;
|
var outfile_buf: bun.OSPathBuffer = undefined;
|
||||||
const outfile_slice = brk: {
|
const outfile_slice = brk: {
|
||||||
@@ -871,52 +963,59 @@ pub const StandaloneModuleGraph = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
bun.windows.moveOpenedFileAtLoose(fd, .fromStdDir(root_dir), outfile_slice, true).unwrap() catch |err| {
|
bun.windows.moveOpenedFileAtLoose(fd, .fromStdDir(root_dir), 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)});
|
|
||||||
} else {
|
|
||||||
Output.err(err, "failed to move executable to result path", .{});
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = bun.windows.deleteOpenedFile(fd);
|
_ = bun.windows.deleteOpenedFile(fd);
|
||||||
|
if (err == error.EISDIR) {
|
||||||
Global.exit(1);
|
return CompileResult.fail(std.fmt.allocPrint(allocator, "{s} is a directory. Please choose a different --outfile or delete the directory", .{outfile}) catch "outfile is a directory");
|
||||||
|
} else {
|
||||||
|
return CompileResult.fail(std.fmt.allocPrint(allocator, "failed to move executable to result path: {s}", .{@errorName(err)}) catch "failed to move executable");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fd.close();
|
fd.close();
|
||||||
|
fd = bun.invalid_fd;
|
||||||
|
|
||||||
if (windows_icon) |icon_utf8| {
|
if (windows_icon) |icon_utf8| {
|
||||||
var icon_buf: bun.OSPathBuffer = undefined;
|
var icon_buf: bun.OSPathBuffer = undefined;
|
||||||
const icon = bun.strings.toWPathNormalized(&icon_buf, icon_utf8);
|
const icon = bun.strings.toWPathNormalized(&icon_buf, icon_utf8);
|
||||||
bun.windows.rescle.setIcon(outfile_slice, icon) catch {
|
bun.windows.rescle.setIcon(outfile_slice, icon) catch |err| {
|
||||||
Output.warn("Failed to set executable icon", .{});
|
Output.debug("Warning: Failed to set Windows icon for executable: {s}", .{@errorName(err)});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return;
|
return .success;
|
||||||
}
|
}
|
||||||
|
|
||||||
var buf: bun.PathBuffer = undefined;
|
var buf: bun.PathBuffer = undefined;
|
||||||
const temp_location = bun.getFdPath(fd, &buf) catch |err| {
|
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)});
|
return CompileResult.fail(std.fmt.allocPrint(allocator, "failed to get path for fd: {s}", .{@errorName(err)}) catch "failed to get path for file descriptor");
|
||||||
Global.exit(1);
|
};
|
||||||
|
const temp_posix = std.posix.toPosixPath(temp_location) catch |err| {
|
||||||
|
return CompileResult.fail(std.fmt.allocPrint(allocator, "path too long: {s}", .{@errorName(err)}) catch "path too long");
|
||||||
|
};
|
||||||
|
const outfile_basename = std.fs.path.basename(outfile);
|
||||||
|
const outfile_posix = std.posix.toPosixPath(outfile_basename) catch |err| {
|
||||||
|
return CompileResult.fail(std.fmt.allocPrint(allocator, "outfile name too long: {s}", .{@errorName(err)}) catch "outfile name too long");
|
||||||
};
|
};
|
||||||
|
|
||||||
bun.sys.moveFileZWithHandle(
|
bun.sys.moveFileZWithHandle(
|
||||||
fd,
|
fd,
|
||||||
bun.FD.cwd(),
|
bun.FD.cwd(),
|
||||||
bun.sliceTo(&(try std.posix.toPosixPath(temp_location)), 0),
|
bun.sliceTo(&temp_posix, 0),
|
||||||
.fromStdDir(root_dir),
|
.fromStdDir(root_dir),
|
||||||
bun.sliceTo(&(try std.posix.toPosixPath(std.fs.path.basename(outfile))), 0),
|
bun.sliceTo(&outfile_posix, 0),
|
||||||
) catch |err| {
|
) catch |err| {
|
||||||
if (err == error.IsDir or err == error.EISDIR) {
|
fd.close();
|
||||||
Output.prettyErrorln("<r><red>error<r><d>:<r> {} is a directory. Please choose a different --outfile or delete the directory", .{bun.fmt.quote(outfile)});
|
fd = bun.invalid_fd;
|
||||||
} else {
|
|
||||||
Output.prettyErrorln("<r><red>error<r><d>:<r> failed to rename {s} to {s}: {s}", .{ temp_location, outfile, @errorName(err) });
|
|
||||||
}
|
|
||||||
_ = Syscall.unlink(
|
|
||||||
&(try std.posix.toPosixPath(temp_location)),
|
|
||||||
);
|
|
||||||
|
|
||||||
Global.exit(1);
|
_ = Syscall.unlink(&temp_posix);
|
||||||
|
|
||||||
|
if (err == error.IsDir or err == error.EISDIR) {
|
||||||
|
return CompileResult.fail(std.fmt.allocPrint(allocator, "{s} is a directory. Please choose a different --outfile or delete the directory", .{outfile}) catch "outfile is a directory");
|
||||||
|
} else {
|
||||||
|
return CompileResult.fail(std.fmt.allocPrint(allocator, "failed to rename {s} to {s}: {s}", .{ temp_location, outfile, @errorName(err) }) catch "failed to rename file");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return .success;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fromExecutable(allocator: std.mem.Allocator) !?StandaloneModuleGraph {
|
pub fn fromExecutable(allocator: std.mem.Allocator) !?StandaloneModuleGraph {
|
||||||
|
|||||||
@@ -37,6 +37,119 @@ pub const JSBundler = struct {
|
|||||||
env_behavior: api.DotEnvBehavior = .disable,
|
env_behavior: api.DotEnvBehavior = .disable,
|
||||||
env_prefix: OwnedString = OwnedString.initEmpty(bun.default_allocator),
|
env_prefix: OwnedString = OwnedString.initEmpty(bun.default_allocator),
|
||||||
tsconfig_override: OwnedString = OwnedString.initEmpty(bun.default_allocator),
|
tsconfig_override: OwnedString = OwnedString.initEmpty(bun.default_allocator),
|
||||||
|
compile: ?CompileOptions = null,
|
||||||
|
|
||||||
|
pub const CompileOptions = struct {
|
||||||
|
compile_target: CompileTarget = .{},
|
||||||
|
exec_argv: OwnedString = OwnedString.initEmpty(bun.default_allocator),
|
||||||
|
executable_path: OwnedString = OwnedString.initEmpty(bun.default_allocator),
|
||||||
|
windows_hide_console: bool = false,
|
||||||
|
windows_icon_path: OwnedString = OwnedString.initEmpty(bun.default_allocator),
|
||||||
|
windows_title: OwnedString = OwnedString.initEmpty(bun.default_allocator),
|
||||||
|
outfile: OwnedString = OwnedString.initEmpty(bun.default_allocator),
|
||||||
|
|
||||||
|
pub fn fromJS(globalThis: *jsc.JSGlobalObject, config: jsc.JSValue, allocator: std.mem.Allocator, compile_target: ?CompileTarget) JSError!?CompileOptions {
|
||||||
|
var this = CompileOptions{
|
||||||
|
.exec_argv = OwnedString.initEmpty(allocator),
|
||||||
|
.executable_path = OwnedString.initEmpty(allocator),
|
||||||
|
.windows_icon_path = OwnedString.initEmpty(allocator),
|
||||||
|
.windows_title = OwnedString.initEmpty(allocator),
|
||||||
|
.outfile = OwnedString.initEmpty(allocator),
|
||||||
|
.compile_target = compile_target orelse .{},
|
||||||
|
};
|
||||||
|
errdefer this.deinit();
|
||||||
|
|
||||||
|
const object = brk: {
|
||||||
|
const compile_value = try config.getTruthy(globalThis, "compile") orelse return null;
|
||||||
|
|
||||||
|
if (compile_value.isBoolean()) {
|
||||||
|
if (compile_value == .false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
} else if (compile_value.isString()) {
|
||||||
|
this.compile_target = try CompileTarget.fromJS(globalThis, compile_value);
|
||||||
|
return this;
|
||||||
|
} else if (compile_value.isObject()) {
|
||||||
|
break :brk compile_value;
|
||||||
|
} else {
|
||||||
|
return globalThis.throwInvalidArguments("Expected compile to be a boolean or string or options object", .{});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (try object.getOwn(globalThis, "target")) |target| {
|
||||||
|
this.compile_target = try CompileTarget.fromJS(globalThis, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (try object.getOwnArray(globalThis, "execArgv")) |exec_argv| {
|
||||||
|
var iter = try exec_argv.arrayIterator(globalThis);
|
||||||
|
var is_first = true;
|
||||||
|
while (try iter.next()) |arg| {
|
||||||
|
var slice = try arg.toSlice(globalThis, bun.default_allocator);
|
||||||
|
defer slice.deinit();
|
||||||
|
if (is_first) {
|
||||||
|
is_first = false;
|
||||||
|
try this.exec_argv.appendSlice(slice.slice());
|
||||||
|
} else {
|
||||||
|
try this.exec_argv.appendChar(' ');
|
||||||
|
try this.exec_argv.appendSlice(slice.slice());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (try object.getOwn(globalThis, "executablePath")) |executable_path| {
|
||||||
|
var slice = try executable_path.toSlice(globalThis, bun.default_allocator);
|
||||||
|
defer slice.deinit();
|
||||||
|
if (bun.sys.existsAtType(bun.FD.cwd(), slice.slice()).unwrapOr(.directory) != .file) {
|
||||||
|
return globalThis.throwInvalidArguments("executablePath must be a valid path to a Bun executable", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
try this.executable_path.appendSliceExact(slice.slice());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (try object.getOwnTruthy(globalThis, "windows")) |windows| {
|
||||||
|
if (!windows.isObject()) {
|
||||||
|
return globalThis.throwInvalidArguments("windows must be an object", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (try windows.getOwn(globalThis, "hideConsole")) |hide_console| {
|
||||||
|
this.windows_hide_console = hide_console.toBoolean();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (try windows.getOwn(globalThis, "icon")) |windows_icon_path| {
|
||||||
|
var slice = try windows_icon_path.toSlice(globalThis, bun.default_allocator);
|
||||||
|
defer slice.deinit();
|
||||||
|
if (bun.sys.existsAtType(bun.FD.cwd(), slice.slice()).unwrapOr(.directory) != .file) {
|
||||||
|
return globalThis.throwInvalidArguments("windows.icon must be a valid path to an ico file", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
try this.windows_icon_path.appendSliceExact(slice.slice());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (try windows.getOwn(globalThis, "title")) |windows_title| {
|
||||||
|
var slice = try windows_title.toSlice(globalThis, bun.default_allocator);
|
||||||
|
defer slice.deinit();
|
||||||
|
try this.windows_title.appendSliceExact(slice.slice());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (try object.getOwn(globalThis, "outfile")) |outfile| {
|
||||||
|
var slice = try outfile.toSlice(globalThis, bun.default_allocator);
|
||||||
|
defer slice.deinit();
|
||||||
|
try this.outfile.appendSliceExact(slice.slice());
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(this: *CompileOptions) void {
|
||||||
|
this.exec_argv.deinit();
|
||||||
|
this.executable_path.deinit();
|
||||||
|
this.windows_icon_path.deinit();
|
||||||
|
this.windows_title.deinit();
|
||||||
|
this.outfile.deinit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
pub const List = bun.StringArrayHashMapUnmanaged(Config);
|
pub const List = bun.StringArrayHashMapUnmanaged(Config);
|
||||||
|
|
||||||
@@ -58,9 +171,20 @@ pub const JSBundler = struct {
|
|||||||
errdefer if (plugins.*) |plugin| plugin.deinit();
|
errdefer if (plugins.*) |plugin| plugin.deinit();
|
||||||
|
|
||||||
var did_set_target = false;
|
var did_set_target = false;
|
||||||
if (try config.getOptionalEnum(globalThis, "target", options.Target)) |target| {
|
if (try config.getOptional(globalThis, "target", ZigString.Slice)) |slice| {
|
||||||
this.target = target;
|
defer slice.deinit();
|
||||||
did_set_target = true;
|
if (strings.hasPrefixComptime(slice.slice(), "bun-")) {
|
||||||
|
this.compile = .{
|
||||||
|
.compile_target = try CompileTarget.fromSlice(globalThis, slice.slice()),
|
||||||
|
};
|
||||||
|
this.target = .bun;
|
||||||
|
did_set_target = true;
|
||||||
|
} else {
|
||||||
|
this.target = options.Target.Map.get(slice.slice()) orelse {
|
||||||
|
return globalThis.throwInvalidArguments("Expected target to be one of 'browser', 'node', 'bun', 'macro', or 'bun-<target>', got {s}", .{slice.slice()});
|
||||||
|
};
|
||||||
|
did_set_target = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Plugins must be resolved first as they are allowed to mutate the config JSValue
|
// Plugins must be resolved first as they are allowed to mutate the config JSValue
|
||||||
@@ -450,6 +574,52 @@ pub const JSBundler = struct {
|
|||||||
this.throw_on_error = flag;
|
this.throw_on_error = flag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (try CompileOptions.fromJS(
|
||||||
|
globalThis,
|
||||||
|
config,
|
||||||
|
bun.default_allocator,
|
||||||
|
if (this.compile) |*compile| compile.compile_target else null,
|
||||||
|
)) |compile| {
|
||||||
|
this.compile = compile;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.compile) |*compile| {
|
||||||
|
this.target = .bun;
|
||||||
|
|
||||||
|
const define_keys = compile.compile_target.defineKeys();
|
||||||
|
const define_values = compile.compile_target.defineValues();
|
||||||
|
for (define_keys, define_values) |key, value| {
|
||||||
|
try this.define.insert(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const base_public_path = bun.StandaloneModuleGraph.targetBasePublicPath(this.compile.?.compile_target.os, "root/");
|
||||||
|
try this.public_path.append(base_public_path);
|
||||||
|
|
||||||
|
if (compile.outfile.isEmpty()) {
|
||||||
|
const entry_point = this.entry_points.keys()[0];
|
||||||
|
var outfile = std.fs.path.basename(entry_point);
|
||||||
|
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(entry_point) orelse "index");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strings.eqlComptime(outfile, "bun")) {
|
||||||
|
outfile = std.fs.path.basename(std.fs.path.dirname(entry_point) orelse "bun");
|
||||||
|
}
|
||||||
|
|
||||||
|
// If argv[0] is "bun" or "bunx", we don't check if the binary is standalone
|
||||||
|
if (strings.eqlComptime(outfile, "bun") or strings.eqlComptime(outfile, "bunx")) {
|
||||||
|
return globalThis.throwInvalidArguments("cannot use compile with an output file named 'bun' because bun won't realize it's a standalone executable. Please choose a different name for compile.outfile", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
try compile.outfile.appendSliceExact(outfile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -506,6 +676,9 @@ pub const JSBundler = struct {
|
|||||||
self.conditions.deinit();
|
self.conditions.deinit();
|
||||||
self.drop.deinit();
|
self.drop.deinit();
|
||||||
self.banner.deinit();
|
self.banner.deinit();
|
||||||
|
if (self.compile) |*compile| {
|
||||||
|
compile.deinit();
|
||||||
|
}
|
||||||
self.env_prefix.deinit();
|
self.env_prefix.deinit();
|
||||||
self.footer.deinit();
|
self.footer.deinit();
|
||||||
self.tsconfig_override.deinit();
|
self.tsconfig_override.deinit();
|
||||||
@@ -1281,6 +1454,7 @@ pub const BuildArtifact = struct {
|
|||||||
|
|
||||||
const string = []const u8;
|
const string = []const u8;
|
||||||
|
|
||||||
|
const CompileTarget = @import("../../compile_target.zig");
|
||||||
const Fs = @import("../../fs.zig");
|
const Fs = @import("../../fs.zig");
|
||||||
const resolve_path = @import("../../resolver/resolve_path.zig");
|
const resolve_path = @import("../../resolver/resolve_path.zig");
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|||||||
@@ -1738,6 +1738,7 @@ pub const BundleV2 = struct {
|
|||||||
transpiler.options.public_path = config.public_path.list.items;
|
transpiler.options.public_path = config.public_path.list.items;
|
||||||
transpiler.options.output_format = config.format;
|
transpiler.options.output_format = config.format;
|
||||||
transpiler.options.bytecode = config.bytecode;
|
transpiler.options.bytecode = config.bytecode;
|
||||||
|
transpiler.options.compile = config.compile != null;
|
||||||
|
|
||||||
transpiler.options.output_dir = config.outdir.slice();
|
transpiler.options.output_dir = config.outdir.slice();
|
||||||
transpiler.options.root_dir = config.rootdir.slice();
|
transpiler.options.root_dir = config.rootdir.slice();
|
||||||
@@ -1782,6 +1783,114 @@ pub const BundleV2 = struct {
|
|||||||
bun.destroy(this);
|
bun.destroy(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn doCompilation(this: *JSBundleCompletionTask, output_files: *std.ArrayList(options.OutputFile)) bun.StandaloneModuleGraph.CompileResult {
|
||||||
|
const compile_options = &(this.config.compile orelse @panic("Unexpected: No compile options provided"));
|
||||||
|
|
||||||
|
const entry_point_index: usize = brk: {
|
||||||
|
for (output_files.items, 0..) |*output_file, i| {
|
||||||
|
if (output_file.output_kind == .@"entry-point" and (output_file.side orelse .server) == .server) {
|
||||||
|
break :brk i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bun.StandaloneModuleGraph.CompileResult.fail("No entry point found for compilation");
|
||||||
|
};
|
||||||
|
|
||||||
|
const output_file = &output_files.items[entry_point_index];
|
||||||
|
const outbuf = bun.path_buffer_pool.get();
|
||||||
|
defer bun.path_buffer_pool.put(outbuf);
|
||||||
|
var full_outfile_path = if (this.config.outdir.slice().len > 0)
|
||||||
|
bun.path.joinAbsStringBuf(this.config.outdir.slice(), outbuf, &[_][]const u8{compile_options.outfile.slice()}, .loose)
|
||||||
|
else
|
||||||
|
compile_options.outfile.slice();
|
||||||
|
|
||||||
|
// Add .exe extension for Windows targets if not already present
|
||||||
|
if (compile_options.compile_target.os == .windows and !strings.hasSuffixComptime(full_outfile_path, ".exe")) {
|
||||||
|
full_outfile_path = std.fmt.allocPrint(bun.default_allocator, "{s}.exe", .{full_outfile_path}) catch bun.outOfMemory();
|
||||||
|
} else {
|
||||||
|
full_outfile_path = bun.default_allocator.dupe(u8, full_outfile_path) catch bun.outOfMemory();
|
||||||
|
}
|
||||||
|
|
||||||
|
const dirname = std.fs.path.dirname(full_outfile_path) orelse ".";
|
||||||
|
const basename = std.fs.path.basename(full_outfile_path);
|
||||||
|
|
||||||
|
var root_dir = bun.FD.cwd().stdDir();
|
||||||
|
defer {
|
||||||
|
if (bun.FD.fromStdDir(root_dir) != bun.FD.cwd()) {
|
||||||
|
root_dir.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(dirname.len == 0 or strings.eqlComptime(dirname, "."))) {
|
||||||
|
root_dir = root_dir.makeOpenPath(dirname, .{}) catch |err| {
|
||||||
|
return bun.StandaloneModuleGraph.CompileResult.fail(std.fmt.allocPrint(bun.default_allocator, "Failed to open output directory {s}: {s}", .{ dirname, @errorName(err) }) catch bun.outOfMemory());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = bun.StandaloneModuleGraph.toExecutable(
|
||||||
|
&compile_options.compile_target,
|
||||||
|
bun.default_allocator,
|
||||||
|
output_files.items,
|
||||||
|
root_dir,
|
||||||
|
this.config.public_path.slice(),
|
||||||
|
basename,
|
||||||
|
this.env,
|
||||||
|
this.config.format,
|
||||||
|
compile_options.windows_hide_console,
|
||||||
|
if (compile_options.windows_icon_path.slice().len > 0)
|
||||||
|
compile_options.windows_icon_path.slice()
|
||||||
|
else
|
||||||
|
null,
|
||||||
|
compile_options.exec_argv.slice(),
|
||||||
|
if (compile_options.executable_path.slice().len > 0)
|
||||||
|
compile_options.executable_path.slice()
|
||||||
|
else
|
||||||
|
null,
|
||||||
|
) catch |err| {
|
||||||
|
return bun.StandaloneModuleGraph.CompileResult.fail(std.fmt.allocPrint(bun.default_allocator, "{s}", .{@errorName(err)}) catch bun.outOfMemory());
|
||||||
|
};
|
||||||
|
|
||||||
|
if (result == .success) {
|
||||||
|
output_file.dest_path = full_outfile_path;
|
||||||
|
output_file.is_executable = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (output_files.items, 0..) |*current, i| {
|
||||||
|
if (i != entry_point_index) {
|
||||||
|
current.deinit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const entry_point_output_file = output_files.swapRemove(entry_point_index);
|
||||||
|
output_files.items.len = 1;
|
||||||
|
output_files.items[0] = entry_point_output_file;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn toJSError(this: *JSBundleCompletionTask, promise: *jsc.JSPromise, globalThis: *jsc.JSGlobalObject) void {
|
||||||
|
if (this.config.throw_on_error) {
|
||||||
|
promise.reject(globalThis, this.log.toJSAggregateError(globalThis, bun.String.static("Bundle failed")));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const root_obj = jsc.JSValue.createEmptyObject(globalThis, 3);
|
||||||
|
root_obj.put(globalThis, jsc.ZigString.static("outputs"), jsc.JSValue.createEmptyArray(globalThis, 0) catch return promise.reject(globalThis, error.JSError));
|
||||||
|
root_obj.put(
|
||||||
|
globalThis,
|
||||||
|
jsc.ZigString.static("success"),
|
||||||
|
jsc.JSValue.jsBoolean(false),
|
||||||
|
);
|
||||||
|
root_obj.put(
|
||||||
|
globalThis,
|
||||||
|
jsc.ZigString.static("logs"),
|
||||||
|
this.log.toJSArray(globalThis, bun.default_allocator) catch |err| {
|
||||||
|
return promise.reject(globalThis, err);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
promise.resolve(globalThis, root_obj);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn onComplete(this: *JSBundleCompletionTask) void {
|
pub fn onComplete(this: *JSBundleCompletionTask) void {
|
||||||
var globalThis = this.globalThis;
|
var globalThis = this.globalThis;
|
||||||
defer this.deref();
|
defer this.deref();
|
||||||
@@ -1799,33 +1908,25 @@ pub const BundleV2 = struct {
|
|||||||
|
|
||||||
const promise = this.promise.swap();
|
const promise = this.promise.swap();
|
||||||
|
|
||||||
|
if (this.result == .value) {
|
||||||
|
if (this.config.compile != null) {
|
||||||
|
var compile_result = this.doCompilation(&this.result.value.output_files);
|
||||||
|
defer compile_result.deinit();
|
||||||
|
|
||||||
|
if (compile_result != .success) {
|
||||||
|
this.log.addError(null, Logger.Loc.Empty, this.log.msgs.allocator.dupe(u8, compile_result.error_message) catch bun.outOfMemory()) catch bun.outOfMemory();
|
||||||
|
this.result.value.deinit();
|
||||||
|
this.result = .{ .err = error.CompilationFailed };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch (this.result) {
|
switch (this.result) {
|
||||||
.pending => unreachable,
|
.pending => unreachable,
|
||||||
.err => brk: {
|
.err => this.toJSError(promise, globalThis),
|
||||||
if (this.config.throw_on_error) {
|
|
||||||
promise.reject(globalThis, this.log.toJSAggregateError(globalThis, bun.String.static("Bundle failed")));
|
|
||||||
break :brk;
|
|
||||||
}
|
|
||||||
|
|
||||||
const root_obj = jsc.JSValue.createEmptyObject(globalThis, 3);
|
|
||||||
root_obj.put(globalThis, jsc.ZigString.static("outputs"), jsc.JSValue.createEmptyArray(globalThis, 0) catch return promise.reject(globalThis, error.JSError));
|
|
||||||
root_obj.put(
|
|
||||||
globalThis,
|
|
||||||
jsc.ZigString.static("success"),
|
|
||||||
jsc.JSValue.jsBoolean(false),
|
|
||||||
);
|
|
||||||
root_obj.put(
|
|
||||||
globalThis,
|
|
||||||
jsc.ZigString.static("logs"),
|
|
||||||
this.log.toJSArray(globalThis, bun.default_allocator) catch |err| {
|
|
||||||
return promise.reject(globalThis, err);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
promise.resolve(globalThis, root_obj);
|
|
||||||
},
|
|
||||||
.value => |*build| {
|
.value => |*build| {
|
||||||
const root_obj = jsc.JSValue.createEmptyObject(globalThis, 3);
|
const root_obj = jsc.JSValue.createEmptyObject(globalThis, 3);
|
||||||
const output_files: []options.OutputFile = build.output_files.items;
|
const output_files = build.output_files.items;
|
||||||
const output_files_js = jsc.JSValue.createEmptyArray(globalThis, output_files.len) catch return promise.reject(globalThis, error.JSError);
|
const output_files_js = jsc.JSValue.createEmptyArray(globalThis, output_files.len) catch return promise.reject(globalThis, error.JSError);
|
||||||
if (output_files_js == .zero) {
|
if (output_files_js == .zero) {
|
||||||
@panic("Unexpected pending JavaScript exception in JSBundleCompletionTask.onComplete. This is a bug in Bun.");
|
@panic("Unexpected pending JavaScript exception in JSBundleCompletionTask.onComplete. This is a bug in Bun.");
|
||||||
@@ -1848,7 +1949,7 @@ pub const BundleV2 = struct {
|
|||||||
bun.default_allocator.dupe(
|
bun.default_allocator.dupe(
|
||||||
u8,
|
u8,
|
||||||
bun.path.joinAbsString(
|
bun.path.joinAbsString(
|
||||||
Fs.FileSystem.instance.top_level_dir,
|
bun.fs.FileSystem.instance.top_level_dir,
|
||||||
&[_]string{ this.config.dir.slice(), this.config.outdir.slice(), output_file.dest_path },
|
&[_]string{ this.config.dir.slice(), this.config.outdir.slice(), output_file.dest_path },
|
||||||
.auto,
|
.auto,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -318,7 +318,9 @@ pub fn generateChunksInParallel(
|
|||||||
var static_route_visitor = StaticRouteVisitor{ .c = c, .visited = bun.bit_set.AutoBitSet.initEmpty(bun.default_allocator, c.graph.files.len) catch bun.outOfMemory() };
|
var static_route_visitor = StaticRouteVisitor{ .c = c, .visited = bun.bit_set.AutoBitSet.initEmpty(bun.default_allocator, c.graph.files.len) catch bun.outOfMemory() };
|
||||||
defer static_route_visitor.deinit();
|
defer static_route_visitor.deinit();
|
||||||
|
|
||||||
if (root_path.len > 0) {
|
// Don't write to disk if compile mode is enabled - we need buffer values for compilation
|
||||||
|
const is_compile = bundler.transpiler.options.compile;
|
||||||
|
if (root_path.len > 0 and !is_compile) {
|
||||||
try c.writeOutputFilesToDisk(root_path, chunks, &output_files);
|
try c.writeOutputFilesToDisk(root_path, chunks, &output_files);
|
||||||
} else {
|
} else {
|
||||||
// In-memory build
|
// In-memory build
|
||||||
|
|||||||
@@ -1,10 +1,4 @@
|
|||||||
pub const BuildCommand = struct {
|
pub const BuildCommand = struct {
|
||||||
const compile_define_keys = &.{
|
|
||||||
"process.platform",
|
|
||||||
"process.arch",
|
|
||||||
"process.versions.bun",
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn exec(ctx: Command.Context, fetcher: ?*BundleV2.DependenciesScanner) !void {
|
pub fn exec(ctx: Command.Context, fetcher: ?*BundleV2.DependenciesScanner) !void {
|
||||||
Global.configureAllocator(.{ .long_running = true });
|
Global.configureAllocator(.{ .long_running = true });
|
||||||
const allocator = ctx.allocator;
|
const allocator = ctx.allocator;
|
||||||
@@ -26,7 +20,9 @@ pub const BuildCommand = struct {
|
|||||||
const compile_target = &ctx.bundler_options.compile_target;
|
const compile_target = &ctx.bundler_options.compile_target;
|
||||||
|
|
||||||
if (ctx.bundler_options.compile) {
|
if (ctx.bundler_options.compile) {
|
||||||
|
const compile_define_keys = compile_target.defineKeys();
|
||||||
const compile_define_values = compile_target.defineValues();
|
const compile_define_values = compile_target.defineValues();
|
||||||
|
|
||||||
if (ctx.args.define) |*define| {
|
if (ctx.args.define) |*define| {
|
||||||
var keys = try std.ArrayList(string).initCapacity(bun.default_allocator, compile_define_keys.len + define.keys.len);
|
var keys = try std.ArrayList(string).initCapacity(bun.default_allocator, compile_define_keys.len + define.keys.len);
|
||||||
keys.appendSliceAssumeCapacity(compile_define_keys);
|
keys.appendSliceAssumeCapacity(compile_define_keys);
|
||||||
@@ -426,7 +422,7 @@ pub const BuildCommand = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try bun.StandaloneModuleGraph.toExecutable(
|
const result = bun.StandaloneModuleGraph.toExecutable(
|
||||||
compile_target,
|
compile_target,
|
||||||
allocator,
|
allocator,
|
||||||
output_files,
|
output_files,
|
||||||
@@ -438,7 +434,17 @@ pub const BuildCommand = struct {
|
|||||||
ctx.bundler_options.windows_hide_console,
|
ctx.bundler_options.windows_hide_console,
|
||||||
ctx.bundler_options.windows_icon,
|
ctx.bundler_options.windows_icon,
|
||||||
ctx.bundler_options.compile_exec_argv orelse "",
|
ctx.bundler_options.compile_exec_argv orelse "",
|
||||||
);
|
null,
|
||||||
|
) catch |err| {
|
||||||
|
Output.printErrorln("failed to create executable: {s}", .{@errorName(err)});
|
||||||
|
Global.exit(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (result != .success) {
|
||||||
|
Output.printErrorln("{s}", .{result.error_message});
|
||||||
|
Global.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
const compiled_elapsed = @divTrunc(@as(i64, @truncate(std.time.nanoTimestamp() - bundled_end)), @as(i64, std.time.ns_per_ms));
|
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) {
|
const compiled_elapsed_digit_count: isize = switch (compiled_elapsed) {
|
||||||
0...9 => 3,
|
0...9 => 3,
|
||||||
|
|||||||
@@ -1,15 +1,10 @@
|
|||||||
const CompileTarget = @This();
|
|
||||||
|
|
||||||
/// Used for `bun build --compile`
|
/// Used for `bun build --compile`
|
||||||
///
|
///
|
||||||
/// This downloads and extracts the bun binary for the target platform
|
/// This downloads and extracts the bun binary for the target platform
|
||||||
/// It uses npm to download the bun binary from the npm registry
|
/// It uses npm to download the bun binary from the npm registry
|
||||||
/// It stores the downloaded binary into the bun install cache.
|
/// It stores the downloaded binary into the bun install cache.
|
||||||
///
|
///
|
||||||
const bun = @import("bun");
|
const CompileTarget = @This();
|
||||||
const Environment = bun.Environment;
|
|
||||||
const strings = bun.strings;
|
|
||||||
const Output = bun.Output;
|
|
||||||
|
|
||||||
os: Environment.OperatingSystem = Environment.os,
|
os: Environment.OperatingSystem = Environment.os,
|
||||||
arch: Environment.Architecture = Environment.arch,
|
arch: Environment.Architecture = Environment.arch,
|
||||||
@@ -52,6 +47,16 @@ const BaselineFormatter = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const DownloadError = error{
|
||||||
|
TargetNotFound,
|
||||||
|
NetworkError,
|
||||||
|
InvalidResponse,
|
||||||
|
ExtractionFailed,
|
||||||
|
InvalidTarget,
|
||||||
|
OutOfMemory,
|
||||||
|
NoSpaceLeft,
|
||||||
|
};
|
||||||
|
|
||||||
pub fn eql(this: *const CompileTarget, other: *const CompileTarget) bool {
|
pub fn eql(this: *const CompileTarget, other: *const CompileTarget) bool {
|
||||||
return this.os == other.os and this.arch == other.arch and this.baseline == other.baseline and this.version.eql(other.version) and this.libc == other.libc;
|
return this.os == other.os and this.arch == other.arch and this.baseline == other.baseline and this.version.eql(other.version) and this.libc == other.libc;
|
||||||
}
|
}
|
||||||
@@ -70,12 +75,17 @@ pub fn toNPMRegistryURL(this: *const CompileTarget, buf: []u8) ![]const u8 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn toNPMRegistryURLWithURL(this: *const CompileTarget, buf: []u8, registry_url: []const u8) ![]const u8 {
|
pub fn toNPMRegistryURLWithURL(this: *const CompileTarget, buf: []u8, registry_url: []const u8) ![]const u8 {
|
||||||
|
// Validate the target is supported before building URL
|
||||||
|
if (!this.isSupported()) {
|
||||||
|
return error.UnsupportedTarget;
|
||||||
|
}
|
||||||
|
|
||||||
return switch (this.os) {
|
return switch (this.os) {
|
||||||
inline else => |os| switch (this.arch) {
|
inline else => |os| switch (this.arch) {
|
||||||
inline else => |arch| switch (this.libc) {
|
inline else => |arch| switch (this.libc) {
|
||||||
inline else => |libc| switch (this.baseline) {
|
inline else => |libc| switch (this.baseline) {
|
||||||
// https://registry.npmjs.org/@oven/bun-linux-x64/-/bun-linux-x64-0.1.6.tgz
|
// https://registry.npmjs.org/@oven/bun-linux-x64/-/bun-linux-x64-0.1.6.tgz
|
||||||
inline else => |is_baseline| try std.fmt.bufPrint(buf, comptime "{s}/@oven/bun-" ++
|
inline else => |is_baseline| std.fmt.bufPrint(buf, comptime "{s}/@oven/bun-" ++
|
||||||
os.npmName() ++ "-" ++ arch.npmName() ++
|
os.npmName() ++ "-" ++ arch.npmName() ++
|
||||||
libc.npmName() ++
|
libc.npmName() ++
|
||||||
(if (is_baseline) "-baseline" else "") ++
|
(if (is_baseline) "-baseline" else "") ++
|
||||||
@@ -89,7 +99,13 @@ pub fn toNPMRegistryURLWithURL(this: *const CompileTarget, buf: []u8, registry_u
|
|||||||
this.version.major,
|
this.version.major,
|
||||||
this.version.minor,
|
this.version.minor,
|
||||||
this.version.patch,
|
this.version.patch,
|
||||||
}),
|
}) catch |err| {
|
||||||
|
// Catch buffer overflow or other formatting errors
|
||||||
|
if (err == error.NoSpaceLeft) {
|
||||||
|
return error.BufferTooSmall;
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -145,9 +161,6 @@ pub fn exePath(this: *const CompileTarget, buf: *bun.PathBuffer, version_str: [:
|
|||||||
return dest;
|
return dest;
|
||||||
}
|
}
|
||||||
|
|
||||||
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) !void {
|
||||||
HTTP.HTTPThread.init(&.{});
|
HTTP.HTTPThread.init(&.{});
|
||||||
var refresher = bun.Progress{};
|
var refresher = bun.Progress{};
|
||||||
@@ -160,8 +173,12 @@ pub fn downloadToPath(this: *const CompileTarget, env: *bun.DotEnv.Loader, alloc
|
|||||||
var compressed_archive_bytes = try allocator.create(MutableString);
|
var compressed_archive_bytes = try allocator.create(MutableString);
|
||||||
compressed_archive_bytes.* = try MutableString.init(allocator, 24 * 1024 * 1024);
|
compressed_archive_bytes.* = try MutableString.init(allocator, 24 * 1024 * 1024);
|
||||||
var url_buffer: [2048]u8 = undefined;
|
var url_buffer: [2048]u8 = undefined;
|
||||||
const url_str = try bun.default_allocator.dupe(u8, try this.toNPMRegistryURL(&url_buffer));
|
const url_str = this.toNPMRegistryURL(&url_buffer) catch |err| {
|
||||||
const url = bun.URL.parse(url_str);
|
// Return error without printing - let caller decide how to handle
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
const url_str_copy = try bun.default_allocator.dupe(u8, url_str);
|
||||||
|
const url = bun.URL.parse(url_str_copy);
|
||||||
{
|
{
|
||||||
var progress = refresher.start("Downloading", 0);
|
var progress = refresher.start("Downloading", 0);
|
||||||
defer progress.end();
|
defer progress.end();
|
||||||
@@ -186,30 +203,15 @@ pub fn downloadToPath(this: *const CompileTarget, env: *bun.DotEnv.Loader, alloc
|
|||||||
|
|
||||||
switch (response.status_code) {
|
switch (response.status_code) {
|
||||||
404 => {
|
404 => {
|
||||||
Output.errGeneric(
|
// Return error without printing - let caller handle the messaging
|
||||||
\\Does this target and version of Bun exist?
|
return error.TargetNotFound;
|
||||||
\\
|
|
||||||
\\404 downloading {} from {s}
|
|
||||||
, .{
|
|
||||||
this.*,
|
|
||||||
url_str,
|
|
||||||
});
|
|
||||||
Global.exit(1);
|
|
||||||
},
|
},
|
||||||
403, 429, 499...599 => |status| {
|
403, 429, 499...599 => {
|
||||||
Output.errGeneric(
|
// Return error without printing - let caller handle the messaging
|
||||||
\\Failed to download cross-compilation target.
|
return error.NetworkError;
|
||||||
\\
|
|
||||||
\\HTTP {d} downloading {} from {s}
|
|
||||||
, .{
|
|
||||||
status,
|
|
||||||
this.*,
|
|
||||||
url_str,
|
|
||||||
});
|
|
||||||
Global.exit(1);
|
|
||||||
},
|
},
|
||||||
200 => {},
|
200 => {},
|
||||||
else => return error.HTTPError,
|
else => return error.NetworkError,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,44 +221,22 @@ pub fn downloadToPath(this: *const CompileTarget, env: *bun.DotEnv.Loader, alloc
|
|||||||
defer compressed_archive_bytes.list.deinit(allocator);
|
defer compressed_archive_bytes.list.deinit(allocator);
|
||||||
|
|
||||||
if (compressed_archive_bytes.list.items.len == 0) {
|
if (compressed_archive_bytes.list.items.len == 0) {
|
||||||
Output.errGeneric(
|
// Return error without printing - let caller handle the messaging
|
||||||
\\Failed to verify the integrity of the downloaded tarball.
|
return error.InvalidResponse;
|
||||||
\\
|
|
||||||
\\Received empty content downloading {} from {s}
|
|
||||||
, .{
|
|
||||||
this.*,
|
|
||||||
url_str,
|
|
||||||
});
|
|
||||||
Global.exit(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
var node = refresher.start("Decompressing", 0);
|
var node = refresher.start("Decompressing", 0);
|
||||||
defer node.end();
|
defer node.end();
|
||||||
var gunzip = bun.zlib.ZlibReaderArrayList.init(compressed_archive_bytes.list.items, &tarball_bytes, allocator) catch |err| {
|
var gunzip = bun.zlib.ZlibReaderArrayList.init(compressed_archive_bytes.list.items, &tarball_bytes, allocator) catch {
|
||||||
node.end();
|
node.end();
|
||||||
Output.err(err,
|
// Return error without printing - let caller handle the messaging
|
||||||
\\Failed to decompress the downloaded tarball
|
return error.InvalidResponse;
|
||||||
\\
|
|
||||||
\\After downloading {} from {s}
|
|
||||||
, .{
|
|
||||||
this.*,
|
|
||||||
url_str,
|
|
||||||
});
|
|
||||||
Global.exit(1);
|
|
||||||
};
|
};
|
||||||
gunzip.readAll() catch |err| {
|
gunzip.readAll() catch {
|
||||||
node.end();
|
node.end();
|
||||||
// One word difference so if someone reports the bug we can tell if it happened in init or readAll.
|
// Return error without printing - let caller handle the messaging
|
||||||
Output.err(err,
|
return error.InvalidResponse;
|
||||||
\\Failed to deflate the downloaded tarball
|
|
||||||
\\
|
|
||||||
\\After downloading {} from {s}
|
|
||||||
, .{
|
|
||||||
this.*,
|
|
||||||
url_str,
|
|
||||||
});
|
|
||||||
Global.exit(1);
|
|
||||||
};
|
};
|
||||||
gunzip.deinit();
|
gunzip.deinit();
|
||||||
}
|
}
|
||||||
@@ -282,22 +262,15 @@ pub fn downloadToPath(this: *const CompileTarget, env: *bun.DotEnv.Loader, alloc
|
|||||||
// "package/bin"
|
// "package/bin"
|
||||||
.depth_to_skip = 2,
|
.depth_to_skip = 2,
|
||||||
},
|
},
|
||||||
) catch |err| {
|
) catch {
|
||||||
node.end();
|
node.end();
|
||||||
Output.err(err,
|
// Return error without printing - let caller handle the messaging
|
||||||
\\Failed to extract the downloaded tarball
|
return error.ExtractionFailed;
|
||||||
\\
|
|
||||||
\\After downloading {} from {s}
|
|
||||||
, .{
|
|
||||||
this.*,
|
|
||||||
url_str,
|
|
||||||
});
|
|
||||||
Global.exit(1);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var did_retry = false;
|
var did_retry = false;
|
||||||
while (true) {
|
while (true) {
|
||||||
bun.sys.moveFileZ(.fromStdDir(tmpdir), if (this.os == .windows) "bun.exe" else "bun", bun.invalid_fd, dest_z) catch |err| {
|
bun.sys.moveFileZ(.fromStdDir(tmpdir), if (this.os == .windows) "bun.exe" else "bun", bun.invalid_fd, dest_z) catch {
|
||||||
if (!did_retry) {
|
if (!did_retry) {
|
||||||
did_retry = true;
|
did_retry = true;
|
||||||
const dirname = bun.path.dirname(dest_z, .loose);
|
const dirname = bun.path.dirname(dest_z, .loose);
|
||||||
@@ -309,8 +282,8 @@ pub fn downloadToPath(this: *const CompileTarget, env: *bun.DotEnv.Loader, alloc
|
|||||||
// fallthrough, failed for another reason
|
// fallthrough, failed for another reason
|
||||||
}
|
}
|
||||||
node.end();
|
node.end();
|
||||||
Output.err(err, "Failed to move cross-compiled bun binary into cache directory {}", .{bun.fmt.fmtPath(u8, dest_z, .{})});
|
// Return error without printing - let caller handle the messaging
|
||||||
Global.exit(1);
|
return error.ExtractionFailed;
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -331,9 +304,13 @@ pub fn isSupported(this: *const CompileTarget) bool {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from(input_: []const u8) CompileTarget {
|
pub const ParseError = error{
|
||||||
var this = CompileTarget{};
|
UnsupportedTarget,
|
||||||
|
InvalidTarget,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn tryFrom(input_: []const u8) ParseError!CompileTarget {
|
||||||
|
var this = CompileTarget{};
|
||||||
const input = bun.strings.trim(input_, " \t\r");
|
const input = bun.strings.trim(input_, " \t\r");
|
||||||
if (input.len == 0) {
|
if (input.len == 0) {
|
||||||
return this;
|
return this;
|
||||||
@@ -373,8 +350,7 @@ pub fn from(input_: []const u8) CompileTarget {
|
|||||||
const version = bun.Semver.Version.parse(bun.Semver.SlicedString.init(token[1..], token[1..]));
|
const version = bun.Semver.Version.parse(bun.Semver.SlicedString.init(token[1..], token[1..]));
|
||||||
if (version.valid) {
|
if (version.valid) {
|
||||||
if (version.version.major == null or version.version.minor == null or version.version.patch == null) {
|
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, .{});
|
return error.InvalidTarget;
|
||||||
Global.exit(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.version = .{
|
this.version = .{
|
||||||
@@ -390,21 +366,15 @@ pub fn from(input_: []const u8) CompileTarget {
|
|||||||
found_libc = true;
|
found_libc = true;
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
Output.errGeneric(
|
return error.UnsupportedTarget;
|
||||||
\\Unsupported target {} in "bun{s}"
|
|
||||||
\\To see the supported targets:
|
|
||||||
\\ https://bun.com/docs/bundler/executables
|
|
||||||
,
|
|
||||||
.{
|
|
||||||
bun.fmt.quote(token),
|
|
||||||
// received input starts at "-"
|
|
||||||
input_,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
Global.exit(1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!found_libc and this.libc == .musl and this.os != .linux) {
|
||||||
|
// "bun-windows-x64" should not implicitly be "bun-windows-x64-musl"
|
||||||
|
this.libc = .default;
|
||||||
|
}
|
||||||
|
|
||||||
if (found_os and !found_arch) {
|
if (found_os and !found_arch) {
|
||||||
// default to x64 if no arch is specified but OS is specified
|
// default to x64 if no arch is specified but OS is specified
|
||||||
// On macOS arm64, it's kind of surprising to choose Linux arm64 or Windows arm64
|
// On macOS arm64, it's kind of surprising to choose Linux arm64 or Windows arm64
|
||||||
@@ -418,18 +388,77 @@ pub fn from(input_: []const u8) CompileTarget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.libc == .musl and this.os != .linux) {
|
if (this.libc == .musl and this.os != .linux) {
|
||||||
Output.errGeneric("invalid target, musl libc only exists on linux", .{});
|
return error.InvalidTarget;
|
||||||
Global.exit(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.arch == .wasm or this.os == .wasm) {
|
if (this.arch == .wasm or this.os == .wasm) {
|
||||||
Output.errGeneric("invalid target, WebAssembly is not supported. Sorry!", .{});
|
return error.InvalidTarget;
|
||||||
Global.exit(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from(input_: []const u8) CompileTarget {
|
||||||
|
return tryFrom(input_) catch |err| {
|
||||||
|
switch (err) {
|
||||||
|
ParseError.UnsupportedTarget => {
|
||||||
|
const input = bun.strings.trim(input_, " \t\r");
|
||||||
|
var splitter = bun.strings.split(input, "-");
|
||||||
|
var unsupported_token: ?[]const u8 = null;
|
||||||
|
while (splitter.next()) |token| {
|
||||||
|
if (token.len == 0) continue;
|
||||||
|
if (Environment.Architecture.names.get(token) == null and
|
||||||
|
Environment.OperatingSystem.names.get(token) == null and
|
||||||
|
!strings.eqlComptime(token, "modern") and
|
||||||
|
!strings.eqlComptime(token, "baseline") and
|
||||||
|
!strings.eqlComptime(token, "musl") and
|
||||||
|
!(strings.hasPrefixComptime(token, "v1.") or strings.hasPrefixComptime(token, "v0.")))
|
||||||
|
{
|
||||||
|
unsupported_token = token;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unsupported_token) |token| {
|
||||||
|
Output.errGeneric(
|
||||||
|
\\Unsupported target {} in "bun{s}"
|
||||||
|
\\To see the supported targets:
|
||||||
|
\\ https://bun.com/docs/bundler/executables
|
||||||
|
, .{
|
||||||
|
bun.fmt.quote(token),
|
||||||
|
input_,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Output.errGeneric("Unsupported target: {s}", .{input_});
|
||||||
|
}
|
||||||
|
Global.exit(1);
|
||||||
|
},
|
||||||
|
ParseError.InvalidTarget => {
|
||||||
|
const input = bun.strings.trim(input_, " \t\r");
|
||||||
|
if (strings.containsComptime(input, "musl") and !strings.containsComptime(input, "linux")) {
|
||||||
|
Output.errGeneric("invalid target, musl libc only exists on linux", .{});
|
||||||
|
} else if (strings.containsComptime(input, "wasm")) {
|
||||||
|
Output.errGeneric("invalid target, WebAssembly is not supported. Sorry!", .{});
|
||||||
|
} else if (strings.containsComptime(input, "v")) {
|
||||||
|
Output.errGeneric("Please pass a complete version number to --target. For example, --target=bun-v" ++ Environment.version_string, .{});
|
||||||
|
} else {
|
||||||
|
Output.errGeneric("Invalid target: {s}", .{input_});
|
||||||
|
}
|
||||||
|
Global.exit(1);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exists for consistentcy with values.
|
||||||
|
pub fn defineKeys(_: *const CompileTarget) []const []const u8 {
|
||||||
|
return &.{
|
||||||
|
"process.platform",
|
||||||
|
"process.arch",
|
||||||
|
"process.versions.bun",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub fn defineValues(this: *const CompileTarget) []const []const u8 {
|
pub fn defineValues(this: *const CompileTarget) []const []const u8 {
|
||||||
// Use inline else to avoid extra allocations.
|
// Use inline else to avoid extra allocations.
|
||||||
switch (this.os) {
|
switch (this.os) {
|
||||||
@@ -452,4 +481,35 @@ pub fn defineValues(this: *const CompileTarget) []const []const u8 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn fromJS(global: *jsc.JSGlobalObject, value: jsc.JSValue) bun.JSError!CompileTarget {
|
||||||
|
const slice = try value.toSlice(global, bun.default_allocator);
|
||||||
|
defer slice.deinit();
|
||||||
|
if (!strings.hasPrefixComptime(slice.slice(), "bun-")) {
|
||||||
|
return global.throwInvalidArguments("Expected compile target to start with 'bun-', got {s}", .{slice.slice()});
|
||||||
|
}
|
||||||
|
|
||||||
|
return fromSlice(global, slice.slice());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fromSlice(global: *jsc.JSGlobalObject, slice_with_bun_prefix: []const u8) bun.JSError!CompileTarget {
|
||||||
|
const slice = slice_with_bun_prefix["bun-".len..];
|
||||||
|
const target_parsed = tryFrom(slice) catch {
|
||||||
|
return global.throwInvalidArguments("Unknown compile target: {s}", .{slice_with_bun_prefix});
|
||||||
|
};
|
||||||
|
if (!target_parsed.isSupported()) {
|
||||||
|
return global.throwInvalidArguments("Unsupported compile target: {s}", .{slice_with_bun_prefix});
|
||||||
|
}
|
||||||
|
|
||||||
|
return target_parsed;
|
||||||
|
}
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
|
const bun = @import("bun");
|
||||||
|
const Environment = bun.Environment;
|
||||||
|
const Global = bun.Global;
|
||||||
|
const HTTP = bun.http;
|
||||||
|
const MutableString = bun.MutableString;
|
||||||
|
const Output = bun.Output;
|
||||||
|
const jsc = bun.jsc;
|
||||||
|
const strings = bun.strings;
|
||||||
|
|||||||
64
test/bundler/bun-build-compile.test.ts
Normal file
64
test/bundler/bun-build-compile.test.ts
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import { describe, expect, test } from "bun:test";
|
||||||
|
import { isArm64, isLinux, isMacOS, isMusl, isWindows, tempDir } from "harness";
|
||||||
|
import { join } from "path";
|
||||||
|
|
||||||
|
describe("Bun.build compile", () => {
|
||||||
|
test("compile with current platform target string", async () => {
|
||||||
|
using dir = tempDir("build-compile-target", {
|
||||||
|
"app.js": `console.log("Cross-compiled app");`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const os = isMacOS ? "darwin" : isLinux ? "linux" : isWindows ? "windows" : "unknown";
|
||||||
|
const arch = isArm64 ? "aarch64" : "x64";
|
||||||
|
const musl = isMusl ? "-musl" : "";
|
||||||
|
const target = `bun-${os}-${arch}${musl}` as any;
|
||||||
|
const outdir = join(dir + "", "out");
|
||||||
|
|
||||||
|
const result = await Bun.build({
|
||||||
|
entrypoints: [join(dir + "", "app.js")],
|
||||||
|
outdir,
|
||||||
|
compile: {
|
||||||
|
target: target,
|
||||||
|
outfile: "app-cross",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
expect(result.outputs.length).toBe(1);
|
||||||
|
expect(result.outputs[0].path).toEndWith(isWindows ? "app-cross.exe" : "app-cross");
|
||||||
|
|
||||||
|
const exists = await Bun.file(result.outputs[0].path).exists();
|
||||||
|
|
||||||
|
// Verify that we do write it to the outdir.
|
||||||
|
expect(result.outputs[0].path.replaceAll("\\", "/")).toStartWith(outdir.replaceAll("\\", "/"));
|
||||||
|
expect(exists).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("compile with invalid target fails gracefully", async () => {
|
||||||
|
using dir = tempDir("build-compile-invalid", {
|
||||||
|
"index.js": `console.log("test");`,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(() =>
|
||||||
|
Bun.build({
|
||||||
|
entrypoints: [join(dir, "index.js")],
|
||||||
|
compile: {
|
||||||
|
target: "bun-invalid-platform",
|
||||||
|
outfile: join(dir, "invalid-app"),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toThrowErrorMatchingInlineSnapshot(`"Unknown compile target: bun-invalid-platform"`);
|
||||||
|
|
||||||
|
expect(() =>
|
||||||
|
Bun.build({
|
||||||
|
entrypoints: [join(dir, "index.js")],
|
||||||
|
compile: {
|
||||||
|
target: "bun-windows-arm64",
|
||||||
|
outfile: join(dir, "invalid-app"),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toThrowErrorMatchingInlineSnapshot(`"Unsupported compile target: bun-windows-arm64"`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// file command test works well
|
||||||
@@ -17,7 +17,6 @@ describe("bundler", () => {
|
|||||||
itBundled("compile/HelloWorldWithProcessVersionsBun", {
|
itBundled("compile/HelloWorldWithProcessVersionsBun", {
|
||||||
compile: true,
|
compile: true,
|
||||||
files: {
|
files: {
|
||||||
[`/${process.platform}-${process.arch}.js`]: "module.exports = process.versions.bun;",
|
|
||||||
"/entry.ts": /* js */ `
|
"/entry.ts": /* js */ `
|
||||||
process.exitCode = 1;
|
process.exitCode = 1;
|
||||||
process.versions.bun = "bun!";
|
process.versions.bun = "bun!";
|
||||||
@@ -26,9 +25,49 @@ describe("bundler", () => {
|
|||||||
process.exitCode = 0;
|
process.exitCode = 0;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
|
[`/${process.platform}-${process.arch}.js`]: "module.exports = process.versions.bun;",
|
||||||
},
|
},
|
||||||
run: { exitCode: 0 },
|
run: { exitCode: 0 },
|
||||||
});
|
});
|
||||||
|
itBundled("compile/HelloWorldWithProcessVersionsBunAPI", {
|
||||||
|
compile: true,
|
||||||
|
backend: "api",
|
||||||
|
outfile: "dist/out",
|
||||||
|
files: {
|
||||||
|
"/entry.ts": /* js */ `
|
||||||
|
import { foo } from "hello:world";
|
||||||
|
if (foo !== "bar") throw new Error("fail");
|
||||||
|
process.exitCode = 1;
|
||||||
|
process.versions.bun = "bun!";
|
||||||
|
if (process.versions.bun === "bun!") throw new Error("fail");
|
||||||
|
const another = require("./${process.platform}-${process.arch}.js").replaceAll("-debug", "");
|
||||||
|
if (another === "${Bun.version.replaceAll("-debug", "")}") {
|
||||||
|
process.exitCode = 0;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
[`/${process.platform}-${process.arch}.js`]: "module.exports = process.versions.bun;",
|
||||||
|
},
|
||||||
|
run: { exitCode: 0, stdout: "hello world" },
|
||||||
|
plugins: [
|
||||||
|
{
|
||||||
|
name: "hello-world",
|
||||||
|
setup(api) {
|
||||||
|
api.onResolve({ filter: /hello:world/, namespace: "file" }, args => {
|
||||||
|
return {
|
||||||
|
path: args.path,
|
||||||
|
namespace: "hello",
|
||||||
|
};
|
||||||
|
});
|
||||||
|
api.onLoad({ filter: /.*/, namespace: "hello" }, args => {
|
||||||
|
return {
|
||||||
|
contents: "export const foo = 'bar'; console.log('hello world');",
|
||||||
|
loader: "js",
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
itBundled("compile/HelloWorldBytecode", {
|
itBundled("compile/HelloWorldBytecode", {
|
||||||
compile: true,
|
compile: true,
|
||||||
bytecode: true,
|
bytecode: true,
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ import { describe } from "bun:test";
|
|||||||
import { itBundled } from "./expectBundled";
|
import { itBundled } from "./expectBundled";
|
||||||
|
|
||||||
describe("bundler", () => {
|
describe("bundler", () => {
|
||||||
itBundled("compile/HTMLServerBasic", {
|
for (const backend of ["api", "cli"] as const) {
|
||||||
compile: true,
|
itBundled(`compile/${backend}/HTMLServerBasic`, {
|
||||||
files: {
|
compile: true,
|
||||||
"/entry.ts": /* js */ `
|
backend: backend,
|
||||||
|
files: {
|
||||||
|
"/entry.ts": /* js */ `
|
||||||
import index from "./index.html";
|
import index from "./index.html";
|
||||||
|
|
||||||
using server = Bun.serve({
|
using server = Bun.serve({
|
||||||
@@ -24,7 +26,7 @@ describe("bundler", () => {
|
|||||||
console.log("Has h1:", html.includes("Hello HTML"));
|
console.log("Has h1:", html.includes("Hello HTML"));
|
||||||
|
|
||||||
`,
|
`,
|
||||||
"/index.html": /* html */ `
|
"/index.html": /* html */ `
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
@@ -37,24 +39,25 @@ describe("bundler", () => {
|
|||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
`,
|
`,
|
||||||
"/styles.css": /* css */ `
|
"/styles.css": /* css */ `
|
||||||
body {
|
body {
|
||||||
background: blue;
|
background: blue;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
"/app.js": /* js */ `
|
"/app.js": /* js */ `
|
||||||
console.log("Client app loaded");
|
console.log("Client app loaded");
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
run: {
|
run: {
|
||||||
stdout: "Status: 200\nContent-Type: text/html;charset=utf-8\nHas HTML tag: true\nHas h1: true",
|
stdout: "Status: 200\nContent-Type: text/html;charset=utf-8\nHas HTML tag: true\nHas h1: true",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
itBundled("compile/HTMLServerMultipleRoutes", {
|
itBundled(`compile/${backend}/HTMLServerMultipleRoutes`, {
|
||||||
compile: true,
|
compile: true,
|
||||||
files: {
|
backend: backend,
|
||||||
"/entry.ts": /* js */ `
|
files: {
|
||||||
|
"/entry.ts": /* js */ `
|
||||||
import home from "./home.html";
|
import home from "./home.html";
|
||||||
import about from "./about.html";
|
import about from "./about.html";
|
||||||
|
|
||||||
@@ -78,7 +81,7 @@ describe("bundler", () => {
|
|||||||
const aboutHtml = await aboutRes.text();
|
const aboutHtml = await aboutRes.text();
|
||||||
console.log("About has content:", aboutHtml.includes("About Page"));
|
console.log("About has content:", aboutHtml.includes("About Page"));
|
||||||
`,
|
`,
|
||||||
"/home.html": /* html */ `
|
"/home.html": /* html */ `
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
@@ -91,7 +94,7 @@ describe("bundler", () => {
|
|||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
`,
|
`,
|
||||||
"/about.html": /* html */ `
|
"/about.html": /* html */ `
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
@@ -104,18 +107,19 @@ describe("bundler", () => {
|
|||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
`,
|
`,
|
||||||
"/styles.css": /* css */ `
|
"/styles.css": /* css */ `
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
"/app.js": /* js */ `
|
"/app.js": /* js */ `
|
||||||
console.log("App loaded");
|
console.log("App loaded");
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
run: {
|
run: {
|
||||||
stdout: "Home status: 200\nHome has content: true\nAbout status: 200\nAbout has content: true",
|
stdout: "Home status: 200\nHome has content: true\nAbout status: 200\nAbout has content: true",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import { itBundled } from "./expectBundled";
|
|||||||
describe("bundler", () => {
|
describe("bundler", () => {
|
||||||
// Test that the --compile-exec-argv flag works for both runtime processing and execArgv
|
// Test that the --compile-exec-argv flag works for both runtime processing and execArgv
|
||||||
itBundled("compile/CompileExecArgvDualBehavior", {
|
itBundled("compile/CompileExecArgvDualBehavior", {
|
||||||
compile: true,
|
compile: {
|
||||||
compileArgv: "--title=CompileExecArgvDualBehavior --smol",
|
execArgv: ["--title=CompileExecArgvDualBehavior", "--smol"],
|
||||||
|
},
|
||||||
files: {
|
files: {
|
||||||
"/entry.ts": /* js */ `
|
"/entry.ts": /* js */ `
|
||||||
// Test that --compile-exec-argv both processes flags AND populates execArgv
|
// Test that --compile-exec-argv both processes flags AND populates execArgv
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* See `./expectBundled.md` for how this works.
|
* See `./expectBundled.md` for how this works.
|
||||||
*/
|
*/
|
||||||
import { BuildConfig, BuildOutput, BunPlugin, fileURLToPath, PluginBuilder, Loader } from "bun";
|
import { BuildConfig, BuildOutput, BunPlugin, fileURLToPath, PluginBuilder, Loader, CompileBuildOptions } from "bun";
|
||||||
import { callerSourceOrigin } from "bun:jsc";
|
import { callerSourceOrigin } from "bun:jsc";
|
||||||
import type { Matchers } from "bun:test";
|
import type { Matchers } from "bun:test";
|
||||||
import * as esbuild from "esbuild";
|
import * as esbuild from "esbuild";
|
||||||
@@ -148,9 +148,8 @@ export interface BundlerTestInput {
|
|||||||
/** Use when doing something weird with entryPoints and you need to check other output paths. */
|
/** Use when doing something weird with entryPoints and you need to check other output paths. */
|
||||||
outputPaths?: string[];
|
outputPaths?: string[];
|
||||||
/** Use --compile */
|
/** Use --compile */
|
||||||
compile?: boolean;
|
|
||||||
/** Use --compile-exec-argv to prepend arguments to standalone executable */
|
compile?: boolean | string | CompileBuildOptions;
|
||||||
compileArgv?: string | string[];
|
|
||||||
|
|
||||||
/** force using cli or js api. defaults to api if possible, then cli otherwise */
|
/** force using cli or js api. defaults to api if possible, then cli otherwise */
|
||||||
backend?: "cli" | "api";
|
backend?: "cli" | "api";
|
||||||
@@ -432,7 +431,6 @@ function expectBundled(
|
|||||||
chunkNaming,
|
chunkNaming,
|
||||||
cjs2esm,
|
cjs2esm,
|
||||||
compile,
|
compile,
|
||||||
compileArgv,
|
|
||||||
conditions,
|
conditions,
|
||||||
dce,
|
dce,
|
||||||
dceKeepMarkerCount,
|
dceKeepMarkerCount,
|
||||||
@@ -696,8 +694,8 @@ function expectBundled(
|
|||||||
...(entryPointsRaw ?? []),
|
...(entryPointsRaw ?? []),
|
||||||
bundling === false ? "--no-bundle" : [],
|
bundling === false ? "--no-bundle" : [],
|
||||||
compile ? "--compile" : [],
|
compile ? "--compile" : [],
|
||||||
compileArgv
|
compile && typeof compile === "object" && "execArgv" in compile
|
||||||
? `--compile-exec-argv=${Array.isArray(compileArgv) ? compileArgv.join(" ") : compileArgv}`
|
? `--compile-exec-argv=${Array.isArray(compile.execArgv) ? compile.execArgv.join(" ") : compile.execArgv}`
|
||||||
: [],
|
: [],
|
||||||
outfile ? `--outfile=${outfile}` : `--outdir=${outdir}`,
|
outfile ? `--outfile=${outfile}` : `--outdir=${outdir}`,
|
||||||
define && Object.entries(define).map(([k, v]) => ["--define", `${k}=${v}`]),
|
define && Object.entries(define).map(([k, v]) => ["--define", `${k}=${v}`]),
|
||||||
@@ -1026,6 +1024,19 @@ function expectBundled(
|
|||||||
if (!ESBUILD) {
|
if (!ESBUILD) {
|
||||||
const buildOutDir = useOutFile ? path.dirname(outfile!) : outdir!;
|
const buildOutDir = useOutFile ? path.dirname(outfile!) : outdir!;
|
||||||
|
|
||||||
|
if (outfile && compile) {
|
||||||
|
if (typeof compile === "boolean" && compile) {
|
||||||
|
compile = {
|
||||||
|
outfile: outfile,
|
||||||
|
};
|
||||||
|
} else if (typeof compile === "string") {
|
||||||
|
compile = {
|
||||||
|
target: compile,
|
||||||
|
outfile: outfile,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const buildConfig = {
|
const buildConfig = {
|
||||||
entrypoints: [...entryPaths, ...(entryPointsRaw ?? [])],
|
entrypoints: [...entryPaths, ...(entryPointsRaw ?? [])],
|
||||||
external,
|
external,
|
||||||
@@ -1053,6 +1064,7 @@ function expectBundled(
|
|||||||
drop,
|
drop,
|
||||||
define: define ?? {},
|
define: define ?? {},
|
||||||
throw: false,
|
throw: false,
|
||||||
|
compile,
|
||||||
} as BuildConfig;
|
} as BuildConfig;
|
||||||
|
|
||||||
if (dotenv) {
|
if (dotenv) {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export const isLinux = process.platform === "linux";
|
|||||||
export const isPosix = isMacOS || isLinux;
|
export const isPosix = isMacOS || isLinux;
|
||||||
export const isWindows = process.platform === "win32";
|
export const isWindows = process.platform === "win32";
|
||||||
export const isIntelMacOS = isMacOS && process.arch === "x64";
|
export const isIntelMacOS = isMacOS && process.arch === "x64";
|
||||||
|
export const isArm64 = process.arch === "arm64";
|
||||||
export const isDebug = Bun.version.includes("debug");
|
export const isDebug = Bun.version.includes("debug");
|
||||||
export const isCI = process.env.CI !== undefined;
|
export const isCI = process.env.CI !== undefined;
|
||||||
export const libcFamily: "glibc" | "musl" =
|
export const libcFamily: "glibc" | "musl" =
|
||||||
@@ -263,6 +264,24 @@ export function tempDirWithFiles(
|
|||||||
return base;
|
return base;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class DisposableString extends String {
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
fs.rmSync(this + "", { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
[Symbol.asyncDispose]() {
|
||||||
|
return fs.promises.rm(this + "", { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function tempDir(
|
||||||
|
basename: string,
|
||||||
|
filesOrAbsolutePathToCopyFolderFrom: DirectoryTree | string,
|
||||||
|
): DisposableString {
|
||||||
|
const base = tempDirWithFiles(basename, filesOrAbsolutePathToCopyFolderFrom);
|
||||||
|
|
||||||
|
return new DisposableString(base);
|
||||||
|
}
|
||||||
|
|
||||||
export function tempDirWithFilesAnon(filesOrAbsolutePathToCopyFolderFrom: DirectoryTree | string): string {
|
export function tempDirWithFilesAnon(filesOrAbsolutePathToCopyFolderFrom: DirectoryTree | string): string {
|
||||||
const base = tmpdirSync();
|
const base = tmpdirSync();
|
||||||
makeTreeSync(base, filesOrAbsolutePathToCopyFolderFrom);
|
makeTreeSync(base, filesOrAbsolutePathToCopyFolderFrom);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"!= alloc.ptr": 0,
|
"!= alloc.ptr": 0,
|
||||||
"!= allocator.ptr": 0,
|
"!= allocator.ptr": 0,
|
||||||
".arguments_old(": 279,
|
".arguments_old(": 279,
|
||||||
".stdDir()": 40,
|
".stdDir()": 41,
|
||||||
".stdFile()": 18,
|
".stdFile()": 18,
|
||||||
"// autofix": 168,
|
"// autofix": 168,
|
||||||
": [^=]+= undefined,$": 260,
|
": [^=]+= undefined,$": 260,
|
||||||
|
|||||||
Reference in New Issue
Block a user