From 98d253d9bb8684ecbb324ed989ea499e8f5d0b07 Mon Sep 17 00:00:00 2001 From: Dylan Conway <35280289+dylan-conway@users.noreply.github.com> Date: Thu, 11 Jan 2024 22:00:47 -0800 Subject: [PATCH] fix(install): incremental support for windows (#7991) * update for windows * use correct sizes * mode * fchmod and correct error checking * some progress * walker_skippable.zig for windows * no segfault * comptime only * normalize * create files in .bin * bun.sys.write * string formatters in bun.fmt, diriterator enum * isAllASCII comptime * update more * some cleanup * fix 1 * fix 2 * simlink * cast * remove normalize * to zig err * update fchmod * fixup * fix running bin files * --bun for running binaries * --bun for scripts * remove binary linking * update * remove todo * todos * fix * simlink * cast * more cast --- src/bun.js/api/server.zig | 6 +- src/bun.js/base.zig | 2 +- src/bun.js/bindings/bindings.zig | 10 +- src/bun.js/bindings/exports.zig | 2 +- src/bun.js/node/dir_iterator.zig | 136 ++- src/bun.js/node/node_fs.zig | 55 +- src/bun.js/node/types.zig | 4 +- src/bun.js/test/jest.zig | 4 +- src/bun.js/test/pretty_format.zig | 2 +- src/bun.js/webcore/request.zig | 4 +- src/bun.zig | 990 ++++-------------- src/bundler/bundle_v2.zig | 4 +- src/c.zig | 27 +- src/cli.zig | 10 +- src/cli/bunx_command.zig | 2 +- src/cli/create_command.zig | 33 +- src/cli/run_command.zig | 3 +- src/copy_file.zig | 53 +- src/deps/libuv.zig | 56 -- src/deps/zlib.win32.zig | 2 +- src/fmt.zig | 1110 +++++++++++++++++++++ src/fs.zig | 8 +- src/glob.zig | 2 +- src/http/websocket_http_client.zig | 2 +- src/install/bin.zig | 27 +- src/install/extract_tarball.zig | 52 +- src/install/install.zig | 179 ++-- src/install/lockfile.zig | 21 +- src/install/resolvers/folder_resolver.zig | 2 +- src/js_parser.zig | 6 +- src/libarchive/libarchive-bindings.zig | 7 +- src/libarchive/libarchive.zig | 36 +- src/logger.zig | 2 +- src/napi/napi.zig | 2 +- src/options.zig | 3 + src/output.zig | 2 +- src/resolver/resolver.zig | 6 +- src/standalone_bun.zig | 27 +- src/string.zig | 2 +- src/string_immutable.zig | 330 +----- src/sys.zig | 41 +- src/url.zig | 4 +- src/walker_skippable.zig | 160 +-- src/windows.zig | 22 +- src/windows_c.zig | 56 ++ 45 files changed, 1943 insertions(+), 1571 deletions(-) create mode 100644 src/fmt.zig diff --git a/src/bun.js/api/server.zig b/src/bun.js/api/server.zig index 9bd1282957..273b8a85fb 100644 --- a/src/bun.js/api/server.zig +++ b/src/bun.js/api/server.zig @@ -5368,7 +5368,7 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp pub fn getURL(this: *ThisServer, globalThis: *JSGlobalObject) callconv(.C) JSC.JSValue { const fmt = switch (this.config.address) { - .unix => |unix| strings.URLFormatter{ + .unix => |unix| bun.fmt.URLFormatter{ .proto = .unix, .hostname = bun.sliceTo(@constCast(unix), 0), }, @@ -5377,7 +5377,7 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp if (this.listener) |listener| { port = @intCast(listener.getLocalPort()); } - break :blk strings.URLFormatter{ + break :blk bun.fmt.URLFormatter{ .proto = if (comptime ssl_enabled_) .https else .http, .hostname = if (tcp.hostname) |hostname| bun.sliceTo(@constCast(hostname), 0) else null, .port = port, @@ -5638,7 +5638,7 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp }, .unix => |unix| { error_instance = (JSC.SystemError{ - .message = bun.String.init(std.fmt.bufPrint(&output_buf, "Failed to listen on unix socket {}", .{strings.QuotedFormatter{ .text = bun.sliceTo(unix, 0) }}) catch "Failed to start server"), + .message = bun.String.init(std.fmt.bufPrint(&output_buf, "Failed to listen on unix socket {}", .{bun.fmt.QuotedFormatter{ .text = bun.sliceTo(unix, 0) }}) catch "Failed to start server"), .code = bun.String.static("EADDRINUSE"), .syscall = bun.String.static("listen"), }).toErrorInstance(this.globalThis); diff --git a/src/bun.js/base.zig b/src/bun.js/base.zig index 46d9d28030..47e8fec0aa 100644 --- a/src/bun.js/base.zig +++ b/src/bun.js/base.zig @@ -168,7 +168,7 @@ pub fn createError( ) JSC.JSValue { if (comptime std.meta.fields(@TypeOf(args)).len == 0) { var zig_str = JSC.ZigString.init(fmt); - if (comptime !strings.isAllASCIISimple(fmt)) { + if (comptime !strings.isAllASCII(fmt)) { zig_str.markUTF16(); } diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig index a4f06f829e..7274bf1da6 100644 --- a/src/bun.js/bindings/bindings.zig +++ b/src/bun.js/bindings/bindings.zig @@ -366,7 +366,7 @@ pub const ZigString = extern struct { return strings.eqlComptimeUTF16(this.utf16SliceAligned(), other); } - if (comptime strings.isAllASCIISimple(other)) { + if (comptime strings.isAllASCII(other)) { if (this.len != other.len) return false; @@ -616,7 +616,7 @@ pub const ZigString = extern struct { pub fn format(this: GithubActionFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { var bytes = this.text.toSlice(bun.default_allocator); defer bytes.deinit(); - try strings.githubActionWriter(writer, bytes.slice()); + try bun.fmt.githubActionWriter(writer, bytes.slice()); } }; @@ -717,11 +717,11 @@ pub const ZigString = extern struct { } if (self.is16Bit()) { - try strings.formatUTF16(self.utf16Slice(), writer); + try bun.fmt.formatUTF16(self.utf16Slice(), writer); return; } - try strings.formatLatin1(self.slice(), writer); + try bun.fmt.formatLatin1(self.slice(), writer); } pub inline fn toRef(slice_: []const u8, global: *JSGlobalObject) C_API.JSValueRef { @@ -2692,7 +2692,7 @@ pub const JSGlobalObject = extern struct { var str = ZigString.fromUTF8(buf.toOwnedSliceLeaky()); return str.toErrorInstance(this); } else { - if (comptime strings.isAllASCIISimple(fmt)) { + if (comptime strings.isAllASCII(fmt)) { return ZigString.static(fmt).toErrorInstance(this); } else { return ZigString.initUTF8(fmt).toErrorInstance(this); diff --git a/src/bun.js/bindings/exports.zig b/src/bun.js/bindings/exports.zig index 91b9b248b1..aa088c4f13 100644 --- a/src/bun.js/bindings/exports.zig +++ b/src/bun.js/bindings/exports.zig @@ -2191,7 +2191,7 @@ pub const ZigConsoleClient = struct { } pub inline fn write16Bit(self: *@This(), input: []const u16) void { - strings.formatUTF16Type([]const u16, input, self.ctx) catch { + bun.fmt.formatUTF16Type([]const u16, input, self.ctx) catch { self.failed = true; }; } diff --git a/src/bun.js/node/dir_iterator.zig b/src/bun.js/node/dir_iterator.zig index ded2051fc6..bc6f1e10dd 100644 --- a/src/bun.js/node/dir_iterator.zig +++ b/src/bun.js/node/dir_iterator.zig @@ -27,7 +27,18 @@ pub const IteratorResult = struct { const Result = Maybe(?IteratorResult); const IteratorResultW = struct { - name: []const u16, + // fake PathString to have less `if (Environment.isWindows) ...` + name: struct { + data: [:0]const u16, + + pub fn slice(this: @This()) []const u16 { + return this.data; + } + + pub fn sliceAssumeZ(this: @This()) [:0]const u16 { + return this.data; + } + }, kind: Entry.Kind, }; const ResultW = Maybe(?IteratorResultW); @@ -171,6 +182,7 @@ pub fn NewIterator(comptime use_windows_ospath: bool) type { end_index: usize, first: bool, name_data: [256]u8, + name_data_w: [128]u16, const Self = @This(); @@ -244,7 +256,13 @@ pub fn NewIterator(comptime use_windows_ospath: bool) type { self.index = self.buf.len; } - const name_utf16le = @as([*]u16, @ptrCast(&dir_info.FileName))[0 .. dir_info.FileNameLength / 2]; + const length = dir_info.FileNameLength / 2; + @memcpy(self.name_data_w[0..length], @as([*]u16, @ptrCast(&dir_info.FileName))[0..length]); + self.name_data_w[length] = 0; + const name_utf16le = self.name_data_w[0..length :0]; + + if (mem.eql(u16, name_utf16le, &[_]u16{'.'}) or mem.eql(u16, name_utf16le, &[_]u16{ '.', '.' })) + continue; const kind = blk: { const attrs = dir_info.FileAttributes; @@ -257,13 +275,11 @@ pub fn NewIterator(comptime use_windows_ospath: bool) type { return .{ .result = IteratorResultW{ .kind = kind, - .name = name_utf16le, + .name = .{ .data = name_utf16le }, }, }; } - if (mem.eql(u16, name_utf16le, &[_]u16{'.'}) or mem.eql(u16, name_utf16le, &[_]u16{ '.', '.' })) - continue; // Trust that Windows gives us valid UTF-16LE const name_utf8 = strings.fromWPath(self.name_data[0..], name_utf16le); @@ -344,60 +360,70 @@ pub fn NewIterator(comptime use_windows_ospath: bool) type { }; } -pub const WrappedIterator = struct { - iter: Iterator, - const Self = @This(); +const PathType = enum { u8, u16 }; - pub const Error = IteratorError; +pub fn NewWrappedIterator(comptime path_type: PathType) type { + const IteratorType = if (path_type == .u16) IteratorW else Iterator; + const ResultType = if (path_type == .u16) ResultW else Result; + return struct { + iter: IteratorType, + const Self = @This(); - pub inline fn next(self: *Self) Result { - return self.iter.next(); - } -}; + pub const Error = IteratorError; -pub fn iterate(self: Dir) WrappedIterator { - return WrappedIterator{ - .iter = _iterate(self), + pub inline fn next(self: *Self) ResultType { + return self.iter.next(); + } + + pub fn init(dir: Dir) Self { + return Self{ + .iter = switch (builtin.os.tag) { + .macos, + .ios, + .freebsd, + .netbsd, + .dragonfly, + .openbsd, + .solaris, + => IteratorType{ + .dir = dir, + .seek = 0, + .index = 0, + .end_index = 0, + .buf = undefined, + }, + .linux, .haiku => IteratorType{ + .dir = dir, + .index = 0, + .end_index = 0, + .buf = undefined, + }, + .windows => IteratorType{ + .dir = dir, + .index = 0, + .end_index = 0, + .first = true, + .buf = undefined, + .name_data = undefined, + .name_data_w = undefined, + }, + .wasi => IteratorType{ + .dir = dir, + .cookie = os.wasi.DIRCOOKIE_START, + .index = 0, + .end_index = 0, + .buf = undefined, + }, + else => @compileError("unimplemented"), + }, + }; + } }; } -fn _iterate(self: Dir) Iterator { - switch (builtin.os.tag) { - .macos, - .ios, - .freebsd, - .netbsd, - .dragonfly, - .openbsd, - .solaris, - => return Iterator{ - .dir = self, - .seek = 0, - .index = 0, - .end_index = 0, - .buf = undefined, - }, - .linux, .haiku => return Iterator{ - .dir = self, - .index = 0, - .end_index = 0, - .buf = undefined, - }, - .windows => return Iterator{ - .dir = self, - .index = 0, - .end_index = 0, - .first = true, - .buf = undefined, - .name_data = undefined, - }, - .wasi => return Iterator{ - .dir = self, - .cookie = os.wasi.DIRCOOKIE_START, - .index = 0, - .end_index = 0, - .buf = undefined, - }, - else => @compileError("unimplemented"), - } +pub const WrappedIterator = NewWrappedIterator(.u8); +pub const WrappedIteratorW = NewWrappedIterator(.u16); + +pub fn iterate(self: Dir, comptime path_type: PathType) NewWrappedIterator(path_type) { + return NewWrappedIterator(path_type).init(self); } diff --git a/src/bun.js/node/node_fs.zig b/src/bun.js/node/node_fs.zig index a273187bcd..354f309251 100644 --- a/src/bun.js/node/node_fs.zig +++ b/src/bun.js/node/node_fs.zig @@ -4252,7 +4252,7 @@ pub const NodeFS = struct { pub fn mkdirRecursive(this: *NodeFS, args: Arguments.Mkdir, comptime flavor: Flavor) Maybe(Return.Mkdir) { _ = flavor; var buf: bun.OSPathBuffer = undefined; - const path: bun.OSPathSlice = if (!Environment.isWindows) + const path: bun.OSPathSliceZ = if (!Environment.isWindows) args.path.osPath(&buf) else brk: { // TODO(@paperdave): clean this up a lot. @@ -4273,15 +4273,15 @@ pub const NodeFS = struct { }; } - pub fn _isSep(char: std.meta.Child(bun.OSPathSlice)) bool { + pub fn _isSep(char: bun.OSPathChar) bool { return if (Environment.isWindows) char == '/' or char == '\\' else char == '/'; } - pub fn mkdirRecursiveOSPath(this: *NodeFS, path: bun.OSPathSlice, mode: Mode, comptime return_path: bool) Maybe(Return.Mkdir) { - const Char = std.meta.Child(bun.OSPathSlice); + pub fn mkdirRecursiveOSPath(this: *NodeFS, path: bun.OSPathSliceZ, mode: Mode, comptime return_path: bool) Maybe(Return.Mkdir) { + const Char = bun.OSPathChar; const len = @as(u16, @truncate(path.len)); // First, attempt to create the desired directory @@ -4628,7 +4628,7 @@ pub const NodeFS = struct { entries: *std.ArrayList(ExpectedType), ) Maybe(void) { const dir = fd.asDir(); - var iterator = DirIterator.iterate(dir); + var iterator = DirIterator.iterate(dir, .u8); var entry = iterator.next(); while (switch (entry) { @@ -4724,7 +4724,7 @@ pub const NodeFS = struct { } } - var iterator = DirIterator.iterate(fd.asDir()); + var iterator = DirIterator.iterate(fd.asDir(), .u8); var entry = iterator.next(); while (switch (entry) { @@ -4867,7 +4867,7 @@ pub const NodeFS = struct { } } - var iterator = DirIterator.iterate(fd.asDir()); + var iterator = DirIterator.iterate(fd.asDir(), .u8); var entry = iterator.next(); while (switch (entry) { @@ -5750,7 +5750,7 @@ pub const NodeFS = struct { } const watcher = args.createStatWatcher() catch |err| { - const buf = std.fmt.allocPrint(bun.default_allocator, "{s} watching {}", .{ @errorName(err), strings.QuotedFormatter{ .text = args.path.slice() } }) catch unreachable; + const buf = std.fmt.allocPrint(bun.default_allocator, "{s} watching {}", .{ @errorName(err), bun.fmt.QuotedFormatter{ .text = args.path.slice() } }) catch unreachable; defer bun.default_allocator.free(buf); args.global_this.throwValue((JSC.SystemError{ .message = bun.String.init(buf), @@ -5849,7 +5849,7 @@ pub const NodeFS = struct { } const watcher = args.createFSWatcher() catch |err| { - const buf = std.fmt.allocPrint(bun.default_allocator, "{s} watching {}", .{ @errorName(err), strings.QuotedFormatter{ .text = args.path.slice() } }) catch unreachable; + const buf = std.fmt.allocPrint(bun.default_allocator, "{s} watching {}", .{ @errorName(err), bun.fmt.QuotedFormatter{ .text = args.path.slice() } }) catch unreachable; defer bun.default_allocator.free(buf); args.global_this.throwValue((JSC.SystemError{ .message = bun.String.init(buf), @@ -6008,18 +6008,7 @@ pub const NodeFS = struct { var iterator = iterator: { const dir = fd.asDir(); - if (Environment.isWindows) { - // iterate directly over [:0]const u16 instead of converting to utf8 - break :iterator DirIterator.IteratorW{ - .dir = dir, - .index = 0, - .end_index = 0, - .first = true, - .buf = undefined, - .name_data = undefined, - }; - } - break :iterator DirIterator.iterate(dir); + break :iterator DirIterator.iterate(dir, if (Environment.isWindows) .u16 else .u8); }; var entry = iterator.next(); while (switch (entry) { @@ -6028,23 +6017,23 @@ pub const NodeFS = struct { }, .result => |ent| ent, }) |current| : (entry = iterator.next()) { - const name_slice = if (Environment.isWindows) current.name else current.name.slice(); + const name_slice = current.name.slice(); - @memcpy(src_buf[src_dir_len + 1 .. src_dir_len + 1 + current.name.len], name_slice); + @memcpy(src_buf[src_dir_len + 1 .. src_dir_len + 1 + name_slice.len], name_slice); src_buf[src_dir_len] = std.fs.path.sep; - src_buf[src_dir_len + 1 + current.name.len] = 0; + src_buf[src_dir_len + 1 + name_slice.len] = 0; - @memcpy(dest_buf[dest_dir_len + 1 .. dest_dir_len + 1 + current.name.len], name_slice); + @memcpy(dest_buf[dest_dir_len + 1 .. dest_dir_len + 1 + name_slice.len], name_slice); dest_buf[dest_dir_len] = std.fs.path.sep; - dest_buf[dest_dir_len + 1 + current.name.len] = 0; + dest_buf[dest_dir_len + 1 + name_slice.len] = 0; switch (current.kind) { .directory => { const r = this._cpSync( src_buf, - src_dir_len + 1 + current.name.len, + src_dir_len + @as(PathString.PathInt, @intCast(1 + name_slice.len)), dest_buf, - dest_dir_len + 1 + current.name.len, + dest_dir_len + @as(PathString.PathInt, @intCast(1 + name_slice.len)), args, ); switch (r) { @@ -6054,8 +6043,8 @@ pub const NodeFS = struct { }, else => { const r = this._copySingleFileSync( - src_buf[0 .. src_dir_len + 1 + current.name.len :0], - dest_buf[0 .. dest_dir_len + 1 + current.name.len :0], + src_buf[0 .. src_dir_len + 1 + name_slice.len :0], + dest_buf[0 .. dest_dir_len + 1 + name_slice.len :0], @enumFromInt((if (args.errorOnExist or !args.force) Constants.COPYFILE_EXCL else @as(u8, 0))), null, ); @@ -6077,8 +6066,8 @@ pub const NodeFS = struct { /// This is `copyFile`, but it copies symlinks as-is pub fn _copySingleFileSync( this: *NodeFS, - src: bun.OSPathSlice, - dest: bun.OSPathSlice, + src: bun.OSPathSliceZ, + dest: bun.OSPathSliceZ, mode: Constants.Copyfile, /// Stat on posix, file attributes on windows reuse_stat: ?if (Environment.isWindows) windows.DWORD else std.os.Stat, @@ -6464,7 +6453,7 @@ pub const NodeFS = struct { } const dir = fd.asDir(); - var iterator = DirIterator.iterate(dir); + var iterator = DirIterator.iterate(dir, .u8); var entry = iterator.next(); while (switch (entry) { .err => |err| { diff --git a/src/bun.js/node/types.zig b/src/bun.js/node/types.zig index a415605cfa..416f6b4ad9 100644 --- a/src/bun.js/node/types.zig +++ b/src/bun.js/node/types.zig @@ -639,7 +639,7 @@ pub const PathLike = union(enum) { return bun.strings.toWPath(@alignCast(std.mem.bytesAsSlice(u16, buf)), this.slice()); } - pub inline fn osPath(this: PathLike, buf: *[bun.MAX_PATH_BYTES]u8) bun.OSPathSlice { + pub inline fn osPath(this: PathLike, buf: *[bun.MAX_PATH_BYTES]u8) bun.OSPathSliceZ { if (comptime Environment.isWindows) { return sliceW(this, buf); } @@ -1439,7 +1439,7 @@ pub fn StatType(comptime Big: bool) type { return @truncate(this.mode); } - const S = if (!Environment.isWindows) os.system.S else bun.windows.libuv.S; + const S = if (!Environment.isWindows) os.system.S else bun.C.S; pub fn isBlockDevice(this: *This) JSC.JSValue { return JSC.JSValue.jsBoolean(S.ISBLK(@intCast(this.modeInternal()))); diff --git a/src/bun.js/test/jest.zig b/src/bun.js/test/jest.zig index eb5fb0786d..078581f80b 100644 --- a/src/bun.js/test/jest.zig +++ b/src/bun.js/test/jest.zig @@ -654,7 +654,7 @@ pub const TestScope = struct { vm.autoGarbageCollect(); } JSC.markBinding(@src()); - debug("test({})", .{strings.QuotedFormatter{ .text = this.label }}); + debug("test({})", .{bun.fmt.QuotedFormatter{ .text = this.label }}); var initial_value = JSValue.zero; if (test_elapsed_timer) |timer| { @@ -1080,7 +1080,7 @@ pub const DescribeScope = struct { defer callback.unprotect(); this.push(); defer this.pop(); - debug("describe({})", .{strings.QuotedFormatter{ .text = this.label }}); + debug("describe({})", .{bun.fmt.QuotedFormatter{ .text = this.label }}); if (callback == .zero) { this.runTests(globalObject); diff --git a/src/bun.js/test/pretty_format.zig b/src/bun.js/test/pretty_format.zig index 090383b4b1..355530a5b2 100644 --- a/src/bun.js/test/pretty_format.zig +++ b/src/bun.js/test/pretty_format.zig @@ -653,7 +653,7 @@ pub const JestPrettyFormat = struct { } pub inline fn write16Bit(self: *@This(), input: []const u16) void { - strings.formatUTF16Type([]const u16, input, self.ctx) catch { + bun.fmt.formatUTF16Type([]const u16, input, self.ctx) catch { self.failed = true; }; } diff --git a/src/bun.js/webcore/request.zig b/src/bun.js/webcore/request.zig index bfbe7db833..509d61a316 100644 --- a/src/bun.js/webcore/request.zig +++ b/src/bun.js/webcore/request.zig @@ -328,7 +328,7 @@ pub const Request = struct { const req_url = req.url(); if (req_url.len > 0 and req_url[0] == '/') { if (req.header("host")) |host| { - const fmt = strings.HostFormatter{ + const fmt = bun.fmt.HostFormatter{ .is_https = this.https, .host = host, }; @@ -355,7 +355,7 @@ pub const Request = struct { const req_url = req.url(); if (req_url.len > 0 and req_url[0] == '/') { if (req.header("host")) |host| { - const fmt = strings.HostFormatter{ + const fmt = bun.fmt.HostFormatter{ .is_https = this.https, .host = host, }; diff --git a/src/bun.zig b/src/bun.zig index ab85f50a0f..93eb488561 100644 --- a/src/bun.zig +++ b/src/bun.zig @@ -32,799 +32,9 @@ pub const ComptimeStringMap = @import("./comptime_string_map.zig").ComptimeStrin pub const base64 = @import("./base64/base64.zig"); pub const path = @import("./resolver/resolve_path.zig"); pub const resolver = @import("./resolver//resolver.zig"); +pub const DirIterator = @import("./bun.js/node/dir_iterator.zig"); pub const PackageJSON = @import("./resolver/package_json.zig").PackageJSON; -pub const fmt = struct { - pub usingnamespace std.fmt; - - pub fn fmtJavaScript(text: []const u8, enable_ansi_colors: bool) QuickAndDirtyJavaScriptSyntaxHighlighter { - return QuickAndDirtyJavaScriptSyntaxHighlighter{ - .text = text, - .enable_colors = enable_ansi_colors, - }; - } - - pub const QuickAndDirtyJavaScriptSyntaxHighlighter = struct { - text: []const u8, - enable_colors: bool = false, - limited: bool = true, - - const ColorCode = enum { - magenta, - blue, - orange, - red, - pink, - - pub fn color(this: ColorCode) []const u8 { - return switch (this) { - .magenta => "\x1b[35m", - .blue => "\x1b[34m", - .orange => "\x1b[33m", - .red => "\x1b[31m", - // light pink - .pink => "\x1b[38;5;206m", - }; - } - }; - - pub const Keyword = enum { - abstract, - as, - @"async", - @"await", - case, - @"catch", - class, - @"const", - @"continue", - debugger, - default, - delete, - do, - @"else", - @"enum", - @"export", - extends, - false, - finally, - @"for", - function, - @"if", - implements, - import, - in, - instanceof, - interface, - let, - new, - null, - package, - private, - protected, - public, - @"return", - static, - super, - @"switch", - this, - throw, - @"break", - true, - @"try", - type, - typeof, - @"var", - void, - @"while", - with, - yield, - string, - number, - boolean, - symbol, - any, - object, - unknown, - never, - namespace, - declare, - readonly, - undefined, - - pub fn colorCode(this: Keyword) ColorCode { - return switch (this) { - Keyword.abstract => ColorCode.blue, - Keyword.as => ColorCode.blue, - Keyword.@"async" => ColorCode.magenta, - Keyword.@"await" => ColorCode.magenta, - Keyword.case => ColorCode.magenta, - Keyword.@"catch" => ColorCode.magenta, - Keyword.class => ColorCode.magenta, - Keyword.@"const" => ColorCode.magenta, - Keyword.@"continue" => ColorCode.magenta, - Keyword.debugger => ColorCode.magenta, - Keyword.default => ColorCode.magenta, - Keyword.delete => ColorCode.red, - Keyword.do => ColorCode.magenta, - Keyword.@"else" => ColorCode.magenta, - Keyword.@"break" => ColorCode.magenta, - Keyword.undefined => ColorCode.orange, - Keyword.@"enum" => ColorCode.blue, - Keyword.@"export" => ColorCode.magenta, - Keyword.extends => ColorCode.magenta, - Keyword.false => ColorCode.orange, - Keyword.finally => ColorCode.magenta, - Keyword.@"for" => ColorCode.magenta, - Keyword.function => ColorCode.magenta, - Keyword.@"if" => ColorCode.magenta, - Keyword.implements => ColorCode.blue, - Keyword.import => ColorCode.magenta, - Keyword.in => ColorCode.magenta, - Keyword.instanceof => ColorCode.magenta, - Keyword.interface => ColorCode.blue, - Keyword.let => ColorCode.magenta, - Keyword.new => ColorCode.magenta, - Keyword.null => ColorCode.orange, - Keyword.package => ColorCode.magenta, - Keyword.private => ColorCode.blue, - Keyword.protected => ColorCode.blue, - Keyword.public => ColorCode.blue, - Keyword.@"return" => ColorCode.magenta, - Keyword.static => ColorCode.magenta, - Keyword.super => ColorCode.magenta, - Keyword.@"switch" => ColorCode.magenta, - Keyword.this => ColorCode.orange, - Keyword.throw => ColorCode.magenta, - Keyword.true => ColorCode.orange, - Keyword.@"try" => ColorCode.magenta, - Keyword.type => ColorCode.blue, - Keyword.typeof => ColorCode.magenta, - Keyword.@"var" => ColorCode.magenta, - Keyword.void => ColorCode.magenta, - Keyword.@"while" => ColorCode.magenta, - Keyword.with => ColorCode.magenta, - Keyword.yield => ColorCode.magenta, - Keyword.string => ColorCode.blue, - Keyword.number => ColorCode.blue, - Keyword.boolean => ColorCode.blue, - Keyword.symbol => ColorCode.blue, - Keyword.any => ColorCode.blue, - Keyword.object => ColorCode.blue, - Keyword.unknown => ColorCode.blue, - Keyword.never => ColorCode.blue, - Keyword.namespace => ColorCode.blue, - Keyword.declare => ColorCode.blue, - Keyword.readonly => ColorCode.blue, - }; - } - }; - - pub const Keywords = ComptimeStringMap(Keyword, .{ - .{ "abstract", Keyword.abstract }, - .{ "any", Keyword.any }, - .{ "as", Keyword.as }, - .{ "async", Keyword.@"async" }, - .{ "await", Keyword.@"await" }, - .{ "boolean", Keyword.boolean }, - .{ "break", Keyword.@"break" }, - .{ "case", Keyword.case }, - .{ "catch", Keyword.@"catch" }, - .{ "class", Keyword.class }, - .{ "const", Keyword.@"const" }, - .{ "continue", Keyword.@"continue" }, - .{ "debugger", Keyword.debugger }, - .{ "declare", Keyword.declare }, - .{ "default", Keyword.default }, - .{ "delete", Keyword.delete }, - .{ "do", Keyword.do }, - .{ "else", Keyword.@"else" }, - .{ "enum", Keyword.@"enum" }, - .{ "export", Keyword.@"export" }, - .{ "extends", Keyword.extends }, - .{ "false", Keyword.false }, - .{ "finally", Keyword.finally }, - .{ "for", Keyword.@"for" }, - .{ "function", Keyword.function }, - .{ "if", Keyword.@"if" }, - .{ "implements", Keyword.implements }, - .{ "import", Keyword.import }, - .{ "in", Keyword.in }, - .{ "instanceof", Keyword.instanceof }, - .{ "interface", Keyword.interface }, - .{ "let", Keyword.let }, - .{ "namespace", Keyword.namespace }, - .{ "never", Keyword.never }, - .{ "new", Keyword.new }, - .{ "null", Keyword.null }, - .{ "number", Keyword.number }, - .{ "object", Keyword.object }, - .{ "package", Keyword.package }, - .{ "private", Keyword.private }, - .{ "protected", Keyword.protected }, - .{ "public", Keyword.public }, - .{ "readonly", Keyword.readonly }, - .{ "return", Keyword.@"return" }, - .{ "static", Keyword.static }, - .{ "string", Keyword.string }, - .{ "super", Keyword.super }, - .{ "switch", Keyword.@"switch" }, - .{ "symbol", Keyword.symbol }, - .{ "this", Keyword.this }, - .{ "throw", Keyword.throw }, - .{ "true", Keyword.true }, - .{ "try", Keyword.@"try" }, - .{ "type", Keyword.type }, - .{ "typeof", Keyword.typeof }, - .{ "undefined", Keyword.undefined }, - .{ "unknown", Keyword.unknown }, - .{ "var", Keyword.@"var" }, - .{ "void", Keyword.void }, - .{ "while", Keyword.@"while" }, - .{ "with", Keyword.with }, - .{ "yield", Keyword.yield }, - }); - - pub fn format(this: @This(), comptime _: []const u8, _: fmt.FormatOptions, writer: anytype) !void { - const text = this.text; - - if (this.limited) { - if (!this.enable_colors or text.len > 2048 or text.len == 0 or !strings.isAllASCII(text)) { - try writer.writeAll(text); - return; - } - } - - var remain = text; - var prev_keyword: ?Keyword = null; - - outer: while (remain.len > 0) { - if (js_lexer.isIdentifierStart(remain[0])) { - var i: usize = 1; - - while (i < remain.len and js_lexer.isIdentifierContinue(remain[i])) { - i += 1; - } - - if (Keywords.get(remain[0..i])) |keyword| { - if (keyword != .as) - prev_keyword = keyword; - const code = keyword.colorCode(); - try writer.print(Output.prettyFmt("{s}{s}", true), .{ code.color(), remain[0..i] }); - } else { - write: { - if (prev_keyword) |prev| { - switch (prev) { - .new => { - prev_keyword = null; - - if (i < remain.len and remain[i] == '(') { - try writer.print(Output.prettyFmt("{s}", true), .{remain[0..i]}); - break :write; - } - }, - .abstract, .namespace, .declare, .type, .interface => { - try writer.print(Output.prettyFmt("{s}", true), .{remain[0..i]}); - prev_keyword = null; - break :write; - }, - .import => { - if (strings.eqlComptime(remain[0..i], "from")) { - const code = ColorCode.magenta; - try writer.print(Output.prettyFmt("{s}{s}", true), .{ code.color(), remain[0..i] }); - prev_keyword = null; - - break :write; - } - }, - else => {}, - } - } - - try writer.writeAll(remain[0..i]); - } - } - remain = remain[i..]; - } else { - switch (remain[0]) { - '0'...'9' => { - prev_keyword = null; - var i: usize = 1; - if (remain.len > 1 and remain[0] == '0' and remain[1] == 'x') { - i += 1; - while (i < remain.len and switch (remain[i]) { - '0'...'9', 'a'...'f', 'A'...'F' => true, - else => false, - }) { - i += 1; - } - } else { - while (i < remain.len and switch (remain[i]) { - '0'...'9', '.', 'e', 'E', 'x', 'X', 'b', 'B', 'o', 'O' => true, - else => false, - }) { - i += 1; - } - } - - try writer.print(Output.prettyFmt("{s}", true), .{remain[0..i]}); - remain = remain[i..]; - }, - inline '`', '"', '\'' => |char| { - prev_keyword = null; - - var i: usize = 1; - while (i < remain.len and remain[i] != char) { - if (comptime char == '`') { - if (remain[i] == '$' and i + 1 < remain.len and remain[i + 1] == '{') { - const curly_start = i; - i += 2; - - while (i < remain.len and remain[i] != '}') { - if (remain[i] == '\\') { - i += 1; - } - i += 1; - } - - try writer.print(Output.prettyFmt("{s}", true), .{remain[0..curly_start]}); - try writer.writeAll("${"); - const curly_remain = QuickAndDirtyJavaScriptSyntaxHighlighter{ - .text = remain[curly_start + 2 .. i], - .enable_colors = this.enable_colors, - .limited = false, - }; - - if (curly_remain.text.len > 0) { - try curly_remain.format("", .{}, writer); - } - - if (i < remain.len and remain[i] == '}') { - i += 1; - } - try writer.writeAll("}"); - remain = remain[i..]; - i = 0; - if (remain.len > 0 and remain[0] == char) { - try writer.writeAll(Output.prettyFmt("`", true)); - remain = remain[1..]; - continue :outer; - } - continue; - } - } - - if (i + 1 < remain.len and remain[i] == '\\') { - i += 1; - } - - i += 1; - } - - // Include the trailing quote, if any - i += @as(usize, @intFromBool(i > 1 and i < remain.len and remain[i] == char)); - - try writer.print(Output.prettyFmt("{s}", true), .{remain[0..i]}); - remain = remain[i..]; - }, - '/' => { - prev_keyword = null; - var i: usize = 1; - - // the start of a line comment - if (i < remain.len and remain[i] == '/') { - while (i < remain.len and remain[i] != '\n') { - i += 1; - } - - const remain_to_print = remain[0..i]; - if (i < remain.len and remain[i] == '\n') { - i += 1; - } - - if (i < remain.len and remain[i] == '\r') { - i += 1; - } - - try writer.print(Output.prettyFmt("{s}", true), .{remain_to_print}); - remain = remain[i..]; - continue; - } - - as_multiline_comment: { - if (i < remain.len and remain[i] == '*') { - i += 1; - - while (i + 2 < remain.len and !strings.eqlComptime(remain[i..][0..2], "*/")) { - i += 1; - } - - if (i + 2 < remain.len and strings.eqlComptime(remain[i..][0..2], "*/")) { - i += 2; - } else { - i = 1; - break :as_multiline_comment; - } - - try writer.print(Output.prettyFmt("{s}", true), .{remain[0..i]}); - remain = remain[i..]; - continue; - } - } - - try writer.writeAll(remain[0..i]); - remain = remain[i..]; - }, - '}', '{' => { - // support potentially highlighting "from" in an import statement - if ((prev_keyword orelse Keyword.@"continue") != .import) { - prev_keyword = null; - } - - try writer.writeAll(remain[0..1]); - remain = remain[1..]; - }, - '[', ']' => { - prev_keyword = null; - try writer.writeAll(remain[0..1]); - remain = remain[1..]; - }, - ';' => { - prev_keyword = null; - try writer.print(Output.prettyFmt(";", true), .{}); - remain = remain[1..]; - }, - '.' => { - prev_keyword = null; - var i: usize = 1; - if (remain.len > 1 and (js_lexer.isIdentifierStart(remain[1]) or remain[1] == '#')) { - i = 2; - - while (i < remain.len and js_lexer.isIdentifierContinue(remain[i])) { - i += 1; - } - - if (i < remain.len and (remain[i] == '(')) { - try writer.print(Output.prettyFmt("{s}", true), .{remain[0..i]}); - remain = remain[i..]; - continue; - } - i = 1; - } - - try writer.writeAll(remain[0..1]); - remain = remain[1..]; - }, - - '<' => { - var i: usize = 1; - - // JSX - jsx: { - if (remain.len > 1 and remain[0] == '/') { - i = 2; - } - prev_keyword = null; - - while (i < remain.len and js_lexer.isIdentifierContinue(remain[i])) { - i += 1; - } else { - i = 1; - break :jsx; - } - - while (i < remain.len and remain[i] != '>') { - i += 1; - - if (i < remain.len and remain[i] == '<') { - i = 1; - break :jsx; - } - } - - if (i < remain.len and remain[i] == '>') { - i += 1; - try writer.print(Output.prettyFmt("{s}", true), .{remain[0..i]}); - remain = remain[i..]; - continue; - } - - i = 1; - } - - try writer.print(Output.prettyFmt("{s}", true), .{remain[0..i]}); - remain = remain[i..]; - }, - - else => { - try writer.writeAll(remain[0..1]); - remain = remain[1..]; - }, - } - } - } - } - }; - - pub fn quote(self: string) strings.QuotedFormatter { - return strings.QuotedFormatter{ - .text = self, - }; - } - - pub fn EnumTagListFormatter(comptime Enum: type, comptime Separator: @Type(.EnumLiteral)) type { - return struct { - pretty: bool = true, - const output = brk: { - var text: []const u8 = ""; - const names = std.meta.fieldNames(Enum); - for (names, 0..) |name, i| { - if (Separator == .list) { - if (i > 0) { - if (i + 1 == names.len) { - text = text ++ ", or "; - } else { - text = text ++ ", "; - } - } - - text = text ++ "\"" ++ name ++ "\""; - } else if (Separator == .dash) { - text = text ++ "\n- " ++ name; - } else { - @compileError("Unknown separator type: must be .dash or .list"); - } - } - break :brk text; - }; - pub fn format(_: @This(), comptime _: []const u8, _: fmt.FormatOptions, writer: anytype) !void { - try writer.writeAll(output); - } - }; - } - - pub fn enumTagList(comptime Enum: type, comptime separator: @Type(.EnumLiteral)) EnumTagListFormatter(Enum, separator) { - return EnumTagListFormatter(Enum, separator){}; - } - - pub fn formatIp(address: std.net.Address, into: []u8) ![]u8 { - // std.net.Address.format includes `:` and square brackets (IPv6) - // while Node does neither. This uses format then strips these to bring - // the result into conformance with Node. - var result = try std.fmt.bufPrint(into, "{}", .{address}); - - // Strip `:` - if (std.mem.lastIndexOfScalar(u8, result, ':')) |colon| { - result = result[0..colon]; - } - // Strip brackets - if (result[0] == '[' and result[result.len - 1] == ']') { - result = result[1 .. result.len - 1]; - } - return result; - } - - // https://lemire.me/blog/2021/06/03/computing-the-number-of-digits-of-an-integer-even-faster/ - pub fn fastDigitCount(x: u64) u64 { - if (x == 0) { - return 1; - } - - const table = [_]u64{ - 4294967296, - 8589934582, - 8589934582, - 8589934582, - 12884901788, - 12884901788, - 12884901788, - 17179868184, - 17179868184, - 17179868184, - 21474826480, - 21474826480, - 21474826480, - 21474826480, - 25769703776, - 25769703776, - 25769703776, - 30063771072, - 30063771072, - 30063771072, - 34349738368, - 34349738368, - 34349738368, - 34349738368, - 38554705664, - 38554705664, - 38554705664, - 41949672960, - 41949672960, - 41949672960, - 42949672960, - 42949672960, - }; - return x + table[std.math.log2(x)] >> 32; - } - - pub const SizeFormatter = struct { - value: usize = 0, - pub fn format(self: SizeFormatter, comptime _: []const u8, opts: fmt.FormatOptions, writer: anytype) !void { - const math = std.math; - const value = self.value; - if (value == 0) { - return writer.writeAll("0 KB"); - } - - if (value < 512) { - try fmt.formatInt(self.value, 10, .lower, opts, writer); - return writer.writeAll(" bytes"); - } - - const mags_si = " KMGTPEZY"; - const log2 = math.log2(value); - const magnitude = @min(log2 / comptime math.log2(1000), mags_si.len - 1); - const new_value = math.lossyCast(f64, value) / math.pow(f64, 1000, math.lossyCast(f64, magnitude)); - const suffix = mags_si[magnitude]; - - if (suffix == ' ') { - try fmt.formatFloatDecimal(new_value / 1000.0, .{ .precision = 2 }, writer); - return writer.writeAll(" KB"); - } else { - try fmt.formatFloatDecimal(new_value, .{ .precision = if (std.math.approxEqAbs(f64, new_value, @trunc(new_value), 0.100)) @as(usize, 1) else @as(usize, 2) }, writer); - } - return writer.writeAll(&[_]u8{ ' ', suffix, 'B' }); - } - }; - - pub fn size(value: anytype) SizeFormatter { - return switch (@TypeOf(value)) { - f64, f32, f128 => SizeFormatter{ - .value = @as(u64, @intFromFloat(value)), - }, - else => SizeFormatter{ .value = @as(u64, @intCast(value)) }, - }; - } - - const lower_hex_table = [_]u8{ - '0', - '1', - '2', - '3', - '4', - '5', - '6', - '7', - '8', - '9', - 'a', - 'b', - 'c', - 'd', - 'e', - 'f', - }; - const upper_hex_table = [_]u8{ - '0', - '1', - '2', - '3', - '4', - '5', - '6', - '7', - '8', - '9', - 'A', - 'B', - 'C', - 'D', - 'E', - 'F', - }; - pub fn HexIntFormatter(comptime Int: type, comptime lower: bool) type { - return struct { - value: Int, - - const table = if (lower) lower_hex_table else upper_hex_table; - - const BufType = [@bitSizeOf(Int) / 4]u8; - - fn getOutBuf(value: Int) BufType { - var buf: BufType = undefined; - comptime var i: usize = 0; - inline while (i < buf.len) : (i += 1) { - // value relative to the current nibble - buf[i] = table[@as(u8, @as(u4, @truncate(value >> comptime ((buf.len - i - 1) * 4)))) & 0xF]; - } - - return buf; - } - - pub fn format(self: @This(), comptime _: []const u8, _: fmt.FormatOptions, writer: anytype) !void { - const value = self.value; - try writer.writeAll(&getOutBuf(value)); - } - }; - } - - pub fn HexInt(comptime Int: type, comptime lower: std.fmt.Case, value: Int) HexIntFormatter(Int, lower == .lower) { - const Formatter = HexIntFormatter(Int, lower == .lower); - return Formatter{ .value = value }; - } - - pub fn hexIntLower(value: anytype) HexIntFormatter(@TypeOf(value), true) { - const Formatter = HexIntFormatter(@TypeOf(value), true); - return Formatter{ .value = value }; - } - - pub fn hexIntUpper(value: anytype) HexIntFormatter(@TypeOf(value), false) { - const Formatter = HexIntFormatter(@TypeOf(value), false); - return Formatter{ .value = value }; - } - - const FormatDurationData = struct { - ns: u64, - negative: bool = false, - }; - - /// This is copied from std.fmt.formatDuration, except it will only print one decimal instead of three - fn formatDurationOneDecimal(data: FormatDurationData, comptime _: []const u8, opts: std.fmt.FormatOptions, writer: anytype) !void { - // worst case: "-XXXyXXwXXdXXhXXmXX.XXXs".len = 24 - var buf: [24]u8 = undefined; - var fbs = std.io.fixedBufferStream(&buf); - var buf_writer = fbs.writer(); - if (data.negative) { - buf_writer.writeByte('-') catch unreachable; - } - - var ns_remaining = data.ns; - inline for (.{ - .{ .ns = 365 * std.time.ns_per_day, .sep = 'y' }, - .{ .ns = std.time.ns_per_week, .sep = 'w' }, - .{ .ns = std.time.ns_per_day, .sep = 'd' }, - .{ .ns = std.time.ns_per_hour, .sep = 'h' }, - .{ .ns = std.time.ns_per_min, .sep = 'm' }, - }) |unit| { - if (ns_remaining >= unit.ns) { - const units = ns_remaining / unit.ns; - std.fmt.formatInt(units, 10, .lower, .{}, buf_writer) catch unreachable; - buf_writer.writeByte(unit.sep) catch unreachable; - ns_remaining -= units * unit.ns; - if (ns_remaining == 0) - return std.fmt.formatBuf(fbs.getWritten(), opts, writer); - } - } - - inline for (.{ - .{ .ns = std.time.ns_per_s, .sep = "s" }, - .{ .ns = std.time.ns_per_ms, .sep = "ms" }, - .{ .ns = std.time.ns_per_us, .sep = "us" }, - }) |unit| { - const kunits = ns_remaining * 1000 / unit.ns; - if (kunits >= 1000) { - std.fmt.formatInt(kunits / 1000, 10, .lower, .{}, buf_writer) catch unreachable; - const frac = @divFloor(kunits % 1000, 100); - if (frac > 0) { - var decimal_buf = [_]u8{ '.', 0 }; - _ = std.fmt.formatIntBuf(decimal_buf[1..], frac, 10, .lower, .{ .fill = '0', .width = 1 }); - buf_writer.writeAll(&decimal_buf) catch unreachable; - } - buf_writer.writeAll(unit.sep) catch unreachable; - return std.fmt.formatBuf(fbs.getWritten(), opts, writer); - } - } - - std.fmt.formatInt(ns_remaining, 10, .lower, .{}, buf_writer) catch unreachable; - buf_writer.writeAll("ns") catch unreachable; - return std.fmt.formatBuf(fbs.getWritten(), opts, writer); - } - - /// Return a Formatter for number of nanoseconds according to its magnitude: - /// [#y][#w][#d][#h][#m]#[.###][n|u|m]s - pub fn fmtDurationOneDecimal(ns: u64) std.fmt.Formatter(formatDurationOneDecimal) { - return .{ .data = FormatDurationData{ .ns = ns } }; - } -}; +pub const fmt = @import("./fmt.zig"); pub const Output = @import("./output.zig"); pub const Global = @import("./__global.zig"); @@ -915,10 +125,11 @@ pub const RefCount = @import("./ref_count.zig").RefCount; pub const MAX_PATH_BYTES: usize = if (Environment.isWasm) 1024 else std.fs.MAX_PATH_BYTES; pub const PathBuffer = [MAX_PATH_BYTES]u8; -pub const OSPathSlice = if (Environment.isWindows) [:0]const u16 else [:0]const u8; -pub const OSPathSliceWithoutSentinel = if (Environment.isWindows) []const u16 else []const u8; -pub const OSPathBuffer = if (Environment.isWindows) WPathBuffer else PathBuffer; pub const WPathBuffer = [MAX_PATH_BYTES / 2]u16; +pub const OSPathChar = if (Environment.isWindows) u16 else u8; +pub const OSPathSliceZ = [:0]const OSPathChar; +pub const OSPathSlice = []const OSPathChar; +pub const OSPathBuffer = if (Environment.isWindows) WPathBuffer else PathBuffer; pub inline fn cast(comptime To: type, value: anytype) To { if (@typeInfo(@TypeOf(value)) == .Int) { @@ -1942,6 +1153,21 @@ pub fn getFdPath(fd_: anytype, buf: *[@This().MAX_PATH_BYTES]u8) ![]u8 { }; } +pub fn getFdPathW(fd_: anytype, buf: *WPathBuffer) ![]u16 { + const fd = fdcast(toFD(fd_)); + + if (comptime Environment.isWindows) { + var temp: [MAX_PATH_BYTES]u8 = undefined; + var temp2: [MAX_PATH_BYTES]u8 = undefined; + const temp_slice = try std.os.getFdPath(fd, &temp); + const slice = path.normalizeBuf(temp_slice, &temp2, .loose); + strings.copyU8IntoU16(buf, slice); + return buf[0..slice.len]; + } + + @panic("TODO unsupported platform for getFdPathW"); +} + fn lenSliceTo(ptr: anytype, comptime end: meta.Elem(@TypeOf(ptr))) usize { switch (@typeInfo(@TypeOf(ptr))) { .Pointer => |ptr_info| switch (ptr_info.size) { @@ -2573,7 +1799,7 @@ pub inline fn uvfdcast(fd: anytype) FDImpl.UV { } return decoded.uv(); } else { - return @intCast(fd); + return fd.cast(); } } @@ -2831,7 +2057,7 @@ pub const Async = @import("async"); /// This is a helper for writing path string literals that are compatible with Windows. /// Returns the string as-is on linux, on windows replace `/` with `\` pub inline fn pathLiteral(comptime literal: anytype) *const [literal.len:0]u8 { - if (!Environment.isWindows) return literal; + if (!Environment.isWindows) return @ptrCast(literal); return comptime { var buf: [literal.len:0]u8 = undefined; for (literal, 0..) |c, i| { @@ -2842,6 +2068,174 @@ pub inline fn pathLiteral(comptime literal: anytype) *const [literal.len:0]u8 { }; } +/// Same as `pathLiteral`, but the character type is chosen from platform. +pub inline fn OSPathLiteral(comptime literal: anytype) *const [literal.len:0]OSPathChar { + if (!Environment.isWindows) return @ptrCast(literal); + return comptime { + var buf: [literal.len:0]OSPathChar = undefined; + for (literal, 0..) |c, i| { + buf[i] = if (c == '/') '\\' else c; + } + buf[buf.len] = 0; + return &buf; + }; +} + +const builtin = @import("builtin"); + +pub const MakePath = struct { + /// copy/paste of `std.fs.Dir.makePath` and related functions and modified to support u16 slices. + /// inside `MakePath` scope to make deleting later easier. + /// TODO(dylan-conway) delete `MakePath` + pub fn makePath(comptime T: type, self: std.fs.Dir, sub_path: []const T) !void { + var it = try componentIterator(T, sub_path); + var component = it.last() orelse return; + while (true) { + (if (T == u16) makeDirW else std.fs.Dir.makeDir)(self, component.path) catch |err| switch (err) { + error.PathAlreadyExists => { + // TODO stat the file and return an error if it's not a directory + // this is important because otherwise a dangling symlink + // could cause an infinite loop + }, + error.FileNotFound => |e| { + component = it.previous() orelse return e; + continue; + }, + else => |e| return e, + }; + component = it.next() orelse return; + } + } + + fn makeDirW(self: std.fs.Dir, sub_path: []const u16) !void { + try std.os.mkdiratW(self.fd, sub_path, 0o755); + } + + fn componentIterator(comptime T: type, path_: []const T) !std.fs.path.ComponentIterator(switch (builtin.target.os.tag) { + .windows => .windows, + .uefi => .uefi, + else => .posix, + }, T) { + return std.fs.path.ComponentIterator(switch (builtin.target.os.tag) { + .windows => .windows, + .uefi => .uefi, + else => .posix, + }, T).init(path_); + } +}; + +pub const Dirname = struct { + /// copy/paste of `std.fs.path.dirname` and related functions and modified to support u16 slices. + /// inside `Dirname` scope to make deleting later easier. + /// TODO(dylan-conway) delete `Dirname` + pub fn dirname(comptime T: type, path_: []const T) ?[]const T { + if (builtin.target.os.tag == .windows) { + return dirnameWindows(T, path_); + } else { + return std.fs.path.dirnamePosix(path_); + } + } + + fn dirnameWindows(comptime T: type, path_: []const T) ?[]const T { + if (path_.len == 0) + return null; + + const root_slice = diskDesignatorWindows(T, path_); + if (path_.len == root_slice.len) + return null; + + const have_root_slash = path_.len > root_slice.len and (path_[root_slice.len] == '/' or path_[root_slice.len] == '\\'); + + var end_index: usize = path_.len - 1; + + while (path_[end_index] == '/' or path_[end_index] == '\\') { + if (end_index == 0) + return null; + end_index -= 1; + } + + while (path_[end_index] != '/' and path_[end_index] != '\\') { + if (end_index == 0) + return null; + end_index -= 1; + } + + if (have_root_slash and end_index == root_slice.len) { + end_index += 1; + } + + if (end_index == 0) + return null; + + return path_[0..end_index]; + } + + fn diskDesignatorWindows(comptime T: type, path_: []const T) []const T { + return windowsParsePath(T, path_).disk_designator; + } + + fn windowsParsePath(comptime T: type, path_: []const T) WindowsPath(T) { + const WindowsPath_ = WindowsPath(T); + if (path_.len >= 2 and path_[1] == ':') { + return WindowsPath_{ + .is_abs = if (comptime T == u16) std.fs.path.isAbsoluteWindowsWTF16(path_) else std.fs.path.isAbsolute(path_), + .kind = WindowsPath_.Kind.Drive, + .disk_designator = path_[0..2], + }; + } + if (path_.len >= 1 and (path_[0] == '/' or path_[0] == '\\') and + (path_.len == 1 or (path_[1] != '/' and path_[1] != '\\'))) + { + return WindowsPath_{ + .is_abs = true, + .kind = WindowsPath_.Kind.None, + .disk_designator = path_[0..0], + }; + } + const relative_path = WindowsPath_{ + .kind = WindowsPath_.Kind.None, + .disk_designator = &[_]T{}, + .is_abs = false, + }; + if (path_.len < "//a/b".len) { + return relative_path; + } + + inline for ("/\\") |this_sep| { + const two_sep = [_]T{ this_sep, this_sep }; + if (std.mem.startsWith(T, path_, &two_sep)) { + if (path_[2] == this_sep) { + return relative_path; + } + + var it = std.mem.tokenizeScalar(T, path_, this_sep); + _ = (it.next() orelse return relative_path); + _ = (it.next() orelse return relative_path); + return WindowsPath_{ + .is_abs = if (T == u16) std.fs.path.isAbsoluteWindowsWTF16(path_) else std.fs.path.isAbsolute(path_), + .kind = WindowsPath_.Kind.NetworkShare, + .disk_designator = path_[0..it.index], + }; + } + } + return relative_path; + } + + fn WindowsPath(comptime T: type) type { + return struct { + is_abs: bool, + kind: Kind, + disk_designator: []const T, + + pub const Kind = enum { + None, + Drive, + NetworkShare, + }; + }; + } +}; + pub noinline fn outOfMemory() noreturn { @setCold(true); @@ -3081,7 +2475,7 @@ pub fn errnoToZigErr(err: anytype) anyerror { return error.Unexpected; } -pub const S = if (Environment.isWindows) windows.libuv.S else std.os.S; +pub const S = if (Environment.isWindows) C.S else std.os.S; /// Deprecated! pub const trait = @import("./trait.zig"); diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 0bd819d4d3..7985308da4 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -4711,7 +4711,7 @@ const LinkerContext = struct { var count: usize = 0; if (is_entry_point and this.options.output_format == .esm) { for (aliases) |alias| { - count += std.fmt.count("export_{}", .{strings.fmtIdentifier(alias)}); + count += std.fmt.count("export_{}", .{bun.fmt.fmtIdentifier(alias)}); } } @@ -4748,7 +4748,7 @@ const LinkerContext = struct { const copies = this.allocator.alloc(Ref, aliases.len) catch unreachable; for (aliases, copies) |alias, *copy| { - const original_name = builder.fmt("export_{}", .{strings.fmtIdentifier(alias)}); + const original_name = builder.fmt("export_{}", .{bun.fmt.fmtIdentifier(alias)}); copy.* = this.graph.generateNewSymbol(source_index, .other, original_name); } this.graph.meta.items(.cjs_export_copies)[id] = copies; diff --git a/src/c.zig b/src/c.zig index 6d9fe3a60c..e6c857e3eb 100644 --- a/src/c.zig +++ b/src/c.zig @@ -18,7 +18,7 @@ const Stat = std.fs.File.Stat; const Kind = std.fs.File.Kind; const StatError = std.fs.File.StatError; const errno = os.errno; -const mode_t = C.mode_t; +const mode_t = bun.Mode; // TODO: this is wrong on Windows const libc_stat = bun.Stat; @@ -160,6 +160,10 @@ pub fn moveFileZSlow(from_dir: std.os.fd_t, filename: [:0]const u8, to_dir: std. } pub fn copyFileZSlowWithHandle(in_handle: bun.FileDescriptor, to_dir: bun.FileDescriptor, destination: [:0]const u8) !void { + if (comptime Environment.isWindows) { + @panic("TODO windows"); + } + const stat_ = if (comptime Environment.isPosix) try std.os.fstat(in_handle.cast()) else void{}; // Attempt to delete incase it already existed. @@ -186,18 +190,15 @@ pub fn copyFileZSlowWithHandle(in_handle: bun.FileDescriptor, to_dir: bun.FileDe } } -pub fn kindFromMode(mode: os.mode_t) std.fs.File.Kind { - if (comptime Environment.isWindows) { - @panic("TODO on Windows"); - } - return switch (mode & os.S.IFMT) { - os.S.IFBLK => std.fs.File.Kind.block_device, - os.S.IFCHR => std.fs.File.Kind.character_device, - os.S.IFDIR => std.fs.File.Kind.directory, - os.S.IFIFO => std.fs.File.Kind.named_pipe, - os.S.IFLNK => std.fs.File.Kind.sym_link, - os.S.IFREG => std.fs.File.Kind.file, - os.S.IFSOCK => std.fs.File.Kind.unix_domain_socket, +pub fn kindFromMode(mode: mode_t) std.fs.File.Kind { + return switch (mode & bun.S.IFMT) { + bun.S.IFBLK => std.fs.File.Kind.block_device, + bun.S.IFCHR => std.fs.File.Kind.character_device, + bun.S.IFDIR => std.fs.File.Kind.directory, + bun.S.IFIFO => std.fs.File.Kind.named_pipe, + bun.S.IFLNK => std.fs.File.Kind.sym_link, + bun.S.IFREG => std.fs.File.Kind.file, + bun.S.IFSOCK => std.fs.File.Kind.unix_domain_socket, else => .unknown, }; } diff --git a/src/cli.zig b/src/cli.zig index 6c7c1d1f46..0fba3078d6 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -458,7 +458,7 @@ pub const Arguments = struct { Output.prettyErrorln( "error: --test-name-pattern expects a valid regular expression but received {}", .{ - strings.QuotedFormatter{ + bun.fmt.QuotedFormatter{ .text = namePattern, }, }, @@ -1768,9 +1768,13 @@ pub const Command = struct { var file_path = script_name_to_search; const file_: anyerror!std.fs.File = brk: { if (std.fs.path.isAbsoluteWindows(script_name_to_search)) { - var winResolver = resolve_path.PosixToWinNormalizer{}; + var win_resolver = resolve_path.PosixToWinNormalizer{}; + var resolved = win_resolver.resolveCWD(script_name_to_search) catch @panic("Could not resolve path"); + if (comptime Environment.isWindows) { + resolved = resolve_path.normalizeString(resolved, true, .windows); + } break :brk bun.openFile( - winResolver.resolveCWD(script_name_to_search) catch @panic("Could not resolve path"), + resolved, .{ .mode = .read_only }, ); } else if (!strings.hasPrefix(script_name_to_search, "..") and script_name_to_search[0] != '~') { diff --git a/src/cli/bunx_command.zig b/src/cli/bunx_command.zig index c6a61fb487..03ac4c3db8 100644 --- a/src/cli/bunx_command.zig +++ b/src/cli/bunx_command.zig @@ -97,7 +97,7 @@ pub const BunxCommand = struct { const bin_dir = try std.os.openat(dir_fd, dir_name, std.os.O.RDONLY, 0); defer std.os.close(bin_dir); const dir = std.fs.Dir{ .fd = bin_dir }; - var iterator = @import("../bun.js/node/dir_iterator.zig").iterate(dir); + var iterator = bun.DirIterator.iterate(dir, .u8); var entry = iterator.next(); while (true) : (entry = iterator.next()) { const current = switch (entry) { diff --git a/src/cli/create_command.zig b/src/cli/create_command.zig index a88df85ba0..9af7a9dbe0 100644 --- a/src/cli/create_command.zig +++ b/src/cli/create_command.zig @@ -54,11 +54,14 @@ pub fn initializeStore() void { js_ast.Stmt.Data.Store.create(default_allocator); } -const skip_dirs = &[_]string{ "node_modules", ".git" }; -const skip_files = &[_]string{ - "package-lock.json", - "yarn.lock", - "pnpm-lock.yaml", +const skip_dirs = &[_]bun.OSPathSlice{ + bun.OSPathLiteral("node_modules"), + bun.OSPathLiteral(".git"), +}; +const skip_files = &[_]bun.OSPathSlice{ + bun.OSPathLiteral("package-lock.json"), + bun.OSPathLiteral("yarn.lock"), + bun.OSPathLiteral("pnpm-lock.yaml"), }; const never_conflict = &[_]string{ @@ -480,35 +483,37 @@ pub const CreateCommand = struct { while (try walker.next()) |entry| { if (entry.kind != .file) continue; - var outfile = destination_dir_.createFile(entry.path, .{}) catch brk: { - if (std.fs.path.dirname(entry.path)) |entry_dirname| { - destination_dir_.makePath(entry_dirname) catch {}; + const createFile = if (comptime Environment.isWindows) std.fs.Dir.createFileW else std.fs.Dir.createFile; + var outfile = createFile(destination_dir_, entry.path, .{}) catch brk: { + if (bun.Dirname.dirname(bun.OSPathChar, entry.path)) |entry_dirname| { + bun.MakePath.makePath(bun.OSPathChar, destination_dir_, entry_dirname) catch {}; } - break :brk destination_dir_.createFile(entry.path, .{}) catch |err| { + break :brk createFile(destination_dir_, entry.path, .{}) catch |err| { node_.end(); progress_.refresh(); - Output.prettyErrorln("{s}: copying file {s}", .{ @errorName(err), entry.path }); + Output.prettyError("{s}: copying file {}", .{ @errorName(err), bun.fmt.fmtOSPath(entry.path) }); Global.exit(1); }; }; defer outfile.close(); defer node_.completeOne(); - var infile = try entry.dir.openFile(entry.basename, .{ .mode = .read_only }); + const openFile = if (comptime Environment.isWindows) std.fs.Dir.openFileW else std.fs.Dir.openFile; + var infile = try openFile(entry.dir, entry.basename, .{ .mode = .read_only }); defer infile.close(); if (comptime Environment.isPosix) { // Assumption: you only really care about making sure something that was executable is still executable const stat = infile.stat() catch continue; - _ = C.fchmod(outfile.handle, stat.mode); + _ = C.fchmod(outfile.handle, @intCast(stat.mode)); } else { @panic("TODO on Windows"); } CopyFile.copyFile(infile.handle, outfile.handle) catch |err| { - Output.prettyErrorln("{s}: copying file {s}", .{ @errorName(err), entry.path }); + Output.prettyError("{s}: copying file {}", .{ @errorName(err), bun.fmt.fmtOSPath(entry.path) }); Global.exit(1); }; } @@ -1755,7 +1760,7 @@ pub const Example = struct { switch (entry.kind) { .directory => { inline for (skip_dirs) |skip_dir| { - if (strings.eqlComptime(entry.name, skip_dir)) { + if (strings.eqlComptime(entry.name, comptime bun.pathLiteral(skip_dir))) { continue :loop; } } diff --git a/src/cli/run_command.zig b/src/cli/run_command.zig index 2b8d045bf1..21c1f2706d 100644 --- a/src/cli/run_command.zig +++ b/src/cli/run_command.zig @@ -315,6 +315,7 @@ pub const RunCommand = struct { } var child_process = std.ChildProcess.init(&argv, allocator); + var buf_map = try env.map.cloneToEnvMap(allocator); child_process.env_map = &buf_map; @@ -581,7 +582,7 @@ pub const RunCommand = struct { } else { ctx.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), false) catch {}; } - Output.prettyErrorln("error: {s} loading directory {}", .{ @errorName(err), strings.QuotedFormatter{ .text = this_bundler.fs.top_level_dir } }); + Output.prettyErrorln("error: {s} loading directory {}", .{ @errorName(err), bun.fmt.QuotedFormatter{ .text = this_bundler.fs.top_level_dir } }); Output.flush(); return err; } orelse { diff --git a/src/copy_file.zig b/src/copy_file.zig index 933b51a699..4d9c9ba061 100644 --- a/src/copy_file.zig +++ b/src/copy_file.zig @@ -2,12 +2,14 @@ const std = @import("std"); const os = std.os; const math = std.math; const bun = @import("root").bun; +const strings = bun.strings; +const Environment = bun.Environment; pub const CopyFileRangeError = error{ FileTooBig, InputOutput, - /// `fd_in` is not open for reading; or `fd_out` is not open for writing; - /// or the `O.APPEND` flag is set for `fd_out`. + /// `in` is not open for reading; or `out` is not open for writing; + /// or the `O.APPEND` flag is set for `out`. FilesOpenedWithWrongFlags, IsDir, OutOfMemory, @@ -22,9 +24,11 @@ const CopyFileError = error{SystemResources} || CopyFileRangeError || os.SendFil // Transfer all the data between two file descriptors in the most efficient way. // The copy starts at offset 0, the initial offsets are preserved. // No metadata is transferred over. -pub fn copyFile(fd_in: os.fd_t, fd_out: os.fd_t) CopyFileError!void { - if (comptime bun.Environment.isMac) { - const rc = os.system.fcopyfile(fd_in, fd_out, null, os.system.COPYFILE_DATA); + +const InputType = if (Environment.isWindows) bun.OSPathSliceZ else os.fd_t; +pub fn copyFile(in: InputType, out: InputType) CopyFileError!void { + if (comptime Environment.isMac) { + const rc = os.system.fcopyfile(in, out, null, os.system.COPYFILE_DATA); switch (os.errno(rc)) { .SUCCESS => return, .NOMEM => return error.SystemResources, @@ -35,11 +39,11 @@ pub fn copyFile(fd_in: os.fd_t, fd_out: os.fd_t) CopyFileError!void { } } - if (comptime bun.Environment.isLinux) { + if (comptime Environment.isLinux) { if (can_use_ioctl_ficlone()) { // We only check once if the ioctl is supported, and cache the result. // EXT4 does not support FICLONE. - const rc = bun.C.linux.ioctl_ficlone(bun.toFD(fd_out), bun.toFD(fd_in)); + const rc = bun.C.linux.ioctl_ficlone(bun.toFD(out), bun.toFD(in)); switch (std.os.linux.getErrno(rc)) { .SUCCESS => return, .FBIG => return error.FileTooBig, @@ -66,7 +70,7 @@ pub fn copyFile(fd_in: os.fd_t, fd_out: os.fd_t) CopyFileError!void { // The kernel checks the u64 value `offset+count` for overflow, use // a 32 bit value so that the syscall won't return EINVAL except for // impossibly large files (> 2^64-1 - 2^32-1). - const amt = try copyFileRange(fd_in, offset, fd_out, offset, math.maxInt(u32), 0); + const amt = try copyFileRange(in, offset, out, offset, math.maxInt(u32), 0); // Terminate when no data was copied if (amt == 0) break :cfr_loop; offset += amt; @@ -74,8 +78,23 @@ pub fn copyFile(fd_in: os.fd_t, fd_out: os.fd_t) CopyFileError!void { return; } - if (comptime bun.Environment.isWindows) { - @panic("TODO on Windows"); + if (comptime Environment.isWindows) { + if (bun.windows.CopyFileW(in.ptr, out.ptr, 0) == bun.windows.FALSE) { + switch (@as(bun.C.E, @enumFromInt(@intFromEnum(bun.windows.GetLastError())))) { + .SUCCESS => return, + .FBIG => return error.FileTooBig, + .IO => return error.InputOutput, + .ISDIR => return error.IsDir, + .NOMEM => return error.OutOfMemory, + .NOSPC => return error.NoSpaceLeft, + .OVERFLOW => return error.Unseekable, + .PERM => return error.PermissionDenied, + .TXTBSY => return error.FileBusy, + else => return error.Unexpected, + } + } + + return; } // Sendfile is a zero-copy mechanism iff the OS supports it, otherwise the @@ -83,7 +102,7 @@ pub fn copyFile(fd_in: os.fd_t, fd_out: os.fd_t) CopyFileError!void { const empty_iovec = [0]os.iovec_const{}; var offset: u64 = 0; sendfile_loop: while (true) { - const amt = try os.sendfile(fd_out, fd_in, offset, 0, &empty_iovec, &empty_iovec, 0); + const amt = try os.sendfile(out, in, offset, 0, &empty_iovec, &empty_iovec, 0); // Terminate when no data was copied if (amt == 0) break :sendfile_loop; offset += amt; @@ -94,7 +113,7 @@ const Platform = @import("root").bun.analytics.GenerateHeader.GeneratePlatform; var can_use_copy_file_range = std.atomic.Value(i32).init(0); pub inline fn disableCopyFileRangeSyscall() void { - if (comptime !bun.Environment.isLinux) { + if (comptime !Environment.isLinux) { return; } can_use_copy_file_range.store(-1, .Monotonic); @@ -126,7 +145,7 @@ pub fn canUseCopyFileRangeSyscall() bool { pub var can_use_ioctl_ficlone_ = std.atomic.Value(i32).init(0); pub inline fn disable_ioctl_ficlone() void { - if (comptime !bun.Environment.isLinux) { + if (comptime !Environment.isLinux) { return; } can_use_ioctl_ficlone_.store(-1, .Monotonic); @@ -157,12 +176,12 @@ pub fn can_use_ioctl_ficlone() bool { } const fd_t = std.os.fd_t; -pub fn copyFileRange(fd_in: fd_t, off_in: u64, fd_out: fd_t, off_out: u64, len: usize, flags: u32) CopyFileRangeError!usize { +pub fn copyFileRange(in: fd_t, off_in: u64, out: fd_t, off_out: u64, len: usize, flags: u32) CopyFileRangeError!usize { if (canUseCopyFileRangeSyscall()) { var off_in_copy = @as(i64, @bitCast(off_in)); var off_out_copy = @as(i64, @bitCast(off_out)); - const rc = std.os.linux.copy_file_range(fd_in, &off_in_copy, fd_out, &off_out_copy, len, flags); + const rc = std.os.linux.copy_file_range(in, &off_in_copy, out, &off_out_copy, len, flags); switch (std.os.linux.getErrno(rc)) { .SUCCESS => return @as(usize, @intCast(rc)), .BADF => return error.FilesOpenedWithWrongFlags, @@ -189,9 +208,9 @@ pub fn copyFileRange(fd_in: fd_t, off_in: u64, fd_out: fd_t, off_out: u64, len: var buf: [8 * 4096]u8 = undefined; const adjusted_count = @min(buf.len, len); - const amt_read = try os.pread(fd_in, buf[0..adjusted_count], off_in); + const amt_read = try os.pread(in, buf[0..adjusted_count], off_in); // TODO without @as the line below fails to compile for wasm32-wasi: // error: integer value 0 cannot be coerced to type 'os.PWriteError!usize' if (amt_read == 0) return @as(usize, 0); - return os.pwrite(fd_out, buf[0..amt_read], off_out); + return os.pwrite(out, buf[0..amt_read], off_out); } diff --git a/src/deps/libuv.zig b/src/deps/libuv.zig index 6953f640cb..266c960c56 100644 --- a/src/deps/libuv.zig +++ b/src/deps/libuv.zig @@ -2387,60 +2387,4 @@ pub const ReturnCodeI64 = extern struct { } }; -pub const S = struct { - pub const IFMT = 0o170000; - - pub const IFDIR = 0o040000; - pub const IFCHR = 0o020000; - pub const IFBLK = 0o060000; - pub const IFREG = 0o100000; - pub const IFIFO = 0o010000; - pub const IFLNK = 0o120000; - pub const IFSOCK = 0o140000; - - pub const ISUID = 0o4000; - pub const ISGID = 0o2000; - pub const ISVTX = 0o1000; - pub const IRUSR = 0o400; - pub const IWUSR = 0o200; - pub const IXUSR = 0o100; - pub const IRWXU = 0o700; - pub const IRGRP = 0o040; - pub const IWGRP = 0o020; - pub const IXGRP = 0o010; - pub const IRWXG = 0o070; - pub const IROTH = 0o004; - pub const IWOTH = 0o002; - pub const IXOTH = 0o001; - pub const IRWXO = 0o007; - - pub inline fn ISREG(m: i32) bool { - return m & IFMT == IFREG; - } - - pub inline fn ISDIR(m: i32) bool { - return m & IFMT == IFDIR; - } - - pub inline fn ISCHR(m: i32) bool { - return m & IFMT == IFCHR; - } - - pub inline fn ISBLK(m: i32) bool { - return m & IFMT == IFBLK; - } - - pub inline fn ISFIFO(m: i32) bool { - return m & IFMT == IFIFO; - } - - pub inline fn ISLNK(m: i32) bool { - return m & IFMT == IFLNK; - } - - pub inline fn ISSOCK(m: i32) bool { - return m & IFMT == IFSOCK; - } -}; - pub const addrinfo = std.os.windows.ws2_32.addrinfo; diff --git a/src/deps/zlib.win32.zig b/src/deps/zlib.win32.zig index 3d6aba35cb..6560576c20 100644 --- a/src/deps/zlib.win32.zig +++ b/src/deps/zlib.win32.zig @@ -10,7 +10,7 @@ const voidpf = *anyopaque; const Bytef = [*]u8; const Byte = u8; const uInt = c_uint; -const uLong = c_ulong; +const uLong = u64; const z_size_t = usize; const intf = ReturnCode; const uIntf = uInt; diff --git a/src/fmt.zig b/src/fmt.zig new file mode 100644 index 0000000000..33f15bff8b --- /dev/null +++ b/src/fmt.zig @@ -0,0 +1,1110 @@ +const std = @import("std"); +const bun = @import("root").bun; +const Output = bun.Output; +const strings = bun.strings; +const string = bun.string; +const js_lexer = bun.js_lexer; +const ComptimeStringMap = bun.ComptimeStringMap; +const fmt = std.fmt; +const Environment = bun.Environment; + +pub usingnamespace std.fmt; + +const SharedTempBuffer = [32 * 1024]u8; +fn getSharedBuffer() []u8 { + return std.mem.asBytes(shared_temp_buffer_ptr orelse brk: { + shared_temp_buffer_ptr = bun.default_allocator.create(SharedTempBuffer) catch unreachable; + break :brk shared_temp_buffer_ptr.?; + }); +} +threadlocal var shared_temp_buffer_ptr: ?*SharedTempBuffer = null; + +pub fn formatUTF16Type(comptime Slice: type, slice_: Slice, writer: anytype) !void { + var chunk = getSharedBuffer(); + + // Defensively ensure recursion doesn't cause the buffer to be overwritten in-place + shared_temp_buffer_ptr = null; + defer { + if (shared_temp_buffer_ptr) |existing| { + if (existing != chunk.ptr) { + bun.default_allocator.destroy(@as(*SharedTempBuffer, @ptrCast(chunk.ptr))); + } + } else { + shared_temp_buffer_ptr = @ptrCast(chunk.ptr); + } + } + + var slice = slice_; + + while (slice.len > 0) { + const result = strings.copyUTF16IntoUTF8(chunk, Slice, slice, true); + if (result.read == 0 or result.written == 0) + break; + try writer.writeAll(chunk[0..result.written]); + slice = slice[result.read..]; + } +} + +pub fn formatUTF16(slice_: []align(1) const u16, writer: anytype) !void { + return formatUTF16Type([]align(1) const u16, slice_, writer); +} + +pub const FormatUTF16 = struct { + buf: []const u16, + pub fn format(self: @This(), comptime _: []const u8, opts: anytype, writer: anytype) !void { + _ = opts; + try formatUTF16Type([]const u16, self.buf, writer); + } +}; + +pub const FormatUTF8 = struct { + buf: []const u8, + pub fn format(self: @This(), comptime _: []const u8, _: anytype, writer: anytype) !void { + try writer.writeAll(self.buf); + } +}; + +pub fn fmtUTF16(buf: []const u16) FormatUTF16 { + return FormatUTF16{ .buf = buf }; +} + +pub const FormatOSPath = if (Environment.isWindows) FormatUTF16 else FormatUTF8; + +pub fn fmtOSPath(buf: bun.OSPathSlice) FormatOSPath { + return FormatOSPath{ .buf = buf }; +} + +pub fn formatLatin1(slice_: []const u8, writer: anytype) !void { + var chunk = getSharedBuffer(); + var slice = slice_; + + // Defensively ensure recursion doesn't cause the buffer to be overwritten in-place + shared_temp_buffer_ptr = null; + defer { + if (shared_temp_buffer_ptr) |existing| { + if (existing != chunk.ptr) { + bun.default_allocator.destroy(@as(*SharedTempBuffer, @ptrCast(chunk.ptr))); + } + } else { + shared_temp_buffer_ptr = @ptrCast(chunk.ptr); + } + } + + while (strings.firstNonASCII(slice)) |i| { + if (i > 0) { + try writer.writeAll(slice[0..i]); + slice = slice[i..]; + } + const result = strings.copyLatin1IntoUTF8(chunk, @TypeOf(slice), slice[0..@min(chunk.len, slice.len)]); + if (result.read == 0 or result.written == 0) + break; + try writer.writeAll(chunk[0..result.written]); + slice = slice[result.read..]; + } + + if (slice.len > 0) + try writer.writeAll(slice); // write the remaining bytes +} + +pub const URLFormatter = struct { + proto: Proto = .http, + hostname: ?string = null, + port: ?u16 = null, + + const Proto = enum { + http, + https, + unix, + }; + + pub fn format(this: URLFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + try writer.print("{s}://", .{switch (this.proto) { + .http => "http", + .https => "https", + .unix => "unix", + }}); + + if (this.hostname) |hostname| { + const needs_brackets = hostname[0] != '[' and strings.isIPV6Address(hostname); + if (needs_brackets) { + try writer.print("[{s}]", .{hostname}); + } else { + try writer.writeAll(hostname); + } + } else { + try writer.writeAll("localhost"); + } + + if (this.proto == .unix) { + return; + } + + const is_port_optional = this.port == null or (this.proto == .https and this.port == 443) or + (this.proto == .http and this.port == 80); + if (is_port_optional) { + try writer.writeAll("/"); + } else { + try writer.print(":{d}/", .{this.port.?}); + } + } +}; + +pub const HostFormatter = struct { + host: string, + port: ?u16 = null, + is_https: bool = false, + + pub fn format(formatter: HostFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + if (strings.indexOfChar(formatter.host, ':') != null) { + try writer.writeAll(formatter.host); + return; + } + + try writer.writeAll(formatter.host); + + const is_port_optional = formatter.port == null or (formatter.is_https and formatter.port == 443) or + (!formatter.is_https and formatter.port == 80); + if (!is_port_optional) { + try writer.print(":{d}", .{formatter.port.?}); + return; + } + } +}; + +/// Format a string to an ECMAScript identifier. +/// Unlike the string_mutable.zig version, this always allocate/copy +pub fn fmtIdentifier(name: string) FormatValidIdentifier { + return FormatValidIdentifier{ .name = name }; +} + +/// Format a string to an ECMAScript identifier. +/// Different implementation than string_mutable because string_mutable may avoid allocating +/// This will always allocate +pub const FormatValidIdentifier = struct { + name: string, + pub fn format(self: FormatValidIdentifier, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + var iterator = strings.CodepointIterator.init(self.name); + var cursor = strings.CodepointIterator.Cursor{}; + + var has_needed_gap = false; + var needs_gap = false; + var start_i: usize = 0; + + if (!iterator.next(&cursor)) { + try writer.writeAll("_"); + return; + } + + // Common case: no gap necessary. No allocation necessary. + needs_gap = !js_lexer.isIdentifierStart(cursor.c); + if (!needs_gap) { + // Are there any non-alphanumeric chars at all? + while (iterator.next(&cursor)) { + if (!js_lexer.isIdentifierContinue(cursor.c) or cursor.width > 1) { + needs_gap = true; + start_i = cursor.i; + break; + } + } + } + + if (needs_gap) { + needs_gap = false; + if (start_i > 0) try writer.writeAll(self.name[0..start_i]); + var slice = self.name[start_i..]; + iterator = strings.CodepointIterator.init(slice); + cursor = strings.CodepointIterator.Cursor{}; + + while (iterator.next(&cursor)) { + if (js_lexer.isIdentifierContinue(cursor.c) and cursor.width == 1) { + if (needs_gap) { + try writer.writeAll("_"); + needs_gap = false; + has_needed_gap = true; + } + try writer.writeAll(slice[cursor.i .. cursor.i + @as(u32, cursor.width)]); + } else if (!needs_gap) { + needs_gap = true; + // skip the code point, replace it with a single _ + } + } + + // If it ends with an emoji + if (needs_gap) { + try writer.writeAll("_"); + needs_gap = false; + has_needed_gap = true; + } + + return; + } + + try writer.writeAll(self.name); + } +}; + +// Formats a string to be safe to output in a Github action. +// - Encodes "\n" as "%0A" to support multi-line strings. +// https://github.com/actions/toolkit/issues/193#issuecomment-605394935 +// - Strips ANSI output as it will appear malformed. +pub fn githubActionWriter(writer: anytype, self: string) !void { + var offset: usize = 0; + const end = @as(u32, @truncate(self.len)); + while (offset < end) { + if (strings.indexOfNewlineOrNonASCIIOrANSI(self, @as(u32, @truncate(offset)))) |i| { + const byte = self[i]; + if (byte > 0x7F) { + offset += @max(strings.wtf8ByteSequenceLength(byte), 1); + continue; + } + if (i > 0) { + try writer.writeAll(self[offset..i]); + } + var n: usize = 1; + if (byte == '\n') { + try writer.writeAll("%0A"); + } else if (i + 1 < end) { + const next = self[i + 1]; + if (byte == '\r' and next == '\n') { + n += 1; + try writer.writeAll("%0A"); + } else if (byte == '\x1b' and next == '[') { + n += 1; + if (i + 2 < end) { + const remain = self[(i + 2)..@min(i + 5, end)]; + if (strings.indexOfChar(remain, 'm')) |j| { + n += j + 1; + } + } + } + } + offset = i + n; + } else { + try writer.writeAll(self[offset..end]); + break; + } + } +} + +pub const GithubActionFormatter = struct { + text: string, + + pub fn format(this: GithubActionFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + try githubActionWriter(writer, this.text); + } +}; + +pub fn githubAction(self: string) strings.GithubActionFormatter { + return GithubActionFormatter{ + .text = self, + }; +} + +pub fn quotedWriter(writer: anytype, self: string) !void { + const remain = self; + if (strings.containsNewlineOrNonASCIIOrQuote(remain)) { + try bun.js_printer.writeJSONString(self, @TypeOf(writer), writer, strings.Encoding.utf8); + } else { + try writer.writeAll("\""); + try writer.writeAll(self); + try writer.writeAll("\""); + } +} + +pub const QuotedFormatter = struct { + text: []const u8, + + pub fn format(this: QuotedFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + try quotedWriter(writer, this.text); + } +}; + +pub fn fmtJavaScript(text: []const u8, enable_ansi_colors: bool) QuickAndDirtyJavaScriptSyntaxHighlighter { + return QuickAndDirtyJavaScriptSyntaxHighlighter{ + .text = text, + .enable_colors = enable_ansi_colors, + }; +} + +pub const QuickAndDirtyJavaScriptSyntaxHighlighter = struct { + text: []const u8, + enable_colors: bool = false, + limited: bool = true, + + const ColorCode = enum { + magenta, + blue, + orange, + red, + pink, + + pub fn color(this: ColorCode) []const u8 { + return switch (this) { + .magenta => "\x1b[35m", + .blue => "\x1b[34m", + .orange => "\x1b[33m", + .red => "\x1b[31m", + // light pink + .pink => "\x1b[38;5;206m", + }; + } + }; + + pub const Keyword = enum { + abstract, + as, + @"async", + @"await", + case, + @"catch", + class, + @"const", + @"continue", + debugger, + default, + delete, + do, + @"else", + @"enum", + @"export", + extends, + false, + finally, + @"for", + function, + @"if", + implements, + import, + in, + instanceof, + interface, + let, + new, + null, + package, + private, + protected, + public, + @"return", + static, + super, + @"switch", + this, + throw, + @"break", + true, + @"try", + type, + typeof, + @"var", + void, + @"while", + with, + yield, + string, + number, + boolean, + symbol, + any, + object, + unknown, + never, + namespace, + declare, + readonly, + undefined, + + pub fn colorCode(this: Keyword) ColorCode { + return switch (this) { + Keyword.abstract => ColorCode.blue, + Keyword.as => ColorCode.blue, + Keyword.@"async" => ColorCode.magenta, + Keyword.@"await" => ColorCode.magenta, + Keyword.case => ColorCode.magenta, + Keyword.@"catch" => ColorCode.magenta, + Keyword.class => ColorCode.magenta, + Keyword.@"const" => ColorCode.magenta, + Keyword.@"continue" => ColorCode.magenta, + Keyword.debugger => ColorCode.magenta, + Keyword.default => ColorCode.magenta, + Keyword.delete => ColorCode.red, + Keyword.do => ColorCode.magenta, + Keyword.@"else" => ColorCode.magenta, + Keyword.@"break" => ColorCode.magenta, + Keyword.undefined => ColorCode.orange, + Keyword.@"enum" => ColorCode.blue, + Keyword.@"export" => ColorCode.magenta, + Keyword.extends => ColorCode.magenta, + Keyword.false => ColorCode.orange, + Keyword.finally => ColorCode.magenta, + Keyword.@"for" => ColorCode.magenta, + Keyword.function => ColorCode.magenta, + Keyword.@"if" => ColorCode.magenta, + Keyword.implements => ColorCode.blue, + Keyword.import => ColorCode.magenta, + Keyword.in => ColorCode.magenta, + Keyword.instanceof => ColorCode.magenta, + Keyword.interface => ColorCode.blue, + Keyword.let => ColorCode.magenta, + Keyword.new => ColorCode.magenta, + Keyword.null => ColorCode.orange, + Keyword.package => ColorCode.magenta, + Keyword.private => ColorCode.blue, + Keyword.protected => ColorCode.blue, + Keyword.public => ColorCode.blue, + Keyword.@"return" => ColorCode.magenta, + Keyword.static => ColorCode.magenta, + Keyword.super => ColorCode.magenta, + Keyword.@"switch" => ColorCode.magenta, + Keyword.this => ColorCode.orange, + Keyword.throw => ColorCode.magenta, + Keyword.true => ColorCode.orange, + Keyword.@"try" => ColorCode.magenta, + Keyword.type => ColorCode.blue, + Keyword.typeof => ColorCode.magenta, + Keyword.@"var" => ColorCode.magenta, + Keyword.void => ColorCode.magenta, + Keyword.@"while" => ColorCode.magenta, + Keyword.with => ColorCode.magenta, + Keyword.yield => ColorCode.magenta, + Keyword.string => ColorCode.blue, + Keyword.number => ColorCode.blue, + Keyword.boolean => ColorCode.blue, + Keyword.symbol => ColorCode.blue, + Keyword.any => ColorCode.blue, + Keyword.object => ColorCode.blue, + Keyword.unknown => ColorCode.blue, + Keyword.never => ColorCode.blue, + Keyword.namespace => ColorCode.blue, + Keyword.declare => ColorCode.blue, + Keyword.readonly => ColorCode.blue, + }; + } + }; + + pub const Keywords = ComptimeStringMap(Keyword, .{ + .{ "abstract", Keyword.abstract }, + .{ "any", Keyword.any }, + .{ "as", Keyword.as }, + .{ "async", Keyword.@"async" }, + .{ "await", Keyword.@"await" }, + .{ "boolean", Keyword.boolean }, + .{ "break", Keyword.@"break" }, + .{ "case", Keyword.case }, + .{ "catch", Keyword.@"catch" }, + .{ "class", Keyword.class }, + .{ "const", Keyword.@"const" }, + .{ "continue", Keyword.@"continue" }, + .{ "debugger", Keyword.debugger }, + .{ "declare", Keyword.declare }, + .{ "default", Keyword.default }, + .{ "delete", Keyword.delete }, + .{ "do", Keyword.do }, + .{ "else", Keyword.@"else" }, + .{ "enum", Keyword.@"enum" }, + .{ "export", Keyword.@"export" }, + .{ "extends", Keyword.extends }, + .{ "false", Keyword.false }, + .{ "finally", Keyword.finally }, + .{ "for", Keyword.@"for" }, + .{ "function", Keyword.function }, + .{ "if", Keyword.@"if" }, + .{ "implements", Keyword.implements }, + .{ "import", Keyword.import }, + .{ "in", Keyword.in }, + .{ "instanceof", Keyword.instanceof }, + .{ "interface", Keyword.interface }, + .{ "let", Keyword.let }, + .{ "namespace", Keyword.namespace }, + .{ "never", Keyword.never }, + .{ "new", Keyword.new }, + .{ "null", Keyword.null }, + .{ "number", Keyword.number }, + .{ "object", Keyword.object }, + .{ "package", Keyword.package }, + .{ "private", Keyword.private }, + .{ "protected", Keyword.protected }, + .{ "public", Keyword.public }, + .{ "readonly", Keyword.readonly }, + .{ "return", Keyword.@"return" }, + .{ "static", Keyword.static }, + .{ "string", Keyword.string }, + .{ "super", Keyword.super }, + .{ "switch", Keyword.@"switch" }, + .{ "symbol", Keyword.symbol }, + .{ "this", Keyword.this }, + .{ "throw", Keyword.throw }, + .{ "true", Keyword.true }, + .{ "try", Keyword.@"try" }, + .{ "type", Keyword.type }, + .{ "typeof", Keyword.typeof }, + .{ "undefined", Keyword.undefined }, + .{ "unknown", Keyword.unknown }, + .{ "var", Keyword.@"var" }, + .{ "void", Keyword.void }, + .{ "while", Keyword.@"while" }, + .{ "with", Keyword.with }, + .{ "yield", Keyword.yield }, + }); + + pub fn format(this: @This(), comptime _: []const u8, _: fmt.FormatOptions, writer: anytype) !void { + const text = this.text; + + if (this.limited) { + if (!this.enable_colors or text.len > 2048 or text.len == 0 or !strings.isAllASCII(text)) { + try writer.writeAll(text); + return; + } + } + + var remain = text; + var prev_keyword: ?Keyword = null; + + outer: while (remain.len > 0) { + if (js_lexer.isIdentifierStart(remain[0])) { + var i: usize = 1; + + while (i < remain.len and js_lexer.isIdentifierContinue(remain[i])) { + i += 1; + } + + if (Keywords.get(remain[0..i])) |keyword| { + if (keyword != .as) + prev_keyword = keyword; + const code = keyword.colorCode(); + try writer.print(Output.prettyFmt("{s}{s}", true), .{ code.color(), remain[0..i] }); + } else { + write: { + if (prev_keyword) |prev| { + switch (prev) { + .new => { + prev_keyword = null; + + if (i < remain.len and remain[i] == '(') { + try writer.print(Output.prettyFmt("{s}", true), .{remain[0..i]}); + break :write; + } + }, + .abstract, .namespace, .declare, .type, .interface => { + try writer.print(Output.prettyFmt("{s}", true), .{remain[0..i]}); + prev_keyword = null; + break :write; + }, + .import => { + if (strings.eqlComptime(remain[0..i], "from")) { + const code = ColorCode.magenta; + try writer.print(Output.prettyFmt("{s}{s}", true), .{ code.color(), remain[0..i] }); + prev_keyword = null; + + break :write; + } + }, + else => {}, + } + } + + try writer.writeAll(remain[0..i]); + } + } + remain = remain[i..]; + } else { + switch (remain[0]) { + '0'...'9' => { + prev_keyword = null; + var i: usize = 1; + if (remain.len > 1 and remain[0] == '0' and remain[1] == 'x') { + i += 1; + while (i < remain.len and switch (remain[i]) { + '0'...'9', 'a'...'f', 'A'...'F' => true, + else => false, + }) { + i += 1; + } + } else { + while (i < remain.len and switch (remain[i]) { + '0'...'9', '.', 'e', 'E', 'x', 'X', 'b', 'B', 'o', 'O' => true, + else => false, + }) { + i += 1; + } + } + + try writer.print(Output.prettyFmt("{s}", true), .{remain[0..i]}); + remain = remain[i..]; + }, + inline '`', '"', '\'' => |char| { + prev_keyword = null; + + var i: usize = 1; + while (i < remain.len and remain[i] != char) { + if (comptime char == '`') { + if (remain[i] == '$' and i + 1 < remain.len and remain[i + 1] == '{') { + const curly_start = i; + i += 2; + + while (i < remain.len and remain[i] != '}') { + if (remain[i] == '\\') { + i += 1; + } + i += 1; + } + + try writer.print(Output.prettyFmt("{s}", true), .{remain[0..curly_start]}); + try writer.writeAll("${"); + const curly_remain = QuickAndDirtyJavaScriptSyntaxHighlighter{ + .text = remain[curly_start + 2 .. i], + .enable_colors = this.enable_colors, + .limited = false, + }; + + if (curly_remain.text.len > 0) { + try curly_remain.format("", .{}, writer); + } + + if (i < remain.len and remain[i] == '}') { + i += 1; + } + try writer.writeAll("}"); + remain = remain[i..]; + i = 0; + if (remain.len > 0 and remain[0] == char) { + try writer.writeAll(Output.prettyFmt("`", true)); + remain = remain[1..]; + continue :outer; + } + continue; + } + } + + if (i + 1 < remain.len and remain[i] == '\\') { + i += 1; + } + + i += 1; + } + + // Include the trailing quote, if any + i += @as(usize, @intFromBool(i > 1 and i < remain.len and remain[i] == char)); + + try writer.print(Output.prettyFmt("{s}", true), .{remain[0..i]}); + remain = remain[i..]; + }, + '/' => { + prev_keyword = null; + var i: usize = 1; + + // the start of a line comment + if (i < remain.len and remain[i] == '/') { + while (i < remain.len and remain[i] != '\n') { + i += 1; + } + + const remain_to_print = remain[0..i]; + if (i < remain.len and remain[i] == '\n') { + i += 1; + } + + if (i < remain.len and remain[i] == '\r') { + i += 1; + } + + try writer.print(Output.prettyFmt("{s}", true), .{remain_to_print}); + remain = remain[i..]; + continue; + } + + as_multiline_comment: { + if (i < remain.len and remain[i] == '*') { + i += 1; + + while (i + 2 < remain.len and !strings.eqlComptime(remain[i..][0..2], "*/")) { + i += 1; + } + + if (i + 2 < remain.len and strings.eqlComptime(remain[i..][0..2], "*/")) { + i += 2; + } else { + i = 1; + break :as_multiline_comment; + } + + try writer.print(Output.prettyFmt("{s}", true), .{remain[0..i]}); + remain = remain[i..]; + continue; + } + } + + try writer.writeAll(remain[0..i]); + remain = remain[i..]; + }, + '}', '{' => { + // support potentially highlighting "from" in an import statement + if ((prev_keyword orelse Keyword.@"continue") != .import) { + prev_keyword = null; + } + + try writer.writeAll(remain[0..1]); + remain = remain[1..]; + }, + '[', ']' => { + prev_keyword = null; + try writer.writeAll(remain[0..1]); + remain = remain[1..]; + }, + ';' => { + prev_keyword = null; + try writer.print(Output.prettyFmt(";", true), .{}); + remain = remain[1..]; + }, + '.' => { + prev_keyword = null; + var i: usize = 1; + if (remain.len > 1 and (js_lexer.isIdentifierStart(remain[1]) or remain[1] == '#')) { + i = 2; + + while (i < remain.len and js_lexer.isIdentifierContinue(remain[i])) { + i += 1; + } + + if (i < remain.len and (remain[i] == '(')) { + try writer.print(Output.prettyFmt("{s}", true), .{remain[0..i]}); + remain = remain[i..]; + continue; + } + i = 1; + } + + try writer.writeAll(remain[0..1]); + remain = remain[1..]; + }, + + '<' => { + var i: usize = 1; + + // JSX + jsx: { + if (remain.len > 1 and remain[0] == '/') { + i = 2; + } + prev_keyword = null; + + while (i < remain.len and js_lexer.isIdentifierContinue(remain[i])) { + i += 1; + } else { + i = 1; + break :jsx; + } + + while (i < remain.len and remain[i] != '>') { + i += 1; + + if (i < remain.len and remain[i] == '<') { + i = 1; + break :jsx; + } + } + + if (i < remain.len and remain[i] == '>') { + i += 1; + try writer.print(Output.prettyFmt("{s}", true), .{remain[0..i]}); + remain = remain[i..]; + continue; + } + + i = 1; + } + + try writer.print(Output.prettyFmt("{s}", true), .{remain[0..i]}); + remain = remain[i..]; + }, + + else => { + try writer.writeAll(remain[0..1]); + remain = remain[1..]; + }, + } + } + } + } +}; + +pub fn quote(self: string) bun.fmt.QuotedFormatter { + return bun.fmt.QuotedFormatter{ + .text = self, + }; +} + +pub fn EnumTagListFormatter(comptime Enum: type, comptime Separator: @Type(.EnumLiteral)) type { + return struct { + pretty: bool = true, + const output = brk: { + var text: []const u8 = ""; + const names = std.meta.fieldNames(Enum); + for (names, 0..) |name, i| { + if (Separator == .list) { + if (i > 0) { + if (i + 1 == names.len) { + text = text ++ ", or "; + } else { + text = text ++ ", "; + } + } + + text = text ++ "\"" ++ name ++ "\""; + } else if (Separator == .dash) { + text = text ++ "\n- " ++ name; + } else { + @compileError("Unknown separator type: must be .dash or .list"); + } + } + break :brk text; + }; + pub fn format(_: @This(), comptime _: []const u8, _: fmt.FormatOptions, writer: anytype) !void { + try writer.writeAll(output); + } + }; +} + +pub fn enumTagList(comptime Enum: type, comptime separator: @Type(.EnumLiteral)) EnumTagListFormatter(Enum, separator) { + return EnumTagListFormatter(Enum, separator){}; +} + +pub fn formatIp(address: std.net.Address, into: []u8) ![]u8 { + // std.net.Address.format includes `:` and square brackets (IPv6) + // while Node does neither. This uses format then strips these to bring + // the result into conformance with Node. + var result = try std.fmt.bufPrint(into, "{}", .{address}); + + // Strip `:` + if (std.mem.lastIndexOfScalar(u8, result, ':')) |colon| { + result = result[0..colon]; + } + // Strip brackets + if (result[0] == '[' and result[result.len - 1] == ']') { + result = result[1 .. result.len - 1]; + } + return result; +} + +// https://lemire.me/blog/2021/06/03/computing-the-number-of-digits-of-an-integer-even-faster/ +pub fn fastDigitCount(x: u64) u64 { + if (x == 0) { + return 1; + } + + const table = [_]u64{ + 4294967296, + 8589934582, + 8589934582, + 8589934582, + 12884901788, + 12884901788, + 12884901788, + 17179868184, + 17179868184, + 17179868184, + 21474826480, + 21474826480, + 21474826480, + 21474826480, + 25769703776, + 25769703776, + 25769703776, + 30063771072, + 30063771072, + 30063771072, + 34349738368, + 34349738368, + 34349738368, + 34349738368, + 38554705664, + 38554705664, + 38554705664, + 41949672960, + 41949672960, + 41949672960, + 42949672960, + 42949672960, + }; + return x + table[std.math.log2(x)] >> 32; +} + +pub const SizeFormatter = struct { + value: usize = 0, + pub fn format(self: SizeFormatter, comptime _: []const u8, opts: fmt.FormatOptions, writer: anytype) !void { + const math = std.math; + const value = self.value; + if (value == 0) { + return writer.writeAll("0 KB"); + } + + if (value < 512) { + try fmt.formatInt(self.value, 10, .lower, opts, writer); + return writer.writeAll(" bytes"); + } + + const mags_si = " KMGTPEZY"; + const log2 = math.log2(value); + const magnitude = @min(log2 / comptime math.log2(1000), mags_si.len - 1); + const new_value = math.lossyCast(f64, value) / math.pow(f64, 1000, math.lossyCast(f64, magnitude)); + const suffix = mags_si[magnitude]; + + if (suffix == ' ') { + try fmt.formatFloatDecimal(new_value / 1000.0, .{ .precision = 2 }, writer); + return writer.writeAll(" KB"); + } else { + try fmt.formatFloatDecimal(new_value, .{ .precision = if (std.math.approxEqAbs(f64, new_value, @trunc(new_value), 0.100)) @as(usize, 1) else @as(usize, 2) }, writer); + } + return writer.writeAll(&[_]u8{ ' ', suffix, 'B' }); + } +}; + +pub fn size(value: anytype) SizeFormatter { + return switch (@TypeOf(value)) { + f64, f32, f128 => SizeFormatter{ + .value = @as(u64, @intFromFloat(value)), + }, + else => SizeFormatter{ .value = @as(u64, @intCast(value)) }, + }; +} + +const lower_hex_table = [_]u8{ + '0', + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + 'a', + 'b', + 'c', + 'd', + 'e', + 'f', +}; +const upper_hex_table = [_]u8{ + '0', + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + 'A', + 'B', + 'C', + 'D', + 'E', + 'F', +}; +pub fn HexIntFormatter(comptime Int: type, comptime lower: bool) type { + return struct { + value: Int, + + const table = if (lower) lower_hex_table else upper_hex_table; + + const BufType = [@bitSizeOf(Int) / 4]u8; + + fn getOutBuf(value: Int) BufType { + var buf: BufType = undefined; + comptime var i: usize = 0; + inline while (i < buf.len) : (i += 1) { + // value relative to the current nibble + buf[i] = table[@as(u8, @as(u4, @truncate(value >> comptime ((buf.len - i - 1) * 4)))) & 0xF]; + } + + return buf; + } + + pub fn format(self: @This(), comptime _: []const u8, _: fmt.FormatOptions, writer: anytype) !void { + const value = self.value; + try writer.writeAll(&getOutBuf(value)); + } + }; +} + +pub fn HexInt(comptime Int: type, comptime lower: std.fmt.Case, value: Int) HexIntFormatter(Int, lower == .lower) { + const Formatter = HexIntFormatter(Int, lower == .lower); + return Formatter{ .value = value }; +} + +pub fn hexIntLower(value: anytype) HexIntFormatter(@TypeOf(value), true) { + const Formatter = HexIntFormatter(@TypeOf(value), true); + return Formatter{ .value = value }; +} + +pub fn hexIntUpper(value: anytype) HexIntFormatter(@TypeOf(value), false) { + const Formatter = HexIntFormatter(@TypeOf(value), false); + return Formatter{ .value = value }; +} + +const FormatDurationData = struct { + ns: u64, + negative: bool = false, +}; + +/// This is copied from std.fmt.formatDuration, except it will only print one decimal instead of three +fn formatDurationOneDecimal(data: FormatDurationData, comptime _: []const u8, opts: std.fmt.FormatOptions, writer: anytype) !void { + // worst case: "-XXXyXXwXXdXXhXXmXX.XXXs".len = 24 + var buf: [24]u8 = undefined; + var fbs = std.io.fixedBufferStream(&buf); + var buf_writer = fbs.writer(); + if (data.negative) { + buf_writer.writeByte('-') catch unreachable; + } + + var ns_remaining = data.ns; + inline for (.{ + .{ .ns = 365 * std.time.ns_per_day, .sep = 'y' }, + .{ .ns = std.time.ns_per_week, .sep = 'w' }, + .{ .ns = std.time.ns_per_day, .sep = 'd' }, + .{ .ns = std.time.ns_per_hour, .sep = 'h' }, + .{ .ns = std.time.ns_per_min, .sep = 'm' }, + }) |unit| { + if (ns_remaining >= unit.ns) { + const units = ns_remaining / unit.ns; + std.fmt.formatInt(units, 10, .lower, .{}, buf_writer) catch unreachable; + buf_writer.writeByte(unit.sep) catch unreachable; + ns_remaining -= units * unit.ns; + if (ns_remaining == 0) + return std.fmt.formatBuf(fbs.getWritten(), opts, writer); + } + } + + inline for (.{ + .{ .ns = std.time.ns_per_s, .sep = "s" }, + .{ .ns = std.time.ns_per_ms, .sep = "ms" }, + .{ .ns = std.time.ns_per_us, .sep = "us" }, + }) |unit| { + const kunits = ns_remaining * 1000 / unit.ns; + if (kunits >= 1000) { + std.fmt.formatInt(kunits / 1000, 10, .lower, .{}, buf_writer) catch unreachable; + const frac = @divFloor(kunits % 1000, 100); + if (frac > 0) { + var decimal_buf = [_]u8{ '.', 0 }; + _ = std.fmt.formatIntBuf(decimal_buf[1..], frac, 10, .lower, .{ .fill = '0', .width = 1 }); + buf_writer.writeAll(&decimal_buf) catch unreachable; + } + buf_writer.writeAll(unit.sep) catch unreachable; + return std.fmt.formatBuf(fbs.getWritten(), opts, writer); + } + } + + std.fmt.formatInt(ns_remaining, 10, .lower, .{}, buf_writer) catch unreachable; + buf_writer.writeAll("ns") catch unreachable; + return std.fmt.formatBuf(fbs.getWritten(), opts, writer); +} + +/// Return a Formatter for number of nanoseconds according to its magnitude: +/// [#y][#w][#d][#h][#m]#[.###][n|u|m]s +pub fn fmtDurationOneDecimal(ns: u64) std.fmt.Formatter(formatDurationOneDecimal) { + return .{ .data = FormatDurationData{ .ns = ns } }; +} +// }; diff --git a/src/fs.zig b/src/fs.zig index 43c5386088..e76c04f388 100644 --- a/src/fs.zig +++ b/src/fs.zig @@ -695,7 +695,7 @@ pub const FileSystem = struct { const flags = std.os.O.CREAT | std.os.O.WRONLY | std.os.O.CLOEXEC; const result = try bun.sys.openat(bun.toFD(tmpdir_.fd), name, flags, 0).unwrap(); - this.fd = bun.toFD(result); + this.fd = bun.toLibUVOwnedFD(result); var buf: [bun.MAX_PATH_BYTES]u8 = undefined; const existing_path = try bun.getFdPath(this.fd, &buf); this.existing_path = try bun.default_allocator.dupe(u8, existing_path); @@ -712,7 +712,7 @@ pub const FileSystem = struct { else bun.strings.toWPathNormalized(&existing_buf, name); if (comptime Environment.allow_assert) { - debug("moveFileExW({s}, {s})", .{ strings.fmtUTF16(existing), strings.fmtUTF16(new) }); + debug("moveFileExW({s}, {s})", .{ bun.fmt.fmtUTF16(existing), bun.fmt.fmtUTF16(new) }); } if (bun.windows.kernel32.MoveFileExW(existing.ptr, new.ptr, bun.windows.MOVEFILE_COPY_ALLOWED | bun.windows.MOVEFILE_REPLACE_EXISTING | bun.windows.MOVEFILE_WRITE_THROUGH) == bun.windows.FALSE) { @@ -1440,8 +1440,8 @@ pub const PathName = struct { return this.dir; } - pub fn fmtIdentifier(self: *const PathName) strings.FormatValidIdentifier { - return strings.fmtIdentifier(self.nonUniqueNameStringBase()); + pub fn fmtIdentifier(self: *const PathName) bun.fmt.FormatValidIdentifier { + return bun.fmt.fmtIdentifier(self.nonUniqueNameStringBase()); } // For readability, the names of certain automatically-generated symbols are diff --git a/src/glob.zig b/src/glob.zig index 371215fcb2..ac0b94abea 100644 --- a/src/glob.zig +++ b/src/glob.zig @@ -328,7 +328,7 @@ pub fn GlobWalker_( this.iter_state.directory.fd = fd; const dir = std.fs.Dir{ .fd = bun.fdcast(fd) }; - const iterator = DirIterator.iterate(dir); + const iterator = DirIterator.iterate(dir, .u8); this.iter_state.directory.iter = iterator; this.iter_state.directory.iter_closed = false; diff --git a/src/http/websocket_http_client.zig b/src/http/websocket_http_client.zig index a7a5ce745a..a8be49c584 100644 --- a/src/http/websocket_http_client.zig +++ b/src/http/websocket_http_client.zig @@ -91,7 +91,7 @@ fn buildRequestBody( host_.deinit(); } - const host_fmt = strings.HostFormatter{ + const host_fmt = bun.fmt.HostFormatter{ .is_https = is_https, .host = host_.slice(), .port = port, diff --git a/src/install/bin.zig b/src/install/bin.zig index 3d1629ba07..220fd24af1 100644 --- a/src/install/bin.zig +++ b/src/install/bin.zig @@ -13,6 +13,7 @@ const Fs = @import("../fs.zig"); const stringZ = @import("root").bun.stringZ; const Resolution = @import("./resolution.zig").Resolution; const bun = @import("root").bun; +const string = bun.string; /// Normalized `bin` field in [package.json](https://docs.npmjs.com/cli/v8/configuring-npm/package-json#bin) /// Can be a: /// - file path (relative to the package root) @@ -327,13 +328,10 @@ pub const Bin = extern struct { fn setPermissions(folder: std.os.fd_t, target: [:0]const u8) void { // we use fchmodat to avoid any issues with current working directory - _ = C.fchmodat(folder, target, umask | 0o777, 0); + _ = C.fchmodat(folder, target, @intCast(umask | 0o777), 0); } - fn setSimlinkAndPermissions(this: *Linker, target_path: [:0]const u8, dest_path: [:0]const u8) void { - if (comptime Environment.isWindows) { - @panic("TODO on Windows"); - } + fn setSymlinkAndPermissions(this: *Linker, target_path: [:0]const u8, dest_path: [:0]const u8) void { const node_modules = this.package_installed_node_modules.asDir(); std.os.symlinkatZ(target_path, node_modules.fd, dest_path) catch |err| { // Silently ignore PathAlreadyExists @@ -359,6 +357,9 @@ pub const Bin = extern struct { // That way, if you move your node_modules folder around, the symlinks in .bin still work // If we used absolute paths for the symlinks, you'd end up with broken symlinks pub fn link(this: *Linker, link_global: bool) void { + if (comptime Environment.isWindows) { + return bun.todo(@src(), {}); + } var target_buf: [bun.MAX_PATH_BYTES]u8 = undefined; var dest_buf: [bun.MAX_PATH_BYTES]u8 = undefined; var from_remain: []u8 = &target_buf; @@ -369,7 +370,7 @@ pub const Bin = extern struct { const from = root_dir.realpath(dot_bin, &target_buf) catch |realpath_err| brk: { if (realpath_err == error.FileNotFound) { if (comptime Environment.isWindows) { - std.os.mkdiratW(root_dir.fd, bun.strings.w(".bin"), 0) catch |err| { + std.os.mkdiratW(root_dir.fd, comptime bun.OSPathLiteral(".bin"), 0) catch |err| { this.err = err; return; }; @@ -427,11 +428,6 @@ pub const Bin = extern struct { remain[0] = std.fs.path.sep; remain = remain[1..]; - if (comptime Environment.isWindows) { - // TODO: Bin.Linker.link() needs to be updated to generate .cmd files on Windows - @panic("TODO on Windows"); - } - switch (this.bin.tag) { .none => { if (comptime Environment.isDebug) { @@ -459,7 +455,7 @@ pub const Bin = extern struct { from_remain[0] = 0; const dest_path: [:0]u8 = target_buf[0 .. @intFromPtr(from_remain.ptr) - @intFromPtr(&target_buf) :0]; - this.setSimlinkAndPermissions(target_path, dest_path); + this.setSymlinkAndPermissions(target_path, dest_path); }, .named_file => { var target = this.bin.value.named_file[1].slice(this.string_buf); @@ -479,13 +475,14 @@ pub const Bin = extern struct { from_remain[0] = 0; const dest_path: [:0]u8 = target_buf[0 .. @intFromPtr(from_remain.ptr) - @intFromPtr(&target_buf) :0]; - this.setSimlinkAndPermissions(target_path, dest_path); + this.setSymlinkAndPermissions(target_path, dest_path); }, .map => { var extern_string_i: u32 = this.bin.value.map.off; const end = this.bin.value.map.len + extern_string_i; const _from_remain = from_remain; const _remain = remain; + while (extern_string_i < end) : (extern_string_i += 2) { from_remain = _from_remain; remain = _remain; @@ -509,7 +506,7 @@ pub const Bin = extern struct { from_remain[0] = 0; const dest_path: [:0]u8 = target_buf[0 .. @intFromPtr(from_remain.ptr) - @intFromPtr(&target_buf) :0]; - this.setSimlinkAndPermissions(target_path, dest_path); + this.setSymlinkAndPermissions(target_path, dest_path); } }, .dir => { @@ -558,7 +555,7 @@ pub const Bin = extern struct { else std.fmt.bufPrintZ(&dest_buf, "{s}", .{entry.name}) catch continue; - this.setSimlinkAndPermissions(from_path, to_path); + this.setSymlinkAndPermissions(from_path, to_path); }, else => {}, } diff --git a/src/install/extract_tarball.zig b/src/install/extract_tarball.zig index b5010e7b13..23e85a39b7 100644 --- a/src/install/extract_tarball.zig +++ b/src/install/extract_tarball.zig @@ -15,6 +15,9 @@ const Semver = @import("./semver.zig"); const std = @import("std"); const string = @import("../string_types.zig").string; const strings = @import("../string_immutable.zig"); +const Path = @import("../resolver/resolve_path.zig"); +const Environment = bun.Environment; + const ExtractTarball = @This(); name: strings.StringOrTinyString, @@ -149,9 +152,9 @@ pub fn buildURLWithPrinter( } } -threadlocal var final_path_buf: [bun.MAX_PATH_BYTES]u8 = undefined; -threadlocal var folder_name_buf: [bun.MAX_PATH_BYTES]u8 = undefined; -threadlocal var json_path_buf: [bun.MAX_PATH_BYTES]u8 = undefined; +threadlocal var final_path_buf: bun.PathBuffer = undefined; +threadlocal var folder_name_buf: bun.PathBuffer = undefined; +threadlocal var json_path_buf: bun.PathBuffer = undefined; fn extract(this: *const ExtractTarball, tgz_bytes: []const u8) !Install.ExtractData { var tmpdir = this.temp_dir; @@ -279,18 +282,49 @@ fn extract(this: *const ExtractTarball, tgz_bytes: []const u8) !Install.ExtractD } // Now that we've extracted the archive, we rename. - switch (bun.sys.renameat(bun.toFD(tmpdir.fd), bun.sliceTo(tmpname, 0), bun.toFD(cache_dir.fd), folder_name)) { - .err => |err| { + if (comptime Environment.isWindows) { + // TODO(dylan-conway) make this less painful + var from_buf: bun.PathBuffer = undefined; + const tmpdir_path = try bun.getFdPath(tmpdir.fd, &from_buf); + const from_path = Path.joinAbsStringZ(tmpdir_path, &.{bun.sliceTo(tmpname, 0)}, .auto); + + var to_buf: bun.PathBuffer = undefined; + const cache_dir_path = try bun.getFdPath(cache_dir.fd, &to_buf); + const to_path = Path.joinAbsStringBufZ(cache_dir_path, &to_buf, &.{folder_name}, .auto); + + var from_path_buf_w: bun.WPathBuffer = undefined; + const from_path_w = bun.strings.toWPath(&from_path_buf_w, from_path); + var to_path_buf_w: bun.WPathBuffer = undefined; + const to_path_w = bun.strings.toWPath(&to_path_buf_w, to_path); + + if (bun.windows.MoveFileExW( + from_path_w, + to_path_w, + bun.windows.MOVEFILE_COPY_ALLOWED | bun.windows.MOVEFILE_REPLACE_EXISTING | bun.windows.MOVEFILE_WRITE_THROUGH, + ) == bun.windows.FALSE) { this.package_manager.log.addErrorFmt( null, logger.Loc.Empty, this.package_manager.allocator, - "moving \"{s}\" to cache dir failed: {}\n From: {s}\n To: {s}", - .{ name, err, tmpname, folder_name }, + "moving \"{s}\" to cache dir failed: From: {s}\n To: {s}", + .{ name, tmpname, folder_name }, ) catch unreachable; return error.InstallFailed; - }, - .result => {}, + } + } else { + switch (bun.sys.renameat(bun.toFD(tmpdir.fd), bun.sliceTo(tmpname, 0), bun.toFD(cache_dir.fd), folder_name)) { + .err => |err| { + this.package_manager.log.addErrorFmt( + null, + logger.Loc.Empty, + this.package_manager.allocator, + "moving \"{s}\" to cache dir failed: {}\n From: {s}\n To: {s}", + .{ name, err, tmpname, folder_name }, + ) catch unreachable; + return error.InstallFailed; + }, + .result => {}, + } } // We return a resolved absolute absolute file path to the cache dir. diff --git a/src/install/install.zig b/src/install/install.zig index 664d51c6f8..cff3182df1 100644 --- a/src/install/install.zig +++ b/src/install/install.zig @@ -325,7 +325,7 @@ const NetworkTask = struct { if (tmp.tag == .Dead) { const msg = .{ .fmt = "Failed to join registry {} and package {} URLs", - .args = .{ strings.QuotedFormatter{ .text = scope.url.href }, strings.QuotedFormatter{ .text = name } }, + .args = .{ bun.fmt.QuotedFormatter{ .text = scope.url.href }, bun.fmt.QuotedFormatter{ .text = name } }, }; if (warn_on_error) @@ -1110,8 +1110,8 @@ const PackageInstall = struct { var walker_ = Walker.walk( cached_package_dir, this.allocator, - &[_]string{}, - &[_]string{}, + &[_]bun.OSPathSlice{}, + &[_]bun.OSPathSlice{}, ) catch |err| return Result{ .fail = .{ .err = err, .step = .opening_cache_dir }, }; @@ -1226,8 +1226,8 @@ const PackageInstall = struct { var walker_ = Walker.walk( cached_package_dir, this.allocator, - &[_]string{}, - &[_]string{}, + &[_]bun.OSPathSlice{}, + &[_]bun.OSPathSlice{}, ) catch |err| return Result{ .fail = .{ .err = err, .step = .opening_cache_dir }, }; @@ -1240,41 +1240,68 @@ const PackageInstall = struct { progress_: *Progress, ) !u32 { var real_file_count: u32 = 0; + + var in_buf: if (Environment.isWindows) bun.OSPathBuffer else void = undefined; + var out_buf: if (Environment.isWindows) bun.OSPathBuffer else void = undefined; + while (try walker.next()) |entry| { if (entry.kind != .file) continue; real_file_count += 1; - var outfile = destination_dir_.createFile(entry.path, .{}) catch brk: { - if (std.fs.path.dirname(entry.path)) |entry_dirname| { - destination_dir_.makePath(entry_dirname) catch {}; + const createFile = if (comptime Environment.isWindows) std.fs.Dir.createFileW else std.fs.Dir.createFile; + + var outfile = createFile(destination_dir_, entry.path, .{}) catch brk: { + if (bun.Dirname.dirname(bun.OSPathChar, entry.path)) |entry_dirname| { + bun.MakePath.makePath(bun.OSPathChar, destination_dir_, entry_dirname) catch {}; } - break :brk destination_dir_.createFile(entry.path, .{}) catch |err| { + break :brk createFile(destination_dir_, entry.path, .{}) catch |err| { progress_.root.end(); progress_.refresh(); - Output.prettyErrorln("{s}: copying file {s}", .{ @errorName(err), entry.path }); + Output.prettyErrorln("{s}: copying file {}", .{ @errorName(err), bun.fmt.fmtOSPath(entry.path) }); Global.crash(); }; }; defer outfile.close(); - var in_file = try entry.dir.openFile(entry.basename, .{ .mode = .read_only }); + const openFile = if (comptime Environment.isWindows) std.fs.Dir.openFileW else std.fs.Dir.openFile; + + var in_file = try openFile(entry.dir, entry.basename, .{ .mode = .read_only }); defer in_file.close(); - if (comptime Environment.isPosix) { - const stat = in_file.stat() catch continue; - _ = C.fchmod(outfile.handle, stat.mode); + if (comptime Environment.isWindows) { + const in_path = bun.getFdPathW(in_file.handle, &in_buf) catch unreachable; + in_buf[in_path.len] = 0; + const in = in_buf[0..in_path.len :0]; + + const out_path = bun.getFdPathW(outfile.handle, &out_buf) catch unreachable; + out_buf[out_path.len] = 0; + const out = out_buf[0..out_path.len :0]; + + bun.copyFile(in, out) catch |err| { + progress_.root.end(); + + progress_.refresh(); + + Output.prettyError("{s}: copying file {}", .{ @errorName(err), bun.fmt.fmtOSPath(entry.path) }); + Global.crash(); + }; + } else { + if (comptime Environment.isPosix) { + const stat = in_file.stat() catch continue; + _ = C.fchmod(outfile.handle, @intCast(stat.mode)); + } + + bun.copyFile(in_file.handle, outfile.handle) catch |err| { + progress_.root.end(); + + progress_.refresh(); + + Output.prettyError("{s}: copying file {}", .{ @errorName(err), bun.fmt.fmtOSPath(entry.path) }); + Global.crash(); + }; } - - bun.copyFile(in_file.handle, outfile.handle) catch |err| { - progress_.root.end(); - - progress_.refresh(); - - Output.prettyErrorln("{s}: copying file {s}", .{ @errorName(err), entry.path }); - Global.crash(); - }; } return real_file_count; @@ -1306,34 +1333,54 @@ const PackageInstall = struct { var walker_ = Walker.walk( cached_package_dir, this.allocator, - &[_]string{}, - &[_]string{"node_modules"}, + &[_]bun.OSPathSlice{}, + &[_]bun.OSPathSlice{bun.OSPathLiteral("node_modules")}, ) catch |err| return Result{ .fail = .{ .err = err, .step = .opening_cache_dir }, }; defer walker_.deinit(); - var buf: if (Environment.isWindows) [2048]u16 else [0]u16 = undefined; - var buf2: if (Environment.isWindows) [2048]u16 else [0]u16 = undefined; + var subdir = this.destination_dir.makeOpenPath(bun.span(this.destination_dir_subpath), .{}) catch |err| return Result{ + .fail = .{ .err = err, .step = .opening_cache_dir }, + }; + + defer subdir.close(); + + var buf: if (Environment.isWindows) bun.WPathBuffer else [0]u16 = undefined; + var buf2: if (Environment.isWindows) bun.WPathBuffer else [0]u16 = undefined; var to_copy_buf: []u16 = undefined; var to_copy_buf2: []u16 = undefined; if (comptime Environment.isWindows) { - buf[0] = '\\'; - buf[1] = '\\'; - buf[2] = '?'; - buf[3] = '\\'; + var fd_path_buf: bun.PathBuffer = undefined; - strings.copyU8IntoU16(buf[3..], this.destination_dir_subpath); - buf[3 + this.destination_dir_subpath.len] = 0; + // buf[0] = '\\'; + // buf[1] = '\\'; + // buf[2] = '?'; + // buf[3] = '\\'; + // const dest_path = try bun.getFdPath(subdir.fd, &fd_path_buf); + // strings.copyU8IntoU16(buf[4..], dest_path); + // buf[dest_path.len + 4] = '\\'; + // to_copy_buf = buf[dest_path.len + 5 ..]; - buf2[0] = '\\'; - buf2[1] = '\\'; - buf2[2] = '?'; - buf2[3] = '\\'; - strings.copyU8IntoU16(buf2[3..], this.cache_dir_subpath); + // buf2[0] = '\\'; + // buf2[1] = '\\'; + // buf2[2] = '?'; + // buf2[3] = '\\'; + // const cache_path = try bun.getFdPath(cached_package_dir.fd, &fd_path_buf); + // strings.copyU8IntoU16(buf2[4..], cache_path); + // buf2[cache_path.len + 4] = '\\'; + // to_copy_buf2 = buf2[cache_path.len + 5 ..]; - to_copy_buf = buf[0 .. 4 + this.destination_dir_subpath.len :0]; - to_copy_buf2 = buf2[0 .. 4 + this.cache_dir_subpath.len :0]; + // TODO(dylan-conway): find out why //?/ isn't working + const dest_path = try bun.getFdPath(subdir.fd, &fd_path_buf); + strings.copyU8IntoU16(&buf, dest_path); + buf[dest_path.len] = '\\'; + to_copy_buf = buf[dest_path.len + 1 ..]; + + const cache_path = try bun.getFdPath(cached_package_dir.fd, &fd_path_buf); + strings.copyU8IntoU16(&buf2, cache_path); + buf2[cache_path.len] = '\\'; + to_copy_buf2 = buf2[cache_path.len + 1 ..]; } const FileCopier = struct { @@ -1349,7 +1396,8 @@ const PackageInstall = struct { while (try walker.next()) |entry| { switch (entry.kind) { .directory => { - std.os.mkdirat(destination_dir.fd, entry.path, 0o755) catch {}; + const mkdirat = if (comptime Environment.isWindows) std.os.mkdiratW else std.os.mkdirat; + mkdirat(destination_dir.fd, entry.path, 0o755) catch {}; }, .file => { if (comptime Environment.isWindows) { @@ -1357,18 +1405,11 @@ const PackageInstall = struct { return error.NameTooLong; } - // TODO: this copy shouldn't be necessary in the first place. - strings.copyU8IntoU16( - to_copy_into1, - entry.path, - ); + @memcpy(to_copy_into1[0..entry.path.len], entry.path); head1[entry.path.len + (head1.len - to_copy_into1.len)] = 0; const dest: [:0]u16 = head1[0 .. entry.path.len + head1.len - to_copy_into1.len :0]; - strings.copyU8IntoU16( - to_copy_into2, - entry.path, - ); + @memcpy(to_copy_into2[0..entry.path.len], entry.path); head2[entry.path.len + (head1.len - to_copy_into2.len)] = 0; const src: [:0]u16 = head2[0 .. entry.path.len + head2.len - to_copy_into2.len :0]; @@ -1404,12 +1445,6 @@ const PackageInstall = struct { } }; - var subdir = this.destination_dir.makeOpenPath(bun.span(this.destination_dir_subpath), .{}) catch |err| return Result{ - .fail = .{ .err = err, .step = .opening_cache_dir }, - }; - - defer subdir.close(); - this.file_count = FileCopier.copy( subdir, &walker_, @@ -1448,8 +1483,11 @@ const PackageInstall = struct { var walker_ = Walker.walk( cached_package_dir, this.allocator, - &[_]string{}, - &[_]string{ "node_modules", ".git" }, + &[_]bun.OSPathSlice{}, + &[_]bun.OSPathSlice{ + bun.OSPathLiteral("node_modules"), + bun.OSPathLiteral(".git"), + }, ) catch |err| return Result{ .fail = .{ .err = err, .step = .opening_cache_dir }, }; @@ -6005,9 +6043,6 @@ pub const PackageManager = struct { cli: CommandLineArguments, comptime subcommand: Subcommand, ) !*PackageManager { - if (Environment.isWindows and !Environment.isDebug) { - @panic("Windows support for bun install is not implemented yet"); - } // assume that spawning a thread will take a lil so we do that asap try HTTP.HTTPThread.init(); @@ -7245,10 +7280,6 @@ pub const PackageManager = struct { comptime op: Lockfile.Package.Diff.Op, comptime subcommand: Subcommand, ) !void { - if (Environment.isWindows and !Environment.isDebug) { - @panic("Windows support for bun install is not implemented yet"); - } - var manager = init(ctx, subcommand) catch |err| brk: { if (err == error.MissingPackageJSON) { switch (op) { @@ -7689,6 +7720,9 @@ pub const PackageManager = struct { /// Increments the number of installed packages for a tree id and runs available scripts /// if the tree is finished. pub fn incrementTreeInstallCount(this: *PackageInstaller, tree_id: Lockfile.Tree.Id, comptime log_level: Options.LogLevel) void { + if (comptime Environment.isWindows) { + return bun.todo(@src(), {}); + } if (comptime Environment.allow_assert) { std.debug.assert(tree_id != Lockfile.Tree.invalid_id); } @@ -7715,6 +7749,9 @@ pub const PackageManager = struct { } pub fn runAvailableScripts(this: *PackageInstaller, comptime log_level: Options.LogLevel) void { + if (comptime Environment.isWindows) { + return bun.todo(@src(), {}); + } var i: usize = this.pending_lifecycle_scripts.items.len; while (i > 0) { i -= 1; @@ -7751,6 +7788,9 @@ pub const PackageManager = struct { } pub fn completeRemainingScripts(this: *PackageInstaller, comptime log_level: Options.LogLevel) void { + if (comptime Environment.isWindows) { + return bun.todo(@src(), {}); + } for (this.pending_lifecycle_scripts.items) |entry| { const package_name = entry.list.first().package_name; while (LifecycleScriptSubprocess.alive_count.load(.Monotonic) >= this.manager.options.max_concurrent_lifecycle_scripts) { @@ -8238,7 +8278,7 @@ pub const PackageManager = struct { const binding_dot_gyp_path = Path.joinAbsStringZ( this.node_modules_folder_path.items, &[_]string{ folder_name, "binding.gyp" }, - .posix, + .auto, ); break :brk Syscall.exists(binding_dot_gyp_path); @@ -8248,7 +8288,7 @@ pub const PackageManager = struct { this.node_modules_folder_path.items, &path_buf_to_use, &[_]string{destination_dir_subpath}, - .posix, + .auto, ); if (scripts.enqueue( @@ -9316,7 +9356,7 @@ pub const PackageManager = struct { const binding_dot_gyp_path = Path.joinAbsStringZ( Fs.FileSystem.instance.top_level_dir, &[_]string{"binding.gyp"}, - .posix, + .auto, ); if (root.scripts.hasAny()) { const add_node_gyp_rebuild_script = root.scripts.install.isEmpty() and root.scripts.postinstall.isEmpty() and Syscall.exists(binding_dot_gyp_path); @@ -9476,6 +9516,9 @@ pub const PackageManager = struct { list: Lockfile.Package.Scripts.List, comptime log_level: PackageManager.Options.LogLevel, ) !void { + if (comptime Environment.isWindows) { + return bun.todo(@src(), {}); + } var any_scripts = false; for (list.items) |maybe_item| { if (maybe_item != null) { diff --git a/src/install/lockfile.zig b/src/install/lockfile.zig index 2c3a40c5ba..af19bd9109 100644 --- a/src/install/lockfile.zig +++ b/src/install/lockfile.zig @@ -1068,7 +1068,7 @@ pub const Printer = struct { }, .not_found => { Output.prettyErrorln("lockfile not found: {}", .{ - strings.QuotedFormatter{ .text = std.mem.sliceAsBytes(lockfile_path) }, + bun.fmt.QuotedFormatter{ .text = std.mem.sliceAsBytes(lockfile_path) }, }); Global.crash(); }, @@ -1645,7 +1645,7 @@ pub fn saveToDisk(this: *Lockfile, filename: stringZ) void { .file = .{ .fd = bun.toFD(file.handle), }, - .dirfd = if (!Environment.isWindows) bun.invalid_fd else @panic("TODO"), + .dirfd = bun.invalid_fd, .data = .{ .string = .{ .utf8 = bun.JSC.ZigString.Slice.from(bytes.items, bun.default_allocator) } }, }, .sync, @@ -1659,15 +1659,14 @@ pub fn saveToDisk(this: *Lockfile, filename: stringZ) void { } } - if (comptime Environment.isWindows) { - // TODO: make this executable - @panic("TODO on Windows"); - } else { - _ = C.fchmod( - tmpfile.fd.cast(), - // chmod 777 - 0o0000010 | 0o0000100 | 0o0000001 | 0o0001000 | 0o0000040 | 0o0000004 | 0o0000002 | 0o0000400 | 0o0000200 | 0o0000020, - ); + // chmod 777 + switch (bun.sys.fchmod(tmpfile.fd, 0o777)) { + .err => |err| { + tmpfile.dir().deleteFileZ(tmpname) catch {}; + Output.prettyErrorln("error: failed to change lockfile permissions: {s}", .{@tagName(err.getErrno())}); + Global.crash(); + }, + .result => {}, } tmpfile.promoteToCWD(tmpname, filename) catch |err| { diff --git a/src/install/resolvers/folder_resolver.zig b/src/install/resolvers/folder_resolver.zig index 98ee85ec79..f64bd404a3 100644 --- a/src/install/resolvers/folder_resolver.zig +++ b/src/install/resolvers/folder_resolver.zig @@ -41,7 +41,7 @@ pub const FolderResolution = union(Tag) { } if (this.quoted) { - const quoted = strings.QuotedFormatter{ + const quoted = bun.fmt.QuotedFormatter{ .text = paths.rel, }; try quoted.format(fmt, opts, writer); diff --git a/src/js_parser.zig b/src/js_parser.zig index 86d080d4a7..ec741c1268 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -3709,7 +3709,7 @@ pub const Parser = struct { var notes = ListManaged(logger.Data).init(p.allocator); try notes.append(logger.Data{ - .text = try std.fmt.allocPrint(p.allocator, "Try require({}) instead", .{strings.QuotedFormatter{ .text = record.path.text }}), + .text = try std.fmt.allocPrint(p.allocator, "Try require({}) instead", .{bun.fmt.QuotedFormatter{ .text = record.path.text }}), }); if (uses_module_ref) { @@ -17708,7 +17708,7 @@ fn NewParser_( if (!named_export_entry.found_existing) { const new_ref = p.newSymbol( .other, - std.fmt.allocPrint(p.allocator, "${any}", .{strings.fmtIdentifier(key)}) catch unreachable, + std.fmt.allocPrint(p.allocator, "${any}", .{bun.fmt.fmtIdentifier(key)}) catch unreachable, ) catch unreachable; p.module_scope.generated.push(p.allocator, new_ref) catch unreachable; named_export_entry.value_ptr.* = .{ @@ -17814,7 +17814,7 @@ fn NewParser_( if (!named_export_entry.found_existing) { const new_ref = p.newSymbol( .other, - std.fmt.allocPrint(p.allocator, "${any}", .{strings.fmtIdentifier(name)}) catch unreachable, + std.fmt.allocPrint(p.allocator, "${any}", .{bun.fmt.fmtIdentifier(name)}) catch unreachable, ) catch unreachable; p.module_scope.generated.push(p.allocator, new_ref) catch unreachable; named_export_entry.value_ptr.* = .{ diff --git a/src/libarchive/libarchive-bindings.zig b/src/libarchive/libarchive-bindings.zig index 2f71491a51..73a6cc8cb7 100644 --- a/src/libarchive/libarchive-bindings.zig +++ b/src/libarchive/libarchive-bindings.zig @@ -1,3 +1,4 @@ +const bun = @import("root").bun; pub const wchar_t = c_int; pub const la_int64_t = i64; pub const la_ssize_t = isize; @@ -5,7 +6,7 @@ pub const struct_archive = opaque {}; pub const struct_archive_entry = opaque {}; pub const archive = struct_archive; pub const archive_entry = struct_archive_entry; -const mode_t = @import("std").c.mode_t; +const mode_t = bun.Mode; const FILE = @import("std").c.FILE; // const time_t = @import("std").c.time_t; const dev_t = @import("std").c.dev_t; @@ -427,7 +428,7 @@ pub extern fn archive_entry_dev_is_set(*struct_archive_entry) c_int; pub extern fn archive_entry_devmajor(*struct_archive_entry) dev_t; pub extern fn archive_entry_devminor(*struct_archive_entry) dev_t; pub extern fn archive_entry_filetype(*struct_archive_entry) mode_t; -pub extern fn archive_entry_fflags(*struct_archive_entry, [*c]c_ulong, [*c]c_ulong) void; +pub extern fn archive_entry_fflags(*struct_archive_entry, [*c]u64, [*c]u64) void; pub extern fn archive_entry_fflags_text(*struct_archive_entry) [*c]const u8; pub extern fn archive_entry_gid(*struct_archive_entry) la_int64_t; pub extern fn archive_entry_gname(*struct_archive_entry) [*c]const u8; @@ -477,7 +478,7 @@ pub extern fn archive_entry_set_dev(*struct_archive_entry, dev_t) void; pub extern fn archive_entry_set_devmajor(*struct_archive_entry, dev_t) void; pub extern fn archive_entry_set_devminor(*struct_archive_entry, dev_t) void; pub extern fn archive_entry_set_filetype(*struct_archive_entry, c_uint) void; -pub extern fn archive_entry_set_fflags(*struct_archive_entry, c_ulong, c_ulong) void; +pub extern fn archive_entry_set_fflags(*struct_archive_entry, u64, u64) void; pub extern fn archive_entry_copy_fflags_text(*struct_archive_entry, [*c]const u8) [*c]const u8; pub extern fn archive_entry_copy_fflags_text_w(*struct_archive_entry, [*c]const wchar_t) [*c]const wchar_t; pub extern fn archive_entry_set_gid(*struct_archive_entry, la_int64_t) void; diff --git a/src/libarchive/libarchive.zig b/src/libarchive/libarchive.zig index c030c6d08c..a5e2f10db5 100644 --- a/src/libarchive/libarchive.zig +++ b/src/libarchive/libarchive.zig @@ -479,10 +479,6 @@ pub const Archive = struct { comptime close_handles: bool, comptime log: bool, ) !u32 { - if (Environment.isWindows) { - @panic("TODO: sort out the file descriptor issues here."); - } - var entry: *lib.archive_entry = undefined; var stream: BufferReadStream = undefined; @@ -510,7 +506,11 @@ pub const Archive = struct { } } - var tokenizer = std.mem.tokenize(u8, bun.asByteSlice(pathname), std.fs.path.sep_str); + var tokenizer = if (comptime Environment.isWindows) + // TODO(dylan-conway): I think this should only be '/' + std.mem.tokenizeAny(u8, bun.asByteSlice(pathname), "/\\") + else + std.mem.tokenizeScalar(u8, bun.asByteSlice(pathname), '/'); comptime var depth_i: usize = 0; inline while (depth_i < depth_to_skip) : (depth_i += 1) { @@ -576,7 +576,7 @@ pub const Archive = struct { }; }, Kind.file => { - const mode = @as(std.os.mode_t, @intCast(lib.archive_entry_perm(entry))); + const mode: bun.Mode = if (comptime Environment.isWindows) 0 else @intCast(lib.archive_entry_perm(entry)); const file = dir.createFileZ(pathname, .{ .truncate = true, .mode = mode }) catch |err| brk: { switch (err) { error.AccessDenied, error.FileNotFound => { @@ -591,7 +591,11 @@ pub const Archive = struct { }, } }; - defer if (comptime close_handles) file.close(); + const file_handle = bun.toLibUVOwnedFD(file.handle); + + defer { + if (comptime close_handles) _ = bun.sys.close(file_handle); + } const entry_size = @max(lib.archive_entry_size(entry), 0); const size = @as(usize, @intCast(entry_size)); @@ -616,24 +620,26 @@ pub const Archive = struct { const read = lib.archive_read_data(archive, plucker_.contents.list.items.ptr, size); try plucker_.contents.inflate(@as(usize, @intCast(read))); plucker_.found = read > 0; - plucker_.fd = bun.toFD(file.handle); + plucker_.fd = file_handle; continue :loop; } } } // archive_read_data_into_fd reads in chunks of 1 MB // #define MAX_WRITE (1024 * 1024) - if (size > 1_000_000) { - C.preallocate_file( - file.handle, - 0, - entry_size, - ) catch {}; + if (comptime Environment.isLinux) { + if (size > 1_000_000) { + C.preallocate_file( + file_handle.cast(), + 0, + entry_size, + ) catch {}; + } } var retries_remaining: u8 = 5; possibly_retry: while (retries_remaining != 0) : (retries_remaining -= 1) { - switch (lib.archive_read_data_into_fd(archive, bun.uvfdcast(file.handle))) { + switch (lib.archive_read_data_into_fd(archive, bun.uvfdcast(file_handle))) { lib.ARCHIVE_EOF => break :loop, lib.ARCHIVE_OK => break :possibly_retry, lib.ARCHIVE_RETRY => { diff --git a/src/logger.zig b/src/logger.zig index 3d350113ed..d01fb2c951 100644 --- a/src/logger.zig +++ b/src/logger.zig @@ -1328,7 +1328,7 @@ pub const Source = struct { index: Index = Index.source(0), - pub fn fmtIdentifier(this: *const Source) strings.FormatValidIdentifier { + pub fn fmtIdentifier(this: *const Source) bun.fmt.FormatValidIdentifier { return this.path.name.fmtIdentifier(); } diff --git a/src/napi/napi.zig b/src/napi/napi.zig index 8e75a430fa..f371336aec 100644 --- a/src/napi/napi.zig +++ b/src/napi/napi.zig @@ -347,7 +347,7 @@ pub export fn napi_create_string_utf16(env: napi_env, str: ?[*]const char16_t, l }; if (comptime bun.Environment.allow_assert) - log("napi_create_string_utf16: {d} {any}", .{ slice.len, strings.FormatUTF16{ .buf = slice[0..@min(slice.len, 512)] } }); + log("napi_create_string_utf16: {d} {any}", .{ slice.len, bun.fmt.FormatUTF16{ .buf = slice[0..@min(slice.len, 512)] } }); if (slice.len == 0) { setNapiValue(result, bun.String.empty.toJS(env)); diff --git a/src/options.zig b/src/options.zig index 5fc909a7c7..523838abb3 100644 --- a/src/options.zig +++ b/src/options.zig @@ -1995,6 +1995,9 @@ pub const OutputFile = struct { Fs.FileSystem.setMaxFd(fd_out); Fs.FileSystem.setMaxFd(fd_in); do_close = Fs.FileSystem.instance.fs.needToCloseFiles(); + + // use paths instead of bun.getFdPathW() + @panic("TODO windows"); } defer { diff --git a/src/output.zig b/src/output.zig index 7d729e8621..fdba450708 100644 --- a/src/output.zig +++ b/src/output.zig @@ -186,7 +186,7 @@ pub var stderr_descriptor_type = OutputStreamDescriptor.unknown; pub var stdout_descriptor_type = OutputStreamDescriptor.unknown; pub inline fn isEmojiEnabled() bool { - return enable_ansi_colors and !Environment.isWindows; + return enable_ansi_colors; } pub fn isGithubAction() bool { diff --git a/src/resolver/resolver.zig b/src/resolver/resolver.zig index a1f3392849..1ea2ef68cf 100644 --- a/src/resolver/resolver.zig +++ b/src/resolver/resolver.zig @@ -3975,9 +3975,9 @@ pub const Resolver = struct { const pretty = r.prettyPath(Path.init(tsconfigpath)); if (err == error.ENOENT or err == error.FileNotFound) { - r.log.addErrorFmt(null, logger.Loc.Empty, r.allocator, "Cannot find tsconfig file {}", .{bun.strings.QuotedFormatter{ .text = pretty }}) catch {}; + r.log.addErrorFmt(null, logger.Loc.Empty, r.allocator, "Cannot find tsconfig file {}", .{bun.fmt.QuotedFormatter{ .text = pretty }}) catch {}; } else if (err != error.ParseErrorAlreadyLogged and err != error.IsDir and err != error.EISDIR) { - r.log.addErrorFmt(null, logger.Loc.Empty, r.allocator, "Cannot read file {}: {s}", .{ bun.strings.QuotedFormatter{ .text = pretty }, @errorName(err) }) catch {}; + r.log.addErrorFmt(null, logger.Loc.Empty, r.allocator, "Cannot read file {}: {s}", .{ bun.fmt.QuotedFormatter{ .text = pretty }, @errorName(err) }) catch {}; } break :brk null; }; @@ -3992,7 +3992,7 @@ pub const Resolver = struct { const parent_config_maybe = r.parseTSConfig(abs_path, bun.STDIN_FD) catch |err| { r.log.addDebugFmt(null, logger.Loc.Empty, r.allocator, "{s} loading tsconfig.json extends {}", .{ @errorName(err), - strings.QuotedFormatter{ + bun.fmt.QuotedFormatter{ .text = abs_path, }, }) catch {}; diff --git a/src/standalone_bun.zig b/src/standalone_bun.zig index e5819f7467..110dcb42e8 100644 --- a/src/standalone_bun.zig +++ b/src/standalone_bun.zig @@ -4,6 +4,7 @@ const bun = @import("root").bun; const std = @import("std"); const Schema = bun.Schema.Api; +const strings = bun.strings; const Environment = bun.Environment; @@ -329,11 +330,27 @@ pub const StandaloneModuleGraph = struct { }; defer _ = Syscall.close(self_fd); - bun.copyFile(bun.fdcast(self_fd), bun.fdcast(fd)) catch |err| { - Output.prettyErrorln("error: failed to copy bun executable into temporary file: {s}", .{@errorName(err)}); - cleanup(zname, fd); - Global.exit(1); - }; + + if (comptime Environment.isWindows) { + var in_buf: bun.WPathBuffer = undefined; + strings.copyU8IntoU16(&in_buf, self_exeZ); + const in = in_buf[0..self_exe.len :0]; + var out_buf: bun.WPathBuffer = undefined; + strings.copyU8IntoU16(&out_buf, zname); + const out = out_buf[0..zname.len :0]; + + bun.copyFile(in, out) catch |err| { + Output.prettyErrorln("error: failed to copy bun executable into temporary file: {s}", .{@errorName(err)}); + cleanup(zname, fd); + Global.exit(1); + }; + } else { + bun.copyFile(bun.fdcast(self_fd), bun.fdcast(fd)) catch |err| { + Output.prettyErrorln("error: failed to copy bun executable into temporary file: {s}", .{@errorName(err)}); + cleanup(zname, fd); + Global.exit(1); + }; + } break :brk fd; }; diff --git a/src/string.zig b/src/string.zig index 47af6ddb19..93afb1658c 100644 --- a/src/string.zig +++ b/src/string.zig @@ -372,7 +372,7 @@ pub const String = extern struct { return BunString__fromUTF16(bytes.ptr, bytes.len); } - pub fn createFromOSPath(os_path: bun.OSPathSliceWithoutSentinel) String { + pub fn createFromOSPath(os_path: bun.OSPathSlice) String { return switch (@TypeOf(os_path)) { []const u8 => create(os_path), []const u16 => createUTF16(os_path), diff --git a/src/string_immutable.zig b/src/string_immutable.zig index c3758b3400..6a6fd76ea2 100644 --- a/src/string_immutable.zig +++ b/src/string_immutable.zig @@ -25,6 +25,7 @@ pub inline fn contains(self: string, str: string) bool { } pub fn w(comptime str: []const u8) [:0]const u16 { + if (!@inComptime()) @compileError("strings.w() must be called in a comptime context"); comptime var output: [str.len + 1]u16 = undefined; for (str, 0..) |c, i| { @@ -206,78 +207,6 @@ pub fn indexOfCharNeg(self: string, char: u8) i32 { return -1; } -/// Format a string to an ECMAScript identifier. -/// Unlike the string_mutable.zig version, this always allocate/copy -pub fn fmtIdentifier(name: string) FormatValidIdentifier { - return FormatValidIdentifier{ .name = name }; -} - -/// Format a string to an ECMAScript identifier. -/// Different implementation than string_mutable because string_mutable may avoid allocating -/// This will always allocate -pub const FormatValidIdentifier = struct { - name: string, - pub fn format(self: FormatValidIdentifier, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { - var iterator = strings.CodepointIterator.init(self.name); - var cursor = strings.CodepointIterator.Cursor{}; - - var has_needed_gap = false; - var needs_gap = false; - var start_i: usize = 0; - - if (!iterator.next(&cursor)) { - try writer.writeAll("_"); - return; - } - - // Common case: no gap necessary. No allocation necessary. - needs_gap = !js_lexer.isIdentifierStart(cursor.c); - if (!needs_gap) { - // Are there any non-alphanumeric chars at all? - while (iterator.next(&cursor)) { - if (!js_lexer.isIdentifierContinue(cursor.c) or cursor.width > 1) { - needs_gap = true; - start_i = cursor.i; - break; - } - } - } - - if (needs_gap) { - needs_gap = false; - if (start_i > 0) try writer.writeAll(self.name[0..start_i]); - var slice = self.name[start_i..]; - iterator = strings.CodepointIterator.init(slice); - cursor = strings.CodepointIterator.Cursor{}; - - while (iterator.next(&cursor)) { - if (js_lexer.isIdentifierContinue(cursor.c) and cursor.width == 1) { - if (needs_gap) { - try writer.writeAll("_"); - needs_gap = false; - has_needed_gap = true; - } - try writer.writeAll(slice[cursor.i .. cursor.i + @as(u32, cursor.width)]); - } else if (!needs_gap) { - needs_gap = true; - // skip the code point, replace it with a single _ - } - } - - // If it ends with an emoji - if (needs_gap) { - try writer.writeAll("_"); - needs_gap = false; - has_needed_gap = true; - } - - return; - } - - try writer.writeAll(self.name); - } -}; - pub fn indexOfSigned(self: string, str: string) i32 { const i = std.mem.indexOf(u8, self, str) orelse return -1; return @as(i32, @intCast(i)); @@ -768,82 +697,6 @@ pub fn endsWithAny(self: string, str: string) bool { return false; } -// Formats a string to be safe to output in a Github action. -// - Encodes "\n" as "%0A" to support multi-line strings. -// https://github.com/actions/toolkit/issues/193#issuecomment-605394935 -// - Strips ANSI output as it will appear malformed. -pub fn githubActionWriter(writer: anytype, self: string) !void { - var offset: usize = 0; - const end = @as(u32, @truncate(self.len)); - while (offset < end) { - if (indexOfNewlineOrNonASCIIOrANSI(self, @as(u32, @truncate(offset)))) |i| { - const byte = self[i]; - if (byte > 0x7F) { - offset += @max(wtf8ByteSequenceLength(byte), 1); - continue; - } - if (i > 0) { - try writer.writeAll(self[offset..i]); - } - var n: usize = 1; - if (byte == '\n') { - try writer.writeAll("%0A"); - } else if (i + 1 < end) { - const next = self[i + 1]; - if (byte == '\r' and next == '\n') { - n += 1; - try writer.writeAll("%0A"); - } else if (byte == '\x1b' and next == '[') { - n += 1; - if (i + 2 < end) { - const remain = self[(i + 2)..@min(i + 5, end)]; - if (indexOfChar(remain, 'm')) |j| { - n += j + 1; - } - } - } - } - offset = i + n; - } else { - try writer.writeAll(self[offset..end]); - break; - } - } -} - -pub const GithubActionFormatter = struct { - text: string, - - pub fn format(this: GithubActionFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { - try githubActionWriter(writer, this.text); - } -}; - -pub fn githubAction(self: string) strings.GithubActionFormatter { - return GithubActionFormatter{ - .text = self, - }; -} - -pub fn quotedWriter(writer: anytype, self: string) !void { - const remain = self; - if (strings.containsNewlineOrNonASCIIOrQuote(remain)) { - try bun.js_printer.writeJSONString(self, @TypeOf(writer), writer, strings.Encoding.utf8); - } else { - try writer.writeAll("\""); - try writer.writeAll(self); - try writer.writeAll("\""); - } -} - -pub const QuotedFormatter = struct { - text: []const u8, - - pub fn format(this: QuotedFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { - try strings.quotedWriter(writer, this.text); - } -}; - pub fn quotedAlloc(allocator: std.mem.Allocator, self: string) !string { var count: usize = 0; for (self) |char| { @@ -3446,6 +3299,15 @@ pub fn isValidUTF8(slice: []const u8) bool { } pub fn isAllASCII(slice: []const u8) bool { + if (@inComptime()) { + for (slice) |char| { + if (char > 127) { + return false; + } + } + return true; + } + if (bun.FeatureFlags.use_simdutf) return bun.simdutf.validate.ascii(slice); @@ -3486,15 +3348,6 @@ pub fn isAllASCII(slice: []const u8) bool { return true; } -pub fn isAllASCIISimple(comptime slice: []const u8) bool { - for (slice) |char| { - if (char > 127) { - return false; - } - } - return true; -} - //#define U16_LEAD(supplementary) (UChar)(((supplementary)>>10)+0xd7c0) pub inline fn u16Lead(supplementary: anytype) u16 { return @as(u16, @intCast((supplementary >> 10) + 0xd7c0)); @@ -4418,106 +4271,10 @@ test "firstNonASCII16" { } } -const SharedTempBuffer = [32 * 1024]u8; -fn getSharedBuffer() []u8 { - return std.mem.asBytes(shared_temp_buffer_ptr orelse brk: { - shared_temp_buffer_ptr = bun.default_allocator.create(SharedTempBuffer) catch unreachable; - break :brk shared_temp_buffer_ptr.?; - }); -} -threadlocal var shared_temp_buffer_ptr: ?*SharedTempBuffer = null; - -pub fn formatUTF16Type(comptime Slice: type, slice_: Slice, writer: anytype) !void { - var chunk = getSharedBuffer(); - - // Defensively ensure recursion doesn't cause the buffer to be overwritten in-place - shared_temp_buffer_ptr = null; - defer { - if (shared_temp_buffer_ptr) |existing| { - if (existing != chunk.ptr) { - bun.default_allocator.destroy(@as(*SharedTempBuffer, @ptrCast(chunk.ptr))); - } - } else { - shared_temp_buffer_ptr = @ptrCast(chunk.ptr); - } - } - - var slice = slice_; - - while (slice.len > 0) { - const result = strings.copyUTF16IntoUTF8(chunk, Slice, slice, true); - if (result.read == 0 or result.written == 0) - break; - try writer.writeAll(chunk[0..result.written]); - slice = slice[result.read..]; - } -} - -pub fn formatUTF16(slice_: []align(1) const u16, writer: anytype) !void { - return formatUTF16Type([]align(1) const u16, slice_, writer); -} - -pub const FormatUTF16 = struct { - buf: []const u16, - pub fn format(self: @This(), comptime _: []const u8, opts: anytype, writer: anytype) !void { - _ = opts; - try formatUTF16Type([]const u16, self.buf, writer); - } -}; - -pub const FormatUTF8 = struct { - buf: []const u8, - pub fn format(self: @This(), comptime _: []const u8, _: anytype, writer: anytype) !void { - try writer.writeAll(self.buf); - } -}; - -pub fn fmtUTF16(buf: []const u16) FormatUTF16 { - return FormatUTF16{ .buf = buf }; -} - -pub const FormatOSPath = if (Environment.isWindows) FormatUTF16 else FormatUTF8; - -pub fn fmtOSPath(buf: bun.OSPathSliceWithoutSentinel) FormatOSPath { - return FormatOSPath{ .buf = buf }; -} - -pub fn formatLatin1(slice_: []const u8, writer: anytype) !void { - var chunk = getSharedBuffer(); - var slice = slice_; - - // Defensively ensure recursion doesn't cause the buffer to be overwritten in-place - shared_temp_buffer_ptr = null; - defer { - if (shared_temp_buffer_ptr) |existing| { - if (existing != chunk.ptr) { - bun.default_allocator.destroy(@as(*SharedTempBuffer, @ptrCast(chunk.ptr))); - } - } else { - shared_temp_buffer_ptr = @ptrCast(chunk.ptr); - } - } - - while (strings.firstNonASCII(slice)) |i| { - if (i > 0) { - try writer.writeAll(slice[0..i]); - slice = slice[i..]; - } - const result = strings.copyLatin1IntoUTF8(chunk, @TypeOf(slice), slice[0..@min(chunk.len, slice.len)]); - if (result.read == 0 or result.written == 0) - break; - try writer.writeAll(chunk[0..result.written]); - slice = slice[result.read..]; - } - - if (slice.len > 0) - try writer.writeAll(slice); // write the remaining bytes -} - test "print UTF16" { var err = std.io.getStdErr(); const utf16 = comptime toUTF16Literal("❌ ✅ opkay "); - try formatUTF16(utf16, err.writer()); + try bun.fmt.str.formatUTF16(utf16, err.writer()); // std.unicode.fmtUtf16le(utf16le: []const u16) } @@ -5362,71 +5119,6 @@ pub fn concatIfNeeded( std.debug.assert(remain.len == 0); } -pub const HostFormatter = struct { - host: string, - port: ?u16 = null, - is_https: bool = false, - - pub fn format(formatter: HostFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { - if (strings.indexOfChar(formatter.host, ':') != null) { - try writer.writeAll(formatter.host); - return; - } - - try writer.writeAll(formatter.host); - - const is_port_optional = formatter.port == null or (formatter.is_https and formatter.port == 443) or - (!formatter.is_https and formatter.port == 80); - if (!is_port_optional) { - try writer.print(":{d}", .{formatter.port.?}); - return; - } - } -}; - -const Proto = enum { - http, - https, - unix, -}; - -pub const URLFormatter = struct { - proto: Proto = .http, - hostname: ?string = null, - port: ?u16 = null, - - pub fn format(this: URLFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { - try writer.print("{s}://", .{switch (this.proto) { - .http => "http", - .https => "https", - .unix => "unix", - }}); - - if (this.hostname) |hostname| { - const needs_brackets = hostname[0] != '[' and strings.isIPV6Address(hostname); - if (needs_brackets) { - try writer.print("[{s}]", .{hostname}); - } else { - try writer.writeAll(hostname); - } - } else { - try writer.writeAll("localhost"); - } - - if (this.proto == .unix) { - return; - } - - const is_port_optional = this.port == null or (this.proto == .https and this.port == 443) or - (this.proto == .http and this.port == 80); - if (is_port_optional) { - try writer.writeAll("/"); - } else { - try writer.print(":{d}/", .{this.port.?}); - } - } -}; - pub fn convertUTF8toUTF16InBuffer( buf: []u16, input: []const u8, diff --git a/src/sys.zig b/src/sys.zig index 336864d0a7..c830bbba50 100644 --- a/src/sys.zig +++ b/src/sys.zig @@ -158,11 +158,15 @@ pub fn getcwd(buf: *[bun.MAX_PATH_BYTES]u8) Maybe([]const u8) { } pub fn fchmod(fd: bun.FileDescriptor, mode: bun.Mode) Maybe(void) { + if (comptime Environment.isWindows) { + return sys_uv.fchmod(fd, mode); + } + return Maybe(void).errnoSys(C.fchmod(fd.cast(), mode), .fchmod) orelse Maybe(void).success; } -pub fn chdirOSPath(destination: bun.OSPathSlice) Maybe(void) { +pub fn chdirOSPath(destination: bun.OSPathSliceZ) Maybe(void) { if (comptime Environment.isPosix) { const rc = sys.chdir(destination); return Maybe(void).errnoSys(rc, .chdir) orelse Maybe(void).success; @@ -170,11 +174,11 @@ pub fn chdirOSPath(destination: bun.OSPathSlice) Maybe(void) { if (comptime Environment.isWindows) { if (kernel32.SetCurrentDirectory(destination) == windows.FALSE) { - log("SetCurrentDirectory({}) = {d}", .{ bun.strings.fmtUTF16(destination), kernel32.GetLastError() }); + log("SetCurrentDirectory({}) = {d}", .{ bun.fmt.fmtUTF16(destination), kernel32.GetLastError() }); return Maybe(void).errnoSys(0, .chdir) orelse Maybe(void).success; } - log("SetCurrentDirectory({}) = {d}", .{ bun.strings.fmtUTF16(destination), 0 }); + log("SetCurrentDirectory({}) = {d}", .{ bun.fmt.fmtUTF16(destination), 0 }); return Maybe(void).success; } @@ -207,8 +211,8 @@ pub fn chdir(destination: anytype) Maybe(void) { return Maybe(void).success; } - if (comptime Type == bun.OSPathSlice or Type == [:0]u16) { - return chdirOSPath(@as(bun.OSPathSlice, destination)); + if (comptime Type == bun.OSPathSliceZ or Type == [:0]u16) { + return chdirOSPath(@as(bun.OSPathSliceZ, destination)); } var wbuf: bun.WPathBuffer = undefined; @@ -266,7 +270,10 @@ pub fn mkdir(file_path: [:0]const u8, flags: bun.Mode) Maybe(void) { .windows => { var wbuf: bun.WPathBuffer = undefined; const rc = kernel32.CreateDirectoryW(bun.strings.toWPath(&wbuf, file_path).ptr, null); - return Maybe(void).errnoSys(rc, .mkdir) orelse Maybe(void).success; + return if (rc != 0) + Maybe(void).success + else + Maybe(void).errnoSys(rc, .mkdir) orelse Maybe(void).success; }, else => @compileError("mkdir is not implemented on this platform"), @@ -295,12 +302,14 @@ pub fn mkdirA(file_path: []const u8, flags: bun.Mode) Maybe(void) { if (comptime Environment.isWindows) { var wbuf: bun.WPathBuffer = undefined; const rc = kernel32.CreateDirectoryW(bun.strings.toWPath(&wbuf, file_path).ptr, null); - - return Maybe(void).errnoSys(rc, .mkdir) orelse Maybe(void).success; + return if (rc != 0) + Maybe(void).success + else + Maybe(void).errnoSys(rc, .mkdir) orelse Maybe(void).success; } } -pub fn mkdirOSPath(file_path: bun.OSPathSlice, flags: bun.Mode) Maybe(void) { +pub fn mkdirOSPath(file_path: bun.OSPathSliceZ, flags: bun.Mode) Maybe(void) { return switch (Environment.os) { else => mkdir(file_path, flags), .windows => { @@ -416,7 +425,7 @@ pub fn openDirAtWindows( ); if (comptime Environment.allow_assert) { - log("NtCreateFile({d}, {}) = {d} (dir) = {d}", .{ dirFd, bun.strings.fmtUTF16(path), rc, @intFromPtr(fd) }); + log("NtCreateFile({d}, {}) = {d} (dir) = {d}", .{ dirFd, bun.fmt.fmtUTF16(path), rc, @intFromPtr(fd) }); } switch (windows.Win32Error.fromNTStatus(rc)) { @@ -534,7 +543,7 @@ pub fn openatWindows(dirfd: bun.FileDescriptor, path_: []const u16, flags: bun.M ); if (comptime Environment.allow_assert) { - log("NtCreateFile({d}, {}) = {d} (file) = {d}", .{ dirfd, bun.strings.fmtUTF16(path), rc, @intFromPtr(result) }); + log("NtCreateFile({d}, {}) = {d} (file) = {d}", .{ dirfd, bun.fmt.fmtUTF16(path), rc, @intFromPtr(result) }); } switch (windows.Win32Error.fromNTStatus(rc)) { @@ -576,7 +585,7 @@ pub fn openatWindows(dirfd: bun.FileDescriptor, path_: []const u16, flags: bun.M } } -pub fn openatOSPath(dirfd: bun.FileDescriptor, file_path: bun.OSPathSlice, flags: bun.Mode, perm: bun.Mode) Maybe(bun.FileDescriptor) { +pub fn openatOSPath(dirfd: bun.FileDescriptor, file_path: bun.OSPathSliceZ, flags: bun.Mode, perm: bun.Mode) Maybe(bun.FileDescriptor) { if (comptime Environment.isMac) { // https://opensource.apple.com/source/xnu/xnu-7195.81.3/libsyscall/wrappers/open-base.c const rc = system.@"openat$NOCANCEL"(dirfd.cast(), file_path.ptr, @as(c_uint, @intCast(flags)), @as(c_int, @intCast(perm))); @@ -1493,7 +1502,7 @@ pub fn getMaxPipeSizeOnLinux() usize { ); } -pub fn existsOSPath(path: bun.OSPathSlice) bool { +pub fn existsOSPath(path: bun.OSPathSliceZ) bool { if (comptime Environment.isPosix) { return system.access(path, 0) == 0; } @@ -1501,7 +1510,7 @@ pub fn existsOSPath(path: bun.OSPathSlice) bool { if (comptime Environment.isWindows) { const result = kernel32.GetFileAttributesW(path.ptr); if (Environment.isDebug) { - log("GetFileAttributesW({}) = {d}", .{ bun.strings.fmtUTF16(path), result }); + log("GetFileAttributesW({}) = {d}", .{ bun.fmt.fmtUTF16(path), result }); } return result != windows.INVALID_FILE_ATTRIBUTES; } @@ -1557,7 +1566,7 @@ pub fn existsAt(fd: bun.FileDescriptor, subpath: []const u8) bool { pub extern "C" fn is_executable_file(path: [*:0]const u8) bool; -pub fn isExecutableFileOSPath(path: bun.OSPathSlice) bool { +pub fn isExecutableFileOSPath(path: bun.OSPathSliceZ) bool { if (comptime Environment.isPosix) { return is_executable_file(path); } @@ -1584,7 +1593,7 @@ pub fn isExecutableFileOSPath(path: bun.OSPathSlice) bool { // else => false, // }; - // log("GetBinaryTypeW({}) = {d}. isExecutable={}", .{ bun.strings.fmtUTF16(path), out, result }); + // log("GetBinaryTypeW({}) = {d}. isExecutable={}", .{ bun.fmt.fmtUTF16(path), out, result }); // return result; } diff --git a/src/url.zig b/src/url.zig index f73cf872d1..07dc07ef11 100644 --- a/src/url.zig +++ b/src/url.zig @@ -101,8 +101,8 @@ pub const URL = struct { return "localhost"; } - pub fn displayHost(this: *const URL) strings.HostFormatter { - return strings.HostFormatter{ + pub fn displayHost(this: *const URL) bun.fmt.HostFormatter { + return bun.fmt.HostFormatter{ .host = if (this.host.len > 0) this.host else this.displayHostname(), .port = if (this.port.len > 0) this.getPort() else null, .is_https = this.isHTTPS(), diff --git a/src/walker_skippable.zig b/src/walker_skippable.zig index cafb9d8782..434913c9aa 100644 --- a/src/walker_skippable.zig +++ b/src/walker_skippable.zig @@ -3,28 +3,34 @@ const Allocator = std.mem.Allocator; const Walker = @This(); const bun = @import("root").bun; const path = std.fs.path; +const DirIterator = bun.DirIterator; +const Environment = bun.Environment; +const OSPathSlice = bun.OSPathSlice; stack: std.ArrayList(StackItem), -name_buffer: std.ArrayList(u8), +name_buffer: NameBufferList, skip_filenames: []const u64 = &[_]u64{}, skip_dirnames: []const u64 = &[_]u64{}, skip_all: []const u64 = &[_]u64{}, seed: u64 = 0, +const NameBufferList = std.ArrayList(bun.OSPathChar); + const Dir = std.fs.Dir; +const WrappedIterator = DirIterator.NewWrappedIterator(if (Environment.isWindows) .u16 else .u8); pub const WalkerEntry = struct { /// The containing directory. This can be used to operate directly on `basename` /// rather than `path`, avoiding `error.NameTooLong` for deeply nested paths. /// The directory remains open until `next` or `deinit` is called. dir: Dir, - basename: []const u8, - path: []const u8, + basename: OSPathSlice, + path: OSPathSlice, kind: Dir.Entry.Kind, }; const StackItem = struct { - iter: Dir.Iterator, + iter: WrappedIterator, dirname_len: usize, }; @@ -36,73 +42,81 @@ pub fn next(self: *Walker) !?WalkerEntry { // `top` becomes invalid after appending to `self.stack` var top = &self.stack.items[self.stack.items.len - 1]; var dirname_len = top.dirname_len; - if (try top.iter.next()) |base| { - switch (base.kind) { - .directory => { - if (std.mem.indexOfScalar( - u64, - self.skip_dirnames, - // avoid hashing if there will be 0 results - if (self.skip_dirnames.len > 0) bun.hashWithSeed(self.seed, base.name) else 0, - ) != null) continue; - }, - .file => { - if (std.mem.indexOfScalar( - u64, - self.skip_filenames, - // avoid hashing if there will be 0 results - if (self.skip_filenames.len > 0) bun.hashWithSeed(self.seed, base.name) else 0, - ) != null) continue; - }, + switch (top.iter.next()) { + .err => |err| return bun.errnoToZigErr(err.errno), + .result => |res| { + if (res) |base| { + switch (base.kind) { + .directory => { + if (std.mem.indexOfScalar( + u64, + self.skip_dirnames, + // avoid hashing if there will be 0 results + if (self.skip_dirnames.len > 0) bun.hashWithSeed(self.seed, std.mem.sliceAsBytes(base.name.slice())) else 0, + ) != null) continue; + }, + .file => { + if (std.mem.indexOfScalar( + u64, + self.skip_filenames, + // avoid hashing if there will be 0 results + if (self.skip_filenames.len > 0) bun.hashWithSeed(self.seed, std.mem.sliceAsBytes(base.name.slice())) else 0, + ) != null) continue; + }, - // we don't know what it is for a symlink - .sym_link => { - if (std.mem.indexOfScalar( - u64, - self.skip_all, - // avoid hashing if there will be 0 results - if (self.skip_all.len > 0) bun.hashWithSeed(self.seed, base.name) else 0, - ) != null) continue; - }, + // we don't know what it is for a symlink + .sym_link => { + if (std.mem.indexOfScalar( + u64, + self.skip_all, + // avoid hashing if there will be 0 results + if (self.skip_all.len > 0) bun.hashWithSeed(self.seed, std.mem.sliceAsBytes(base.name.slice())) else 0, + ) != null) continue; + }, - else => {}, - } + else => {}, + } - self.name_buffer.shrinkRetainingCapacity(dirname_len); - if (self.name_buffer.items.len != 0) { - try self.name_buffer.append(path.sep); - dirname_len += 1; - } - try self.name_buffer.appendSlice(base.name); - const cur_len = self.name_buffer.items.len; - try self.name_buffer.append(0); - self.name_buffer.shrinkRetainingCapacity(cur_len); + self.name_buffer.shrinkRetainingCapacity(dirname_len); + if (self.name_buffer.items.len != 0) { + try self.name_buffer.append(path.sep); + dirname_len += 1; + } + try self.name_buffer.appendSlice(base.name.slice()); + const cur_len = self.name_buffer.items.len; + try self.name_buffer.append(0); + self.name_buffer.shrinkRetainingCapacity(cur_len); - if (base.kind == .directory) { - var new_dir = top.iter.dir.openDir(base.name, .{ .iterate = true }) catch |err| switch (err) { - error.NameTooLong => unreachable, // no path sep in base.name - else => |e| return e, - }; - { - errdefer new_dir.close(); - try self.stack.append(StackItem{ - .iter = new_dir.iterate(), - .dirname_len = self.name_buffer.items.len, - }); - top = &self.stack.items[self.stack.items.len - 1]; + if (base.kind == .directory) { + var new_dir = (if (Environment.isWindows) + top.iter.iter.dir.openDirW(base.name.sliceAssumeZ(), .{ .iterate = true }) + else + top.iter.iter.dir.openDir(base.name.slice(), .{ .iterate = true })) catch |err| switch (err) { + error.NameTooLong => unreachable, // no path sep in base.name + else => |e| return e, + }; + { + errdefer new_dir.close(); + try self.stack.append(StackItem{ + .iter = DirIterator.iterate(new_dir, if (Environment.isWindows) .u16 else .u8), + .dirname_len = self.name_buffer.items.len, + }); + top = &self.stack.items[self.stack.items.len - 1]; + } + } + return WalkerEntry{ + .dir = top.iter.iter.dir, + .basename = self.name_buffer.items[dirname_len..], + .path = self.name_buffer.items, + .kind = base.kind, + }; + } else { + var item = self.stack.pop(); + if (self.stack.items.len != 0) { + item.iter.iter.dir.close(); + } } - } - return WalkerEntry{ - .dir = top.iter.dir, - .basename = self.name_buffer.items[dirname_len..], - .path = self.name_buffer.items, - .kind = base.kind, - }; - } else { - var item = self.stack.pop(); - if (self.stack.items.len != 0) { - item.iter.dir.close(); - } + }, } } return null; @@ -112,7 +126,7 @@ pub fn deinit(self: *Walker) void { if (self.stack.items.len > 0) { for (self.stack.items[1..]) |*item| { if (self.stack.items.len != 0) { - item.iter.dir.close(); + item.iter.iter.dir.close(); } } self.stack.deinit(); @@ -130,10 +144,10 @@ pub fn deinit(self: *Walker) void { pub fn walk( self: Dir, allocator: Allocator, - skip_filenames: []const []const u8, - skip_dirnames: []const []const u8, + skip_filenames: []const OSPathSlice, + skip_dirnames: []const OSPathSlice, ) !Walker { - var name_buffer = std.ArrayList(u8).init(allocator); + var name_buffer = NameBufferList.init(allocator); errdefer name_buffer.deinit(); var stack = std.ArrayList(Walker.StackItem).init(allocator); @@ -144,18 +158,18 @@ pub fn walk( var skip_name_i: usize = 0; for (skip_filenames) |name| { - skip_names[skip_name_i] = bun.hashWithSeed(seed, name); + skip_names[skip_name_i] = bun.hashWithSeed(seed, std.mem.sliceAsBytes(name)); skip_name_i += 1; } const skip_filenames_ = skip_names[0..skip_name_i]; var skip_dirnames_ = skip_names[skip_name_i..]; for (skip_dirnames, 0..) |name, i| { - skip_dirnames_[i] = bun.hashWithSeed(seed, name); + skip_dirnames_[i] = bun.hashWithSeed(seed, std.mem.sliceAsBytes(name)); } try stack.append(Walker.StackItem{ - .iter = self.iterate(), + .iter = DirIterator.iterate(self, if (Environment.isWindows) .u16 else .u8), .dirname_len = 0, }); diff --git a/src/windows.zig b/src/windows.zig index f048e5a17b..da8d69c0ab 100644 --- a/src/windows.zig +++ b/src/windows.zig @@ -31,6 +31,7 @@ pub const DUPLICATE_SAME_ACCESS = windows.DUPLICATE_SAME_ACCESS; pub const OBJECT_ATTRIBUTES = windows.OBJECT_ATTRIBUTES; pub const kernel32 = windows.kernel32; pub const IO_STATUS_BLOCK = windows.IO_STATUS_BLOCK; +pub const FILE_INFO_BY_HANDLE_CLASS = windows.FILE_INFO_BY_HANDLE_CLASS; pub const FILE_SHARE_READ = windows.FILE_SHARE_READ; pub const FILE_SHARE_WRITE = windows.FILE_SHARE_WRITE; pub const FILE_SHARE_DELETE = windows.FILE_SHARE_DELETE; @@ -2950,13 +2951,20 @@ pub extern fn LoadLibraryA( ) ?*anyopaque; pub extern "kernel32" fn CreateHardLinkW( - newFileName: [*:0]const u16, - existingFileName: [*:0]const u16, + newFileName: LPCWSTR, + existingFileName: LPCWSTR, securityAttributes: ?*win32.SECURITY_ATTRIBUTES, -) win32.BOOL; +) BOOL; pub extern "kernel32" fn CopyFileW( - source: [*:0]const u16, - dest: [*:0]const u16, - bFailIfExists: win32.BOOL, -) win32.BOOL; + source: LPCWSTR, + dest: LPCWSTR, + bFailIfExists: BOOL, +) BOOL; + +pub extern "kernel32" fn SetFileInformationByHandle( + file: HANDLE, + fileInformationClass: FILE_INFO_BY_HANDLE_CLASS, + fileInformation: LPVOID, + bufferSize: DWORD, +) BOOL; diff --git a/src/windows_c.zig b/src/windows_c.zig index 70296deddd..99a80bd34d 100644 --- a/src/windows_c.zig +++ b/src/windows_c.zig @@ -1195,6 +1195,62 @@ pub const E = enum(u16) { UV_EUNATCH = -uv.UV_EUNATCH, }; +pub const S = struct { + pub const IFMT = 0o170000; + + pub const IFDIR = 0o040000; + pub const IFCHR = 0o020000; + pub const IFBLK = 0o060000; + pub const IFREG = 0o100000; + pub const IFIFO = 0o010000; + pub const IFLNK = 0o120000; + pub const IFSOCK = 0o140000; + + pub const ISUID = 0o4000; + pub const ISGID = 0o2000; + pub const ISVTX = 0o1000; + pub const IRUSR = 0o400; + pub const IWUSR = 0o200; + pub const IXUSR = 0o100; + pub const IRWXU = 0o700; + pub const IRGRP = 0o040; + pub const IWGRP = 0o020; + pub const IXGRP = 0o010; + pub const IRWXG = 0o070; + pub const IROTH = 0o004; + pub const IWOTH = 0o002; + pub const IXOTH = 0o001; + pub const IRWXO = 0o007; + + pub inline fn ISREG(m: i32) bool { + return m & IFMT == IFREG; + } + + pub inline fn ISDIR(m: i32) bool { + return m & IFMT == IFDIR; + } + + pub inline fn ISCHR(m: i32) bool { + return m & IFMT == IFCHR; + } + + pub inline fn ISBLK(m: i32) bool { + return m & IFMT == IFBLK; + } + + pub inline fn ISFIFO(m: i32) bool { + return m & IFMT == IFIFO; + } + + pub inline fn ISLNK(m: i32) bool { + return m & IFMT == IFLNK; + } + + pub inline fn ISSOCK(m: i32) bool { + return m & IFMT == IFSOCK; + } +}; + pub fn getErrno(_: anytype) E { if (Win32Error.get().toSystemErrno()) |sys| { return sys.toE();