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 {
199199 .random_seed = parseRandomSeed(seed_arg),
200200 };
201201
202 const cmd = stringToEnum(enum { libc, init, fetch, build }, cmd_name) orelse
202 const cmd = stringToEnum(enum { libc, init, fetch, build, @"cache-cat" }, cmd_name) orelse
203203 fatal("bad command name: {q}", .{cmd_name});
204204 switch (cmd) {
205205 .libc => return cmdLibC(gpa, &graph, args[arg_i..]),
206206 .init => return cmdInit(gpa, &graph, args[arg_i..]),
207207 .fetch => return cmdFetch(gpa, &graph, args[arg_i..]),
208 .@"cache-cat" => return cmdCacheCat(gpa, &graph, args[arg_i..]),
208209 .build => {},
209210 }
210211
......@@ -1880,6 +1881,101 @@ fn cmdFetch(gpa: Allocator, graph: *Graph, args: []const []const u8) !void {
18801881 return process.cleanExit(io);
18811882}
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
18831979const usage_fetch =
18841980 \\Usage: zig fetch [options] <url>
18851981 \\Usage: zig fetch [options] <path>
lib/std/Build/Cache.zig+8-3
......@@ -408,9 +408,14 @@ pub const Manifest = struct {
408408 }
409409
410410 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 {
411416 if (@backingInt(offset) + @sizeOf(File) >= contents.len) return error.InvalidFormat;
412417 if (!mem.isAligned(@backingInt(offset), @alignOf(File))) return error.InvalidFormat;
413 return get(offset, contents);
418 return getConst(offset, contents);
414419 }
415420 };
416421
......@@ -461,7 +466,7 @@ pub const Manifest = struct {
461466 }
462467
463468 /// `path_len` does not include the null byte.
464 fn sizeOf(path_len: usize) usize {
469 pub fn sizeOf(path_len: usize) usize {
465470 const end = @offsetOf(File, "path_start") + path_len;
466471 const needed_alignment = @alignOf(File) - (end % @alignOf(File));
467472 assert(needed_alignment >= 1); // Always need at least a null byte.
......@@ -1615,7 +1620,7 @@ pub const Manifest = struct {
16151620 hasher.update(contents[hash_start..hash_end]);
16161621 }
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 {
16191624 const path_start = @backingInt(off) + @offsetOf(File, "path_start");
16201625 const path_end = mem.findScalarPos(u8, contents, path_start, 0) orelse return error.InvalidFormat;
16211626 return contents[path_start..path_end :0];
src/main.zig+6-4
......@@ -107,6 +107,7 @@ const normal_usage =
107107 \\
108108 \\ env Print lib path, std path, cache directory, and version
109109 \\ help Print this help and exit
110 \\ cache-cat Print a zig-cache manifest file as zon
110111 \\ std View standard library documentation in a browser
111112 \\ libc Display native libc paths file or validate one
112113 \\ targets List available compilation targets
......@@ -242,6 +243,10 @@ const Cmd = enum {
242243 ar,
243244
244245 build,
246 @"cache-cat",
247 fetch,
248 init,
249 libc,
245250
246251 clang,
247252 @"-cc1",
......@@ -258,10 +263,7 @@ const Cmd = enum {
258263 fmt,
259264 objcopy,
260265 objdump,
261 fetch,
262 libc,
263266 std,
264 init,
265267 targets,
266268 version,
267269 env,
......@@ -351,7 +353,7 @@ fn mainArgs(
351353 dev.check(.ar_command);
352354 return process.exit(try llvmArMain(arena, args));
353355 },
354 .build, .fetch, .init, .libc => {
356 .build, .fetch, .init, .libc, .@"cache-cat" => {
355357 return jitCmd(gpa, arena, io, cmd_args, environ_map, .{
356358 .cmd_name = "maker",
357359 .root_src_path = "Maker.zig",