From b9ea8ac3d350a7461b110946bfc6dd02465d3eee Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 13 Oct 2026 20:45:10 +0200 Subject: [PATCH] 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 --- lib/compiler/Maker.zig | 344 +++++++++++++++++++++------ lib/compiler/Maker/Package.zig | 8 +- lib/compiler/Maker/ScannedConfig.zig | 254 +++++++++++--------- 3 files changed, 409 insertions(+), 197 deletions(-) diff --git a/lib/compiler/Maker.zig b/lib/compiler/Maker.zig index 8055927d61e7276106d297c5950913e3a0e78980..325fa1ff1d5c4aa940c9e668a53615cf367e9ea0 100644 --- a/lib/compiler/Maker.zig +++ b/lib/compiler/Maker.zig @@ -23,6 +23,7 @@ const Color = std.zig.Color; const Client = std.zig.Client; const Server = std.zig.Server; const EnvVar = std.zig.EnvVar; +const ErrorBundle = std.zig.ErrorBundle; const default_local_zig_cache_basename = std.zig.default_local_zig_cache_basename; const stringToEnum = std.meta.stringToEnum; @@ -35,6 +36,7 @@ const ScannedConfig = @import("Maker/ScannedConfig.zig"); const PkgConfig = @import("Maker/PkgConfig.zig"); const Fetch = @import("Maker/Fetch.zig"); const Package = @import("Maker/Package.zig"); +const Manifest = Package.Manifest; pub const std_options: std.Options = .{ .side_channels_mitigations = .none, @@ -101,8 +103,30 @@ const InstallPaths = struct { }; const PrintNode = struct { - parent: ?*PrintNode, + parent: ?*const PrintNode, last: bool = false, + + fn printPrefix(node: *const PrintNode, writer: *Writer, mode: Io.Terminal.Mode) Writer.Error!void { + const parent = node.parent orelse return; + if (parent.parent == null) return; + try printPrefix(parent, writer, mode); + if (parent.last) { + try writer.writeAll(" "); + } else { + try printTreeSibling(writer, mode); + } + } + + fn printPrefixedChild(node: *const PrintNode, writer: *Writer, mode: Io.Terminal.Mode) Writer.Error!void { + try printPrefix(node, writer, mode); + if (node.parent != null) { + if (node.last) { + try printTreeChildLast(writer, mode); + } else { + try printTreeChild(writer, mode); + } + } + } }; const ErrorStyle = enum { @@ -125,7 +149,7 @@ const ErrorStyle = enum { }; const MultilineErrors = enum { indent, newline, none }; const Summary = enum { all, new, failures, line, none }; -const PrintConfiguration = enum { none, zon, path }; +const PrintConfiguration = enum { none, zon, path, tree, stop }; const GeneratedFile = struct { data: ?[]const u8, @@ -276,7 +300,6 @@ pub fn main(init: process.Init.Minimal) !void { var cached_passthru_configure: std.ArrayList(u32) = .empty; var forks: std.ArrayList(Fork) = .empty; var system_pkg_dir_path: ?[]const u8 = null; - var fetch_only = false; var fetch_mode: Fetch.JobQueue.Mode = .needed; var debug_target: ?[]const u8 = null; var cache_poison: std.Build.Graph.CachePoison = .pure; @@ -361,12 +384,12 @@ pub fn main(init: process.Init.Minimal) !void { override_local_cache_dir = nextArgOrFatal(args, &arg_i); } else if (mem.eql(u8, arg, "--pkg-dir")) { override_pkg_dir = nextArgOrFatal(args, &arg_i); + } else if (mem.eql(u8, arg, "--print-tree")) { + print_configuration = .tree; + } else if (mem.eql(u8, arg, "--configure")) { + print_configuration = .stop; } else if (mem.eql(u8, arg, "--fetch")) { - fetch_only = true; - } else if (mem.cutPrefix(u8, arg, "--fetch=")) |rest| { - fetch_only = true; - fetch_mode = stringToEnum(Fetch.JobQueue.Mode, rest) orelse - fatal("expected [needed|all] after \"--fetch=\", found {q}", .{rest}); + fetch_mode = .all; } else if (mem.cutPrefix(u8, arg, "--fork=")) |rest| { try forks.append(arena, .init(rest)); } else if (mem.eql(u8, arg, "--fork")) { @@ -593,7 +616,7 @@ pub fn main(init: process.Init.Minimal) !void { } } - const early_exit_mode = fetch_only or help_menu or steps_menu or print_configuration != .none; + const early_exit_mode = fetch_mode == .all or help_menu or steps_menu or print_configuration != .none; const server_mode = !early_exit_mode and (watch_flag or webui_listen != null or fuzz != null or listen); process.raiseFileDescriptorLimit(); @@ -601,10 +624,16 @@ pub fn main(init: process.Init.Minimal) !void { const cwd_path = std.zig.getResolvedCwd(io, arena) catch |err| fatal("resolving current directory path failed: {t}", .{err}); - var build_root = try findBuildRoot(arena, io, .{ + var build_root = findBuildRoot(arena, io, .{ .cwd_path = cwd_path, .build_file = build_file, - }); + }) catch |err| switch (err) { + error.BuildZigNotFound => { + if (help_menu) printUsageWithoutConfiguration(&graph, "build.zig not found"); + process.exit(1); + }, + else => |e| return e, + }; defer build_root.deinit(io); graph.build_root_directory = build_root.directory; @@ -740,7 +769,7 @@ pub fn main(init: process.Init.Minimal) !void { var configure_source_files: Cache.Manifest.SelfContainedFiles = .empty; defer configure_source_files.deinit(gpa); - var configure_error_bundle: std.zig.ErrorBundle = .empty; + var configure_error_bundle: ErrorBundle = .empty; defer configure_error_bundle.deinit(gpa); // 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 { .parent_progress_node = main_progress_node, .fetch_mode = fetch_mode, .system_pkg_dir_path = system_pkg_dir_path, - .fetch_only = fetch_only, .print_configuration = print_configuration, .forks = forks.items, .src_files = &configure_source_files, @@ -789,6 +817,8 @@ pub fn main(init: process.Init.Minimal) !void { return cleanExit(io, &scanned_config); }, .path => unreachable, + .tree => unreachable, + .stop => return cleanExit(io, &scanned_config), } var maker: Maker = .{ @@ -1043,7 +1073,10 @@ pub fn main(init: process.Init.Minimal) !void { } else |err| { const can_fs_watch = switch (err) { error.AlreadyReported => false, - error.FailedButCacheIntact => true, + error.FailedButCacheIntact => c: { + if (help_menu) printUsageWithoutConfiguration(&graph, "build.zig compilation failure"); + break :c true; + }, else => |e| w: { log.err("configuration failed: {t}", .{e}); break :w false; @@ -1065,6 +1098,17 @@ pub fn main(init: process.Init.Minimal) !void { } } +fn printUsageWithoutConfiguration(graph: *Graph, reason: []const u8) noreturn { + const io = graph.io; + _ = io.lockStderr(&.{}, graph.stderr_mode) catch {}; + const writer = initStdoutWriter(io); + ScannedConfig.printUsageWithoutConfiguration(graph.zig_exe, writer, reason) catch |err| switch (err) { + error.WriteFailed => fatal("failed writing to stdout: {t}", .{stdout_writer_allocation.err.?}), + }; + stdout_writer_allocation.flush() catch |err| fatal("failed writing to stdout: {t}", .{err}); + return process.exit(0); +} + /// Temporarily adds the reconfigure pseudostep to step_stack, calls /// `Watch.update`, and then pops it again. fn updateWatch(maker: *Maker, watch: *Watch) !void { @@ -1088,11 +1132,10 @@ const ConfigureOptions = struct { parent_progress_node: std.Progress.Node, fetch_mode: Fetch.JobQueue.Mode, system_pkg_dir_path: ?[]const u8, - fetch_only: bool, print_configuration: PrintConfiguration, forks: []Fork, src_files: *Cache.Manifest.SelfContainedFiles, - error_bundle: ?*std.zig.ErrorBundle = null, + error_bundle: ?*ErrorBundle = null, }; fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig { @@ -1336,11 +1379,27 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig { return error.FailedButCacheIntact; } - if (options.fetch_only) { + if (options.print_configuration == .tree) { + const NO_COLOR = EnvVar.NO_COLOR.isSet(&graph.environ_map); + const CLICOLOR_FORCE = EnvVar.CLICOLOR_FORCE.isSet(&graph.environ_map); + printPkgTree(.{ + .writer = initStdoutWriter(io), + .mode = try .detect(io, .stdout(), NO_COLOR, CLICOLOR_FORCE), + }, arena, &job_queue, &fetch) catch |err| switch (err) { + error.WriteFailed => return stdout_writer_allocation.err.?, + else => |e| return e, + }; + try stdout_writer_allocation.flush(); + process.exit(0); + } + + if (options.fetch_mode == .all) { _ = io.lockStderr(&.{}, .no_color) catch {}; process.exit(0); } + try validateFingerprints(gpa, &job_queue); + // Create the dependencies.zig file for configurer to // obtain via `@import("@dependencies")`. { @@ -1403,7 +1462,7 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig { for (fetches) |f| { if (!f.have_manifest) continue; if (config_man) |man| { - const manifest_path = try f.package_root.join(arena, Package.Manifest.basename); + const manifest_path = try f.package_root.join(arena, Manifest.basename); _ = try man.addInputPath(manifest_path, .{}); } const mod = f.cli_module orelse continue; @@ -1619,7 +1678,8 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig { _ = io.lockStderr(&.{}, .no_color) catch {}; process.exit(0); }, - .none, .zon => {}, + .none, .zon, .stop => {}, + .tree => unreachable, } const configuration = c: { @@ -1657,6 +1717,147 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig { }; } +const ValidFingerprintKey = struct { + project_id: Package.ProjectId, + version: std.SemanticVersion, + + const Context = struct { + pub fn hash(this: @This(), a: ValidFingerprintKey) u32 { + _ = this; + var hasher: std.hash.Wyhash = .init(a.project_id.fingerprint_id); + hasher.update(&a.project_id.padded_name); + hasher.update(@ptrCast((&a.version.major)[0..1])); + hasher.update(@ptrCast((&a.version.minor)[0..1])); + hasher.update(@ptrCast((&a.version.patch)[0..1])); + if (a.version.pre) |pre| hasher.update(pre); + return @truncate(hasher.final()); + } + pub fn eql(this: @This(), a: ValidFingerprintKey, b: ValidFingerprintKey, i: usize) bool { + _ = this; + _ = i; + return a.project_id.eql(&b.project_id) and a.version.order(b.version) == .eq; + } + }; +}; + +const ValidFingerprintTable = std.array_hash_map.Custom( + ValidFingerprintKey, + struct { fetch: *Fetch, pkg_hash: Package.Hash, printed: bool }, + ValidFingerprintKey.Context, + true, +); + +/// Ensure that the dependency tree did not have any fingerprint matches with the same semantic version but +/// differing package hashes. +fn validateFingerprints(gpa: Allocator, job_queue: *const Fetch.JobQueue) !void { + var any_errors = false; + + var table: ValidFingerprintTable = .empty; + defer table.deinit(gpa); + + for (job_queue.table.keys(), job_queue.table.values()) |pkg_hash, fetch| { + if (!fetch.have_manifest) continue; + const gop = try table.getOrPut(gpa, .{ + .project_id = .init(fetch.manifest.name, fetch.manifest.id), + .version = fetch.manifest.version, + }); + if (gop.found_existing) { + any_errors = true; + const fingerprint: Package.Fingerprint = .init(fetch.manifest.id, fetch.manifest.name); + const stripped_version: std.SemanticVersion = .{ + .major = fetch.manifest.version.major, + .minor = fetch.manifest.version.minor, + .patch = fetch.manifest.version.patch, + .pre = fetch.manifest.version.pre, + }; + if (!gop.value_ptr.printed) { + gop.value_ptr.printed = true; + log.err("contradictory hash for package with fingerprint 0x{x} and version {f}: {s}", .{ + fingerprint.int(), stripped_version, gop.value_ptr.pkg_hash.toSlice(), + }); + } + log.err("contradictory hash for package with fingerprint 0x{x} and version {f}: {s}", .{ + fingerprint.int(), stripped_version, pkg_hash.toSlice(), + }); + } else { + gop.value_ptr.* = .{ .fetch = fetch, .pkg_hash = pkg_hash, .printed = false }; + } + } + if (any_errors) { + log.info("potential cause: package ownership dispute", .{}); + log.info("potential cause: maintainer neglected to bump version after making changes", .{}); + log.info("potential cause: dependency on unreleased changes", .{}); + log.info("hint: use --print-tree to visualize all package versions and hashes", .{}); + return error.FailedButCacheIntact; + } +} + +fn printPkgTree( + terminal: Io.Terminal, + arena: Allocator, + job_queue: *const Fetch.JobQueue, + root_fetch: *const Fetch, +) !void { + if (!root_fetch.have_manifest) return; + const names = root_fetch.manifest.dependencies.keys(); + for (names, root_fetch.manifest.dependencies.values(), 0..) |name, *dep, i| { + var print_node: PrintNode = .{ + .parent = null, + .last = i == names.len - 1, + }; + try printPkgTreeChild(terminal, arena, job_queue, &print_node, name, dep.hash); + } +} + +fn printPkgTreeChild( + terminal: Io.Terminal, + arena: Allocator, + job_queue: *const Fetch.JobQueue, + parent_print_node: *const PrintNode, + dep_name: []const u8, + opt_hash: ?[]const u8, +) !void { + const w = terminal.writer; + try parent_print_node.printPrefixedChild(w, terminal.mode); + try w.writeAll(dep_name); + + const hash = opt_hash orelse { + try terminal.setColor(.red); + try w.writeAll(" (missing hash)\n"); + try terminal.setColor(.reset); + return; + }; + try terminal.setColor(.dim); + try w.print(" {s}", .{hash}); + try terminal.setColor(.reset); + const child_fetch = job_queue.table.get(.fromSlice(hash)) orelse { + try terminal.setColor(.red); + try w.writeAll(" (fetch failed)\n"); + try terminal.setColor(.reset); + return; + }; + switch (child_fetch.lazy_status) { + .eager, .available => {}, + .unavailable => { + try terminal.setColor(.yellow); + try w.writeAll(" (not fetched)\n"); + try terminal.setColor(.reset); + return; + }, + } + try w.writeByte('\n'); + if (!child_fetch.have_manifest) return; + + const names = child_fetch.manifest.dependencies.keys(); + for (names, child_fetch.manifest.dependencies.values(), 0..) |name, *dep, i| { + var print_node: PrintNode = .{ + .parent = parent_print_node, + .last = i == names.len - 1, + }; + try printPkgTreeChild(terminal, arena, job_queue, &print_node, name, dep.hash); + } +} + fn cmdFetch(gpa: Allocator, graph: *Graph, args: []const []const u8) !void { const environ_map = &graph.environ_map; const io = graph.io; @@ -1933,8 +2134,8 @@ fn cmdFetch(gpa: Allocator, graph: *Graph, args: []const []const u8) !void { try ast.render(gpa, &aw.writer, fixups); const rendered = aw.written(); - build_root.directory.handle.writeFile(io, .{ .sub_path = Package.Manifest.basename, .data = rendered }) catch |err| { - fatal("unable to write {s} file: {t}", .{ Package.Manifest.basename, err }); + build_root.directory.handle.writeFile(io, .{ .sub_path = Manifest.basename, .data = rendered }) catch |err| { + fatal("unable to write {s} file: {t}", .{ Manifest.basename, err }); }; return process.cleanExit(io); @@ -2136,7 +2337,7 @@ fn cmdInit(gpa: Allocator, graph: *Graph, args: []const []const u8) !void { const s = Dir.path.sep_str; const template_paths = [_][]const u8{ default_build_zig_basename, - Package.Manifest.basename, + Manifest.basename, "src" ++ s ++ "main.zig", "src" ++ s ++ "root.zig", }; @@ -2160,7 +2361,7 @@ fn cmdInit(gpa: Allocator, graph: *Graph, args: []const []const u8) !void { return process.cleanExit(io); }, .minimal => { - Templates.writeSimpleFile(io, Io.Dir.cwd(), Package.Manifest.basename, + Templates.writeSimpleFile(io, Io.Dir.cwd(), Manifest.basename, \\.{{ \\ .name = .{s}, \\ .version = "0.0.1", @@ -2174,8 +2375,8 @@ fn cmdInit(gpa: Allocator, graph: *Graph, args: []const []const u8) !void { builtin.zig_version_string, fingerprint.int(), }) catch |err| switch (err) { - else => fatal("failed to create {q}: {t}", .{ Package.Manifest.basename, err }), - error.PathAlreadyExists => fatal("refusing to overwrite {q}", .{Package.Manifest.basename}), + else => fatal("failed to create {q}: {t}", .{ Manifest.basename, err }), + error.PathAlreadyExists => fatal("refusing to overwrite {q}", .{Manifest.basename}), }; Templates.writeSimpleFile(io, Io.Dir.cwd(), default_build_zig_basename, \\const std = @import("std"); @@ -2190,12 +2391,12 @@ fn cmdInit(gpa: Allocator, graph: *Graph, args: []const []const u8) !void { // their `build.zig.zon` *after* writing their `build.zig`. So this one isn't fatal. error.PathAlreadyExists => { log.info("successfully populated {q}, preserving existing {q}", .{ - Package.Manifest.basename, default_build_zig_basename, + Manifest.basename, default_build_zig_basename, }); return process.cleanExit(io); }, }; - log.info("successfully populated {q} and {q}", .{ Package.Manifest.basename, default_build_zig_basename }); + log.info("successfully populated {q} and {q}", .{ Manifest.basename, default_build_zig_basename }); return process.cleanExit(io); }, } @@ -2928,18 +3129,7 @@ fn printTreeStep( .failures => make_step.state == .success, }; if (skip) return; - try printPrefix(parent_node, stderr); - - if (parent_node.parent != null) { - if (parent_node.last) { - try printChildNodePrefix(stderr); - } else { - try writer.writeAll(switch (stderr.mode) { - .escape_codes => "\x1B\x28\x30\x74\x71\x1B\x28\x42 ", // ├─ - else => "+- ", - }); - } - } + try parent_node.printPrefixedChild(writer, stderr.mode); if (!first) try stderr.setColor(.dim); @@ -3168,23 +3358,22 @@ fn printStepFailure( } } -fn printPrefix(node: *PrintNode, stderr: Io.Terminal) !void { - const parent = node.parent orelse return; - const writer = stderr.writer; - if (parent.parent == null) return; - try printPrefix(parent, stderr); - if (parent.last) { - try writer.writeAll(" "); - } else { - try writer.writeAll(switch (stderr.mode) { - .escape_codes => "\x1B\x28\x30\x78\x1B\x28\x42 ", // │ - else => "| ", - }); - } +fn printTreeChild(writer: *Writer, mode: Io.Terminal.Mode) Writer.Error!void { + return writer.writeAll(switch (mode) { + .escape_codes => "\x1B\x28\x30\x74\x71\x1B\x28\x42 ", // ├─ + else => "+- ", + }); } -fn printChildNodePrefix(stderr: Io.Terminal) !void { - try stderr.writer.writeAll(switch (stderr.mode) { +fn printTreeSibling(writer: *Writer, mode: Io.Terminal.Mode) Writer.Error!void { + return writer.writeAll(switch (mode) { + .escape_codes => "\x1B\x28\x30\x78\x1B\x28\x42 ", // │ + else => "| ", + }); +} + +fn printTreeChildLast(writer: *Writer, mode: Io.Terminal.Mode) !void { + try writer.writeAll(switch (mode) { .escape_codes => "\x1B\x28\x30\x6d\x71\x1B\x28\x42 ", // └─ else => "+- ", }); @@ -3278,7 +3467,7 @@ pub fn invalidateResult(maker: *Maker, step: *Step) error{MustReconfigure}!bool pub fn printErrorMessages( maker: *Maker, failing_step_index: Configuration.Step.Index, - options: std.zig.ErrorBundle.RenderOptions, + options: ErrorBundle.RenderOptions, stderr: Io.Terminal, error_style: ErrorStyle, multiline_errors: MultilineErrors, @@ -3304,7 +3493,7 @@ pub fn printErrorMessages( while (step_stack.pop()) |step_index| : (indent += 1) { if (indent > 0) { try writer.splatByteAll(' ', (indent - 1) * 3); - try printChildNodePrefix(stderr); + try printTreeChildLast(writer, stderr.mode); } try writer.writeAll(step_index.ptr(c).name.slice(c)); @@ -4038,7 +4227,9 @@ const FindBuildRootOptions = struct { cwd_path: []const u8, }; -fn findBuildRoot(arena: Allocator, io: Io, options: FindBuildRootOptions) !BuildRoot { +const FindBuildRootError = error{ OutOfMemory, BuildZigNotFound }; + +fn findBuildRoot(arena: Allocator, io: Io, options: FindBuildRootOptions) FindBuildRootError!BuildRoot { const build_zig_basename = if (options.build_file) |bf| Dir.path.basename(bf) else @@ -4089,11 +4280,12 @@ fn findBuildRoot(arena: Allocator, io: Io, options: FindBuildRootOptions) !Build dirname = Dir.path.dirname(dirname orelse options.cwd_path) orelse { log.info("initialize {s} template file with \"zig init\"", .{std.zig.build_zig_basename}); log.info("see \"zig --help\" for more options", .{}); - fatal("no build.zig file found, in the current directory or any parent directories", .{}); + log.err("no build.zig file found, in the current directory or any parent directories", .{}); + return error.BuildZigNotFound; }; continue; }, - else => |e| return e, + else => |e| fatal("accessing {q} while searching for build.zig file failed: {t}", .{ joined_path, e }), } } } @@ -4101,8 +4293,8 @@ fn findBuildRoot(arena: Allocator, io: Io, options: FindBuildRootOptions) !Build const Fork = struct { path: Path, manifest_ast: std.zig.Ast, - manifest: Package.Manifest, - error_bundle: std.zig.ErrorBundle.Wip, + manifest: Manifest, + error_bundle: ErrorBundle.Wip, failed: bool, arena_allocator: std.heap.ArenaAllocator, @@ -4135,12 +4327,12 @@ const Fork = struct { fork.arena_allocator = .init(gpa); const arena = fork.arena_allocator.allocator(); - var error_bundle: std.zig.ErrorBundle.Wip = try .init(gpa); + var error_bundle: ErrorBundle.Wip = try .init(gpa); defer error_bundle.deinit(); - const manifest_path = try fork.path.join(arena, Package.Manifest.basename); + const manifest_path = try fork.path.join(arena, Manifest.basename); - Package.Manifest.load( + Manifest.load( io, arena, manifest_path, @@ -4190,20 +4382,20 @@ fn loadManifest( arena: Allocator, io: Io, options: LoadManifestOptions, -) !struct { Package.Manifest, std.zig.Ast } { +) !struct { Manifest, std.zig.Ast } { const rng: std.Random.IoSource = .{ .io = io }; const manifest_bytes = while (true) { break options.dir.readFileAllocOptions( io, - Package.Manifest.basename, + Manifest.basename, arena, - .limited(Package.Manifest.max_bytes), + .limited(Manifest.max_bytes), .@"1", 0, ) catch |err| switch (err) { error.FileNotFound => { - Templates.writeSimpleFile(io, options.dir, Package.Manifest.basename, + Templates.writeSimpleFile(io, options.dir, Manifest.basename, \\.{{ \\ .name = .{s}, \\ .version = "0.0.1", @@ -4217,29 +4409,29 @@ fn loadManifest( builtin.zig_version_string, Package.Fingerprint.generate(rng.interface(), options.root_name).int(), }) catch |e| { - fatal("unable to write {s}: {t}", .{ Package.Manifest.basename, e }); + fatal("unable to write {s}: {t}", .{ Manifest.basename, e }); }; continue; }, - else => |e| fatal("unable to load {s}: {t}", .{ Package.Manifest.basename, e }), + else => |e| fatal("unable to load {s}: {t}", .{ Manifest.basename, e }), }; }; var ast = try std.zig.Ast.parse(gpa, manifest_bytes, .{ .mode = .zon }); errdefer ast.deinit(gpa); if (ast.errors.len > 0) { - try std.zig.printAstErrorsToStderr(gpa, io, ast, Package.Manifest.basename, options.color); + try std.zig.printAstErrorsToStderr(gpa, io, ast, Manifest.basename, options.color); process.exit(2); } - var manifest = try Package.Manifest.parse(gpa, &ast, rng.interface(), .{}); + var manifest = try Manifest.parse(gpa, &ast, rng.interface(), .{}); errdefer manifest.deinit(gpa); if (manifest.errors.len > 0) { - var wip_errors: std.zig.ErrorBundle.Wip = try .init(gpa); + var wip_errors: ErrorBundle.Wip = try .init(gpa); defer wip_errors.deinit(); - const src_path = try wip_errors.addString(Package.Manifest.basename); + const src_path = try wip_errors.addString(Manifest.basename); try manifest.copyErrorsIntoBundle(ast, src_path, &wip_errors); var error_bundle = try wip_errors.toOwnedBundle(""); @@ -4263,8 +4455,8 @@ fn sanitizeExampleName(arena: Allocator, bytes: []const u8) error{OutOfMemory}![ else => continue, }; if (!std.zig.isValidId(result.items)) return "foo"; - if (result.items.len > Package.Manifest.max_name_len) - result.shrinkRetainingCapacity(Package.Manifest.max_name_len); + if (result.items.len > Manifest.max_name_len) + result.shrinkRetainingCapacity(Manifest.max_name_len); return result.toOwnedSlice(arena); } diff --git a/lib/compiler/Maker/Package.zig b/lib/compiler/Maker/Package.zig index 8baef22f375f925534380176f618a470f988117e..c31ecf6916ce4d8d279cc9dbe9f529ee99355372 100644 --- a/lib/compiler/Maker/Package.zig +++ b/lib/compiler/Maker/Package.zig @@ -8,13 +8,17 @@ pub const Fingerprint = packed struct(u64) { id: u32, checksum: u32, - pub fn generate(rng: std.Random, name: []const u8) Fingerprint { + pub fn init(id: u32, name: []const u8) Fingerprint { return .{ - .id = rng.intRangeLessThan(u32, 1, 0xffffffff), + .id = id, .checksum = std.hash.Crc32.hash(name), }; } + pub fn generate(rng: std.Random, name: []const u8) Fingerprint { + return .init(rng.intRangeLessThan(u32, 1, 0xffffffff), name); + } + pub fn validate(n: Fingerprint, name: []const u8) bool { switch (n.id) { 0x00000000, 0xffffffff => return false, diff --git a/lib/compiler/Maker/ScannedConfig.zig b/lib/compiler/Maker/ScannedConfig.zig index 0b98a78d877b69c06f9b8fdd5da3d9efa178abb2..bae416b9bb2523a6b042bfa118d8a60ae43213ed 100644 --- a/lib/compiler/Maker/ScannedConfig.zig +++ b/lib/compiler/Maker/ScannedConfig.zig @@ -238,6 +238,18 @@ pub fn printSteps(sc: *const ScannedConfig, graph: *Graph, w: *Writer) !void { } } +pub fn printUsageWithoutConfiguration(zig_exe: []const u8, w: *Writer, reason: []const u8) Writer.Error!void { + try w.print( + \\Usage: {s} build [steps] [options] + \\ + \\Steps: (unavailable due to {s}) + \\Project-Specific Options: (unavailable due to {s}) + \\{s} + \\ (unavailable due to {s}) + \\{s} + , .{ zig_exe, reason, reason, system_integration_usage, reason, general_usage }); +} + pub fn printUsage(sc: *const ScannedConfig, graph: *Graph, w: *Writer) !void { const arena = graph.arena; @@ -274,37 +286,7 @@ pub fn printUsage(sc: *const ScannedConfig, graph: *Graph, w: *Writer) !void { } } - try w.writeAll( - \\ - \\System Integration Options: - \\ --search-prefix [path] Add a path to look for binaries, libraries, headers - \\ --sysroot [path] Set the system root directory (usually /) - \\ --libc [file] Provide a file which specifies libc paths - \\ - \\ --system [pkgdir] Disable package fetching; enable all integrations - \\ -fsys=[name] Enable a system integration - \\ -fno-sys=[name] Disable a system integration - \\ - \\ -fdarling, -fno-darling Integration with system-installed Darling to - \\ execute macOS programs on Linux hosts - \\ (default: no) - \\ -fqemu, -fno-qemu Integration with system-installed QEMU to execute - \\ foreign-architecture programs on Linux hosts - \\ (default: no) - \\ --libc-runtimes [path] Enhances QEMU integration by providing dynamic libc - \\ (e.g. glibc or musl) built for multiple foreign - \\ architectures, allowing execution of non-native - \\ programs that link with libc. - \\ -frosetta, -fno-rosetta Rely on Rosetta to execute x86_64 programs on - \\ ARM64 macOS hosts. (default: no) - \\ -fwasmtime, -fno-wasmtime Integration with system-installed wasmtime to - \\ execute WASI binaries. (default: no) - \\ -fwine, -fno-wine Integration with system-installed Wine to execute - \\ Windows programs on Linux hosts. (default: no) - \\ - \\ Available System Integrations: Enabled: - \\ - ); + try w.writeAll(system_integration_usage); if (sc.configuration.system_integrations.len == 0) { try w.writeAll(" (none) -\n"); } else { @@ -318,92 +300,126 @@ pub fn printUsage(sc: *const ScannedConfig, graph: *Graph, w: *Writer) !void { } } - try w.writeAll( - \\ - \\General Options: - \\ -h, --help Print this help to stdout and exit - \\ -l, --list-steps Print available steps to stdout and exit - \\ - \\ -p, --prefix [path] Where to install files (default: zig-out) - \\ --prefix-lib-dir [path] Where to install libraries - \\ --prefix-exe-dir [path] Where to install executables - \\ --prefix-include-dir [path] Where to install C header files - \\ --release[=mode] Request release mode, optionally specifying a - \\ preferred optimization mode: fast, safe, small - \\ - \\ --verbose Print commands before executing them - \\ --color [auto|off|on] Enable or disable colored error messages - \\ --error-style [style] Control how build errors are printed - \\ verbose (Default) Report errors with full context - \\ minimal Report errors after summary, excluding context like command lines - \\ verbose_clear Like 'verbose', but clear the terminal at the start of each update - \\ minimal_clear Like 'minimal', but clear the terminal at the start of each update - \\ --multiline-errors [style] Control how multi-line error messages are printed - \\ indent (Default) Indent non-initial lines to align with initial line - \\ newline Include a leading newline so that the error message is on its own lines - \\ none Print as usual so the first line is misaligned - \\ --summary [mode] Control the printing of the build summary - \\ all Print the build summary in its entirety - \\ new Omit cached steps - \\ failures (Default if short-lived) Only print failed steps - \\ line (Default if long-lived) Only print the single-line summary - \\ none Do not print the build summary - \\ -j Limit concurrent jobs (default is to use all CPU cores) - \\ --maxrss Limit memory usage (default is to use available memory) - \\ --skip-oom-steps Instead of failing, skip steps that would exceed --maxrss - \\ --test-timeout Limit execution time of unit tests, terminating if exceeded. - \\ The timeout must include a unit: ns, us, ms, s, m, h - \\ --watch Continuously rebuild when source files are modified - \\ --debounce Delay before rebuilding after changed file detected - \\ --webui[=ip] Enable the web interface on the given IP address - \\ --fuzz[=limit] Continuously search for unit test failures with an optional - \\ limit to the max number of iterations. The argument supports - \\ an optional 'K', 'M', or 'G' suffix (e.g. '10K'). Implies - \\ '--webui' when no limit is specified. - \\ --listen=- Enable the build server protocol on stdio - \\ --time-report Force full rebuild and provide detailed information on - \\ compilation time of Zig source code (implies '--webui') - \\ -fincremental Enable incremental compilation - \\ -fno-incremental Disable incremental compilation - \\ - \\Package Management Options: - \\ --fetch[=mode] Fetch dependency tree (optionally choose laziness) and exit - \\ needed (Default) Lazy dependencies are fetched as needed - \\ all Lazy dependencies are always fetched - \\ --fork=[path], --fork [path] Override one or more projects from dependency tree - \\ - \\Advanced Options: - \\ -freference-trace[=num] How many lines of reference trace should be shown per compile error - \\ -fno-reference-trace Disable reference trace - \\ -fallow-so-scripts Allows .so files to be GNU ld scripts - \\ -fno-allow-so-scripts (default) .so files must be ELF files - \\ --error-limit [num] Set the maximum amount of distinct error values - \\ --build-file [file] Override path to build.zig - \\ --cache-dir [path] Override path to local Zig cache directory - \\ --zig-lib=[arg] Override path to Zig lib directory - \\ --seed [integer] For shuffling dependency traversal order (default: random) - \\ --cache-poison[=mode] Override configuration caching behavior - \\ pure (default) Avoid false positive cache hits - \\ poisoned Don't cache the configuration - \\ disallowed Panics when cache would be poisoned - \\ ignored A little poison never hurt anybody - \\ --print-configuration Render configuration as .zon to stdout - \\ --print-configuration-path Print the path to the binary configuration file to stdout - \\ --build-id[=style] At a minor link-time expense, embeds a build ID in binaries - \\ fast 8-byte non-cryptographic hash (COFF, ELF, WASM) - \\ sha1, tree 20-byte cryptographic hash (ELF, WASM) - \\ md5 16-byte cryptographic hash (ELF) - \\ uuid 16-byte random UUID (ELF, WASM) - \\ 0x[hexstring] Constant ID, maximum 32 bytes (ELF, WASM) - \\ none (default) No build ID - \\ --debug-log [scope] Enable debugging the compiler - \\ --debug-pkg-config Fail if unknown pkg-config flags encountered - \\ --verbose-link Enable compiler debug output for linking - \\ --verbose-air Enable compiler debug output for Zig AIR - \\ --verbose-llvm-ir Enable compiler debug output for LLVM IR - \\ --verbose-cimport Enable compiler debug output for C imports - \\ --verbose-cc Enable compiler debug output for C compilation - \\ --verbose-llvm-cpu-features Enable compiler debug output for LLVM CPU features - \\ - ); + try w.writeAll(general_usage); } + +const system_integration_usage = + \\ + \\System Integration Options: + \\ --search-prefix [path] Add a path to look for binaries, libraries, headers + \\ --sysroot [path] Set the system root directory (usually /) + \\ --libc [file] Provide a file which specifies libc paths + \\ + \\ --system [pkgdir] Disable package fetching; enable all integrations + \\ -fsys=[name] Enable a system integration + \\ -fno-sys=[name] Disable a system integration + \\ + \\ -fdarling, -fno-darling Integration with system-installed Darling to + \\ execute macOS programs on Linux hosts + \\ (default: no) + \\ -fqemu, -fno-qemu Integration with system-installed QEMU to execute + \\ foreign-architecture programs on Linux hosts + \\ (default: no) + \\ --libc-runtimes [path] Enhances QEMU integration by providing dynamic libc + \\ (e.g. glibc or musl) built for multiple foreign + \\ architectures, allowing execution of non-native + \\ programs that link with libc. + \\ -frosetta, -fno-rosetta Rely on Rosetta to execute x86_64 programs on + \\ ARM64 macOS hosts. (default: no) + \\ -fwasmtime, -fno-wasmtime Integration with system-installed wasmtime to + \\ execute WASI binaries. (default: no) + \\ -fwine, -fno-wine Integration with system-installed Wine to execute + \\ Windows programs on Linux hosts. (default: no) + \\ + \\ Available System Integrations: Enabled: + \\ +; + +const general_usage = + \\ + \\General Options: + \\ -h, --help Print this help to stdout and exit + \\ -l, --list-steps Print available steps to stdout and exit + \\ + \\ -p, --prefix [path] Where to install files (default: zig-out) + \\ --prefix-lib-dir [path] Where to install libraries + \\ --prefix-exe-dir [path] Where to install executables + \\ --prefix-include-dir [path] Where to install C header files + \\ --release[=mode] Request release mode, optionally specifying a + \\ preferred optimization mode: fast, safe, small + \\ + \\ --verbose Print commands before executing them + \\ --color [auto|off|on] Enable or disable colored error messages + \\ --error-style [style] Control how build errors are printed + \\ verbose (Default) Report errors with full context + \\ minimal Report errors after summary, excluding context like command lines + \\ verbose_clear Like "verbose", but clear the terminal at the start of each update + \\ minimal_clear Like "minimal", but clear the terminal at the start of each update + \\ --multiline-errors [style] Control how multi-line error messages are printed + \\ indent (Default) Indent non-initial lines to align with initial line + \\ newline Include a leading newline so that the error message is on its own lines + \\ none Print as usual so the first line is misaligned + \\ --summary [mode] Control the printing of the build summary + \\ all Print the build summary in its entirety + \\ new Omit cached steps + \\ failures (Default if short-lived) Only print failed steps + \\ line (Default if long-lived) Only print the single-line summary + \\ none Do not print the build summary + \\ -j Limit concurrent jobs (default is to use all CPU cores) + \\ --maxrss Limit memory usage (default is to use available memory) + \\ --skip-oom-steps Instead of failing, skip steps that would exceed --maxrss + \\ --test-timeout Limit execution time of unit tests, terminating if exceeded. + \\ The timeout must include a unit: ns, us, ms, s, m, h + \\ --watch Continuously rebuild when source files are modified + \\ --debounce Delay before rebuilding after changed file detected + \\ --webui[=ip] Enable the web interface on the given IP address + \\ --fuzz[=limit] Continuously search for unit test failures with an optional + \\ limit to the max number of iterations. The argument supports + \\ an optional 'K', 'M', or 'G' suffix (e.g. "10K"). Implies + \\ "--webui" when no limit is specified. + \\ --time-report Force full rebuild and provide detailed information on + \\ compilation time of Zig source code (implies "--webui") + \\ -fincremental Enable incremental compilation + \\ -fno-incremental Disable incremental compilation + \\ --configure Exit after the configuration phase + \\ + \\Package Management Options: + \\ --fetch Fetch all dependencies including lazy ones; exit without configuration + \\ --fork=[path], --fork [path] Override one or more projects from dependency tree + \\ --print-tree Write current state of package dependencies to stdout + \\ + \\Advanced Options: + \\ --listen=- Enable the build server protocol on stdio + \\ -freference-trace[=num] How many lines of reference trace should be shown per compile error + \\ -fno-reference-trace Disable reference trace + \\ -fallow-so-scripts Allows .so files to be GNU ld scripts + \\ -fno-allow-so-scripts (default) .so files must be ELF files + \\ --error-limit [num] Set the maximum amount of distinct error values + \\ --build-file [file] Override path to build.zig + \\ --cache-dir [path] Override path to local Zig cache directory + \\ --zig-lib=[arg] Override path to Zig lib directory + \\ --seed [integer] For shuffling dependency traversal order (default: random) + \\ --cache-poison[=mode] Override configuration caching behavior + \\ pure (default) Avoid false positive cache hits + \\ poisoned Don't cache the configuration + \\ disallowed Panics when cache would be poisoned + \\ ignored A little poison never hurt anybody + \\ --print-configuration Render configuration as .zon to stdout. Implies "--configure" + \\ --print-configuration-path Print the path to the binary configuration file to stdout. + \\ --build-id[=style] At a minor link-time expense, embeds a build ID in binaries + \\ fast 8-byte non-cryptographic hash (COFF, ELF, WASM) + \\ sha1, tree 20-byte cryptographic hash (ELF, WASM) + \\ md5 16-byte cryptographic hash (ELF) + \\ uuid 16-byte random UUID (ELF, WASM) + \\ 0x[hexstring] Constant ID, maximum 32 bytes (ELF, WASM) + \\ none (default) No build ID + \\ --debug-log [scope] Enable debugging the compiler + \\ --debug-pkg-config Fail if unknown pkg-config flags encountered + \\ --verbose-link Enable compiler debug output for linking + \\ --verbose-air Enable compiler debug output for Zig AIR + \\ --verbose-llvm-ir Enable compiler debug output for LLVM IR + \\ --verbose-cimport Enable compiler debug output for C imports + \\ --verbose-cc Enable compiler debug output for C compilation + \\ --verbose-llvm-cpu-features Enable compiler debug output for LLVM CPU features + \\ +; -- 2.54.0