From ad7ee00d82035a4ac755b57a386782e2bcf9cfdb Mon Sep 17 00:00:00 2001 From: Yutao Fang Date: Sat, 25 Apr 2026 22:06:09 +0800 Subject: [PATCH 1/2] fix(cli): print subcommand help on --help/-h Subcommand parsers silently ignored --help/-h flags, causing 'zvm list --help' to execute the command instead of showing help. Added checkHelp() helper with comptime cmd parameter to detect --help/-h in all subcommand parsers, wired printCommandHelp() into the main dispatch, and added missing .completion help case. --- src/cli.zig | 49 +++++++++++++++++++++++++++++++++++++++---------- src/main.zig | 8 ++++++-- 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/src/cli.zig b/src/cli.zig index 62718b9..d10c241 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -87,7 +87,7 @@ pub const ParsedCommand = union(Command) { shell: ShellType, }, version, - help, + help: ?Command, }; /// Global flags that apply before the command (e.g., --color). @@ -147,7 +147,7 @@ pub fn parse(allocator: std.mem.Allocator, init: std.process.Init.Minimal) !stru } else if (std.mem.cutPrefix(u8, arg, "--color=")) |val| { if (parseColorValue(val)) |c| global_flags.color = c; } else if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) { - return .{ .global = global_flags, .cmd = .help }; + return .{ .global = global_flags, .cmd = .{ .help = null } }; } else if (std.mem.eql(u8, arg, "--version") or std.mem.eql(u8, arg, "-v")) { return .{ .global = global_flags, .cmd = .version }; } else { @@ -158,7 +158,7 @@ pub fn parse(allocator: std.mem.Allocator, init: std.process.Init.Minimal) !stru } } - const cmd = maybe_cmd orelse return .{ .global = global_flags, .cmd = .help }; + const cmd = maybe_cmd orelse return .{ .global = global_flags, .cmd = .{ .help = null } }; // Check if the raw command name implies --all const auto_all = if (cmd_raw) |raw| @@ -179,7 +179,7 @@ pub fn parse(allocator: std.mem.Allocator, init: std.process.Init.Minimal) !stru .proxy => try parseProxy(allocator, &args), .completion => try parseCompletion(&args), .version => ParsedCommand.version, - .help => ParsedCommand.help, + .help => ParsedCommand{ .help = null }, }; return .{ .global = global_flags, .cmd = parsed }; @@ -203,11 +203,19 @@ fn parseColorValue(val: []const u8) ?bool { return null; } +fn checkHelp(comptime cmd: Command, arg: []const u8) ?ParsedCommand { + if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) { + return .{ .help = cmd }; + } + return null; +} + fn parseInstall(allocator: std.mem.Allocator, args: anytype) !ParsedCommand { var flags: InstallFlags = .{}; var version: ?[]const u8 = null; while (args.next()) |arg| { + if (checkHelp(.install, arg)) |h| return h; if (std.mem.eql(u8, arg, "--force") or std.mem.eql(u8, arg, "-f")) { flags.force = true; } else if (std.mem.eql(u8, arg, "--zls")) { @@ -232,6 +240,7 @@ fn parseUse(allocator: std.mem.Allocator, args: anytype) !ParsedCommand { var version: ?[]const u8 = null; while (args.next()) |arg| { + if (checkHelp(.use, arg)) |h| return h; if (std.mem.eql(u8, arg, "--sync")) { flags.sync = true; } else { @@ -249,6 +258,7 @@ fn parseList(args: anytype, auto_all: bool) ParsedCommand { var flags: ListFlags = .{ .all = auto_all }; while (args.next()) |arg| { + if (checkHelp(.list, arg)) |h| return h; if (std.mem.eql(u8, arg, "--all") or std.mem.eql(u8, arg, "-a")) { flags.all = true; } else if (std.mem.eql(u8, arg, "--vmu")) { @@ -261,6 +271,7 @@ fn parseList(args: anytype, auto_all: bool) ParsedCommand { fn parseUninstall(allocator: std.mem.Allocator, args: anytype) !ParsedCommand { const version = args.next() orelse return error.MissingArgument; + if (checkHelp(.uninstall, version)) |h| return h; return .{ .uninstall = .{ .version = try allocator.dupe(u8, version), } }; @@ -268,6 +279,7 @@ fn parseUninstall(allocator: std.mem.Allocator, args: anytype) !ParsedCommand { fn parseRun(allocator: std.mem.Allocator, args: anytype) !ParsedCommand { const version = args.next() orelse return error.MissingArgument; + if (checkHelp(.run, version)) |h| return h; var run_args: std.ArrayList([]const u8) = .empty; errdefer run_args.deinit(allocator); @@ -284,6 +296,7 @@ fn parseRun(allocator: std.mem.Allocator, args: anytype) !ParsedCommand { fn parseVmu(allocator: std.mem.Allocator, args: anytype) !ParsedCommand { const subcmd = args.next() orelse return error.MissingArgument; + if (checkHelp(.vmu, subcmd)) |h| return h; const value = args.next() orelse return error.MissingArgument; const target: VmuTarget = if (std.mem.eql(u8, subcmd, "zig")) @@ -301,20 +314,25 @@ fn parseVmu(allocator: std.mem.Allocator, args: anytype) !ParsedCommand { fn parseMirrorlist(allocator: std.mem.Allocator, args: anytype) !ParsedCommand { const url = args.next(); - return .{ .mirrorlist = .{ - .url = if (url) |u| try allocator.dupe(u8, u) else null, - } }; + if (url) |u| { + if (checkHelp(.mirrorlist, u)) |h| return h; + return .{ .mirrorlist = .{ .url = try allocator.dupe(u8, u) } }; + } + return .{ .mirrorlist = .{ .url = null } }; } fn parseProxy(allocator: std.mem.Allocator, args: anytype) !ParsedCommand { const url = args.next(); - return .{ .proxy = .{ - .url = if (url) |u| try allocator.dupe(u8, u) else null, - } }; + if (url) |u| { + if (checkHelp(.proxy, u)) |h| return h; + return .{ .proxy = .{ .url = try allocator.dupe(u8, u) } }; + } + return .{ .proxy = .{ .url = null } }; } fn parseCompletion(args: anytype) !ParsedCommand { const shell_str = args.next() orelse return error.MissingArgument; + if (checkHelp(.completion, shell_str)) |h| return h; const shell: ShellType = if (std.mem.eql(u8, shell_str, "zsh")) .zsh else if (std.mem.eql(u8, shell_str, "bash")) @@ -470,6 +488,17 @@ pub fn printCommandHelp(writer: *std.Io.Writer, cmd: Command) !void { \\ zvm proxy Show current proxy setting \\ ), + .completion => try writer.writeAll( + \\Generate shell completion script. + \\ + \\Usage: + \\ zvm completion + \\ + \\Supported shells: + \\ zsh + \\ bash + \\ + ), .version, .help => printHelp(writer) catch {}, } try writer.flush(); diff --git a/src/main.zig b/src/main.zig index 18e888f..8b8d00b 100644 --- a/src/main.zig +++ b/src/main.zig @@ -72,8 +72,12 @@ pub fn main(init: std.process.Init) !void { // Dispatch to the appropriate command handler switch (parsed.cmd) { - .help => { - try cli.printHelp(console.stdout.writer); + .help => |maybe_cmd| { + if (maybe_cmd) |cmd| { + try cli.printCommandHelp(console.stdout.writer, cmd); + } else { + try cli.printHelp(console.stdout.writer); + } }, .version => { console.plain("zvm {s}", .{fullVersion()}); From 1e06de79c030b055ab519f5d8bebe215110115ae Mon Sep 17 00:00:00 2001 From: Yutao Fang Date: Sat, 25 Apr 2026 22:07:22 +0800 Subject: [PATCH 2/2] fix(build): enable LLVM backend for glibc compatibility --- build.zig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build.zig b/build.zig index 3118088..21439c1 100644 --- a/build.zig +++ b/build.zig @@ -39,6 +39,9 @@ pub fn build(b: *std.Build) void { .optimize = optimize, .link_libc = true, }), + // NOTE: Only for 0.16.0, can be removed in 0.17.0 + // See: https://codeberg.org/ziglang/zig/issues/31272#issuecomment-13790015 + .use_llvm = true, }); const options = b.addOptions();