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 {...@@ -103,8 +103,30 @@ const InstallPaths = struct {
103};103};
104104
105const PrintNode = struct {105const PrintNode = struct {
106 parent: ?*PrintNode,106 parent: ?*const PrintNode,
107 last: bool = false,107 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 }
108};130};
109131
110const ErrorStyle = enum {132const ErrorStyle = enum {
...@@ -127,7 +149,7 @@ const ErrorStyle = enum {...@@ -127,7 +149,7 @@ const ErrorStyle = enum {
127};149};
128const MultilineErrors = enum { indent, newline, none };150const MultilineErrors = enum { indent, newline, none };
129const Summary = enum { all, new, failures, line, none };151const Summary = enum { all, new, failures, line, none };
130const PrintConfiguration = enum { none, zon, path };152const PrintConfiguration = enum { none, zon, path, tree };
131153
132const GeneratedFile = struct {154const GeneratedFile = struct {
133 data: ?[]const u8,155 data: ?[]const u8,
...@@ -363,6 +385,8 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -363,6 +385,8 @@ pub fn main(init: process.Init.Minimal) !void {
363 override_local_cache_dir = nextArgOrFatal(args, &arg_i);385 override_local_cache_dir = nextArgOrFatal(args, &arg_i);
364 } else if (mem.eql(u8, arg, "--pkg-dir")) {386 } else if (mem.eql(u8, arg, "--pkg-dir")) {
365 override_pkg_dir = nextArgOrFatal(args, &arg_i);387 override_pkg_dir = nextArgOrFatal(args, &arg_i);
388 } else if (mem.eql(u8, arg, "--print-tree")) {
389 print_configuration = .tree;
366 } else if (mem.eql(u8, arg, "--fetch")) {390 } else if (mem.eql(u8, arg, "--fetch")) {
367 fetch_only = true;391 fetch_only = true;
368 } else if (mem.cutPrefix(u8, arg, "--fetch=")) |rest| {392 } else if (mem.cutPrefix(u8, arg, "--fetch=")) |rest| {
...@@ -797,6 +821,7 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -797,6 +821,7 @@ pub fn main(init: process.Init.Minimal) !void {
797 return cleanExit(io, &scanned_config);821 return cleanExit(io, &scanned_config);
798 },822 },
799 .path => unreachable,823 .path => unreachable,
824 .tree => unreachable,
800 }825 }
801826
802 var maker: Maker = .{827 var maker: Maker = .{
...@@ -1357,8 +1382,20 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {...@@ -1357,8 +1382,20 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {
1357 return error.FailedButCacheIntact;1382 return error.FailedButCacheIntact;
1358 }1383 }
13591384
1360 if (options.fetch_only) {1385 if (options.fetch_only or options.print_configuration == .tree) {
1361 _ = io.lockStderr(&.{}, .no_color) catch {};1386 _ = 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 }
1362 process.exit(0);1399 process.exit(0);
1363 }1400 }
13641401
...@@ -1641,6 +1678,7 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {...@@ -1641,6 +1678,7 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {
1641 process.exit(0);1678 process.exit(0);
1642 },1679 },
1643 .none, .zon => {},1680 .none, .zon => {},
1681 .tree => unreachable,
1644 }1682 }
16451683
1646 const configuration = c: {1684 const configuration = c: {
...@@ -1678,6 +1716,72 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {...@@ -1678,6 +1716,72 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {
1678 };1716 };
1679}1717}
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
1681fn cmdFetch(gpa: Allocator, graph: *Graph, args: []const []const u8) !void {1785fn cmdFetch(gpa: Allocator, graph: *Graph, args: []const []const u8) !void {
1682 const environ_map = &graph.environ_map;1786 const environ_map = &graph.environ_map;
1683 const io = graph.io;1787 const io = graph.io;
...@@ -2949,18 +3053,7 @@ fn printTreeStep(...@@ -2949,18 +3053,7 @@ fn printTreeStep(
2949 .failures => make_step.state == .success,3053 .failures => make_step.state == .success,
2950 };3054 };
2951 if (skip) return;3055 if (skip) return;
2952 try printPrefix(parent_node, stderr);3056 try parent_node.printPrefixedChild(writer, stderr.mode);
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 }
29643057
2965 if (!first) try stderr.setColor(.dim);3058 if (!first) try stderr.setColor(.dim);
29663059
...@@ -3189,23 +3282,22 @@ fn printStepFailure(...@@ -3189,23 +3282,22 @@ fn printStepFailure(
3189 }3282 }
3190}3283}
31913284
3192fn printPrefix(node: *PrintNode, stderr: Io.Terminal) !void {3285fn printTreeChild(writer: *Writer, mode: Io.Terminal.Mode) Writer.Error!void {
3193 const parent = node.parent orelse return;3286 return writer.writeAll(switch (mode) {
3194 const writer = stderr.writer;3287 .escape_codes => "\x1B\x28\x30\x74\x71\x1B\x28\x42 ", // ├─
3195 if (parent.parent == null) return;3288 else => "+- ",
3196 try printPrefix(parent, stderr);3289 });
3197 if (parent.last) {3290}
3198 try writer.writeAll(" ");3291
3199 } else {3292fn printTreeSibling(writer: *Writer, mode: Io.Terminal.Mode) Writer.Error!void {
3200 try writer.writeAll(switch (stderr.mode) {3293 return writer.writeAll(switch (mode) {
3201 .escape_codes => "\x1B\x28\x30\x78\x1B\x28\x42 ", // │3294 .escape_codes => "\x1B\x28\x30\x78\x1B\x28\x42 ", // │
3202 else => "| ",3295 else => "| ",
3203 });3296 });
3204 }
3205}3297}
32063298
3207fn printChildNodePrefix(stderr: Io.Terminal) !void {3299fn printTreeChildLast(writer: *Writer, mode: Io.Terminal.Mode) !void {
3208 try stderr.writer.writeAll(switch (stderr.mode) {3300 try writer.writeAll(switch (mode) {
3209 .escape_codes => "\x1B\x28\x30\x6d\x71\x1B\x28\x42 ", // └─3301 .escape_codes => "\x1B\x28\x30\x6d\x71\x1B\x28\x42 ", // └─
3210 else => "+- ",3302 else => "+- ",
3211 });3303 });
...@@ -3325,7 +3417,7 @@ pub fn printErrorMessages(...@@ -3325,7 +3417,7 @@ pub fn printErrorMessages(
3325 while (step_stack.pop()) |step_index| : (indent += 1) {3417 while (step_stack.pop()) |step_index| : (indent += 1) {
3326 if (indent > 0) {3418 if (indent > 0) {
3327 try writer.splatByteAll(' ', (indent - 1) * 3);3419 try writer.splatByteAll(' ', (indent - 1) * 3);
3328 try printChildNodePrefix(stderr);3420 try printTreeChildLast(writer, stderr.mode);
3329 }3421 }
33303422
3331 try writer.writeAll(step_index.ptr(c).name.slice(c));3423 try writer.writeAll(step_index.ptr(c).name.slice(c));
lib/compiler/Maker/ScannedConfig.zig+2-2
...@@ -388,6 +388,7 @@ const general_usage =...@@ -388,6 +388,7 @@ const general_usage =
388 \\ needed (Default) Lazy dependencies are fetched as needed388 \\ needed (Default) Lazy dependencies are fetched as needed
389 \\ all Lazy dependencies are always fetched389 \\ all Lazy dependencies are always fetched
390 \\ --fork=[path], --fork [path] Override one or more projects from dependency tree390 \\ --fork=[path], --fork [path] Override one or more projects from dependency tree
391 \\ --print-tree Write current state of package dependencies to stdout
391 \\392 \\
392 \\Advanced Options:393 \\Advanced Options:
393 \\ -freference-trace[=num] How many lines of reference trace should be shown per compile error394 \\ -freference-trace[=num] How many lines of reference trace should be shown per compile error
...@@ -404,7 +405,7 @@ const general_usage =...@@ -404,7 +405,7 @@ const general_usage =
404 \\ poisoned Don't cache the configuration405 \\ poisoned Don't cache the configuration
405 \\ disallowed Panics when cache would be poisoned406 \\ disallowed Panics when cache would be poisoned
406 \\ ignored A little poison never hurt anybody407 \\ ignored A little poison never hurt anybody
407 \\ --print-configuration Render configuration as .zon to stdout408 \\ --print-configuration Render configuration as .zon to stdout and exit
408 \\ --print-configuration-path Print the path to the binary configuration file to stdout409 \\ --print-configuration-path Print the path to the binary configuration file to stdout
409 \\ --build-id[=style] At a minor link-time expense, embeds a build ID in binaries410 \\ --build-id[=style] At a minor link-time expense, embeds a build ID in binaries
410 \\ fast 8-byte non-cryptographic hash (COFF, ELF, WASM)411 \\ fast 8-byte non-cryptographic hash (COFF, ELF, WASM)
...@@ -423,4 +424,3 @@ const general_usage =...@@ -423,4 +424,3 @@ const general_usage =
423 \\ --verbose-llvm-cpu-features Enable compiler debug output for LLVM CPU features424 \\ --verbose-llvm-cpu-features Enable compiler debug output for LLVM CPU features
424 \\425 \\
425;426;
426}