diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/cli.zig | 10 | ||||
-rw-r--r-- | src/cli/create_command.zig | 2 | ||||
-rw-r--r-- | src/cli/package_manager_command.zig | 84 | ||||
-rw-r--r-- | src/cli/upgrade_command.zig | 2 | ||||
-rw-r--r-- | src/install/install.zig | 17 | ||||
-rw-r--r-- | src/install/lockfile.zig | 120 | ||||
-rw-r--r-- | src/string_builder.zig | 31 |
7 files changed, 256 insertions, 10 deletions
diff --git a/src/cli.zig b/src/cli.zig index 5ca464d50..cafb44882 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -686,6 +686,8 @@ const AddCompletions = @import("./cli/add_completions.zig"); pub const PrintBundleCommand = struct { pub fn exec(ctx: Command.Context) !void { + @setCold(true); + const entry_point = ctx.args.entry_points[0]; var out_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined; var stdout = std.io.getStdOut(); @@ -1053,12 +1055,20 @@ pub const Command = struct { } if (strings.eqlComptime(extension, ".lockb")) { + for (std.os.argv) |arg| { + if (strings.eqlComptime(std.mem.span(arg), "--hash")) { + try PackageManagerCommand.printHash(ctx, ctx.args.entry_points[0]); + return; + } + } + try Install.Lockfile.Printer.print( ctx.allocator, ctx.log, ctx.args.entry_points[0], .yarn, ); + return; } } diff --git a/src/cli/create_command.zig b/src/cli/create_command.zig index b82370a5f..b13084c03 100644 --- a/src/cli/create_command.zig +++ b/src/cli/create_command.zig @@ -247,6 +247,8 @@ const BUN_CREATE_DIR = ".bun-create"; var home_dir_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; pub const CreateCommand = struct { pub fn exec(ctx: Command.Context, _: []const []const u8) !void { + @setCold(true); + Global.configureAllocator(.{ .long_running = false }); try NetworkThread.init(); diff --git a/src/cli/package_manager_command.zig b/src/cli/package_manager_command.zig index 12ce37021..80d12125d 100644 --- a/src/cli/package_manager_command.zig +++ b/src/cli/package_manager_command.zig @@ -10,6 +10,34 @@ const Path = @import("../resolver/resolve_path.zig"); pub const PackageManagerCommand = struct { pub fn printHelp(_: std.mem.Allocator) void {} + pub fn printHash(ctx: Command.Context, lockfile_: []const u8) !void { + @setCold(true); + var lockfile_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined; + @memcpy(&lockfile_buffer, lockfile_.ptr, lockfile_.len); + lockfile_buffer[lockfile_.len] = 0; + var lockfile = lockfile_buffer[0..lockfile_.len :0]; + var pm = try PackageManager.init(ctx, null, &PackageManager.install_params); + + const load_lockfile = pm.lockfile.loadFromDisk(ctx.allocator, ctx.log, lockfile); + if (load_lockfile == .not_found) { + if (pm.options.log_level != .silent) + Output.prettyError("Lockfile not found", .{}); + Global.exit(1); + } + + if (load_lockfile == .err) { + if (pm.options.log_level != .silent) + Output.prettyError("Error loading lockfile: {s}", .{@errorName(load_lockfile.err.value)}); + Global.exit(1); + } + + Output.flush(); + Output.disableBuffering(); + try Output.writer().print("{}", .{load_lockfile.ok.fmtMetaHash()}); + Output.enableBuffering(); + Global.exit(0); + } + pub fn exec(ctx: Command.Context) !void { var args = try std.process.argsAlloc(ctx.allocator); args = args[1..]; @@ -57,6 +85,62 @@ pub const PackageManagerCommand = struct { Output.flush(); return; + } else if (strings.eqlComptime(first, "hash")) { + const load_lockfile = pm.lockfile.loadFromDisk(ctx.allocator, ctx.log, "bun.lockb"); + if (load_lockfile == .not_found) { + if (pm.options.log_level != .silent) + Output.prettyError("Lockfile not found", .{}); + Global.exit(1); + } + + if (load_lockfile == .err) { + if (pm.options.log_level != .silent) + Output.prettyError("Error loading lockfile: {s}", .{@errorName(load_lockfile.err.value)}); + Global.exit(1); + } + + _ = try pm.lockfile.hasMetaHashChanged(false); + + Output.flush(); + Output.disableBuffering(); + try Output.writer().print("{}", .{load_lockfile.ok.fmtMetaHash()}); + Output.enableBuffering(); + Global.exit(0); + } else if (strings.eqlComptime(first, "hash-print")) { + const load_lockfile = pm.lockfile.loadFromDisk(ctx.allocator, ctx.log, "bun.lockb"); + if (load_lockfile == .not_found) { + if (pm.options.log_level != .silent) + Output.prettyError("Lockfile not found", .{}); + Global.exit(1); + } + + if (load_lockfile == .err) { + if (pm.options.log_level != .silent) + Output.prettyError("Error loading lockfile: {s}", .{@errorName(load_lockfile.err.value)}); + Global.exit(1); + } + + Output.flush(); + Output.disableBuffering(); + try Output.writer().print("{}", .{load_lockfile.ok.fmtMetaHash()}); + Output.enableBuffering(); + Global.exit(0); + } else if (strings.eqlComptime(first, "hash-string")) { + const load_lockfile = pm.lockfile.loadFromDisk(ctx.allocator, ctx.log, "bun.lockb"); + if (load_lockfile == .not_found) { + if (pm.options.log_level != .silent) + Output.prettyError("Lockfile not found", .{}); + Global.exit(1); + } + + if (load_lockfile == .err) { + if (pm.options.log_level != .silent) + Output.prettyError("Error loading lockfile: {s}", .{@errorName(load_lockfile.err.value)}); + Global.exit(1); + } + + _ = try pm.lockfile.hasMetaHashChanged(true); + Global.exit(0); } } }; diff --git a/src/cli/upgrade_command.zig b/src/cli/upgrade_command.zig index 3febe4cee..b7a1a086f 100644 --- a/src/cli/upgrade_command.zig +++ b/src/cli/upgrade_command.zig @@ -340,6 +340,8 @@ pub const UpgradeCommand = struct { const exe_subpath = Version.folder_name ++ std.fs.path.sep_str ++ "bun"; pub fn exec(ctx: Command.Context) !void { + @setCold(true); + _exec(ctx) catch |err| { Output.prettyErrorln("<r>bun upgrade failed with error: <red><b>{s}<r>\n\n<cyan>Please upgrade manually<r>:\n <b>curl https://bun.sh/install | bash<r>\n\n", .{@errorName(err)}); Output.flush(); diff --git a/src/install/install.zig b/src/install/install.zig index 68bd071fc..371a2948d 100644 --- a/src/install/install.zig +++ b/src/install/install.zig @@ -2925,6 +2925,7 @@ pub const PackageManager = struct { load_lockfile: bool = true, install_packages: bool = true, save_yarn_lock: bool = false, + print_meta_hash_string: bool = false, }; pub const Enable = struct { @@ -4817,7 +4818,7 @@ pub const PackageManager = struct { NetworkThread.global.pool.sleep_on_idle_network_thread = true; const needs_clean_lockfile = had_any_diffs or needs_new_lockfile or manager.package_json_updates.len > 0; - + var did_meta_hash_change = needs_clean_lockfile; if (needs_clean_lockfile) { manager.lockfile = try manager.lockfile.clean(manager.package_json_updates); } @@ -4833,6 +4834,12 @@ pub const PackageManager = struct { manager.lockfile.verifyResolutions(manager.options.local_package_features, manager.options.remote_package_features, log_level); } + if (needs_clean_lockfile or manager.options.enable.force_save_lockfile) { + did_meta_hash_change = try manager.lockfile.hasMetaHashChanged( + PackageManager.verbose_install or manager.options.do.print_meta_hash_string, + ); + } + if (manager.options.global) { try manager.setupGlobalDir(&ctx); } @@ -4843,7 +4850,7 @@ pub const PackageManager = struct { // 2. There is a determinism issue in the file where alignment bytes might be garbage data // This is a bug that needs to be fixed, however we can work around it for now // by avoiding saving the lockfile - if (manager.options.do.save_lockfile and (needs_clean_lockfile or + if (manager.options.do.save_lockfile and (did_meta_hash_change or manager.lockfile.isEmpty() or manager.options.enable.force_save_lockfile)) { @@ -4930,6 +4937,12 @@ pub const PackageManager = struct { try Lockfile.Printer.Tree.print(&printer, Output.WriterType, Output.writer(), false); } + if (!did_meta_hash_change) { + manager.summary.remove = 0; + manager.summary.add = 0; + manager.summary.update = 0; + } + var printed_timestamp = false; if (install_summary.success > 0) { // it's confusing when it shows 3 packages and says it installed 1 diff --git a/src/install/lockfile.zig b/src/install/lockfile.zig index 325f1e96d..835298372 100644 --- a/src/install/lockfile.zig +++ b/src/install/lockfile.zig @@ -80,6 +80,9 @@ const JSAst = @import("../js_ast.zig"); const Origin = @import("./install.zig").Origin; const PackageIDMultiple = @import("./install.zig").PackageIDMultiple; +pub const MetaHash = [std.crypto.hash.sha2.Sha512256.digest_length]u8; +const zero_hash = std.mem.zeroes(MetaHash); + pub const ExternalStringBuilder = StructBuilder.Builder(ExternalString); pub const SmallExternalStringList = ExternalSlice(String); @@ -91,6 +94,8 @@ format: FormatVersion = .v1, /// Eventually, this will be a relative path to a parent lockfile workspace_path: string = "", +meta_hash: MetaHash = zero_hash, + packages: Lockfile.Package.List = Lockfile.Package.List{}, buffers: Buffers = Buffers{}, @@ -649,6 +654,31 @@ pub fn clean(old: *Lockfile, updates: []PackageManager.UpdateRequest) !*Lockfile return new; } +pub const MetaHashFormatter = struct { + meta_hash: *const MetaHash, + + pub fn format(this: MetaHashFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + var remain: []const u8 = this.meta_hash[0..]; + + try std.fmt.format( + writer, + "{}-{}-{}-{}", + .{ + std.fmt.fmtSliceHexUpper(remain[0..8]), + std.fmt.fmtSliceHexLower(remain[8..16]), + std.fmt.fmtSliceHexUpper(remain[16..24]), + std.fmt.fmtSliceHexLower(remain[24..32]), + }, + ); + } +}; + +pub fn fmtMetaHash(this: *const Lockfile) MetaHashFormatter { + return .{ + .meta_hash = &this.meta_hash, + }; +} + pub const TreeFiller = std.fifo.LinearFifo([2]PackageID, .Dynamic); const Cloner = struct { @@ -821,6 +851,8 @@ pub const Printer = struct { lockfile_path_: string, format: Format, ) !void { + @setCold(true); + var lockfile_path: stringZ = ""; if (!std.fs.path.isAbsolute(lockfile_path_)) { @@ -1102,8 +1134,11 @@ pub const Printer = struct { try writer.writeAll( \\# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. \\# yarn lockfile v1 - \\ - \\ + \\# bun ./bun.lockb --hash: + ); + try writer.print( + "{}\n\n", + .{this.lockfile.fmtMetaHash()}, ); try Yarn.packages(this, Writer, writer); @@ -2813,6 +2848,9 @@ pub const Serializer = struct { var writer = stream.writer(); try writer.writeAll(header_bytes); try writer.writeIntLittle(u32, @enumToInt(this.format)); + + try writer.writeAll(&this.meta_hash); + const pos = try stream.getPos(); try writer.writeIntLittle(u64, 0); @@ -2848,8 +2886,12 @@ pub const Serializer = struct { if (format != @enumToInt(Lockfile.FormatVersion.current)) { return error.@"Outdated lockfile version"; } + lockfile.format = Lockfile.FormatVersion.current; lockfile.allocator = allocator; + + _ = try reader.readAll(&lockfile.meta_hash); + const total_buffer_size = try reader.readIntLittle(u64); if (total_buffer_size > stream.buffer.len) { return error.@"Lockfile is missing data"; @@ -2864,6 +2906,7 @@ pub const Serializer = struct { if ((try stream.reader().readIntLittle(u64)) != 0) { return error.@"Lockfile is malformed (expected 0 at the end)"; } + std.debug.assert(stream.pos == total_buffer_size); load_workspace: { @@ -2892,3 +2935,76 @@ pub const Serializer = struct { // const end = try reader.readIntLittle(u64); } }; + +pub fn hasMetaHashChanged(this: *Lockfile, print_name_version_string: bool) !bool { + const previous_meta_hash = this.meta_hash; + this.meta_hash = try this.generateMetaHash(print_name_version_string); + return !strings.eqlLong(&previous_meta_hash, &this.meta_hash, false); +} +pub fn generateMetaHash(this: *Lockfile, print_name_version_string: bool) !MetaHash { + if (this.packages.len <= 1) + return zero_hash; + + var string_builder = GlobalStringBuilder{}; + defer string_builder.deinit(this.allocator); + const names: []const String = this.packages.items(.name); + const resolutions: []const Resolution = this.packages.items(.resolution); + const bytes = this.buffers.string_bytes.items; + var alphabetized_names = try this.allocator.alloc(PackageID, this.packages.len -| 1); + defer this.allocator.free(alphabetized_names); + + const hash_prefix = "\n-- BEGIN SHA512/256(`${alphabetize(name)}@${order(version)}`) --\n"; + const hash_suffix = "-- END HASH--\n"; + string_builder.cap += hash_prefix.len + hash_suffix.len; + { + var i: usize = 1; + + while (i + 16 < this.packages.len) : (i += 16) { + comptime var j: usize = 0; + inline while (j < 16) : (j += 1) { + alphabetized_names[(i + j) - 1] = @truncate(PackageID, (i + j)); + string_builder.fmtCount("{s}@{}\n", .{ names[i + j].slice(bytes), resolutions[i + j].fmt(bytes) }); + } + } + + while (i < this.packages.len) : (i += 1) { + alphabetized_names[i - 1] = @truncate(PackageID, i); + string_builder.fmtCount("{s}@{}\n", .{ names[i].slice(bytes), resolutions[i].fmt(bytes) }); + } + } + + std.sort.sort( + PackageID, + alphabetized_names, + Lockfile.Package.Alphabetizer{ + .names = names, + .buf = bytes, + .resolutions = resolutions, + }, + Lockfile.Package.Alphabetizer.isAlphabetical, + ); + + string_builder.allocate(this.allocator) catch unreachable; + string_builder.ptr.?[0..hash_prefix.len].* = hash_prefix.*; + string_builder.len += hash_prefix.len; + + for (alphabetized_names) |i| { + _ = string_builder.fmt("{s}@{}\n", .{ names[i].slice(bytes), resolutions[i].fmt(bytes) }); + } + + string_builder.ptr.?[string_builder.len..string_builder.cap][0..hash_suffix.len].* = hash_suffix.*; + string_builder.len += hash_suffix.len; + + const alphabetized_name_version_string = string_builder.ptr.?[0..string_builder.len]; + if (print_name_version_string) { + Output.flush(); + Output.disableBuffering(); + Output.writer().writeAll(alphabetized_name_version_string) catch unreachable; + Output.enableBuffering(); + } + + var digest = zero_hash; + std.crypto.hash.sha2.Sha512256.hash(alphabetized_name_version_string, &digest, .{}); + + return digest; +} diff --git a/src/string_builder.zig b/src/string_builder.zig index 98ef5ab9b..c3e14fad4 100644 --- a/src/string_builder.zig +++ b/src/string_builder.zig @@ -2,6 +2,7 @@ const string = @import("string_types.zig").string; const Allocator = @import("std").mem.Allocator; const assert = @import("std").debug.assert; const copy = @import("std").mem.copy; +const Env = @import("./env.zig"); const StringBuilder = @This(); @@ -19,28 +20,46 @@ pub fn allocate(this: *StringBuilder, allocator: Allocator) !void { this.len = 0; } +pub fn deinit(this: *StringBuilder, allocator: Allocator) void { + if (this.ptr == null or this.cap == 0) return; + allocator.free(this.ptr.?[0..this.cap]); +} + pub fn append(this: *StringBuilder, slice: string) string { - assert(this.len <= this.cap); // didn't count everything - assert(this.ptr != null); // must call allocate first + if (Env.allow_assert) { + assert(this.len <= this.cap); // didn't count everything + assert(this.ptr != null); // must call allocate first + } copy(u8, this.ptr.?[this.len..this.cap], slice); const result = this.ptr.?[this.len..this.cap][0..slice.len]; this.len += slice.len; - assert(this.len <= this.cap); + if (Env.allow_assert) { + assert(this.len <= this.cap); + } + return result; } const std = @import("std"); pub fn fmt(this: *StringBuilder, comptime str: string, args: anytype) string { - assert(this.len <= this.cap); // didn't count everything - assert(this.ptr != null); // must call allocate first + if (Env.allow_assert) { + assert(this.len <= this.cap); // didn't count everything + assert(this.ptr != null); // must call allocate first + } var buf = this.ptr.?[this.len..this.cap]; const out = std.fmt.bufPrint(buf, str, args) catch unreachable; this.len += out.len; - assert(this.len <= this.cap); + if (Env.allow_assert) { + assert(this.len <= this.cap); + } return out; } + +pub fn fmtCount(this: *StringBuilder, comptime str: string, args: anytype) void { + this.cap += std.fmt.count(str, args); +} |