diff --git a/lib/compiler/Maker.zig b/lib/compiler/Maker.zig index f85a66096c3f513f50faf0e6038337b865213233..0ae85d4fa3bf3ba2b6b78514eb3a8cc4a5a1429b 100644 --- a/lib/compiler/Maker.zig +++ b/lib/compiler/Maker.zig @@ -1397,6 +1397,8 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig { process.exit(0); } + try validateFingerprints(gpa, &job_queue); + // Create the dependencies.zig file for configurer to // obtain via `@import("@dependencies")`. { @@ -1714,6 +1716,81 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig { }; } +const ValidFingerprintKey = struct { + project_id: Package.ProjectId, + version: std.SemanticVersion, + + const Context = struct { + pub fn hash(this: @This(), a: ValidFingerprintKey) u32 { + _ = this; + var hasher: std.hash.Wyhash = .init(a.project_id.fingerprint_id); + hasher.update(&a.project_id.padded_name); + hasher.update(@ptrCast((&a.version.major)[0..1])); + hasher.update(@ptrCast((&a.version.minor)[0..1])); + hasher.update(@ptrCast((&a.version.patch)[0..1])); + if (a.version.pre) |pre| hasher.update(pre); + return @truncate(hasher.final()); + } + pub fn eql(this: @This(), a: ValidFingerprintKey, b: ValidFingerprintKey, i: usize) bool { + _ = this; + _ = i; + return a.project_id.eql(&b.project_id) and a.version.order(b.version) == .eq; + } + }; +}; + +const ValidFingerprintTable = std.array_hash_map.Custom( + ValidFingerprintKey, + struct { fetch: *Fetch, pkg_hash: Package.Hash, printed: bool }, + ValidFingerprintKey.Context, + true, +); + +/// Ensure that the dependency tree did not have any fingerprint matches with the same semantic version but +/// differing package hashes. +fn validateFingerprints(gpa: Allocator, job_queue: *const Fetch.JobQueue) !void { + var any_errors = false; + + var table: ValidFingerprintTable = .empty; + defer table.deinit(gpa); + + for (job_queue.table.keys(), job_queue.table.values()) |pkg_hash, fetch| { + if (!fetch.have_manifest) continue; + const gop = try table.getOrPut(gpa, .{ + .project_id = .init(fetch.manifest.name, fetch.manifest.id), + .version = fetch.manifest.version, + }); + if (gop.found_existing) { + any_errors = true; + const fingerprint: Package.Fingerprint = .init(fetch.manifest.id, fetch.manifest.name); + const stripped_version: std.SemanticVersion = .{ + .major = fetch.manifest.version.major, + .minor = fetch.manifest.version.minor, + .patch = fetch.manifest.version.patch, + .pre = fetch.manifest.version.pre, + }; + if (!gop.value_ptr.printed) { + gop.value_ptr.printed = true; + log.err("contradictory hash for package with fingerprint 0x{x} and version {f}: {s}", .{ + fingerprint.int(), stripped_version, gop.value_ptr.pkg_hash.toSlice(), + }); + } + log.err("contradictory hash for package with fingerprint 0x{x} and version {f}: {s}", .{ + fingerprint.int(), stripped_version, pkg_hash.toSlice(), + }); + } else { + gop.value_ptr.* = .{ .fetch = fetch, .pkg_hash = pkg_hash, .printed = false }; + } + } + if (any_errors) { + log.info("potential cause: package ownership dispute", .{}); + log.info("potential cause: maintainer neglected to bump version after making changes", .{}); + log.info("potential cause: dependency on unreleased changes", .{}); + log.info("hint: use --print-tree to visualize all package versions and hashes", .{}); + return error.FailedButCacheIntact; + } +} + fn printPkgTree( terminal: Io.Terminal, arena: Allocator, diff --git a/lib/compiler/Maker/Package.zig b/lib/compiler/Maker/Package.zig index 8baef22f375f925534380176f618a470f988117e..c31ecf6916ce4d8d279cc9dbe9f529ee99355372 100644 --- a/lib/compiler/Maker/Package.zig +++ b/lib/compiler/Maker/Package.zig @@ -8,13 +8,17 @@ pub const Fingerprint = packed struct(u64) { id: u32, checksum: u32, - pub fn generate(rng: std.Random, name: []const u8) Fingerprint { + pub fn init(id: u32, name: []const u8) Fingerprint { return .{ - .id = rng.intRangeLessThan(u32, 1, 0xffffffff), + .id = id, .checksum = std.hash.Crc32.hash(name), }; } + pub fn generate(rng: std.Random, name: []const u8) Fingerprint { + return .init(rng.intRangeLessThan(u32, 1, 0xffffffff), name); + } + pub fn validate(n: Fingerprint, name: []const u8) bool { switch (n.id) { 0x00000000, 0xffffffff => return false,