diff options
author | 2021-12-18 17:50:35 -0800 | |
---|---|---|
committer | 2021-12-18 17:50:35 -0800 | |
commit | 1c9131a5d71f601baabef9460b8e396b2790f3f1 (patch) | |
tree | 02f172c1c648ea2c288dd4185c3678353c24807d /src | |
parent | fb758a32e1f28bbcd91616df589f17a7335e41ba (diff) | |
download | bun-1c9131a5d71f601baabef9460b8e396b2790f3f1.tar.gz bun-1c9131a5d71f601baabef9460b8e396b2790f3f1.tar.zst bun-1c9131a5d71f601baabef9460b8e396b2790f3f1.zip |
Not using hop!
Diffstat (limited to 'src')
-rw-r--r-- | src/global.zig | 3 | ||||
-rw-r--r-- | src/hop/hop.zig | 324 | ||||
-rw-r--r-- | src/hop/schema.zig | 511 | ||||
-rw-r--r-- | src/install/install.zig | 241 | ||||
-rw-r--r-- | src/libarchive/libarchive.zig | 174 |
5 files changed, 89 insertions, 1164 deletions
diff --git a/src/global.zig b/src/global.zig index 3407e837b..08cc77fab 100644 --- a/src/global.zig +++ b/src/global.zig @@ -1,8 +1,7 @@ const std = @import("std"); pub const Environment = @import("env.zig"); -const use_mimalloc = false; - +const use_mimalloc = !Environment.isTest; pub const default_allocator: *std.mem.Allocator = if (!use_mimalloc) std.heap.c_allocator diff --git a/src/hop/hop.zig b/src/hop/hop.zig deleted file mode 100644 index 90def6dc6..000000000 --- a/src/hop/hop.zig +++ /dev/null @@ -1,324 +0,0 @@ -const std = @import("std"); -const C = @import("../c.zig"); -const Schema = @import("./schema.zig"); -const Hop = Schema.Hop; -const Environment = @import("../env.zig"); - -const string = []const u8; - -pub fn cmpStringsAsc(ctx: void, a: string, b: string) bool { - return std.mem.order(u8, a, b) == .lt; -} - -pub fn cmpStringsDesc(ctx: void, a: string, b: string) bool { - return std.mem.order(u8, a, b) == .gt; -} - -const sort_asc = std.sort.asc(u8); -const sort_desc = std.sort.desc(u8); - -pub fn sortAsc(in: []string) void { - std.sort.sort([]const u8, in, {}, cmpStringsAsc); -} - -pub fn sortDesc(in: []string) void { - std.sort.sort([]const u8, in, {}, cmpStringsDesc); -} - -pub const Library = struct { - pub const magic_bytes = "#!/usr/bin/env hop\n\n"; - const Header = [magic_bytes.len + 5]u8; - pub usingnamespace Schema.Hop; - - archive: Hop.Archive, - allocator: *std.mem.Allocator, - metadata_bytes: []u8, - fd: ?std.os.fd_t, - - pub const Builder = struct { - allocator: *std.mem.Allocator, - files: std.ArrayListUnmanaged(Hop.File), - directories: std.ArrayListUnmanaged(Hop.Directory), - metadata_bytes: std.ArrayListUnmanaged(u8), - destination: std.fs.File = undefined, - - pub fn init(allocator: *std.mem.Allocator) Builder { - return Builder{ - .allocator = allocator, - .metadata_bytes = .{}, - .directories = .{}, - .files = std.ArrayListUnmanaged(Hop.File){}, - }; - } - - pub fn start(this: *Builder, file: std.fs.File) !void { - this.destination = file; - try file.seekTo(0); - - // Write the header with 0 set as the content offset - try file.writeAll(magic_bytes ++ [5]u8{ 0, 0, 0, 0, '\n' }); - } - - const FileSorter = struct { - metadata: []const u8, - pub fn sortByName(this: FileSorter, lhs: Hop.File, rhs: Hop.File) bool { - return std.mem.order(u8, this.metadata[lhs.name.off..][0..lhs.name.len], this.metadata[rhs.name.off..][0..rhs.name.len]) == .lt; - } - }; - - const DirSorter = struct { - metadata: []const u8, - pub fn sortByName(this: DirSorter, lhs: Hop.Directory, rhs: Hop.Directory) bool { - return std.mem.order(u8, this.metadata[lhs.name.off..][0..lhs.name.len], this.metadata[rhs.name.off..][0..rhs.name.len]) == .lt; - } - }; - - pub fn done(this: *Builder) !Hop.Archive { - const metadata_offset = @truncate(u32, try this.destination.getPos()); - - { - var sorter = FileSorter{ - .metadata = this.metadata_bytes.items, - }; - - std.sort.sort(Hop.File, this.files.items, sorter, FileSorter.sortByName); - } - - { - var sorter = DirSorter{ - .metadata = this.metadata_bytes.items, - }; - - std.sort.sort(Hop.Directory, this.directories.items, sorter, DirSorter.sortByName); - } - - var name_hashes = try this.allocator.alloc(u32, this.files.items.len); - - for (this.files.items) |file, i| { - name_hashes[i] = file.name_hash; - } - - var archive = Hop.Archive{ - .version = 1, - .files = this.files.items, - .directories = this.directories.items, - .name_hashes = name_hashes, - .content_offset = metadata_offset, - .metadata = this.metadata_bytes.items, - }; - - var schema_writer = Schema.FileWriter.init(this.destination); - try archive.encode(&schema_writer); - - var header: Header = undefined; - header[0..magic_bytes.len].* = magic_bytes.*; - std.mem.writeIntNative(u32, header[magic_bytes.len..][0..4], metadata_offset); - header[magic_bytes.len..][4] = '\n'; - try this.destination.pwriteAll(&header, 0); - - _ = C.fchmod( - this.destination.handle, - // chmod 777 - 0000010 | 0000100 | 0000001 | 0001000 | 0000040 | 0000004 | 0000002 | 0000400 | 0000200 | 0000020, - ); - - return archive; - } - - pub fn appendMetadata(this: *Builder, bytes: []const u8) !Hop.StringPointer { - const off = @truncate(u32, this.metadata_bytes.items.len); - - // Keep a null ptr at the end of the metadata so that C APIs expecting sentinel ptrs work without copying - try this.metadata_bytes.appendSlice(this.allocator, bytes); - try this.metadata_bytes.append(this.allocator, 0); - return Hop.StringPointer{ - .off = off, - .len = @truncate(u32, bytes.len), - }; - } - - pub fn appendContent(this: *Builder, bytes: []const u8) !Hop.StringPointer { - const off = try this.destination.getPos(); - try this.destination.writeAll(bytes); - return Hop.StringPointer{ - .off = off, - .len = bytes.len, - }; - } - - pub fn appendContentFromDisk(this: *Builder, name: []const u8, in: std.fs.File) !void { - var stat = try in.stat(); - - _ = try this.destination.write("\n"); - const off_in = try this.destination.getPos(); - const written = try std.os.copy_file_range(in.handle, 0, this.destination.handle, off_in, stat.size, 0); - try this.destination.seekTo(off_in + written); - const end = try this.destination.getPos(); - try this.appendFileMetadataFromDisk(name, off_in, end, stat); - try this.destination.writeAll(&[_]u8{0}); - } - - pub fn appendDirectoryFromDisk(this: *Builder, name: []const u8, in: std.fs.Dir) !void { - var stat = try std.os.fstatat(in.fd, name, 0); - - try this.appendDirMetadataFromDisk(name, stat); - } - - pub fn appendFileMetadataFromDisk(this: *Builder, name_buf: []const u8, start_pos: u64, end_pos: u64, stat: std.fs.File.Stat) !void { - const name = try this.appendMetadata(name_buf); - try this.files.append( - this.allocator, - Hop.File{ - .name = name, - .name_hash = @truncate(u32, std.hash.Wyhash.hash(0, name_buf)), - .data = Schema.Hop.StringPointer{ .off = @truncate(u32, start_pos), .len = @truncate(u32, end_pos - start_pos) }, - .chmod = @truncate(u32, stat.mode), - .mtime = @truncate(u32, @intCast(u128, @divFloor(stat.mtime, std.time.ns_per_s))), - .ctime = @truncate(u32, @intCast(u128, @divFloor(stat.ctime, std.time.ns_per_s))), - }, - ); - } - - pub fn appendDirMetadataFromDisk(this: *Builder, name_buf: []const u8, stat: std.fs.File.Stat) !void { - const name = try this.appendMetadata(name_buf); - try this.directories.append( - this.allocator, - Hop.Directory{ - .name = name, - .name_hash = @truncate(u32, std.hash.Wyhash.hash(0, name_buf)), - .chmod = @truncate(u32, stat.mode), - }, - ); - } - - pub fn appendFileMetadata(this: *Builder, name_buf: []const u8, meta: Hop.File) !void { - const name = try this.appendMetadata(name_buf); - try this.files.append( - this.allocator, - Hop.File{ - .name = name, - .name_hash = @truncate(u32, std.hash.Wyhash.hash(0, name_buf)), - .data = meta.data, - .chmod = meta.chmod, - .mtime = meta.mtime, - .ctime = meta.ctime, - }, - ); - } - - pub fn appendDirectoryRecursively(this: *Builder, dir: std.fs.Dir) !void { - var walker = try dir.walk(this.allocator); - defer walker.deinit(); - while (try walker.next()) |entry_| { - const entry: std.fs.Dir.Walker.WalkerEntry = entry_; - - switch (entry.kind) { - .Directory => { - try this.appendDirectoryFromDisk(entry.path, entry.basename, entry.dir); - }, - .File => { - try this.appendContentFromDisk(entry.path, try entry.dir.openFile(entry.basename, .{ .read = true })); - }, - else => {}, - } - } - } - }; - - pub fn extract(this: *Library, dest: std.fs.Dir, comptime verbose: bool) !void { - for (this.archive.files) |file| { - var name_slice = this.archive.metadata[file.name.off..][0..file.name.len :0]; - - var out = dest.createFileZ(name_slice, .{ .truncate = true }) catch brk: { - if (std.fs.path.dirname(name_slice)) |dirname| { - dest.makePath(dirname) catch |err2| { - std.log.err("error: {s} Failed to mkdir {s}\n", .{ @errorName(err2), dirname }); - continue; - }; - } - - break :brk dest.createFileZ(name_slice, .{ .truncate = true }) catch |err2| { - std.log.err("error: {s} Failed to create file: {s}\n", .{ @errorName(err2), name_slice }); - continue; - }; - }; - - if (file.data.len > std.mem.page_size) { - if (comptime Environment.isLinux) { - _ = std.os.system.fallocate(out.handle, 0, 0, @intCast(i64, file.data.len)); - } else if (comptime Environment.isMac) { - try C.preallocate_file( - out.handle, - @intCast(std.os.off_t, 0), - @intCast(std.os.off_t, file.data.len), - ); - } - } - - var remain: usize = file.data.len; - var written: usize = 0; - var in_off: usize = file.data.off; - - while (remain > 0) { - const wrote = try std.os.copy_file_range( - this.fd.?, - in_off, - out.handle, - written, - remain, - 0, - ); - in_off += wrote; - remain -= wrote; - written += wrote; - } - - if (verbose) { - std.log.info("Extracted file: {s} ({d} bytes)\n", .{ name_slice, written }); - } - } - } - - pub fn load( - fd: std.os.fd_t, - allocator: *std.mem.Allocator, - ) !Library { - var file = std.fs.File{ .handle = fd }; - - var header_buf: Header = std.mem.zeroes(Header); - var header = file.pread(&header_buf, 0) catch |err| { - std.log.err("Archive is corrupt. Failed to read header: {s}", .{@errorName(err)}); - return err; - }; - - const content_offset = std.mem.readIntNative(u32, header_buf[magic_bytes.len..][0..4]); - - const end = file.getEndPos() catch |err| { - std.log.err("Unable to get archive end position {s}", .{@errorName(err)}); - return error.IOError; - }; - - if (content_offset == 0 or std.math.maxInt(u32) == content_offset) { - std.log.err("Archive is corrupt. content_offset {d} is invalid", .{content_offset}); - return error.CorruptArchive; - } - - if (content_offset >= end) { - std.log.err("Archive is corrupt. content_offset is {d} greater than end of file", .{content_offset}); - return error.CorruptArchive; - } - - var metadata_buf = try allocator.alloc(u8, end - content_offset); - var metadata = file.preadAll(metadata_buf, content_offset) catch |err| { - std.log.err("Error reading archive metadata {s}", .{@errorName(err)}); - return err; - }; - var reader = Schema.Reader.init(metadata_buf, allocator); - var archive = Hop.Archive.decode(&reader) catch |err| { - std.log.err("Archive is corrupt. Failed to decode archive: {s}", .{@errorName(err)}); - return err; - }; - - return Library{ .fd = fd, .archive = archive, .allocator = allocator, .metadata_bytes = metadata_buf }; - } -}; diff --git a/src/hop/schema.zig b/src/hop/schema.zig deleted file mode 100644 index 8e8a15eaf..000000000 --- a/src/hop/schema.zig +++ /dev/null @@ -1,511 +0,0 @@ -const std = @import("std"); - -pub const Reader = struct { - const Self = @This(); - pub const ReadError = error{EOF}; - - buf: []u8, - remain: []u8, - allocator: *std.mem.Allocator, - - pub fn init(buf: []u8, allocator: *std.mem.Allocator) Reader { - return Reader{ - .buf = buf, - .remain = buf, - .allocator = allocator, - }; - } - - pub fn read(this: *Self, count: usize) ![]u8 { - const read_count = @minimum(count, this.remain.len); - if (read_count < count) { - return error.EOF; - } - - var slice = this.remain[0..read_count]; - - this.remain = this.remain[read_count..]; - - return slice; - } - - pub inline fn readAs(this: *Self, comptime T: type) !T { - if (!std.meta.trait.hasUniqueRepresentation(T)) { - @compileError(@typeName(T) ++ " must have unique representation."); - } - - return std.mem.bytesAsValue(T, try this.read(@sizeOf(T))); - } - - pub inline fn readByte(this: *Self) !u8 { - return (try this.read(1))[0]; - } - - pub fn readEnum(this: *Self, comptime Enum: type) !Enum { - const E = error{ - /// An integer was read, but it did not match any of the tags in the supplied enum. - InvalidValue, - }; - const type_info = @typeInfo(Enum).Enum; - const tag = try this.readInt(type_info.tag_type); - - inline for (std.meta.fields(Enum)) |field| { - if (tag == field.value) { - return @field(Enum, field.name); - } - } - - return E.InvalidValue; - } - - pub inline fn readArray(this: *Self, comptime T: type) ![]const T { - const length = try this.readInt(u32); - if (length == 0) { - return &([_]T{}); - } - - switch (comptime T) { - u8 => { - return try this.read(length); - }, - u16, u32, i8, i16, i32 => { - var i: u32 = 0; - var array = try this.allocator.alloc(T, length); - while (i < length) : (i += 1) { - array[i] = std.mem.readIntSliceNative(T, (try this.read(@sizeOf(T)))[0..@sizeOf(T)]); - } - return array; - }, - [:0]const u8, []const u8 => { - var i: u32 = 0; - var array = try this.allocator.alloc(T, length); - while (i < length) : (i += 1) { - array[i] = try this.readArray(u8); - } - return array; - }, - else => { - switch (comptime @typeInfo(T)) { - .Struct => |Struct| { - switch (Struct.layout) { - .Packed => { - const sizeof = @sizeOf(T); - var slice = try this.read(sizeof * length); - return std.mem.bytesAsSlice(T, slice); - }, - else => {}, - } - }, - .Enum => |type_info| { - const enum_values = try this.read(length * @sizeOf(type_info.tag_type)); - return @ptrCast([*]T, enum_values.ptr)[0..length]; - }, - else => {}, - } - - var i: u32 = 0; - var array = try this.allocator.alloc(T, length); - while (i < length) : (i += 1) { - array[i] = try this.readValue(T); - } - - return array; - }, - } - } - - pub inline fn readByteArray(this: *Self) ![]u8 { - const length = try this.readInt(u32); - if (length == 0) { - return &([_]u8{}); - } - - return try this.read(@as(usize, length)); - } - - pub inline fn readInt(this: *Self, comptime T: type) !T { - var slice = try this.read(@sizeOf(T)); - - return std.mem.readIntSliceNative(T, slice); - } - - pub inline fn readBool(this: *Self) !bool { - return (try this.readByte()) > 0; - } - - pub inline fn readValue(this: *Self, comptime T: type) !T { - switch (comptime T) { - bool => { - return try this.readBool(); - }, - u8 => { - return try this.readByte(); - }, - [*:0]const u8, [:0]const u8, []const u8 => { - return try this.readArray(u8); - }, - - []const [:0]const u8, []const [*:0]const u8, []const []const u8 => { - return try this.readArray([]const u8); - }, - []u8, [:0]u8, [*:0]u8 => { - return try this.readArray([]u8); - }, - u16, u32, i8, i16, i32 => { - return std.mem.readIntSliceNative(T, try this.read(@sizeOf(T))); - }, - else => { - switch (comptime @typeInfo(T)) { - .Struct => |Struct| { - switch (Struct.layout) { - .Packed => { - const sizeof = @sizeOf(T); - var slice = try this.read(sizeof); - return @ptrCast(*T, slice[0..sizeof]).*; - }, - else => {}, - } - }, - .Enum => |type_info| { - return try this.readEnum(T); - }, - else => {}, - } - - return try T.decode(this); - }, - } - - @compileError("Invalid type passed to readValue"); - } -}; - -pub fn Writer(comptime WritableStream: type) type { - return struct { - const Self = @This(); - writable: WritableStream, - - pub fn init(writable: WritableStream) Self { - return Self{ .writable = writable }; - } - - pub inline fn write(this: *Self, bytes: anytype) !void { - _ = try this.writable.write(bytes); - } - - pub inline fn writeByte(this: *Self, byte: u8) !void { - _ = try this.writable.write(&[1]u8{byte}); - } - - pub inline fn writeInt(this: *Self, int: anytype) !void { - try this.write(std.mem.asBytes(&int)); - } - - pub inline fn writeFieldID(this: *Self, comptime id: comptime_int) !void { - try this.writeByte(id); - } - - pub inline fn writeEnum(this: *Self, val: anytype) !void { - try this.writeInt(@enumToInt(val)); - } - - pub fn writeValue(this: *Self, comptime SliceType: type, slice: SliceType) !void { - switch (SliceType) { - []u16, - []u32, - []i16, - []i32, - []i8, - []const u16, - []const u32, - []const i16, - []const i32, - []const i8, - [:0]u16, - [:0]u32, - [:0]i16, - [:0]i32, - [:0]i8, - [:0]const u16, - [:0]const u32, - [:0]const i16, - [:0]const i32, - [:0]const i8, - [*:0]u16, - [*:0]u32, - [*:0]i16, - [*:0]i32, - [*:0]i8, - [*:0]const u16, - [*:0]const u32, - [*:0]const i16, - [*:0]const i32, - [*:0]const i8, - => { - try this.writeArray(SliceType, slice); - }, - - []u8, - []const u8, - [:0]u8, - [:0]const u8, - [*:0]u8, - [*:0]const u8, - => { - try this.writeArray(u8, slice); - }, - - u8 => { - try this.write(slice); - }, - u16, u32, i16, i32, i8 => { - try this.write(std.mem.asBytes(slice)); - }, - - else => { - try slice.encode(this); - }, - } - } - - pub inline fn writeArray(this: *Self, comptime T: type, slice: anytype) !void { - try this.writeInt(@truncate(u32, slice.len)); - - switch (T) { - u8 => { - try this.write(slice); - }, - u16, u32, i16, i32, i8 => { - try this.write(std.mem.sliceAsBytes(slice)); - }, - [:0]u8, - []u8, - []u16, - []u32, - []i16, - []i32, - []i8, - []const u8, - [:0]const u8, - []const u16, - []const u32, - []const i16, - []const i32, - []const i8, - [:0]u16, - [:0]u32, - [:0]i16, - [:0]i32, - [:0]i8, - [:0]const u16, - [:0]const u32, - [:0]const i16, - [:0]const i32, - [:0]const i8, - [*:0]u16, - [*:0]u32, - [*:0]i16, - [*:0]i32, - [*:0]i8, - [*:0]const u16, - [*:0]const u32, - [*:0]const i16, - [*:0]const i32, - [*:0]const i8, - => { - for (slice) |num_slice| { - try this.writeArray(std.meta.Child(@TypeOf(num_slice)), num_slice); - } - }, - else => { - for (slice) |val| { - try val.encode(this); - } - }, - } - } - - pub inline fn endMessage(this: *Self) !void { - try this.writeByte(0); - } - }; -} - -pub const ByteWriter = Writer(*std.io.FixedBufferStream([]u8)); -pub const FileWriter = Writer(std.fs.File); - -pub const Hop = struct { - pub const StringPointer = packed struct { - /// off - off: u32 = 0, - - /// len - len: u32 = 0, - - pub fn decode(reader: anytype) anyerror!StringPointer { - var this = std.mem.zeroes(StringPointer); - - this.off = try reader.readValue(u32); - this.len = try reader.readValue(u32); - return this; - } - - pub fn encode(this: *const @This(), writer: anytype) anyerror!void { - try writer.writeInt(this.off); - try writer.writeInt(this.len); - } - }; - - pub const File = packed struct { - /// name - name: StringPointer, - - /// name_hash - name_hash: u32 = 0, - - /// chmod - chmod: u32 = 0, - - /// mtime - mtime: u32 = 0, - - /// ctime - ctime: u32 = 0, - - /// data - data: StringPointer, - - pub fn decode(reader: anytype) anyerror!File { - var this = File{ .name = StringPointer{}, .data = .{} }; - - this.name = try reader.readValue(StringPointer); - this.name_hash = try reader.readValue(u32); - this.chmod = try reader.readValue(u32); - this.mtime = try reader.readValue(u32); - this.ctime = try reader.readValue(u32); - this.data = try reader.readValue(StringPointer); - return this; - } - - pub fn encode(this: *const @This(), writer: anytype) anyerror!void { - try writer.writeValue(@TypeOf(this.name), this.name); - try writer.writeInt(this.name_hash); - try writer.writeInt(this.chmod); - try writer.writeInt(this.mtime); - try writer.writeInt(this.ctime); - try writer.writeValue(@TypeOf(this.data), this.data); - } - }; - - pub const Directory = packed struct { - /// name - name: StringPointer, - - /// name_hash - name_hash: u32 = 0, - - /// chmod - chmod: u32 = 0, - - pub fn decode(reader: anytype) anyerror!Directory { - var this = Directory{ - .name = StringPointer{}, - }; - - this.name = try reader.readValue(StringPointer); - this.name_hash = try reader.readValue(u32); - this.chmod = try reader.readValue(u32); - - return this; - } - - pub fn encode(this: *const @This(), writer: anytype) anyerror!void { - try writer.writeValue(@TypeOf(this.name), this.name); - try writer.writeInt(this.name_hash); - try writer.writeInt(this.chmod); - } - }; - - pub const Archive = struct { - /// version - version: ?u32 = null, - - /// content_offset - content_offset: ?u32 = null, - - /// files - files: []align(1) const File, - - /// files - directories: []align(1) const Directory, - - /// name_hashes - name_hashes: []align(1) const u32, - - /// metadata - metadata: []align(1) const u8, - - pub fn decode(reader: anytype) anyerror!Archive { - var this = std.mem.zeroes(Archive); - - while (true) { - switch (try reader.readByte()) { - 0 => { - return this; - }, - - 1 => { - this.version = try reader.readValue(u32); - }, - 2 => { - this.content_offset = try reader.readValue(u32); - }, - 3 => { - this.files = try reader.readArray(File); - }, - 4 => { - this.directories = try reader.readArray(Directory); - }, - 5 => { - this.name_hashes = try reader.readArray(u32); - }, - 6 => { - this.metadata = try reader.readArray(u8); - }, - else => { - return error.InvalidMessage; - }, - } - } - unreachable; - } - - pub fn encode(this: *const @This(), writer: anytype) anyerror!void { - if (this.version) |version| { - try writer.writeFieldID(1); - try writer.writeInt(version); - } - if (this.content_offset) |content_offset| { - try writer.writeFieldID(2); - try writer.writeInt(content_offset); - } - if (this.files.len > 0) { - try writer.writeFieldID(3); - try writer.writeArray(File, this.files); - } - if (this.directories.len > 0) { - try writer.writeFieldID(4); - try writer.writeArray(Directory, this.directories); - } - if (this.name_hashes.len > 0) { - try writer.writeFieldID(5); - try writer.writeArray(u32, this.name_hashes); - } - if (this.metadata.len > 0) { - try writer.writeFieldID(6); - try writer.writeArray(u8, this.metadata); - } - try writer.endMessage(); - } - }; -}; diff --git a/src/install/install.zig b/src/install/install.zig index 486613023..ab3bddb59 100644 --- a/src/install/install.zig +++ b/src/install/install.zig @@ -3138,15 +3138,13 @@ const PackageInstall = struct { // Slower than clonefile clonefile_each_dir, - // Slow! + // On macOS, slow. + // On Linux, fast. hardlink, // Slowest if single-threaded copyfile, - copy_file_range, - io_uring, - const BackendSupport = std.EnumArray(Method, bool); pub const macOS = BackendSupport.initDefault(false, .{ @@ -3159,7 +3157,7 @@ const PackageInstall = struct { pub const linux = BackendSupport.initDefault(false, .{ .io_uring = true, .hardlink = true, - .copy_file_range = true, + .copyfile = true, }); pub inline fn isSupported(this: Method) bool { @@ -3168,13 +3166,6 @@ const PackageInstall = struct { return false; } - - pub inline fn isSync(this: Method) bool { - return switch (this) { - .hardlink, .clonefile_each_dir, .clonefile => true, - else => false, - }; - } }; pub fn verify( @@ -3538,9 +3529,9 @@ const PackageInstall = struct { // we'll catch it the next error if (!skip_delete) this.destination_dir.deleteTree(std.mem.span(this.destination_dir_subpath)) catch {}; - if (comptime Environment.isMac) { - switch (supported_method) { - .clonefile => { + switch (supported_method) { + .clonefile => { + if (comptime Environment.isMac) { // First, attempt to use clonefile // if that fails due to ENOTSUP, mark it as unsupported and then fall back to copyfile if (this.installWithClonefile()) |result| { @@ -3558,8 +3549,10 @@ const PackageInstall = struct { }, } } - }, - .clonefile_each_dir => { + } + }, + .clonefile_each_dir => { + if (comptime Environment.isMac) { if (this.installWithClonefileEachDir()) |result| { return result; } else |err| { @@ -3575,36 +3568,34 @@ const PackageInstall = struct { }, } } - }, - .hardlink => { - if (this.installWithHardlink()) |result| { - return result; - } else |err| { - switch (err) { - error.NotSupported => { - supported_method = .copyfile; - }, - error.FileNotFound => return Result{ - .fail = .{ .err = error.FileNotFound, .step = .opening_cache_dir }, - }, - else => return Result{ - .fail = .{ .err = err, .step = .copying_files }, - }, - } + } + }, + .hardlink => { + if (this.installWithHardlink()) |result| { + return result; + } else |err| { + switch (err) { + error.NotSupported => { + supported_method = .copyfile; + }, + error.FileNotFound => return Result{ + .fail = .{ .err = error.FileNotFound, .step = .opening_cache_dir }, + }, + else => return Result{ + .fail = .{ .err = err, .step = .copying_files }, + }, } - }, - else => {}, - } - - if (supported_method != .copyfile) return Result{ - .success = void{}, - }; - - return this.installWithCopyfile(); + } + }, + else => {}, } - // TODO: linux io_uring + if (supported_method != .copyfile) return Result{ + .success = void{}, + }; + // TODO: linux io_uring + return this.installWithCopyfile(); } }; @@ -5922,6 +5913,7 @@ pub const PackageManager = struct { node_modules_folder: std.fs.Dir, }; + /// Install versions of a package which are waiting on a network request pub fn installEnqueuedPackages( this: *PackageInstaller, package_id: PackageID, @@ -6197,125 +6189,64 @@ pub const PackageManager = struct { .successfully_installed = try std.DynamicBitSetUnmanaged.initEmpty(lockfile.packages.len, this.allocator), }; - // When it's a Good Idea, run the install in single-threaded - // From benchmarking, apfs clonefile() is ~6x faster than copyfile() on macOS - // Running it in parallel is the same or slower. - // However, copyfile() is about 30% faster if run in paralell - // On Linux, the story here will be similar but with io_uring. - // We will have to support versions of Linux that do not have io_uring support - // so in that case, we will still need to support copy_file_range() - // git installs will always need to run in paralell, and tarball installs probably should too - run_install: { - if (PackageInstall.supported_method.isSync()) { - sync_install: { - const cwd = std.fs.cwd(); - while (iterator.nextNodeModulesFolder()) |node_modules| { - try cwd.makePath(std.mem.span(node_modules.relative_path)); - // We deliberately do not close this folder. - // If the package hasn't been downloaded, we will need to install it later - // We use this file descriptor to know where to put it. - var folder = try cwd.openDirZ(node_modules.relative_path, .{ - .iterate = true, - }); - - installer.node_modules_folder = folder; - - for (node_modules.packages) |package_id| { - installer.installPackage(@truncate(PackageID, package_id), log_level); - if (!PackageInstall.supported_method.isSync()) break :sync_install; - if (this.pending_tasks > 16) { - try this.runTasks( - *PackageInstaller, - &installer, - PackageInstaller.installEnqueuedPackages, - log_level, - ); - } - } + const cwd = std.fs.cwd(); + while (iterator.nextNodeModulesFolder()) |node_modules| { + try cwd.makePath(std.mem.span(node_modules.relative_path)); + // We deliberately do not close this folder. + // If the package hasn't been downloaded, we will need to install it later + // We use this file descriptor to know where to put it. + var folder = try cwd.openDirZ(node_modules.relative_path, .{ + .iterate = true, + }); - try this.runTasks( - *PackageInstaller, - &installer, - PackageInstaller.installEnqueuedPackages, - log_level, - ); - } + installer.node_modules_folder = folder; - while (this.pending_tasks > 0) { - try this.runTasks( - *PackageInstaller, - &installer, - PackageInstaller.installEnqueuedPackages, - log_level, - ); - } - break :run_install; + var remaining = node_modules.packages; + + // cache line is 64 bytes on ARM64 and x64 + // PackageIDs are 4 bytes + // Hence, we can fit up to 64 / 4 = 16 package IDs in a cache line + const unroll_count = comptime 64 / @sizeOf(PackageID); + + while (remaining.len > unroll_count) { + comptime var i: usize = 0; + inline while (i < unroll_count) : (i += 1) { + installer.installPackage(remaining[i], comptime log_level); + } + remaining = remaining[unroll_count..]; + + // We want to minimize how often we call this function + // That's part of why we unroll this loop + if (this.pending_tasks > 0) { + try this.runTasks( + *PackageInstaller, + &installer, + PackageInstaller.installEnqueuedPackages, + log_level, + ); } } - // var install_context = try lockfile.allocator.create(PackageInstall.Context); - // install_context.* = .{ - // .cache_dir = cache_dir, - // .progress = progress, - // .metas = metas, - // .names = names, - // .resolutions = resolutions, - // .string_buf = lockfile.buffers.string_bytes.items, - // .allocator = lockfile.allocator, - // }; - // install_context.channel = PackageInstall.Task.Channel.init(); - // var ran: usize = summary.skipped + summary.success + summary.fail; - - // var tasks = try lockfile.allocator.alloc(PackageInstall.Task, toplevel_count - ran); - // var task_i: usize = 0; - // var batch = ThreadPool.Batch{}; - // var remaining_count = task_i; - // while (toplevel_node_modules.next()) |package_id| { - // const meta = &metas[package_id]; - // if (meta.isDisabled()) { - // if (comptime log_level.showProgress()) { - // install_node.completeOne(); - // install_node.setEstimatedTotalItems(installer.install_count); - // } - // continue; - // } - - // tasks[task_i] = PackageInstall.Task{ - // .package_id = @truncate(PackageID, package_id), - // .destination_dir = node_modules_folder, - // .ctx = install_context, - // }; - // batch.push(ThreadPool.Batch.from(&tasks[task_i].task)); - // task_i += 1; - // } + for (remaining) |package_id| { + installer.installPackage(@truncate(PackageID, package_id), log_level); + } - // this.thread_pool.schedule(batch); - - // while (remaining_count > 0) { - // while (install_context.channel.tryReadItem() catch null) |item_| { - // var install_task: *PackageInstall.Task = item_; - // defer remaining_count -= 1; - // switch (install_task.result) { - // .pending => unreachable, - // .skip => summary.skipped += 1, - // .success => summary.success += 1, - // .fail => |cause| { - // Output.prettyErrorln( - // "<r><red>error<r>: <b><red>{s}<r> installing <b>{s}<r>", - // .{ @errorName(cause.err), install_task.ctx.names[install_task.package_id] }, - // ); - // summary.fail += 1; - // }, - // } - // } - // if (comptime log_level.showProgress()) { - // install_node.completeOne(); - // install_node.setEstimatedTotalItems(installer.install_count); - // } - - // std.atomic.spinLoopHint(); + try this.runTasks( + *PackageInstaller, + &installer, + PackageInstaller.installEnqueuedPackages, + log_level, + ); + } + + while (this.pending_tasks > 0) { + try this.runTasks( + *PackageInstaller, + &installer, + PackageInstaller.installEnqueuedPackages, + log_level, + ); } - // } summary.successfully_installed = installer.successfully_installed; outer: for (installer.platform_binlinks.items) |deferred| { diff --git a/src/libarchive/libarchive.zig b/src/libarchive/libarchive.zig index 47ac23645..4e1751e80 100644 --- a/src/libarchive/libarchive.zig +++ b/src/libarchive/libarchive.zig @@ -284,8 +284,8 @@ pub const BufferReadStream = struct { _ = lib.archive_read_support_format_tar(this.archive); _ = lib.archive_read_support_format_gnutar(this.archive); - _ = lib.archive_read_support_filter_gzip(this.archive); - _ = lib.archive_read_support_filter_none(this.archive); + _ = lib.archive_read_support_compression_gzip(this.archive); + // _ = lib.archive_read_support_filter_none(this.archive); const rc = lib.archive_read_open_memory(this.archive, this.buf.ptr, this.buf.len); @@ -523,176 +523,6 @@ pub const Archive = struct { } } - const SeekableBufferedWriter = struct { - pos: u64 = 0, - buf: [std.mem.page_size]u8 = undefined, - len: usize = 0, - fd: std.os.fd_t = 0, - - pub fn flush(this: *SeekableBufferedWriter) !usize { - const end = this.len + this.pos; - var off: usize = this.pos; - const initial = this.pos; - defer this.pos = off; - var slice = this.buf[0..this.len]; - while (slice.len > 0) { - const written = try std.os.pwrite(this.fd, slice, off); - slice = slice[written..]; - off += written; - } - this.len = 0; - return off - initial; - } - - pub fn write(this: *SeekableBufferedWriter, buf: []const u8) !usize { - if (this.buf.len - this.len < 32) { - _ = try this.flush(); - } - var queue = buf; - while (queue.len > 0) { - var to_write = @minimum(this.buf.len - this.len, queue.len); - if (to_write == 0 and this.len > 0) { - _ = try this.flush(); - to_write = @minimum(this.buf.len - this.len, queue.len); - } - - var remainder = queue[0..to_write]; - queue = queue[remainder.len..]; - @memcpy(this.buf[this.len..].ptr, remainder.ptr, remainder.len); - this.len += remainder.len; - } - - return buf.len; - } - }; - - pub fn convertToHop( - hop: *Hop.Builder, - file_buffer: []const u8, - ctx: ?*Archive.Context, - comptime FilePathAppender: type, - appender: FilePathAppender, - comptime depth_to_skip: usize, - comptime log: bool, - ) !u32 { - var entry: *lib.archive_entry = undefined; - var ext: *lib.archive = undefined; - - const flags = @enumToInt(Flags.Extract.time) | @enumToInt(Flags.Extract.perm) | @enumToInt(Flags.Extract.acl) | @enumToInt(Flags.Extract.fflags); - var stream: BufferReadStream = undefined; - stream.init(file_buffer); - defer stream.deinit(); - _ = stream.openRead(); - var archive = stream.archive; - var count: u32 = 0; - - var chunk_remain: usize = 0; - var chunk_offset: isize = 0; - var chunk_output_offset: isize = 0; - var chunk_size: usize = 0; - var chunk_buf: ?[*]u8 = null; - const handle = hop.destination.handle; - - var writer = SeekableBufferedWriter{ - .pos = try hop.destination.getPos(), - .fd = hop.destination.handle, - }; - - loop: while (true) { - const r = @intToEnum(Status, lib.archive_read_next_header(archive, &entry)); - - switch (r) { - Status.eof => break :loop, - Status.failed, Status.fatal, Status.retry => return error.Fail, - else => { - var pathname: [:0]const u8 = std.mem.sliceTo(lib.archive_entry_pathname(entry).?, 0); - var tokenizer = std.mem.tokenize(u8, std.mem.span(pathname), std.fs.path.sep_str); - comptime var depth_i: usize = 0; - - inline while (depth_i < depth_to_skip) : (depth_i += 1) { - if (tokenizer.next() == null) continue :loop; - } - - const Kind = std.fs.Dir.Entry.Kind; - const entry_kind: Kind = switch (lib.archive_entry_filetype(entry)) { - std.os.S_IFDIR => Kind.Directory, - std.os.S_IFREG => Kind.File, - else => continue :loop, - }; - - var pathname_ = tokenizer.rest(); - - count += 1; - const mode = @truncate(u32, lib.archive_entry_perm(entry)); - const slice = std.mem.span(pathname); - const name = hop.appendMetadata(pathname_) catch unreachable; - - if (entry_kind == .Directory) { - hop.directories.append(hop.allocator, .{ - .name = name, - .name_hash = @truncate(u32, std.hash.Wyhash.hash(0, pathname_)), - .chmod = mode, - }) catch unreachable; - - if (comptime log) { - Output.prettyErrorln("Dir: {s}", .{ - pathname_, - }); - } - } else { - var data = Hop.StringPointer{ - .off = @truncate(u32, writer.pos), - }; - - chunk_offset = 0; - chunk_output_offset = 0; - const size = lib.archive_entry_size(entry); - - var data_block_status: c_int = lib.archive_read_data_block(archive, @ptrCast([*c]*const c_void, &chunk_buf), &chunk_size, &chunk_offset); - - while (data_block_status == lib.ARCHIVE_OK) : (data_block_status = lib.archive_read_data_block(archive, @ptrCast([*c]*const c_void, &chunk_buf), &chunk_size, &chunk_offset)) { - if (chunk_offset > chunk_output_offset) { - writer.pos = @intCast(usize, @intCast(isize, writer.pos) + chunk_offset - chunk_output_offset); - } - - while (chunk_size > 0) { - const remain = size - chunk_output_offset; - chunk_size = @minimum(@intCast(usize, remain), chunk_size); - // const written = try std.os.pwrite(handle, chunk_buf.?[0..chunk_size], writer.pos); - // writer.pos += written; - const written = try writer.write(chunk_buf.?[0..chunk_size]); - chunk_buf.? += written; - chunk_size -= written; - chunk_output_offset += @intCast(i64, written); - } - } - - data.len = @intCast(u32, size); - _ = try writer.flush(); - - if (comptime log) { - Output.prettyErrorln("File: {s} - [{d}, {d}]", .{ pathname_, data.off, data.len }); - } - - hop.files.append( - hop.allocator, - Hop.File{ - .name = name, - .name_hash = @truncate(u32, std.hash.Wyhash.hash(0, pathname_)), - .chmod = mode, - .data = data, - }, - ) catch unreachable; - } - }, - } - } - - try hop.destination.seekTo(writer.pos); - - return count; - } - pub fn extractToDisk( file_buffer: []const u8, root: []const u8, |