authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-10-13 20:45:10+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-10-13 20:45:10+02:00
log32534ecae6e87ba3ea22698d06843e6d4f353208
tree5659364939c1fee7aef2623efc3fc1c14f460754
parent16d31ceed5de91b469e010b4a8d515236ad6bf14
parent0c54ae3c0e0099dc8b382ea9fe9b80b37dbaee06

Merge pull request 'package management: --help enhancement, --print-tree, and validate package hash consistency' (#37214) from build-print-tree into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/37214

3 files changed, 409 insertions(+), 197 deletions(-)

lib/compiler/Maker.zig+268-76
......@@ -23,6 +23,7 @@ const Color = std.zig.Color;
2323const Client = std.zig.Client;
2424const Server = std.zig.Server;
2525const EnvVar = std.zig.EnvVar;
26const ErrorBundle = std.zig.ErrorBundle;
2627const default_local_zig_cache_basename = std.zig.default_local_zig_cache_basename;
2728const stringToEnum = std.meta.stringToEnum;
2829
......@@ -35,6 +36,7 @@ const ScannedConfig = @import("Maker/ScannedConfig.zig");
3536const PkgConfig = @import("Maker/PkgConfig.zig");
3637const Fetch = @import("Maker/Fetch.zig");
3738const Package = @import("Maker/Package.zig");
39const Manifest = Package.Manifest;
3840
3941pub const std_options: std.Options = .{
4042 .side_channels_mitigations = .none,
......@@ -101,8 +103,30 @@ const InstallPaths = struct {
101103};
102104
103105const PrintNode = struct {
104 parent: ?*PrintNode,
106 parent: ?*const PrintNode,
105107 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 }
106130};
107131
108132const ErrorStyle = enum {
......@@ -125,7 +149,7 @@ const ErrorStyle = enum {
125149};
126150const MultilineErrors = enum { indent, newline, none };
127151const Summary = enum { all, new, failures, line, none };
128const PrintConfiguration = enum { none, zon, path };
152const PrintConfiguration = enum { none, zon, path, tree, stop };
129153
130154const GeneratedFile = struct {
131155 data: ?[]const u8,
......@@ -276,7 +300,6 @@ pub fn main(init: process.Init.Minimal) !void {
276300 var cached_passthru_configure: std.ArrayList(u32) = .empty;
277301 var forks: std.ArrayList(Fork) = .empty;
278302 var system_pkg_dir_path: ?[]const u8 = null;
279 var fetch_only = false;
280303 var fetch_mode: Fetch.JobQueue.Mode = .needed;
281304 var debug_target: ?[]const u8 = null;
282305 var cache_poison: std.Build.Graph.CachePoison = .pure;
......@@ -361,12 +384,12 @@ pub fn main(init: process.Init.Minimal) !void {
361384 override_local_cache_dir = nextArgOrFatal(args, &arg_i);
362385 } else if (mem.eql(u8, arg, "--pkg-dir")) {
363386 override_pkg_dir = nextArgOrFatal(args, &arg_i);
387 } else if (mem.eql(u8, arg, "--print-tree")) {
388 print_configuration = .tree;
389 } else if (mem.eql(u8, arg, "--configure")) {
390 print_configuration = .stop;
364391 } else if (mem.eql(u8, arg, "--fetch")) {
365 fetch_only = true;
366 } else if (mem.cutPrefix(u8, arg, "--fetch=")) |rest| {
367 fetch_only = true;
368 fetch_mode = stringToEnum(Fetch.JobQueue.Mode, rest) orelse
369 fatal("expected [needed|all] after \"--fetch=\", found {q}", .{rest});
392 fetch_mode = .all;
370393 } else if (mem.cutPrefix(u8, arg, "--fork=")) |rest| {
371394 try forks.append(arena, .init(rest));
372395 } else if (mem.eql(u8, arg, "--fork")) {
......@@ -593,7 +616,7 @@ pub fn main(init: process.Init.Minimal) !void {
593616 }
594617 }
595618
596 const early_exit_mode = fetch_only or help_menu or steps_menu or print_configuration != .none;
619 const early_exit_mode = fetch_mode == .all or help_menu or steps_menu or print_configuration != .none;
597620 const server_mode = !early_exit_mode and (watch_flag or webui_listen != null or fuzz != null or listen);
598621
599622 process.raiseFileDescriptorLimit();
......@@ -601,10 +624,16 @@ pub fn main(init: process.Init.Minimal) !void {
601624 const cwd_path = std.zig.getResolvedCwd(io, arena) catch |err|
602625 fatal("resolving current directory path failed: {t}", .{err});
603626
604 var build_root = try findBuildRoot(arena, io, .{
627 var build_root = findBuildRoot(arena, io, .{
605628 .cwd_path = cwd_path,
606629 .build_file = build_file,
607 });
630 }) catch |err| switch (err) {
631 error.BuildZigNotFound => {
632 if (help_menu) printUsageWithoutConfiguration(&graph, "build.zig not found");
633 process.exit(1);
634 },
635 else => |e| return e,
636 };
608637 defer build_root.deinit(io);
609638
610639 graph.build_root_directory = build_root.directory;
......@@ -740,7 +769,7 @@ pub fn main(init: process.Init.Minimal) !void {
740769 var configure_source_files: Cache.Manifest.SelfContainedFiles = .empty;
741770 defer configure_source_files.deinit(gpa);
742771
743 var configure_error_bundle: std.zig.ErrorBundle = .empty;
772 var configure_error_bundle: ErrorBundle = .empty;
744773 defer configure_error_bundle.deinit(gpa);
745774
746775 // If this fails, we can still start the server and wait for user
......@@ -761,7 +790,6 @@ pub fn main(init: process.Init.Minimal) !void {
761790 .parent_progress_node = main_progress_node,
762791 .fetch_mode = fetch_mode,
763792 .system_pkg_dir_path = system_pkg_dir_path,
764 .fetch_only = fetch_only,
765793 .print_configuration = print_configuration,
766794 .forks = forks.items,
767795 .src_files = &configure_source_files,
......@@ -789,6 +817,8 @@ pub fn main(init: process.Init.Minimal) !void {
789817 return cleanExit(io, &scanned_config);
790818 },
791819 .path => unreachable,
820 .tree => unreachable,
821 .stop => return cleanExit(io, &scanned_config),
792822 }
793823
794824 var maker: Maker = .{
......@@ -1042,7 +1072,10 @@ pub fn main(init: process.Init.Minimal) !void {
10421072 } else |err| {
10431073 const can_fs_watch = switch (err) {
10441074 error.AlreadyReported => false,
1045 error.FailedButCacheIntact => true,
1075 error.FailedButCacheIntact => c: {
1076 if (help_menu) printUsageWithoutConfiguration(&graph, "build.zig compilation failure");
1077 break :c true;
1078 },
10461079 else => |e| w: {
10471080 log.err("configuration failed: {t}", .{e});
10481081 break :w false;
......@@ -1064,6 +1097,17 @@ pub fn main(init: process.Init.Minimal) !void {
10641097 }
10651098}
10661099
1100fn printUsageWithoutConfiguration(graph: *Graph, reason: []const u8) noreturn {
1101 const io = graph.io;
1102 _ = io.lockStderr(&.{}, graph.stderr_mode) catch {};
1103 const writer = initStdoutWriter(io);
1104 ScannedConfig.printUsageWithoutConfiguration(graph.zig_exe, writer, reason) catch |err| switch (err) {
1105 error.WriteFailed => fatal("failed writing to stdout: {t}", .{stdout_writer_allocation.err.?}),
1106 };
1107 stdout_writer_allocation.flush() catch |err| fatal("failed writing to stdout: {t}", .{err});
1108 return process.exit(0);
1109}
1110
10671111/// Temporarily adds the reconfigure pseudostep to step_stack, calls
10681112/// `Watch.update`, and then pops it again.
10691113fn updateWatch(maker: *Maker, watch: *Watch) !void {
......@@ -1087,11 +1131,10 @@ const ConfigureOptions = struct {
10871131 parent_progress_node: std.Progress.Node,
10881132 fetch_mode: Fetch.JobQueue.Mode,
10891133 system_pkg_dir_path: ?[]const u8,
1090 fetch_only: bool,
10911134 print_configuration: PrintConfiguration,
10921135 forks: []Fork,
10931136 src_files: *Cache.Manifest.SelfContainedFiles,
1094 error_bundle: ?*std.zig.ErrorBundle = null,
1137 error_bundle: ?*ErrorBundle = null,
10951138};
10961139
10971140fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {
......@@ -1335,11 +1378,27 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {
13351378 return error.FailedButCacheIntact;
13361379 }
13371380
1338 if (options.fetch_only) {
1381 if (options.print_configuration == .tree) {
1382 const NO_COLOR = EnvVar.NO_COLOR.isSet(&graph.environ_map);
1383 const CLICOLOR_FORCE = EnvVar.CLICOLOR_FORCE.isSet(&graph.environ_map);
1384 printPkgTree(.{
1385 .writer = initStdoutWriter(io),
1386 .mode = try .detect(io, .stdout(), NO_COLOR, CLICOLOR_FORCE),
1387 }, arena, &job_queue, &fetch) catch |err| switch (err) {
1388 error.WriteFailed => return stdout_writer_allocation.err.?,
1389 else => |e| return e,
1390 };
1391 try stdout_writer_allocation.flush();
1392 process.exit(0);
1393 }
1394
1395 if (options.fetch_mode == .all) {
13391396 _ = io.lockStderr(&.{}, .no_color) catch {};
13401397 process.exit(0);
13411398 }
13421399
1400 try validateFingerprints(gpa, &job_queue);
1401
13431402 // Create the dependencies.zig file for configurer to
13441403 // obtain via `@import("@dependencies")`.
13451404 {
......@@ -1402,7 +1461,7 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {
14021461 for (fetches) |f| {
14031462 if (!f.have_manifest) continue;
14041463 if (config_man) |man| {
1405 const manifest_path = try f.package_root.join(arena, Package.Manifest.basename);
1464 const manifest_path = try f.package_root.join(arena, Manifest.basename);
14061465 _ = try man.addInputPath(manifest_path, .{});
14071466 }
14081467 const mod = f.cli_module orelse continue;
......@@ -1618,7 +1677,8 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {
16181677 _ = io.lockStderr(&.{}, .no_color) catch {};
16191678 process.exit(0);
16201679 },
1621 .none, .zon => {},
1680 .none, .zon, .stop => {},
1681 .tree => unreachable,
16221682 }
16231683
16241684 const configuration = c: {
......@@ -1656,6 +1716,147 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {
16561716 };
16571717}
16581718
1719const ValidFingerprintKey = struct {
1720 project_id: Package.ProjectId,
1721 version: std.SemanticVersion,
1722
1723 const Context = struct {
1724 pub fn hash(this: @This(), a: ValidFingerprintKey) u32 {
1725 _ = this;
1726 var hasher: std.hash.Wyhash = .init(a.project_id.fingerprint_id);
1727 hasher.update(&a.project_id.padded_name);
1728 hasher.update(@ptrCast((&a.version.major)[0..1]));
1729 hasher.update(@ptrCast((&a.version.minor)[0..1]));
1730 hasher.update(@ptrCast((&a.version.patch)[0..1]));
1731 if (a.version.pre) |pre| hasher.update(pre);
1732 return @truncate(hasher.final());
1733 }
1734 pub fn eql(this: @This(), a: ValidFingerprintKey, b: ValidFingerprintKey, i: usize) bool {
1735 _ = this;
1736 _ = i;
1737 return a.project_id.eql(&b.project_id) and a.version.order(b.version) == .eq;
1738 }
1739 };
1740};
1741
1742const ValidFingerprintTable = std.array_hash_map.Custom(
1743 ValidFingerprintKey,
1744 struct { fetch: *Fetch, pkg_hash: Package.Hash, printed: bool },
1745 ValidFingerprintKey.Context,
1746 true,
1747);
1748
1749/// Ensure that the dependency tree did not have any fingerprint matches with the same semantic version but
1750/// differing package hashes.
1751fn validateFingerprints(gpa: Allocator, job_queue: *const Fetch.JobQueue) !void {
1752 var any_errors = false;
1753
1754 var table: ValidFingerprintTable = .empty;
1755 defer table.deinit(gpa);
1756
1757 for (job_queue.table.keys(), job_queue.table.values()) |pkg_hash, fetch| {
1758 if (!fetch.have_manifest) continue;
1759 const gop = try table.getOrPut(gpa, .{
1760 .project_id = .init(fetch.manifest.name, fetch.manifest.id),
1761 .version = fetch.manifest.version,
1762 });
1763 if (gop.found_existing) {
1764 any_errors = true;
1765 const fingerprint: Package.Fingerprint = .init(fetch.manifest.id, fetch.manifest.name);
1766 const stripped_version: std.SemanticVersion = .{
1767 .major = fetch.manifest.version.major,
1768 .minor = fetch.manifest.version.minor,
1769 .patch = fetch.manifest.version.patch,
1770 .pre = fetch.manifest.version.pre,
1771 };
1772 if (!gop.value_ptr.printed) {
1773 gop.value_ptr.printed = true;
1774 log.err("contradictory hash for package with fingerprint 0x{x} and version {f}: {s}", .{
1775 fingerprint.int(), stripped_version, gop.value_ptr.pkg_hash.toSlice(),
1776 });
1777 }
1778 log.err("contradictory hash for package with fingerprint 0x{x} and version {f}: {s}", .{
1779 fingerprint.int(), stripped_version, pkg_hash.toSlice(),
1780 });
1781 } else {
1782 gop.value_ptr.* = .{ .fetch = fetch, .pkg_hash = pkg_hash, .printed = false };
1783 }
1784 }
1785 if (any_errors) {
1786 log.info("potential cause: package ownership dispute", .{});
1787 log.info("potential cause: maintainer neglected to bump version after making changes", .{});
1788 log.info("potential cause: dependency on unreleased changes", .{});
1789 log.info("hint: use --print-tree to visualize all package versions and hashes", .{});
1790 return error.FailedButCacheIntact;
1791 }
1792}
1793
1794fn printPkgTree(
1795 terminal: Io.Terminal,
1796 arena: Allocator,
1797 job_queue: *const Fetch.JobQueue,
1798 root_fetch: *const Fetch,
1799) !void {
1800 if (!root_fetch.have_manifest) return;
1801 const names = root_fetch.manifest.dependencies.keys();
1802 for (names, root_fetch.manifest.dependencies.values(), 0..) |name, *dep, i| {
1803 var print_node: PrintNode = .{
1804 .parent = null,
1805 .last = i == names.len - 1,
1806 };
1807 try printPkgTreeChild(terminal, arena, job_queue, &print_node, name, dep.hash);
1808 }
1809}
1810
1811fn printPkgTreeChild(
1812 terminal: Io.Terminal,
1813 arena: Allocator,
1814 job_queue: *const Fetch.JobQueue,
1815 parent_print_node: *const PrintNode,
1816 dep_name: []const u8,
1817 opt_hash: ?[]const u8,
1818) !void {
1819 const w = terminal.writer;
1820 try parent_print_node.printPrefixedChild(w, terminal.mode);
1821 try w.writeAll(dep_name);
1822
1823 const hash = opt_hash orelse {
1824 try terminal.setColor(.red);
1825 try w.writeAll(" (missing hash)\n");
1826 try terminal.setColor(.reset);
1827 return;
1828 };
1829 try terminal.setColor(.dim);
1830 try w.print(" {s}", .{hash});
1831 try terminal.setColor(.reset);
1832 const child_fetch = job_queue.table.get(.fromSlice(hash)) orelse {
1833 try terminal.setColor(.red);
1834 try w.writeAll(" (fetch failed)\n");
1835 try terminal.setColor(.reset);
1836 return;
1837 };
1838 switch (child_fetch.lazy_status) {
1839 .eager, .available => {},
1840 .unavailable => {
1841 try terminal.setColor(.yellow);
1842 try w.writeAll(" (not fetched)\n");
1843 try terminal.setColor(.reset);
1844 return;
1845 },
1846 }
1847 try w.writeByte('\n');
1848 if (!child_fetch.have_manifest) return;
1849
1850 const names = child_fetch.manifest.dependencies.keys();
1851 for (names, child_fetch.manifest.dependencies.values(), 0..) |name, *dep, i| {
1852 var print_node: PrintNode = .{
1853 .parent = parent_print_node,
1854 .last = i == names.len - 1,
1855 };
1856 try printPkgTreeChild(terminal, arena, job_queue, &print_node, name, dep.hash);
1857 }
1858}
1859
16591860fn cmdFetch(gpa: Allocator, graph: *Graph, args: []const []const u8) !void {
16601861 const environ_map = &graph.environ_map;
16611862 const io = graph.io;
......@@ -1932,8 +2133,8 @@ fn cmdFetch(gpa: Allocator, graph: *Graph, args: []const []const u8) !void {
19322133 try ast.render(gpa, &aw.writer, fixups);
19332134 const rendered = aw.written();
19342135
1935 build_root.directory.handle.writeFile(io, .{ .sub_path = Package.Manifest.basename, .data = rendered }) catch |err| {
1936 fatal("unable to write {s} file: {t}", .{ Package.Manifest.basename, err });
2136 build_root.directory.handle.writeFile(io, .{ .sub_path = Manifest.basename, .data = rendered }) catch |err| {
2137 fatal("unable to write {s} file: {t}", .{ Manifest.basename, err });
19372138 };
19382139
19392140 return process.cleanExit(io);
......@@ -2135,7 +2336,7 @@ fn cmdInit(gpa: Allocator, graph: *Graph, args: []const []const u8) !void {
21352336 const s = Dir.path.sep_str;
21362337 const template_paths = [_][]const u8{
21372338 default_build_zig_basename,
2138 Package.Manifest.basename,
2339 Manifest.basename,
21392340 "src" ++ s ++ "main.zig",
21402341 "src" ++ s ++ "root.zig",
21412342 };
......@@ -2159,7 +2360,7 @@ fn cmdInit(gpa: Allocator, graph: *Graph, args: []const []const u8) !void {
21592360 return process.cleanExit(io);
21602361 },
21612362 .minimal => {
2162 Templates.writeSimpleFile(io, Io.Dir.cwd(), Package.Manifest.basename,
2363 Templates.writeSimpleFile(io, Io.Dir.cwd(), Manifest.basename,
21632364 \\.{{
21642365 \\ .name = .{s},
21652366 \\ .version = "0.0.1",
......@@ -2173,8 +2374,8 @@ fn cmdInit(gpa: Allocator, graph: *Graph, args: []const []const u8) !void {
21732374 builtin.zig_version_string,
21742375 fingerprint.int(),
21752376 }) catch |err| switch (err) {
2176 else => fatal("failed to create {q}: {t}", .{ Package.Manifest.basename, err }),
2177 error.PathAlreadyExists => fatal("refusing to overwrite {q}", .{Package.Manifest.basename}),
2377 else => fatal("failed to create {q}: {t}", .{ Manifest.basename, err }),
2378 error.PathAlreadyExists => fatal("refusing to overwrite {q}", .{Manifest.basename}),
21782379 };
21792380 Templates.writeSimpleFile(io, Io.Dir.cwd(), default_build_zig_basename,
21802381 \\const std = @import("std");
......@@ -2189,12 +2390,12 @@ fn cmdInit(gpa: Allocator, graph: *Graph, args: []const []const u8) !void {
21892390 // their `build.zig.zon` *after* writing their `build.zig`. So this one isn't fatal.
21902391 error.PathAlreadyExists => {
21912392 log.info("successfully populated {q}, preserving existing {q}", .{
2192 Package.Manifest.basename, default_build_zig_basename,
2393 Manifest.basename, default_build_zig_basename,
21932394 });
21942395 return process.cleanExit(io);
21952396 },
21962397 };
2197 log.info("successfully populated {q} and {q}", .{ Package.Manifest.basename, default_build_zig_basename });
2398 log.info("successfully populated {q} and {q}", .{ Manifest.basename, default_build_zig_basename });
21982399 return process.cleanExit(io);
21992400 },
22002401 }
......@@ -2927,18 +3128,7 @@ fn printTreeStep(
29273128 .failures => make_step.state == .success,
29283129 };
29293130 if (skip) return;
2930 try printPrefix(parent_node, stderr);
2931
2932 if (parent_node.parent != null) {
2933 if (parent_node.last) {
2934 try printChildNodePrefix(stderr);
2935 } else {
2936 try writer.writeAll(switch (stderr.mode) {
2937 .escape_codes => "\x1B\x28\x30\x74\x71\x1B\x28\x42 ", // ├─
2938 else => "+- ",
2939 });
2940 }
2941 }
3131 try parent_node.printPrefixedChild(writer, stderr.mode);
29423132
29433133 if (!first) try stderr.setColor(.dim);
29443134
......@@ -3167,23 +3357,22 @@ fn printStepFailure(
31673357 }
31683358}
31693359
3170fn printPrefix(node: *PrintNode, stderr: Io.Terminal) !void {
3171 const parent = node.parent orelse return;
3172 const writer = stderr.writer;
3173 if (parent.parent == null) return;
3174 try printPrefix(parent, stderr);
3175 if (parent.last) {
3176 try writer.writeAll(" ");
3177 } else {
3178 try writer.writeAll(switch (stderr.mode) {
3179 .escape_codes => "\x1B\x28\x30\x78\x1B\x28\x42 ", // │
3180 else => "| ",
3181 });
3182 }
3360fn printTreeChild(writer: *Writer, mode: Io.Terminal.Mode) Writer.Error!void {
3361 return writer.writeAll(switch (mode) {
3362 .escape_codes => "\x1B\x28\x30\x74\x71\x1B\x28\x42 ", // ├─
3363 else => "+- ",
3364 });
31833365}
31843366
3185fn printChildNodePrefix(stderr: Io.Terminal) !void {
3186 try stderr.writer.writeAll(switch (stderr.mode) {
3367fn printTreeSibling(writer: *Writer, mode: Io.Terminal.Mode) Writer.Error!void {
3368 return writer.writeAll(switch (mode) {
3369 .escape_codes => "\x1B\x28\x30\x78\x1B\x28\x42 ", // │
3370 else => "| ",
3371 });
3372}
3373
3374fn printTreeChildLast(writer: *Writer, mode: Io.Terminal.Mode) !void {
3375 try writer.writeAll(switch (mode) {
31873376 .escape_codes => "\x1B\x28\x30\x6d\x71\x1B\x28\x42 ", // └─
31883377 else => "+- ",
31893378 });
......@@ -3277,7 +3466,7 @@ pub fn invalidateResult(maker: *Maker, step: *Step) error{MustReconfigure}!bool
32773466pub fn printErrorMessages(
32783467 maker: *Maker,
32793468 failing_step_index: Configuration.Step.Index,
3280 options: std.zig.ErrorBundle.RenderOptions,
3469 options: ErrorBundle.RenderOptions,
32813470 stderr: Io.Terminal,
32823471 error_style: ErrorStyle,
32833472 multiline_errors: MultilineErrors,
......@@ -3303,7 +3492,7 @@ pub fn printErrorMessages(
33033492 while (step_stack.pop()) |step_index| : (indent += 1) {
33043493 if (indent > 0) {
33053494 try writer.splatByteAll(' ', (indent - 1) * 3);
3306 try printChildNodePrefix(stderr);
3495 try printTreeChildLast(writer, stderr.mode);
33073496 }
33083497
33093498 try writer.writeAll(step_index.ptr(c).name.slice(c));
......@@ -4037,7 +4226,9 @@ const FindBuildRootOptions = struct {
40374226 cwd_path: []const u8,
40384227};
40394228
4040fn findBuildRoot(arena: Allocator, io: Io, options: FindBuildRootOptions) !BuildRoot {
4229const FindBuildRootError = error{ OutOfMemory, BuildZigNotFound };
4230
4231fn findBuildRoot(arena: Allocator, io: Io, options: FindBuildRootOptions) FindBuildRootError!BuildRoot {
40414232 const build_zig_basename = if (options.build_file) |bf|
40424233 Dir.path.basename(bf)
40434234 else
......@@ -4088,11 +4279,12 @@ fn findBuildRoot(arena: Allocator, io: Io, options: FindBuildRootOptions) !Build
40884279 dirname = Dir.path.dirname(dirname orelse options.cwd_path) orelse {
40894280 log.info("initialize {s} template file with \"zig init\"", .{std.zig.build_zig_basename});
40904281 log.info("see \"zig --help\" for more options", .{});
4091 fatal("no build.zig file found, in the current directory or any parent directories", .{});
4282 log.err("no build.zig file found, in the current directory or any parent directories", .{});
4283 return error.BuildZigNotFound;
40924284 };
40934285 continue;
40944286 },
4095 else => |e| return e,
4287 else => |e| fatal("accessing {q} while searching for build.zig file failed: {t}", .{ joined_path, e }),
40964288 }
40974289 }
40984290}
......@@ -4100,8 +4292,8 @@ fn findBuildRoot(arena: Allocator, io: Io, options: FindBuildRootOptions) !Build
41004292const Fork = struct {
41014293 path: Path,
41024294 manifest_ast: std.zig.Ast,
4103 manifest: Package.Manifest,
4104 error_bundle: std.zig.ErrorBundle.Wip,
4295 manifest: Manifest,
4296 error_bundle: ErrorBundle.Wip,
41054297 failed: bool,
41064298 arena_allocator: std.heap.ArenaAllocator,
41074299
......@@ -4134,12 +4326,12 @@ const Fork = struct {
41344326 fork.arena_allocator = .init(gpa);
41354327 const arena = fork.arena_allocator.allocator();
41364328
4137 var error_bundle: std.zig.ErrorBundle.Wip = try .init(gpa);
4329 var error_bundle: ErrorBundle.Wip = try .init(gpa);
41384330 defer error_bundle.deinit();
41394331
4140 const manifest_path = try fork.path.join(arena, Package.Manifest.basename);
4332 const manifest_path = try fork.path.join(arena, Manifest.basename);
41414333
4142 Package.Manifest.load(
4334 Manifest.load(
41434335 io,
41444336 arena,
41454337 manifest_path,
......@@ -4189,20 +4381,20 @@ fn loadManifest(
41894381 arena: Allocator,
41904382 io: Io,
41914383 options: LoadManifestOptions,
4192) !struct { Package.Manifest, std.zig.Ast } {
4384) !struct { Manifest, std.zig.Ast } {
41934385 const rng: std.Random.IoSource = .{ .io = io };
41944386
41954387 const manifest_bytes = while (true) {
41964388 break options.dir.readFileAllocOptions(
41974389 io,
4198 Package.Manifest.basename,
4390 Manifest.basename,
41994391 arena,
4200 .limited(Package.Manifest.max_bytes),
4392 .limited(Manifest.max_bytes),
42014393 .@"1",
42024394 0,
42034395 ) catch |err| switch (err) {
42044396 error.FileNotFound => {
4205 Templates.writeSimpleFile(io, options.dir, Package.Manifest.basename,
4397 Templates.writeSimpleFile(io, options.dir, Manifest.basename,
42064398 \\.{{
42074399 \\ .name = .{s},
42084400 \\ .version = "0.0.1",
......@@ -4216,29 +4408,29 @@ fn loadManifest(
42164408 builtin.zig_version_string,
42174409 Package.Fingerprint.generate(rng.interface(), options.root_name).int(),
42184410 }) catch |e| {
4219 fatal("unable to write {s}: {t}", .{ Package.Manifest.basename, e });
4411 fatal("unable to write {s}: {t}", .{ Manifest.basename, e });
42204412 };
42214413 continue;
42224414 },
4223 else => |e| fatal("unable to load {s}: {t}", .{ Package.Manifest.basename, e }),
4415 else => |e| fatal("unable to load {s}: {t}", .{ Manifest.basename, e }),
42244416 };
42254417 };
42264418 var ast = try std.zig.Ast.parse(gpa, manifest_bytes, .{ .mode = .zon });
42274419 errdefer ast.deinit(gpa);
42284420
42294421 if (ast.errors.len > 0) {
4230 try std.zig.printAstErrorsToStderr(gpa, io, ast, Package.Manifest.basename, options.color);
4422 try std.zig.printAstErrorsToStderr(gpa, io, ast, Manifest.basename, options.color);
42314423 process.exit(2);
42324424 }
42334425
4234 var manifest = try Package.Manifest.parse(gpa, &ast, rng.interface(), .{});
4426 var manifest = try Manifest.parse(gpa, &ast, rng.interface(), .{});
42354427 errdefer manifest.deinit(gpa);
42364428
42374429 if (manifest.errors.len > 0) {
4238 var wip_errors: std.zig.ErrorBundle.Wip = try .init(gpa);
4430 var wip_errors: ErrorBundle.Wip = try .init(gpa);
42394431 defer wip_errors.deinit();
42404432
4241 const src_path = try wip_errors.addString(Package.Manifest.basename);
4433 const src_path = try wip_errors.addString(Manifest.basename);
42424434 try manifest.copyErrorsIntoBundle(ast, src_path, &wip_errors);
42434435
42444436 var error_bundle = try wip_errors.toOwnedBundle("");
......@@ -4262,8 +4454,8 @@ fn sanitizeExampleName(arena: Allocator, bytes: []const u8) error{OutOfMemory}![
42624454 else => continue,
42634455 };
42644456 if (!std.zig.isValidId(result.items)) return "foo";
4265 if (result.items.len > Package.Manifest.max_name_len)
4266 result.shrinkRetainingCapacity(Package.Manifest.max_name_len);
4457 if (result.items.len > Manifest.max_name_len)
4458 result.shrinkRetainingCapacity(Manifest.max_name_len);
42674459
42684460 return result.toOwnedSlice(arena);
42694461}
lib/compiler/Maker/Package.zig+6-2
......@@ -8,13 +8,17 @@ pub const Fingerprint = packed struct(u64) {
88 id: u32,
99 checksum: u32,
1010
11 pub fn generate(rng: std.Random, name: []const u8) Fingerprint {
11 pub fn init(id: u32, name: []const u8) Fingerprint {
1212 return .{
13 .id = rng.intRangeLessThan(u32, 1, 0xffffffff),
13 .id = id,
1414 .checksum = std.hash.Crc32.hash(name),
1515 };
1616 }
1717
18 pub fn generate(rng: std.Random, name: []const u8) Fingerprint {
19 return .init(rng.intRangeLessThan(u32, 1, 0xffffffff), name);
20 }
21
1822 pub fn validate(n: Fingerprint, name: []const u8) bool {
1923 switch (n.id) {
2024 0x00000000, 0xffffffff => return false,
lib/compiler/Maker/ScannedConfig.zig+135-119
......@@ -238,6 +238,18 @@ pub fn printSteps(sc: *const ScannedConfig, graph: *Graph, w: *Writer) !void {
238238 }
239239}
240240
241pub fn printUsageWithoutConfiguration(zig_exe: []const u8, w: *Writer, reason: []const u8) Writer.Error!void {
242 try w.print(
243 \\Usage: {s} build [steps] [options]
244 \\
245 \\Steps: (unavailable due to {s})
246 \\Project-Specific Options: (unavailable due to {s})
247 \\{s}
248 \\ (unavailable due to {s})
249 \\{s}
250 , .{ zig_exe, reason, reason, system_integration_usage, reason, general_usage });
251}
252
241253pub fn printUsage(sc: *const ScannedConfig, graph: *Graph, w: *Writer) !void {
242254 const arena = graph.arena;
243255
......@@ -274,37 +286,7 @@ pub fn printUsage(sc: *const ScannedConfig, graph: *Graph, w: *Writer) !void {
274286 }
275287 }
276288
277 try w.writeAll(
278 \\
279 \\System Integration Options:
280 \\ --search-prefix [path] Add a path to look for binaries, libraries, headers
281 \\ --sysroot [path] Set the system root directory (usually /)
282 \\ --libc [file] Provide a file which specifies libc paths
283 \\
284 \\ --system [pkgdir] Disable package fetching; enable all integrations
285 \\ -fsys=[name] Enable a system integration
286 \\ -fno-sys=[name] Disable a system integration
287 \\
288 \\ -fdarling, -fno-darling Integration with system-installed Darling to
289 \\ execute macOS programs on Linux hosts
290 \\ (default: no)
291 \\ -fqemu, -fno-qemu Integration with system-installed QEMU to execute
292 \\ foreign-architecture programs on Linux hosts
293 \\ (default: no)
294 \\ --libc-runtimes [path] Enhances QEMU integration by providing dynamic libc
295 \\ (e.g. glibc or musl) built for multiple foreign
296 \\ architectures, allowing execution of non-native
297 \\ programs that link with libc.
298 \\ -frosetta, -fno-rosetta Rely on Rosetta to execute x86_64 programs on
299 \\ ARM64 macOS hosts. (default: no)
300 \\ -fwasmtime, -fno-wasmtime Integration with system-installed wasmtime to
301 \\ execute WASI binaries. (default: no)
302 \\ -fwine, -fno-wine Integration with system-installed Wine to execute
303 \\ Windows programs on Linux hosts. (default: no)
304 \\
305 \\ Available System Integrations: Enabled:
306 \\
307 );
289 try w.writeAll(system_integration_usage);
308290 if (sc.configuration.system_integrations.len == 0) {
309291 try w.writeAll(" (none) -\n");
310292 } else {
......@@ -318,92 +300,126 @@ pub fn printUsage(sc: *const ScannedConfig, graph: *Graph, w: *Writer) !void {
318300 }
319301 }
320302
321 try w.writeAll(
322 \\
323 \\General Options:
324 \\ -h, --help Print this help to stdout and exit
325 \\ -l, --list-steps Print available steps to stdout and exit
326 \\
327 \\ -p, --prefix [path] Where to install files (default: zig-out)
328 \\ --prefix-lib-dir [path] Where to install libraries
329 \\ --prefix-exe-dir [path] Where to install executables
330 \\ --prefix-include-dir [path] Where to install C header files
331 \\ --release[=mode] Request release mode, optionally specifying a
332 \\ preferred optimization mode: fast, safe, small
333 \\
334 \\ --verbose Print commands before executing them
335 \\ --color [auto|off|on] Enable or disable colored error messages
336 \\ --error-style [style] Control how build errors are printed
337 \\ verbose (Default) Report errors with full context
338 \\ minimal Report errors after summary, excluding context like command lines
339 \\ verbose_clear Like 'verbose', but clear the terminal at the start of each update
340 \\ minimal_clear Like 'minimal', but clear the terminal at the start of each update
341 \\ --multiline-errors [style] Control how multi-line error messages are printed
342 \\ indent (Default) Indent non-initial lines to align with initial line
343 \\ newline Include a leading newline so that the error message is on its own lines
344 \\ none Print as usual so the first line is misaligned
345 \\ --summary [mode] Control the printing of the build summary
346 \\ all Print the build summary in its entirety
347 \\ new Omit cached steps
348 \\ failures (Default if short-lived) Only print failed steps
349 \\ line (Default if long-lived) Only print the single-line summary
350 \\ none Do not print the build summary
351 \\ -j<N> Limit concurrent jobs (default is to use all CPU cores)
352 \\ --maxrss <bytes> Limit memory usage (default is to use available memory)
353 \\ --skip-oom-steps Instead of failing, skip steps that would exceed --maxrss
354 \\ --test-timeout <timeout> Limit execution time of unit tests, terminating if exceeded.
355 \\ The timeout must include a unit: ns, us, ms, s, m, h
356 \\ --watch Continuously rebuild when source files are modified
357 \\ --debounce <ms> Delay before rebuilding after changed file detected
358 \\ --webui[=ip] Enable the web interface on the given IP address
359 \\ --fuzz[=limit] Continuously search for unit test failures with an optional
360 \\ limit to the max number of iterations. The argument supports
361 \\ an optional 'K', 'M', or 'G' suffix (e.g. '10K'). Implies
362 \\ '--webui' when no limit is specified.
363 \\ --listen=- Enable the build server protocol on stdio
364 \\ --time-report Force full rebuild and provide detailed information on
365 \\ compilation time of Zig source code (implies '--webui')
366 \\ -fincremental Enable incremental compilation
367 \\ -fno-incremental Disable incremental compilation
368 \\
369 \\Package Management Options:
370 \\ --fetch[=mode] Fetch dependency tree (optionally choose laziness) and exit
371 \\ needed (Default) Lazy dependencies are fetched as needed
372 \\ all Lazy dependencies are always fetched
373 \\ --fork=[path], --fork [path] Override one or more projects from dependency tree
374 \\
375 \\Advanced Options:
376 \\ -freference-trace[=num] How many lines of reference trace should be shown per compile error
377 \\ -fno-reference-trace Disable reference trace
378 \\ -fallow-so-scripts Allows .so files to be GNU ld scripts
379 \\ -fno-allow-so-scripts (default) .so files must be ELF files
380 \\ --error-limit [num] Set the maximum amount of distinct error values
381 \\ --build-file [file] Override path to build.zig
382 \\ --cache-dir [path] Override path to local Zig cache directory
383 \\ --zig-lib=[arg] Override path to Zig lib directory
384 \\ --seed [integer] For shuffling dependency traversal order (default: random)
385 \\ --cache-poison[=mode] Override configuration caching behavior
386 \\ pure (default) Avoid false positive cache hits
387 \\ poisoned Don't cache the configuration
388 \\ disallowed Panics when cache would be poisoned
389 \\ ignored A little poison never hurt anybody
390 \\ --print-configuration Render configuration as .zon to stdout
391 \\ --print-configuration-path Print the path to the binary configuration file to stdout
392 \\ --build-id[=style] At a minor link-time expense, embeds a build ID in binaries
393 \\ fast 8-byte non-cryptographic hash (COFF, ELF, WASM)
394 \\ sha1, tree 20-byte cryptographic hash (ELF, WASM)
395 \\ md5 16-byte cryptographic hash (ELF)
396 \\ uuid 16-byte random UUID (ELF, WASM)
397 \\ 0x[hexstring] Constant ID, maximum 32 bytes (ELF, WASM)
398 \\ none (default) No build ID
399 \\ --debug-log [scope] Enable debugging the compiler
400 \\ --debug-pkg-config Fail if unknown pkg-config flags encountered
401 \\ --verbose-link Enable compiler debug output for linking
402 \\ --verbose-air Enable compiler debug output for Zig AIR
403 \\ --verbose-llvm-ir Enable compiler debug output for LLVM IR
404 \\ --verbose-cimport Enable compiler debug output for C imports
405 \\ --verbose-cc Enable compiler debug output for C compilation
406 \\ --verbose-llvm-cpu-features Enable compiler debug output for LLVM CPU features
407 \\
408 );
303 try w.writeAll(general_usage);
409304}
305
306const system_integration_usage =
307 \\
308 \\System Integration Options:
309 \\ --search-prefix [path] Add a path to look for binaries, libraries, headers
310 \\ --sysroot [path] Set the system root directory (usually /)
311 \\ --libc [file] Provide a file which specifies libc paths
312 \\
313 \\ --system [pkgdir] Disable package fetching; enable all integrations
314 \\ -fsys=[name] Enable a system integration
315 \\ -fno-sys=[name] Disable a system integration
316 \\
317 \\ -fdarling, -fno-darling Integration with system-installed Darling to
318 \\ execute macOS programs on Linux hosts
319 \\ (default: no)
320 \\ -fqemu, -fno-qemu Integration with system-installed QEMU to execute
321 \\ foreign-architecture programs on Linux hosts
322 \\ (default: no)
323 \\ --libc-runtimes [path] Enhances QEMU integration by providing dynamic libc
324 \\ (e.g. glibc or musl) built for multiple foreign
325 \\ architectures, allowing execution of non-native
326 \\ programs that link with libc.
327 \\ -frosetta, -fno-rosetta Rely on Rosetta to execute x86_64 programs on
328 \\ ARM64 macOS hosts. (default: no)
329 \\ -fwasmtime, -fno-wasmtime Integration with system-installed wasmtime to
330 \\ execute WASI binaries. (default: no)
331 \\ -fwine, -fno-wine Integration with system-installed Wine to execute
332 \\ Windows programs on Linux hosts. (default: no)
333 \\
334 \\ Available System Integrations: Enabled:
335 \\
336;
337
338const general_usage =
339 \\
340 \\General Options:
341 \\ -h, --help Print this help to stdout and exit
342 \\ -l, --list-steps Print available steps to stdout and exit
343 \\
344 \\ -p, --prefix [path] Where to install files (default: zig-out)
345 \\ --prefix-lib-dir [path] Where to install libraries
346 \\ --prefix-exe-dir [path] Where to install executables
347 \\ --prefix-include-dir [path] Where to install C header files
348 \\ --release[=mode] Request release mode, optionally specifying a
349 \\ preferred optimization mode: fast, safe, small
350 \\
351 \\ --verbose Print commands before executing them
352 \\ --color [auto|off|on] Enable or disable colored error messages
353 \\ --error-style [style] Control how build errors are printed
354 \\ verbose (Default) Report errors with full context
355 \\ minimal Report errors after summary, excluding context like command lines
356 \\ verbose_clear Like "verbose", but clear the terminal at the start of each update
357 \\ minimal_clear Like "minimal", but clear the terminal at the start of each update
358 \\ --multiline-errors [style] Control how multi-line error messages are printed
359 \\ indent (Default) Indent non-initial lines to align with initial line
360 \\ newline Include a leading newline so that the error message is on its own lines
361 \\ none Print as usual so the first line is misaligned
362 \\ --summary [mode] Control the printing of the build summary
363 \\ all Print the build summary in its entirety
364 \\ new Omit cached steps
365 \\ failures (Default if short-lived) Only print failed steps
366 \\ line (Default if long-lived) Only print the single-line summary
367 \\ none Do not print the build summary
368 \\ -j<N> Limit concurrent jobs (default is to use all CPU cores)
369 \\ --maxrss <bytes> Limit memory usage (default is to use available memory)
370 \\ --skip-oom-steps Instead of failing, skip steps that would exceed --maxrss
371 \\ --test-timeout <timeout> Limit execution time of unit tests, terminating if exceeded.
372 \\ The timeout must include a unit: ns, us, ms, s, m, h
373 \\ --watch Continuously rebuild when source files are modified
374 \\ --debounce <ms> Delay before rebuilding after changed file detected
375 \\ --webui[=ip] Enable the web interface on the given IP address
376 \\ --fuzz[=limit] Continuously search for unit test failures with an optional
377 \\ limit to the max number of iterations. The argument supports
378 \\ an optional 'K', 'M', or 'G' suffix (e.g. "10K"). Implies
379 \\ "--webui" when no limit is specified.
380 \\ --time-report Force full rebuild and provide detailed information on
381 \\ compilation time of Zig source code (implies "--webui")
382 \\ -fincremental Enable incremental compilation
383 \\ -fno-incremental Disable incremental compilation
384 \\ --configure Exit after the configuration phase
385 \\
386 \\Package Management Options:
387 \\ --fetch Fetch all dependencies including lazy ones; exit without configuration
388 \\ --fork=[path], --fork [path] Override one or more projects from dependency tree
389 \\ --print-tree Write current state of package dependencies to stdout
390 \\
391 \\Advanced Options:
392 \\ --listen=- Enable the build server protocol on stdio
393 \\ -freference-trace[=num] How many lines of reference trace should be shown per compile error
394 \\ -fno-reference-trace Disable reference trace
395 \\ -fallow-so-scripts Allows .so files to be GNU ld scripts
396 \\ -fno-allow-so-scripts (default) .so files must be ELF files
397 \\ --error-limit [num] Set the maximum amount of distinct error values
398 \\ --build-file [file] Override path to build.zig
399 \\ --cache-dir [path] Override path to local Zig cache directory
400 \\ --zig-lib=[arg] Override path to Zig lib directory
401 \\ --seed [integer] For shuffling dependency traversal order (default: random)
402 \\ --cache-poison[=mode] Override configuration caching behavior
403 \\ pure (default) Avoid false positive cache hits
404 \\ poisoned Don't cache the configuration
405 \\ disallowed Panics when cache would be poisoned
406 \\ ignored A little poison never hurt anybody
407 \\ --print-configuration Render configuration as .zon to stdout. Implies "--configure"
408 \\ --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 binaries
410 \\ fast 8-byte non-cryptographic hash (COFF, ELF, WASM)
411 \\ sha1, tree 20-byte cryptographic hash (ELF, WASM)
412 \\ md5 16-byte cryptographic hash (ELF)
413 \\ uuid 16-byte random UUID (ELF, WASM)
414 \\ 0x[hexstring] Constant ID, maximum 32 bytes (ELF, WASM)
415 \\ none (default) No build ID
416 \\ --debug-log [scope] Enable debugging the compiler
417 \\ --debug-pkg-config Fail if unknown pkg-config flags encountered
418 \\ --verbose-link Enable compiler debug output for linking
419 \\ --verbose-air Enable compiler debug output for Zig AIR
420 \\ --verbose-llvm-ir Enable compiler debug output for LLVM IR
421 \\ --verbose-cimport Enable compiler debug output for C imports
422 \\ --verbose-cc Enable compiler debug output for C compilation
423 \\ --verbose-llvm-cpu-features Enable compiler debug output for LLVM CPU features
424 \\
425;