authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-10-12 18:05:37-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-10-12 18:05:37-07:00
log2f60a88308cf6358c06a6eaaff5ea61491873a0b
treed41aaab535aefc2dfd14ea4c892b738e25c53ec3
parentc8160b8a2ecd8ebf23e6e26a5dbb4dd6f3e60ffc

zig build: add `--print-tree` CLI option

for writing the current state of package dependencies to stdout, without running configuration.

2 files changed, 125 insertions(+), 33 deletions(-)

lib/compiler/Maker.zig+123-31
......@@ -103,8 +103,30 @@ const InstallPaths = struct {
103103};
104104
105105const PrintNode = struct {
106 parent: ?*PrintNode,
106 parent: ?*const PrintNode,
107107 last: bool = false,
108
109 fn printPrefix(node: *const PrintNode, writer: *Writer, mode: Io.Terminal.Mode) Writer.Error!void {
110 const parent = node.parent orelse return;
111 if (parent.parent == null) return;
112 try printPrefix(parent, writer, mode);
113 if (parent.last) {
114 try writer.writeAll(" ");
115 } else {
116 try printTreeSibling(writer, mode);
117 }
118 }
119
120 fn printPrefixedChild(node: *const PrintNode, writer: *Writer, mode: Io.Terminal.Mode) Writer.Error!void {
121 try printPrefix(node, writer, mode);
122 if (node.parent != null) {
123 if (node.last) {
124 try printTreeChildLast(writer, mode);
125 } else {
126 try printTreeChild(writer, mode);
127 }
128 }
129 }
108130};
109131
110132const ErrorStyle = enum {
......@@ -127,7 +149,7 @@ const ErrorStyle = enum {
127149};
128150const MultilineErrors = enum { indent, newline, none };
129151const Summary = enum { all, new, failures, line, none };
130const PrintConfiguration = enum { none, zon, path };
152const PrintConfiguration = enum { none, zon, path, tree };
131153
132154const GeneratedFile = struct {
133155 data: ?[]const u8,
......@@ -363,6 +385,8 @@ pub fn main(init: process.Init.Minimal) !void {
363385 override_local_cache_dir = nextArgOrFatal(args, &arg_i);
364386 } else if (mem.eql(u8, arg, "--pkg-dir")) {
365387 override_pkg_dir = nextArgOrFatal(args, &arg_i);
388 } else if (mem.eql(u8, arg, "--print-tree")) {
389 print_configuration = .tree;
366390 } else if (mem.eql(u8, arg, "--fetch")) {
367391 fetch_only = true;
368392 } else if (mem.cutPrefix(u8, arg, "--fetch=")) |rest| {
......@@ -797,6 +821,7 @@ pub fn main(init: process.Init.Minimal) !void {
797821 return cleanExit(io, &scanned_config);
798822 },
799823 .path => unreachable,
824 .tree => unreachable,
800825 }
801826
802827 var maker: Maker = .{
......@@ -1357,8 +1382,20 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {
13571382 return error.FailedButCacheIntact;
13581383 }
13591384
1360 if (options.fetch_only) {
1385 if (options.fetch_only or options.print_configuration == .tree) {
13611386 _ = io.lockStderr(&.{}, .no_color) catch {};
1387 if (options.print_configuration == .tree) {
1388 const NO_COLOR = EnvVar.NO_COLOR.isSet(&graph.environ_map);
1389 const CLICOLOR_FORCE = EnvVar.CLICOLOR_FORCE.isSet(&graph.environ_map);
1390 printPkgTree(.{
1391 .writer = initStdoutWriter(io),
1392 .mode = try .detect(io, .stdout(), NO_COLOR, CLICOLOR_FORCE),
1393 }, arena, &job_queue, &fetch) catch |err| switch (err) {
1394 error.WriteFailed => return stdout_writer_allocation.err.?,
1395 else => |e| return e,
1396 };
1397 try stdout_writer_allocation.flush();
1398 }
13621399 process.exit(0);
13631400 }
13641401
......@@ -1641,6 +1678,7 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {
16411678 process.exit(0);
16421679 },
16431680 .none, .zon => {},
1681 .tree => unreachable,
16441682 }
16451683
16461684 const configuration = c: {
......@@ -1678,6 +1716,72 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {
16781716 };
16791717}
16801718
1719fn printPkgTree(
1720 terminal: Io.Terminal,
1721 arena: Allocator,
1722 job_queue: *const Fetch.JobQueue,
1723 root_fetch: *const Fetch,
1724) !void {
1725 if (!root_fetch.have_manifest) return;
1726 const names = root_fetch.manifest.dependencies.keys();
1727 for (names, root_fetch.manifest.dependencies.values(), 0..) |name, *dep, i| {
1728 var print_node: PrintNode = .{
1729 .parent = null,
1730 .last = i == names.len - 1,
1731 };
1732 try printPkgTreeChild(terminal, arena, job_queue, &print_node, name, dep.hash);
1733 }
1734}
1735
1736fn printPkgTreeChild(
1737 terminal: Io.Terminal,
1738 arena: Allocator,
1739 job_queue: *const Fetch.JobQueue,
1740 parent_print_node: *const PrintNode,
1741 dep_name: []const u8,
1742 opt_hash: ?[]const u8,
1743) !void {
1744 const w = terminal.writer;
1745 try parent_print_node.printPrefixedChild(w, terminal.mode);
1746 try w.writeAll(dep_name);
1747
1748 const hash = opt_hash orelse {
1749 try terminal.setColor(.red);
1750 try w.writeAll(" (missing hash)\n");
1751 try terminal.setColor(.reset);
1752 return;
1753 };
1754 try terminal.setColor(.dim);
1755 try w.print(" {s}", .{hash});
1756 try terminal.setColor(.reset);
1757 const child_fetch = job_queue.table.get(.fromSlice(hash)) orelse {
1758 try terminal.setColor(.red);
1759 try w.writeAll(" (fetch failed)\n");
1760 try terminal.setColor(.reset);
1761 return;
1762 };
1763 switch (child_fetch.lazy_status) {
1764 .eager, .available => {},
1765 .unavailable => {
1766 try terminal.setColor(.yellow);
1767 try w.writeAll(" (not fetched)\n");
1768 try terminal.setColor(.reset);
1769 return;
1770 },
1771 }
1772 try w.writeByte('\n');
1773 if (!child_fetch.have_manifest) return;
1774
1775 const names = child_fetch.manifest.dependencies.keys();
1776 for (names, child_fetch.manifest.dependencies.values(), 0..) |name, *dep, i| {
1777 var print_node: PrintNode = .{
1778 .parent = parent_print_node,
1779 .last = i == names.len - 1,
1780 };
1781 try printPkgTreeChild(terminal, arena, job_queue, &print_node, name, dep.hash);
1782 }
1783}
1784
16811785fn cmdFetch(gpa: Allocator, graph: *Graph, args: []const []const u8) !void {
16821786 const environ_map = &graph.environ_map;
16831787 const io = graph.io;
......@@ -2949,18 +3053,7 @@ fn printTreeStep(
29493053 .failures => make_step.state == .success,
29503054 };
29513055 if (skip) return;
2952 try printPrefix(parent_node, stderr);
2953
2954 if (parent_node.parent != null) {
2955 if (parent_node.last) {
2956 try printChildNodePrefix(stderr);
2957 } else {
2958 try writer.writeAll(switch (stderr.mode) {
2959 .escape_codes => "\x1B\x28\x30\x74\x71\x1B\x28\x42 ", // ├─
2960 else => "+- ",
2961 });
2962 }
2963 }
3056 try parent_node.printPrefixedChild(writer, stderr.mode);
29643057
29653058 if (!first) try stderr.setColor(.dim);
29663059
......@@ -3189,23 +3282,22 @@ fn printStepFailure(
31893282 }
31903283}
31913284
3192fn printPrefix(node: *PrintNode, stderr: Io.Terminal) !void {
3193 const parent = node.parent orelse return;
3194 const writer = stderr.writer;
3195 if (parent.parent == null) return;
3196 try printPrefix(parent, stderr);
3197 if (parent.last) {
3198 try writer.writeAll(" ");
3199 } else {
3200 try writer.writeAll(switch (stderr.mode) {
3201 .escape_codes => "\x1B\x28\x30\x78\x1B\x28\x42 ", // │
3202 else => "| ",
3203 });
3204 }
3285fn printTreeChild(writer: *Writer, mode: Io.Terminal.Mode) Writer.Error!void {
3286 return writer.writeAll(switch (mode) {
3287 .escape_codes => "\x1B\x28\x30\x74\x71\x1B\x28\x42 ", // ├─
3288 else => "+- ",
3289 });
3290}
3291
3292fn printTreeSibling(writer: *Writer, mode: Io.Terminal.Mode) Writer.Error!void {
3293 return writer.writeAll(switch (mode) {
3294 .escape_codes => "\x1B\x28\x30\x78\x1B\x28\x42 ", // │
3295 else => "| ",
3296 });
32053297}
32063298
3207fn printChildNodePrefix(stderr: Io.Terminal) !void {
3208 try stderr.writer.writeAll(switch (stderr.mode) {
3299fn printTreeChildLast(writer: *Writer, mode: Io.Terminal.Mode) !void {
3300 try writer.writeAll(switch (mode) {
32093301 .escape_codes => "\x1B\x28\x30\x6d\x71\x1B\x28\x42 ", // └─
32103302 else => "+- ",
32113303 });
......@@ -3325,7 +3417,7 @@ pub fn printErrorMessages(
33253417 while (step_stack.pop()) |step_index| : (indent += 1) {
33263418 if (indent > 0) {
33273419 try writer.splatByteAll(' ', (indent - 1) * 3);
3328 try printChildNodePrefix(stderr);
3420 try printTreeChildLast(writer, stderr.mode);
33293421 }
33303422
33313423 try writer.writeAll(step_index.ptr(c).name.slice(c));
lib/compiler/Maker/ScannedConfig.zig+2-2
......@@ -388,6 +388,7 @@ const general_usage =
388388 \\ needed (Default) Lazy dependencies are fetched as needed
389389 \\ all Lazy dependencies are always fetched
390390 \\ --fork=[path], --fork [path] Override one or more projects from dependency tree
391 \\ --print-tree Write current state of package dependencies to stdout
391392 \\
392393 \\Advanced Options:
393394 \\ -freference-trace[=num] How many lines of reference trace should be shown per compile error
......@@ -404,7 +405,7 @@ const general_usage =
404405 \\ poisoned Don't cache the configuration
405406 \\ disallowed Panics when cache would be poisoned
406407 \\ ignored A little poison never hurt anybody
407 \\ --print-configuration Render configuration as .zon to stdout
408 \\ --print-configuration Render configuration as .zon to stdout and exit
408409 \\ --print-configuration-path Print the path to the binary configuration file to stdout
409410 \\ --build-id[=style] At a minor link-time expense, embeds a build ID in binaries
410411 \\ fast 8-byte non-cryptographic hash (COFF, ELF, WASM)
......@@ -423,4 +424,3 @@ const general_usage =
423424 \\ --verbose-llvm-cpu-features Enable compiler debug output for LLVM CPU features
424425 \\
425426;
426}