authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-09-04 19:09:38-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-09-04 19:09:38-07:00
log98d3f26362f22aa91ace98b07249af07132e350b
tree6085f0208de7aebad1abbd576a73f0b5f060e9c2
parent0cc885716800a35779cce7ba762021e112be3296

add cache-cat subcommand

for printing .zig-cache/h/* files to zon format

3 files changed, 111 insertions(+), 8 deletions(-)

lib/compiler/Maker.zig+97-1
...@@ -199,12 +199,13 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -199,12 +199,13 @@ pub fn main(init: process.Init.Minimal) !void {
199 .random_seed = parseRandomSeed(seed_arg),199 .random_seed = parseRandomSeed(seed_arg),
200 };200 };
201201
202 const cmd = stringToEnum(enum { libc, init, fetch, build }, cmd_name) orelse202 const cmd = stringToEnum(enum { libc, init, fetch, build, @"cache-cat" }, cmd_name) orelse
203 fatal("bad command name: {q}", .{cmd_name});203 fatal("bad command name: {q}", .{cmd_name});
204 switch (cmd) {204 switch (cmd) {
205 .libc => return cmdLibC(gpa, &graph, args[arg_i..]),205 .libc => return cmdLibC(gpa, &graph, args[arg_i..]),
206 .init => return cmdInit(gpa, &graph, args[arg_i..]),206 .init => return cmdInit(gpa, &graph, args[arg_i..]),
207 .fetch => return cmdFetch(gpa, &graph, args[arg_i..]),207 .fetch => return cmdFetch(gpa, &graph, args[arg_i..]),
208 .@"cache-cat" => return cmdCacheCat(gpa, &graph, args[arg_i..]),
208 .build => {},209 .build => {},
209 }210 }
210211
...@@ -1880,6 +1881,101 @@ fn cmdFetch(gpa: Allocator, graph: *Graph, args: []const []const u8) !void {...@@ -1880,6 +1881,101 @@ fn cmdFetch(gpa: Allocator, graph: *Graph, args: []const []const u8) !void {
1880 return process.cleanExit(io);1881 return process.cleanExit(io);
1881}1882}
18821883
1884fn cmdCacheCat(gpa: Allocator, graph: *Graph, args: []const []const u8) !void {
1885 const io = graph.io;
1886
1887 var arg_i: usize = 0;
1888 var contents: std.ArrayList(u8) = .empty;
1889 defer contents.deinit(gpa);
1890
1891 while (nextArg(args, &arg_i)) |arg| {
1892 if (mem.startsWith(u8, arg, "-")) {
1893 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
1894 try Io.File.stdout().writeStreamingAll(io, usage_cache_cat);
1895 return process.cleanExit(io);
1896 } else {
1897 fatal("unrecognized parameter: {q}", .{arg});
1898 }
1899 } else {
1900 var file = Dir.cwd().openFile(io, arg, .{}) catch |err| fatal("opening {q} failed: {t}", .{ arg, err });
1901 defer file.close(io);
1902
1903 var manifest_reader = file.reader(io, &.{}); // Reads positionally from zero.
1904 contents.clearRetainingCapacity();
1905 manifest_reader.interface.appendRemainingUnlimited(gpa, &contents) catch |err| switch (err) {
1906 error.OutOfMemory => |e| return e,
1907 error.ReadFailed => switch (manifest_reader.err.?) {
1908 error.Canceled => |e| return e,
1909 else => |e| fatal("reading from {q} failed: {t}", .{ arg, e }),
1910 },
1911 };
1912 const hex_digest = Dir.path.basename(arg);
1913 cacheCatOne(hex_digest, contents.items, initStdoutWriter(io)) catch |err| switch (err) {
1914 error.WriteFailed => fatal("writing to stdout failed: {t}", .{stdout_writer_allocation.err.?}),
1915 else => |e| fatal("parsing {q} failed: {t}", .{ arg, e }),
1916 };
1917 try stdout_writer_allocation.flush();
1918 }
1919 }
1920}
1921
1922fn cacheCatOne(input_hex_digest: []const u8, contents: []const u8, writer: *Io.Writer) !void {
1923 var bin_digest: Cache.BinDigest = undefined;
1924 _ = try fmt.hexToBytes(&bin_digest, input_hex_digest);
1925
1926 var hh: Cache.HashHelper = .{};
1927 hh.hasher.update(&bin_digest);
1928
1929 var serializer: std.zon.Serializer = .{ .writer = writer };
1930 var top_level = try serializer.beginStruct(.{});
1931 try top_level.field("input_hash", input_hex_digest, .{});
1932 var files_tuple = try top_level.beginTupleField("files", .{});
1933 var off: usize = 0;
1934 while (off + 1 < contents.len) {
1935 const file_off: Cache.Manifest.File.Offset = @fromBackingInt(@intCast(off));
1936 const file = try file_off.getFallibleConst(contents);
1937 const path = try Cache.Manifest.filePathFallible(contents, file_off);
1938 if (path.len == 0) return error.InvalidFormat;
1939
1940 var file_obj = try files_tuple.beginStructField(.{ .whitespace_style = .{ .wrap = false } });
1941 try file_obj.field("size", file.size, .{});
1942 try file_obj.field("inode", file.inode, .{});
1943 try file_obj.field("mtime", file.mtime, .{});
1944 const hex_digest = Cache.binToHex(file.digest);
1945 try file_obj.field("digest", @as([]const u8, &hex_digest), .{});
1946 if (file.flags.is_directory) try file_obj.field("directory", true, .{});
1947 if (file.flags.metadata_only) try file_obj.field("metadata", true, .{});
1948 try file_obj.field("prefix", file.flags.prefix, .{});
1949 try file_obj.field("path", path, .{});
1950 try file_obj.end();
1951
1952 hh.hasher.update(&file.digest);
1953
1954 off += Cache.Manifest.File.sizeOf(path.len);
1955 }
1956
1957 try files_tuple.end();
1958
1959 var discovered_bin_digest: Cache.BinDigest = undefined;
1960 hh.hasher.final(&discovered_bin_digest);
1961 const discovered_hex_digest = Cache.binToHex(discovered_bin_digest);
1962 try top_level.field("discovered_hash", @as([]const u8, &discovered_hex_digest), .{});
1963
1964 try top_level.end();
1965 try writer.writeByte('\n');
1966}
1967
1968const usage_cache_cat =
1969 \\Usage: zig cache-cat <paths>
1970 \\
1971 \\ Prints .zig-cache/h/* manifest files in text form.
1972 \\
1973 \\Options:
1974 \\ -h, --help Print this help and exit
1975 \\
1976 \\
1977;
1978
1883const usage_fetch =1979const usage_fetch =
1884 \\Usage: zig fetch [options] <url>1980 \\Usage: zig fetch [options] <url>
1885 \\Usage: zig fetch [options] <path>1981 \\Usage: zig fetch [options] <path>
lib/std/Build/Cache.zig+8-3
...@@ -408,9 +408,14 @@ pub const Manifest = struct {...@@ -408,9 +408,14 @@ pub const Manifest = struct {
408 }408 }
409409
410 pub fn getFallible(offset: Offset, contents: []u8) error{InvalidFormat}!*File {410 pub fn getFallible(offset: Offset, contents: []u8) error{InvalidFormat}!*File {
411 // TODO make @constCast support in-memory coercion across error unions and optionals
412 return @constCast(try getFallibleConst(offset, contents));
413 }
414
415 pub fn getFallibleConst(offset: Offset, contents: []const u8) error{InvalidFormat}!*const File {
411 if (@backingInt(offset) + @sizeOf(File) >= contents.len) return error.InvalidFormat;416 if (@backingInt(offset) + @sizeOf(File) >= contents.len) return error.InvalidFormat;
412 if (!mem.isAligned(@backingInt(offset), @alignOf(File))) return error.InvalidFormat;417 if (!mem.isAligned(@backingInt(offset), @alignOf(File))) return error.InvalidFormat;
413 return get(offset, contents);418 return getConst(offset, contents);
414 }419 }
415 };420 };
416421
...@@ -461,7 +466,7 @@ pub const Manifest = struct {...@@ -461,7 +466,7 @@ pub const Manifest = struct {
461 }466 }
462467
463 /// `path_len` does not include the null byte.468 /// `path_len` does not include the null byte.
464 fn sizeOf(path_len: usize) usize {469 pub fn sizeOf(path_len: usize) usize {
465 const end = @offsetOf(File, "path_start") + path_len;470 const end = @offsetOf(File, "path_start") + path_len;
466 const needed_alignment = @alignOf(File) - (end % @alignOf(File));471 const needed_alignment = @alignOf(File) - (end % @alignOf(File));
467 assert(needed_alignment >= 1); // Always need at least a null byte.472 assert(needed_alignment >= 1); // Always need at least a null byte.
...@@ -1615,7 +1620,7 @@ pub const Manifest = struct {...@@ -1615,7 +1620,7 @@ pub const Manifest = struct {
1615 hasher.update(contents[hash_start..hash_end]);1620 hasher.update(contents[hash_start..hash_end]);
1616 }1621 }
16171622
1618 fn filePathFallible(contents: []const u8, off: File.Offset) error{InvalidFormat}![:0]const u8 {1623 pub fn filePathFallible(contents: []const u8, off: File.Offset) error{InvalidFormat}![:0]const u8 {
1619 const path_start = @backingInt(off) + @offsetOf(File, "path_start");1624 const path_start = @backingInt(off) + @offsetOf(File, "path_start");
1620 const path_end = mem.findScalarPos(u8, contents, path_start, 0) orelse return error.InvalidFormat;1625 const path_end = mem.findScalarPos(u8, contents, path_start, 0) orelse return error.InvalidFormat;
1621 return contents[path_start..path_end :0];1626 return contents[path_start..path_end :0];
src/main.zig+6-4
...@@ -107,6 +107,7 @@ const normal_usage =...@@ -107,6 +107,7 @@ const normal_usage =
107 \\107 \\
108 \\ env Print lib path, std path, cache directory, and version108 \\ env Print lib path, std path, cache directory, and version
109 \\ help Print this help and exit109 \\ help Print this help and exit
110 \\ cache-cat Print a zig-cache manifest file as zon
110 \\ std View standard library documentation in a browser111 \\ std View standard library documentation in a browser
111 \\ libc Display native libc paths file or validate one112 \\ libc Display native libc paths file or validate one
112 \\ targets List available compilation targets113 \\ targets List available compilation targets
...@@ -242,6 +243,10 @@ const Cmd = enum {...@@ -242,6 +243,10 @@ const Cmd = enum {
242 ar,243 ar,
243244
244 build,245 build,
246 @"cache-cat",
247 fetch,
248 init,
249 libc,
245250
246 clang,251 clang,
247 @"-cc1",252 @"-cc1",
...@@ -258,10 +263,7 @@ const Cmd = enum {...@@ -258,10 +263,7 @@ const Cmd = enum {
258 fmt,263 fmt,
259 objcopy,264 objcopy,
260 objdump,265 objdump,
261 fetch,
262 libc,
263 std,266 std,
264 init,
265 targets,267 targets,
266 version,268 version,
267 env,269 env,
...@@ -351,7 +353,7 @@ fn mainArgs(...@@ -351,7 +353,7 @@ fn mainArgs(
351 dev.check(.ar_command);353 dev.check(.ar_command);
352 return process.exit(try llvmArMain(arena, args));354 return process.exit(try llvmArMain(arena, args));
353 },355 },
354 .build, .fetch, .init, .libc => {356 .build, .fetch, .init, .libc, .@"cache-cat" => {
355 return jitCmd(gpa, arena, io, cmd_args, environ_map, .{357 return jitCmd(gpa, arena, io, cmd_args, environ_map, .{
356 .cmd_name = "maker",358 .cmd_name = "maker",
357 .root_src_path = "Maker.zig",359 .root_src_path = "Maker.zig",