diff options
author | 2022-02-25 00:28:25 -0800 | |
---|---|---|
committer | 2022-02-25 00:48:36 -0800 | |
commit | 293a9bc811e35943cc4d1dbdcbf20bbf77ac8c2f (patch) | |
tree | 52cd823205ca899e27e91480365bba82c5f6b871 /src/install | |
parent | b8c6865ce0a0385982e51de5614689b43f265562 (diff) | |
download | bun-293a9bc811e35943cc4d1dbdcbf20bbf77ac8c2f.tar.gz bun-293a9bc811e35943cc4d1dbdcbf20bbf77ac8c2f.tar.zst bun-293a9bc811e35943cc4d1dbdcbf20bbf77ac8c2f.zip |
[bun install] Add metadata hash
Diffstat (limited to 'src/install')
-rw-r--r-- | src/install/install.zig | 17 | ||||
-rw-r--r-- | src/install/lockfile.zig | 120 |
2 files changed, 133 insertions, 4 deletions
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; +} |