| ... | @@ -81,6 +81,10 @@ pub const JobQueue = struct { | ... | @@ -81,6 +81,10 @@ pub const JobQueue = struct { |
| 81 | wait_group: WaitGroup = .{}, | 81 | wait_group: WaitGroup = .{}, |
| 82 | global_cache: Cache.Directory, | 82 | global_cache: Cache.Directory, |
| 83 | recursive: bool, | 83 | recursive: bool, |
| | 84 | /// Dumps hash information to stdout which can be used to troubleshoot why |
| | 85 | /// two hashes of the same package do not match. |
| | 86 | /// If this is true, `recursive` must be false. |
| | 87 | debug_hash: bool, |
| 84 | work_around_btrfs_bug: bool, | 88 | work_around_btrfs_bug: bool, |
| 85 | | 89 | |
| 86 | pub const Table = std.AutoArrayHashMapUnmanaged(Manifest.MultiHashHexDigest, *Fetch); | 90 | pub const Table = std.AutoArrayHashMapUnmanaged(Manifest.MultiHashHexDigest, *Fetch); |
| ... | @@ -1315,7 +1319,7 @@ fn computeHash( | ... | @@ -1315,7 +1319,7 @@ fn computeHash( |
| 1315 | const kind: HashedFile.Kind = switch (entry.kind) { | 1319 | const kind: HashedFile.Kind = switch (entry.kind) { |
| 1316 | .directory => unreachable, | 1320 | .directory => unreachable, |
| 1317 | .file => .file, | 1321 | .file => .file, |
| 1318 | .sym_link => .sym_link, | 1322 | .sym_link => .link, |
| 1319 | else => return f.fail(f.location_tok, try eb.printString( | 1323 | else => return f.fail(f.location_tok, try eb.printString( |
| 1320 | "package contains '{s}' which has illegal file type '{s}'", | 1324 | "package contains '{s}' which has illegal file type '{s}'", |
| 1321 | .{ entry.path, @tagName(entry.kind) }, | 1325 | .{ entry.path, @tagName(entry.kind) }, |
| ... | @@ -1329,7 +1333,7 @@ fn computeHash( | ... | @@ -1329,7 +1333,7 @@ fn computeHash( |
| 1329 | const hashed_file = try arena.create(HashedFile); | 1333 | const hashed_file = try arena.create(HashedFile); |
| 1330 | hashed_file.* = .{ | 1334 | hashed_file.* = .{ |
| 1331 | .fs_path = fs_path, | 1335 | .fs_path = fs_path, |
| 1332 | .normalized_path = try normalizePath(arena, fs_path), | 1336 | .normalized_path = try normalizePathAlloc(arena, fs_path), |
| 1333 | .kind = kind, | 1337 | .kind = kind, |
| 1334 | .hash = undefined, // to be populated by the worker | 1338 | .hash = undefined, // to be populated by the worker |
| 1335 | .failure = undefined, // to be populated by the worker | 1339 | .failure = undefined, // to be populated by the worker |
| ... | @@ -1399,9 +1403,36 @@ fn computeHash( | ... | @@ -1399,9 +1403,36 @@ fn computeHash( |
| 1399 | } | 1403 | } |
| 1400 | | 1404 | |
| 1401 | if (any_failures) return error.FetchFailed; | 1405 | if (any_failures) return error.FetchFailed; |
| | 1406 | |
| | 1407 | if (f.job_queue.debug_hash) { |
| | 1408 | assert(!f.job_queue.recursive); |
| | 1409 | // Print something to stdout that can be text diffed to figure out why |
| | 1410 | // the package hash is different. |
| | 1411 | dumpHashInfo(all_files.items) catch |err| { |
| | 1412 | std.debug.print("unable to write to stdout: {s}\n", .{@errorName(err)}); |
| | 1413 | std.process.exit(1); |
| | 1414 | }; |
| | 1415 | } |
| | 1416 | |
| 1402 | return hasher.finalResult(); | 1417 | return hasher.finalResult(); |
| 1403 | } | 1418 | } |
| 1404 | | 1419 | |
| | 1420 | fn dumpHashInfo(all_files: []const *const HashedFile) !void { |
| | 1421 | const stdout = std.io.getStdOut(); |
| | 1422 | var bw = std.io.bufferedWriter(stdout.writer()); |
| | 1423 | const w = bw.writer(); |
| | 1424 | |
| | 1425 | for (all_files) |hashed_file| { |
| | 1426 | try w.print("{s}: {s}: {s}\n", .{ |
| | 1427 | @tagName(hashed_file.kind), |
| | 1428 | std.fmt.fmtSliceHexLower(&hashed_file.hash), |
| | 1429 | hashed_file.normalized_path, |
| | 1430 | }); |
| | 1431 | } |
| | 1432 | |
| | 1433 | try bw.flush(); |
| | 1434 | } |
| | 1435 | |
| 1405 | fn workerHashFile(dir: fs.Dir, hashed_file: *HashedFile, wg: *WaitGroup) void { | 1436 | fn workerHashFile(dir: fs.Dir, hashed_file: *HashedFile, wg: *WaitGroup) void { |
| 1406 | defer wg.finish(); | 1437 | defer wg.finish(); |
| 1407 | hashed_file.failure = hashFileFallible(dir, hashed_file); | 1438 | hashed_file.failure = hashFileFallible(dir, hashed_file); |
| ... | @@ -1427,8 +1458,14 @@ fn hashFileFallible(dir: fs.Dir, hashed_file: *HashedFile) HashedFile.Error!void | ... | @@ -1427,8 +1458,14 @@ fn hashFileFallible(dir: fs.Dir, hashed_file: *HashedFile) HashedFile.Error!void |
| 1427 | hasher.update(buf[0..bytes_read]); | 1458 | hasher.update(buf[0..bytes_read]); |
| 1428 | } | 1459 | } |
| 1429 | }, | 1460 | }, |
| 1430 | .sym_link => { | 1461 | .link => { |
| 1431 | const link_name = try dir.readLink(hashed_file.fs_path, &buf); | 1462 | const link_name = try dir.readLink(hashed_file.fs_path, &buf); |
| | 1463 | if (fs.path.sep != canonical_sep) { |
| | 1464 | // Package hashes are intended to be consistent across |
| | 1465 | // platforms which means we must normalize path separators |
| | 1466 | // inside symlinks. |
| | 1467 | normalizePath(link_name); |
| | 1468 | } |
| 1432 | hasher.update(link_name); | 1469 | hasher.update(link_name); |
| 1433 | }, | 1470 | }, |
| 1434 | } | 1471 | } |
| ... | @@ -1474,7 +1511,7 @@ const HashedFile = struct { | ... | @@ -1474,7 +1511,7 @@ const HashedFile = struct { |
| 1474 | fs.File.StatError || | 1511 | fs.File.StatError || |
| 1475 | fs.Dir.ReadLinkError; | 1512 | fs.Dir.ReadLinkError; |
| 1476 | | 1513 | |
| 1477 | const Kind = enum { file, sym_link }; | 1514 | const Kind = enum { file, link }; |
| 1478 | | 1515 | |
| 1479 | fn lessThan(context: void, lhs: *const HashedFile, rhs: *const HashedFile) bool { | 1516 | fn lessThan(context: void, lhs: *const HashedFile, rhs: *const HashedFile) bool { |
| 1480 | _ = context; | 1517 | _ = context; |
| ... | @@ -1484,22 +1521,20 @@ const HashedFile = struct { | ... | @@ -1484,22 +1521,20 @@ const HashedFile = struct { |
| 1484 | | 1521 | |
| 1485 | /// Make a file system path identical independently of operating system path inconsistencies. | 1522 | /// Make a file system path identical independently of operating system path inconsistencies. |
| 1486 | /// This converts backslashes into forward slashes. | 1523 | /// This converts backslashes into forward slashes. |
| 1487 | fn normalizePath(arena: Allocator, fs_path: []const u8) ![]const u8 { | 1524 | fn normalizePathAlloc(arena: Allocator, fs_path: []const u8) ![]const u8 { |
| 1488 | const canonical_sep = '/'; | 1525 | if (fs.path.sep == canonical_sep) return fs_path; |
| 1489 | | | |
| 1490 | if (fs.path.sep == canonical_sep) | | |
| 1491 | return fs_path; | | |
| 1492 | | | |
| 1493 | const normalized = try arena.dupe(u8, fs_path); | 1526 | const normalized = try arena.dupe(u8, fs_path); |
| 1494 | for (normalized) |*byte| { | 1527 | normalizePath(normalized); |
| 1495 | switch (byte.*) { | | |
| 1496 | fs.path.sep => byte.* = canonical_sep, | | |
| 1497 | else => continue, | | |
| 1498 | } | | |
| 1499 | } | | |
| 1500 | return normalized; | 1528 | return normalized; |
| 1501 | } | 1529 | } |
| 1502 | | 1530 | |
| | 1531 | const canonical_sep = fs.path.sep_posix; |
| | 1532 | |
| | 1533 | fn normalizePath(bytes: []u8) void { |
| | 1534 | assert(fs.path.sep != canonical_sep); |
| | 1535 | std.mem.replaceScalar(u8, bytes, fs.path.sep, canonical_sep); |
| | 1536 | } |
| | 1537 | |
| 1503 | const Filter = struct { | 1538 | const Filter = struct { |
| 1504 | include_paths: std.StringArrayHashMapUnmanaged(void) = .{}, | 1539 | include_paths: std.StringArrayHashMapUnmanaged(void) = .{}, |
| 1505 | | 1540 | |