Skip to content
1,574 changes: 1,574 additions & 0 deletions lib/std/cli.zig

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions lib/std/std.zig
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ pub const base64 = @import("base64.zig");
pub const bit_set = @import("bit_set.zig");
pub const builtin = @import("builtin.zig");
pub const c = @import("c.zig");
pub const cli = @import("cli.zig");
pub const coff = @import("coff.zig");
pub const compress = @import("compress.zig");
pub const static_string_map = @import("static_string_map.zig");
Expand Down
57 changes: 15 additions & 42 deletions tools/docgen.zig
Original file line number Diff line number Diff line change
Expand Up @@ -16,55 +16,28 @@ const max_doc_file_size = 10 * 1024 * 1024;

const obj_ext = builtin.object_format.fileExt(builtin.cpu.arch);

const usage =
\\Usage: docgen [options] input output
\\
\\ Generates an HTML document from a docgen template.
\\
\\Options:
\\ --code-dir dir Path to directory containing code example outputs
\\ -h, --help Print this help and exit
\\
;
const Args = struct {
pub const description = "Generates an HTML document from a docgen template.";
named: struct {
@"code-dir": [:0]const u8,
pub const @"code-dir_help" = "Path to directory containing code example outputs";
},
positional: struct {
input: [:0]const u8,
output: [:0]const u8,
},
};

pub fn main() !void {
var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena_instance.deinit();

const arena = arena_instance.allocator();

var args_it = try process.argsWithAllocator(arena);
if (!args_it.skip()) @panic("expected self arg");

var opt_code_dir: ?[]const u8 = null;
var opt_input: ?[]const u8 = null;
var opt_output: ?[]const u8 = null;

while (args_it.next()) |arg| {
if (mem.startsWith(u8, arg, "-")) {
if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
try fs.File.stdout().writeAll(usage);
process.exit(0);
} else if (mem.eql(u8, arg, "--code-dir")) {
if (args_it.next()) |param| {
opt_code_dir = param;
} else {
fatal("expected parameter after --code-dir", .{});
}
} else {
fatal("unrecognized option: '{s}'", .{arg});
}
} else if (opt_input == null) {
opt_input = arg;
} else if (opt_output == null) {
opt_output = arg;
} else {
fatal("unexpected positional argument: '{s}'", .{arg});
}
}
const input_path = opt_input orelse fatal("missing input file", .{});
const output_path = opt_output orelse fatal("missing output file", .{});
const code_dir_path = opt_code_dir orelse fatal("missing --code-dir argument", .{});
const args = try std.cli.parse(Args, arena, .{});
const input_path = args.positional.input;
const output_path = args.positional.output;
const code_dir_path = args.named.@"code-dir";

var in_file = try fs.cwd().openFile(input_path, .{});
defer in_file.close();
Expand Down
12 changes: 9 additions & 3 deletions tools/dump-cov.zig
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,15 @@ pub fn main() !void {
defer arena_instance.deinit();
const arena = arena_instance.allocator();

const args = try std.process.argsAlloc(arena);
const exe_file_name = args[1];
const cov_file_name = args[2];
const args = try std.cli.parse(struct {
named: struct {},
positional: struct {
exe_file: [:0]const u8,
cov_file: [:0]const u8,
},
}, arena, .{});
const exe_file_name = args.positional.exe_file;
const cov_file_name = args.positional.cov_file;

const exe_path: Path = .{
.root_dir = std.Build.Cache.Directory.cwd(),
Expand Down
58 changes: 14 additions & 44 deletions tools/fetch_them_macos_headers.zig
Original file line number Diff line number Diff line change
Expand Up @@ -55,36 +55,24 @@ const Target = struct {

const headers_source_prefix: []const u8 = "headers";

const usage =
\\fetch_them_macos_headers [options] [cc args]
\\
\\Options:
\\ --sysroot Path to macOS SDK
\\
\\General Options:
\\-h, --help Print this help and exit
;
const Args = struct {
named: struct {
sysroot: []const u8 = "",

@rohlem rohlem Aug 31, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At a glance it might be nicer to instead type it ?[]const u8 and default to null.
Then the code wouldn't have to check for length 0, and could use orelse instead.

iiuc, passing --sysroot="" leads to auto-detection, with that change I assume it would lead to an error and have to be omitted.
Is there a code or interface reason that I'm missing that makes "" more intuitive for auto-detection?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the reason is that i ported code that was expecting null after argument parsing, and so i preserved those semantics. optional arguments declared as ?[]const u8 = null makes a lot of intuitive sense, but i explained why i believe it is a misfeature in #24601.

it could be that the pattern here will be frequent where users want to conflate "" with null, like i'm doing, but a layer of code to translate from a deserialization system into the type you want to use in your code base is a common thing that doesn't mean the deserialization system is missing a feature.

if you disagree that lack of support for null is a misfeature, let's discuss it #24601. the real actual use cases you're pointing to in this review are evidence against my stance.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iiuc, passing --sysroot="" leads to auto-detection

wait, are you saying that "" is different that not being specified? if so, then i've broken something. i'll take a look.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well, i can't tell whether cc -isysroot '' is going to do something meaningful, so i don't know if i broke it or not. on my system, i get this:

error: no SDK found; you can provide one explicitly with '--sysroot' flag

based on the name and lack of comments (git log didn't help either), i guess this is supposed to run on MacOS perhaps? pinging @alexrp to shed insight. is --sysroot="" supposed to be different from omitting the --sysroot argument entirely?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iiuc, passing --sysroot="" leads to auto-detection

wait, are you saying that "" is different that not being specified?

No, my bad, should have been more specific - I was just talking about the behavior of the new code.
I'm not familiar with the old behavior either, but think it's unlikely that "" had any special meaning/behavior previously.

pub const sysroot_help = "Path to macOS SDK";
},
positional: struct {
cc_args: []const [:0]const u8 = &.{},
},
};

pub fn main() anyerror!void {
var arena = std.heap.ArenaAllocator.init(gpa);
defer arena.deinit();
const allocator = arena.allocator();

const args = try std.process.argsAlloc(allocator);

var argv = std.array_list.Managed([]const u8).init(allocator);
var sysroot: ?[]const u8 = null;

var args_iter = ArgsIterator{ .args = args[1..] };
while (args_iter.next()) |arg| {
if (mem.eql(u8, arg, "--help") or mem.eql(u8, arg, "-h")) {
return info(usage, .{});
} else if (mem.eql(u8, arg, "--sysroot")) {
sysroot = args_iter.nextOrFatal();
} else try argv.append(arg);
}
const args = try std.cli.parse(Args, allocator, .{});

const sysroot_path = sysroot orelse blk: {
const sysroot_path = if (args.named.sysroot.len > 0) args.named.sysroot else blk: {
const target = try std.zig.system.resolveTargetQuery(.{});
break :blk std.zig.system.darwin.getSdk(allocator, &target) orelse
fatal("no SDK found; you can provide one explicitly with '--sysroot' flag", .{});
Expand Down Expand Up @@ -121,13 +109,13 @@ pub fn main() anyerror!void {
.arch = arch,
.os_ver = os_ver,
};
try fetchTarget(allocator, argv.items, sysroot_path, target, version, tmp);
try fetchTarget(allocator, args.positional.cc_args, sysroot_path, target, version, tmp);
}
}

fn fetchTarget(
arena: Allocator,
args: []const []const u8,
cc_args: []const []const u8,
sysroot: []const u8,
target: Target,
ver: Version,
Expand Down Expand Up @@ -165,7 +153,7 @@ fn fetchTarget(
"-MF",
headers_list_path,
});
try cc_argv.appendSlice(args);
try cc_argv.appendSlice(cc_args);

const res = try std.process.Child.run(.{
.allocator = arena,
Expand Down Expand Up @@ -229,24 +217,6 @@ fn fetchTarget(
}
}

const ArgsIterator = struct {
args: []const []const u8,
i: usize = 0,

fn next(it: *@This()) ?[]const u8 {
if (it.i >= it.args.len) {
return null;
}
defer it.i += 1;
return it.args[it.i];
}

fn nextOrFatal(it: *@This()) []const u8 {
const arg = it.next() orelse fatal("expected parameter after '{s}'", .{it.args[it.i - 1]});
return arg;
}
};

const Version = struct {
major: u16,
minor: u8,
Expand Down
26 changes: 6 additions & 20 deletions tools/gen_macos_headers_c.zig
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,18 @@ const Allocator = std.mem.Allocator;
var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){};
const gpa = general_purpose_allocator.allocator();

const usage =
\\gen_macos_headers_c [dir]
\\
\\General Options:
\\-h, --help Print this help and exit
;

pub fn main() anyerror!void {
var arena_allocator = std.heap.ArenaAllocator.init(gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();

const args = try std.process.argsAlloc(arena);
if (args.len == 1) fatal("no command or option specified", .{});

var positionals = std.array_list.Managed([]const u8).init(arena);

for (args[1..]) |arg| {
if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) {
return info(usage, .{});
} else try positionals.append(arg);
}

if (positionals.items.len != 1) fatal("expected one positional argument: [dir]", .{});
const args = try std.cli.parse(struct {
positional: struct {
dir: []const u8,
},
}, arena, .{});

var dir = try std.fs.cwd().openDir(positionals.items[0], .{ .no_follow = true });
var dir = try std.fs.cwd().openDir(args.positional.dir, .{ .no_follow = true });
defer dir.close();
var paths = std.array_list.Managed([]const u8).init(arena);
try findHeaders(arena, dir, "", &paths);
Expand Down
2 changes: 1 addition & 1 deletion tools/gen_outline_atomics.zig
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub fn main() !void {
defer arena_instance.deinit();
const arena = arena_instance.allocator();

//const args = try std.process.argsAlloc(arena);
_ = try std.cli.parse(struct {}, arena, .{});

var stdout_buffer: [2000]u8 = undefined;
var stdout_writer = std.fs.File.stdout().writerStreaming(&stdout_buffer);
Expand Down
38 changes: 15 additions & 23 deletions tools/gen_spirv_spec.zig
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,20 @@ const allocator = arena.allocator();
pub fn main() !void {
defer arena.deinit();

const args = try std.process.argsAlloc(allocator);
if (args.len != 3) {
usageAndExit(args[0], 1);
}

const json_path = try std.fs.path.join(allocator, &.{ args[1], "include/spirv/unified1/" });
const args = try std.cli.parse(struct {
pub const description =
\\Generates Zig bindings for SPIR-V specifications found in the SPIRV-Headers
\\repository. The result, printed to stdout, should be used to update
\\files in src/codegen/spirv. Don't forget to format the output.
;
positional: struct {
pub const @"path/to/SPIRV-Headers_help" = "should point to a clone of https://github.com/KhronosGroup/SPIRV-Headers/";
@"path/to/SPIRV-Headers": [:0]const u8,
@"path/to/zig/src/codegen/spirv/extinst.zig.grammar.json": [:0]const u8,
},
}, allocator, .{});

const json_path = try std.fs.path.join(allocator, &.{ args.positional.@"path/to/SPIRV-Headers", "include/spirv/unified1/" });
const dir = try std.fs.cwd().openDir(json_path, .{ .iterate = true });

const core_spec = try readRegistry(CoreRegistry, dir, "spirv.core.grammar.json");
Expand All @@ -80,7 +88,7 @@ pub fn main() !void {
try readExtRegistry(&exts, dir, entry.name);
}

try readExtRegistry(&exts, std.fs.cwd(), args[2]);
try readExtRegistry(&exts, std.fs.cwd(), args.positional.@"path/to/zig/src/codegen/spirv/extinst.zig.grammar.json");

var allocating: std.Io.Writer.Allocating = .init(allocator);
defer allocating.deinit();
Expand Down Expand Up @@ -929,19 +937,3 @@ fn parseHexInt(text: []const u8) !u31 {
return error.InvalidHexInt;
return try std.fmt.parseInt(u31, text[prefix.len..], 16);
}

fn usageAndExit(arg0: []const u8, code: u8) noreturn {
const stderr = std.debug.lockStderrWriter(&.{});
stderr.print(
\\Usage: {s} <SPIRV-Headers repository path> <path/to/zig/src/codegen/spirv/extinst.zig.grammar.json>
\\
\\Generates Zig bindings for SPIR-V specifications found in the SPIRV-Headers
\\repository. The result, printed to stdout, should be used to update
\\files in src/codegen/spirv. Don't forget to format the output.
\\
\\<SPIRV-Headers repository path> should point to a clone of
\\https://github.com/KhronosGroup/SPIRV-Headers/
\\
, .{arg0}) catch std.process.exit(1);
std.process.exit(code);
}
8 changes: 6 additions & 2 deletions tools/gen_stubs.zig
Original file line number Diff line number Diff line change
Expand Up @@ -284,8 +284,12 @@ pub fn main() !void {
defer arena_instance.deinit();
const arena = arena_instance.allocator();

const args = try std.process.argsAlloc(arena);
const build_all_path = args[1];
const args = try std.cli.parse(struct {
positional: struct {
build_all_path: [:0]const u8,
},
}, arena, .{});
const build_all_path = args.positional.build_all_path;

var build_all_dir = try std.fs.cwd().openDir(build_all_path, .{});

Expand Down
6 changes: 5 additions & 1 deletion tools/generate_JSONTestSuite.zig
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
// zig run this file inside the test_parsing/ directory of this repo: https://github.com/nst/JSONTestSuite
const Args = struct {
pub const description = "zig run this file inside the test_parsing/ directory of this repo: https://github.com/nst/JSONTestSuite";
};

const std = @import("std");

pub fn main() !void {
var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
var allocator = gpa.allocator();

_ = try std.cli.parse(Args, allocator, .{});

var stdout_buffer: [2000]u8 = undefined;
var stdout_writer = std.fs.File.stdout().writerStreaming(&stdout_buffer);
const output = &stdout_writer.interface;
Expand Down
21 changes: 11 additions & 10 deletions tools/generate_c_size_and_align_checks.zig
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,22 @@ fn cName(ty: std.Target.CType) []const u8 {
};
}

var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init;

pub fn main() !void {
const gpa = general_purpose_allocator.allocator();
var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init;
defer std.debug.assert(general_purpose_allocator.deinit() == .ok);
const gpa = general_purpose_allocator.allocator();

const args = try std.process.argsAlloc(gpa);
defer std.process.argsFree(gpa, args);
var arena_instance = std.heap.ArenaAllocator.init(gpa);
defer arena_instance.deinit();
const arena = arena_instance.allocator();

if (args.len != 2) {
std.debug.print("Usage: {s} [target_triple]\n", .{args[0]});
std.process.exit(1);
}
const args = try std.cli.parse(struct {
positional: struct {
target_triple: [:0]const u8,
},
}, arena, .{});

const query = try std.Target.Query.parse(.{ .arch_os_abi = args[1] });
const query = try std.Target.Query.parse(.{ .arch_os_abi = args.positional.target_triple });
const target = try std.zig.system.resolveTargetQuery(query);

var buffer: [2000]u8 = undefined;
Expand Down
Loading
Loading