aboutsummaryrefslogtreecommitdiff
path: root/src/install
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2022-02-25 00:28:25 -0800
committerGravatar Jarred Sumner <jarred@jarredsumner.com> 2022-02-25 00:48:36 -0800
commit293a9bc811e35943cc4d1dbdcbf20bbf77ac8c2f (patch)
tree52cd823205ca899e27e91480365bba82c5f6b871 /src/install
parentb8c6865ce0a0385982e51de5614689b43f265562 (diff)
downloadbun-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.zig17
-rw-r--r--src/install/lockfile.zig120
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;
+}