diff options
Diffstat (limited to 'src/main.zig')
| -rw-r--r-- | src/main.zig | 256 |
1 files changed, 147 insertions, 109 deletions
diff --git a/src/main.zig b/src/main.zig index bafcbd8..55e7604 100644 --- a/src/main.zig +++ b/src/main.zig @@ -14,14 +14,12 @@ const Time = @import("time.zig").Time; const default_tree_name = "default"; -pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - const allocator = gpa.allocator(); +pub fn main(init: std.process.Init.Minimal) !void { + var arena_allocator: std.heap.ArenaAllocator = .init(std.heap.page_allocator); + const allocator = arena_allocator.allocator(); - var main_cmd: Main_Command = undefined; - var args: cli.Args = try .init(allocator, "porteur", &main_cmd); - defer args.deinit(allocator); - try main_cmd.execute(allocator, &args); + var main_cmd = cli.init(Main_Command, init.args); + main_cmd.execute(allocator, init.environ); } const Main_Command = struct { @@ -31,6 +29,7 @@ const Main_Command = struct { "porteur [--etc=⟨path⟩] tree ⟨command⟩ [options] [⟨args⟩]", "porteur [--etc=⟨path⟩] tmpl ⟨command⟩ [options] [⟨args⟩]", "porteur [--etc=⟨path⟩] port ⟨command⟩ [options] [⟨args⟩]", + "porteur version", "porteur help [⟨command⟩ [⟨subcommand⟩]]", }, .short_description = "Build your own ports.", @@ -69,11 +68,11 @@ const Main_Command = struct { \\`{{v}}`. It is also possible to use variables in filenames. For more \\information how to define variables, see the VARIABLES section. \\ - \\Normally, the processed template files are used to create the port - \\files, the distribution file is created, and a distinfo is generated. - \\To have more control over this process, a template can define a file - \\named `.config` in its root directory. For a documentation of all - \\configurable options, see the sample configuration file (see FILES). + \\Normally, the port files are generated from the template files, the + \\distribution file is created, and a distinfo is generated. To have + \\more control over this process, a template can define a `.config` + \\file in its root directory for fine-tuning. For a documentation of + \\all configurable options, see the sample configuration file (see FILES). , }, .{ @@ -107,30 +106,30 @@ const Main_Command = struct { \\ \\List of predefined variables: \\ - \\ .portname - Name of the port - \\ .portversion - Current version of the port - \\ .category - Category of the port - \\ .distdir - Directory containing all distfiles - \\ .distname - Name of the archive file containing the distribution + \\ .portname - Name of the port + \\ .portversion - Current version of the port + \\ .category - Category of the port + \\ .distdir - Directory containing all distfiles + \\ .distname - Name of the unnamed distribution file + \\ .distname.⟨name⟩ - Name of a named distribution file , }, .{ .heading = "DISTFILES", .body = - \\To compile a port, a distribution is necessary that contains the source - \\code. Porteur creates a gzipped archive of the Git repository and puts - \\it in the configured `distfiles-dir`. The filename of this archive is - \\provided in the `.dist.name` variable and the directory where its put in - \\is provided in the `.dist.dir` variable (see VARIABLES). So the template - \\can reference the archive's location and tell FreeBSD where to find the - \\source code. + \\To compile a port, a distribution is necessary that contains everything + \\the port needs to be built. By default porteur creates a gzipped archive + \\of the whole Git repository and puts it in the configured `distfiles-dir` + \\(see `porteur.conf.sample`). The filename of this archive is provided + \\in the `.distname` variable and its directory in `.distdir` (see + \\VARIABLES). \\ - \\Sometimes it is necessary to do extra steps before the distribution is - \\created (e.g. vendoring). These steps can be defined as a target in the - \\port Makefile of the template. When porteur executes this target it will - \\set ${WRKSRC} to the port's source tree. The target name can be defined - \\with `dist-prepare-target` in the template's configuration file (see - \\TEMPLATES). + \\This behaviour, however, can be configured. With the `dist.prepare-target` + \\and the `dist.source-path` options in the template config it is possible + \\to prepare a source tree executing a Makefile target and defining the + \\contents of the archive respectively. It is furthermore possible to + \\define multiple archive files for the distribution. For more information + \\see the `template.conf.sample` file. , }, .{ @@ -188,24 +187,26 @@ const Main_Command = struct { pub const Subcommands = union(enum) { help: Help_Command, + version: Version_Command, tmpl: Tmpl_Command, tree: Tree_Command, port: Port_Command, }; - pub fn execute(self: *Main_Command, allocator: std.mem.Allocator, args: *cli.Args) !void { - const subcmd = args.next_subcommand(Main_Command) orelse { + pub fn execute(self: *Main_Command, allocator: std.mem.Allocator, penv: std.process.Environ) void { + const subcmd = cli.next_subcommand(Main_Command) orelse { cli.print_usage(cli.stdout, Main_Command); return; }; const etc_path = self.options.etc.value_or_default(); - const env: Env = .init(allocator, etc_path, args); + const env: Env = .init(allocator, penv, etc_path); switch (subcmd) { - .help => |cmd| cmd.execute(env), - .tmpl => |cmd| cmd.execute(env), - .tree => |cmd| cmd.execute(env), - .port => |cmd| cmd.execute(env), + .help => |cmd| cmd.execute(), + .version => |cmd| cmd.execute(), + .tmpl => |cmd| cmd.execute(&env), + .tree => |cmd| cmd.execute(&env), + .port => |cmd| cmd.execute(&env), } } }; @@ -231,26 +232,26 @@ const Help_Command = struct { , }; - pub const Subcommands = Help_Subcommands(Main_Command.Subcommands); + pub const Subcommands = Main_Command.Subcommands; - fn execute(self: Help_Command, env: Env) void { + fn execute(self: Help_Command) void { _ = self; - const subcmd = env.args.next_subcommand(Help_Command) orelse { + const subcmd = cli.next_subcommand(Help_Command) orelse { cli.print_usage(cli.stdout, Main_Command); return; }; switch (subcmd) { - inline else => |cmd| execute_subcmd(env.args, @TypeOf(cmd)), + inline else => |cmd| execute_subcmd(@TypeOf(cmd)), } } - fn execute_subcmd(args: *cli.Args, comptime Command: type) void { + fn execute_subcmd(comptime Command: type) void { if (@hasDecl(Command, "Subcommands")) { - if (args.next_subcommand(Command)) |subcmd| { + if (cli.next_subcommand(Command)) |subcmd| { switch (subcmd) { inline else => |cmd| { - execute_subcmd(args, @TypeOf(cmd)); + execute_subcmd(@TypeOf(cmd)); return; }, } @@ -258,27 +259,24 @@ const Help_Command = struct { } cli.print_help(Command); } +}; - fn Help_Subcommands(comptime Subcmds: type) type { - assert(@typeInfo(Subcmds) == .@"union"); - const union_info = @typeInfo(Subcmds).@"union"; +const Version_Command = struct { + pub const help = cli.Command_Help{ + .name = "porteur version", + .synopsis = &.{ + "porteur version", + }, + .short_description = "Display the version of porteur.", + .long_description = + \\Print the current version of porteur to stdout. + , + }; - var fields: [union_info.fields.len]std.builtin.Type.UnionField = undefined; - for (union_info.fields, 0..) |field, i| { - fields[i] = .{ - .name = field.name, - .alignment = field.alignment, - .type = struct { - pub const help = field.type.help; - }, - }; - } - return @Type(.{ .@"union" = .{ - .layout = union_info.layout, - .tag_type = union_info.tag_type, - .fields = &fields, - .decls = &.{}, - } }); + fn execute(self: Version_Command) void { + _ = self; + cli.stdout.write_line("{s}", .{options.version}); + cli.stdout.flush(); } }; @@ -310,15 +308,15 @@ const Tmpl_Command = struct { conf: Tmpl_Conf, }; - pub fn execute(self: Tmpl_Command, env: Env) void { + pub fn execute(self: Tmpl_Command, env: *const Env) void { _ = self; - const subcmd = env.args.next_subcommand(Tmpl_Command) orelse { + const subcmd = cli.next_subcommand(Tmpl_Command) orelse { cli.fatal_with_usage(Tmpl_Command, "missing subcommand", .{}); }; switch (subcmd) { - .list => |cmd| cmd.execute(env) catch |err| fatal_err(err, "failed to list templates"), - .conf => |cmd| cmd.execute(env) catch |err| fatal_err(err, "failed to update template configuration"), + .list => |cmd| cmd.execute(env) catch |e| fatal_err(e, "failed to list templates"), + .conf => |cmd| cmd.execute(env) catch |e| fatal_err(e, "failed to update template configuration"), } } @@ -348,7 +346,7 @@ const Tmpl_Command = struct { }), }, - fn execute(self: Tmpl_Ls, env: Env) !void { + fn execute(self: Tmpl_Ls, env: *const Env) !void { const long = self.options.long.value_or_default(); const print_opts: cli.Print_Options = .{ .hanging_indent = 4 }; var iter = try tmpl.iterate(env); @@ -411,8 +409,8 @@ const Tmpl_Command = struct { }), }, - fn execute(self: Tmpl_Conf, env: Env) !void { - const template_name = env.args.next_positional() orelse { + fn execute(self: Tmpl_Conf, env: *const Env) !void { + const template_name = cli.next_positional() orelse { cli.fatal_with_usage(@This(), "missing template name", .{}); }; @@ -425,7 +423,8 @@ const Tmpl_Command = struct { if (edit) |_| { try tmpl.edit_config(env, template_name); } else if (import) |filename| { - try tmpl.import_config(env, template_name, .init(filename)); + const path: fs.Path = .init(filename); + try tmpl.import_config(env, template_name, &path); } else { const template = try tmpl.must_get(env, template_name); const config = try tmpl.read_config(env, template); @@ -459,15 +458,15 @@ const Tree_Command = struct { rm: Tree_Rm, }; - pub fn execute(self: Tree_Command, env: Env) void { + pub fn execute(self: Tree_Command, env: *const Env) void { _ = self; - const subcmd = env.args.next_subcommand(Tree_Command) orelse { + const subcmd = cli.next_subcommand(Tree_Command) orelse { cli.fatal_with_usage(Tree_Command, "missing subcommand", .{}); }; switch (subcmd) { - .list => |cmd| cmd.execute(env) catch |err| fatal_err(err, "failed to iterate ports trees"), - .rm => |cmd| cmd.execute(env) catch |err| fatal_err(err, "failed to remove ports trees"), + .list => |cmd| cmd.execute(env) catch |e| fatal_err(e, "failed to iterate ports trees"), + .rm => |cmd| cmd.execute(env) catch |e| fatal_err(e, "failed to remove ports trees"), } } @@ -488,7 +487,7 @@ const Tree_Command = struct { , }; - fn execute(self: Tree_Ls, env: Env) !void { + fn execute(self: Tree_Ls, env: *const Env) !void { _ = self; var iter = try tree.iterate(env); defer iter.deinit(); @@ -524,9 +523,9 @@ const Tree_Command = struct { }), }, - fn execute(self: Tree_Rm, env: Env) !void { + fn execute(self: Tree_Rm, env: *const Env) !void { const force = self.options.force.value_or_default(); - const name = env.args.next_positional() orelse { + const name = cli.next_positional() orelse { if (force) std.process.exit(0); cli.fatal_with_usage(@This(), "missing tree name", .{}); }; @@ -582,6 +581,7 @@ const Port_Command = struct { pub const Subcommands = union(enum) { list: Port_Ls, info: Port_Info, + version: Port_Version, add: Port_Add, vars: Port_Vars, update: Port_Update, @@ -591,13 +591,13 @@ const Port_Command = struct { rm: Port_Rm, }; - pub fn execute(self: Port_Command, env: Env) void { - const subcmd = env.args.next_subcommand(Port_Command) orelse { + pub fn execute(self: Port_Command, env: *const Env) void { + const subcmd = cli.next_subcommand(Port_Command) orelse { cli.fatal_with_usage(Port_Command, "missing subcommand", .{}); }; const tree_name = self.options.tree.value_or_default(); - const ports_tree = tree.get(env, tree_name) catch |err| fatal_err(err, "failed to get tree"); + const ports_tree = tree.get(env, tree_name) catch |e| fatal_err(e, "failed to get tree"); if (ports_tree == null) cli.fatal("ports tree not found: {s}", .{tree_name}); const ctx = port.Ctx{ @@ -606,15 +606,16 @@ const Port_Command = struct { }; switch (subcmd) { - .list => |cmd| cmd.execute(ctx) catch |err| fatal_err(err, "failed to list ports"), - .info => |cmd| cmd.execute(ctx) catch |err| fatal_err(err, "failed to fetch port"), - .add => |cmd| cmd.execute(ctx) catch |err| fatal_err(err, "failed to write port"), - .vars => |cmd| cmd.execute(ctx) catch |err| fatal_err(err, "failed to update port variables"), - .update => |cmd| cmd.execute(ctx) catch |err| fatal_err(err, "failed to update port"), - .bump_version => |cmd| cmd.execute(ctx) catch |err| fatal_err(err, "failed to bump the port version"), - .history => |cmd| cmd.execute(ctx) catch |err| fatal_err(err, "failed to list distfile history"), - .rollback => |cmd| cmd.execute(ctx) catch |err| fatal_err(err, "failed to roll back the port version"), - .rm => |cmd| cmd.execute(ctx) catch |err| fatal_err(err, "failed to delete port"), + .list => |cmd| cmd.execute(ctx) catch |e| fatal_err(e, "failed to list ports"), + .info => |cmd| cmd.execute(ctx) catch |e| fatal_err(e, "failed to fetch port"), + .version => |cmd| cmd.execute(ctx) catch |e| fatal_err(e, "failed to fetch port version"), + .add => |cmd| cmd.execute(ctx) catch |e| fatal_err(e, "failed to write port"), + .vars => |cmd| cmd.execute(ctx) catch |e| fatal_err(e, "failed to update port variables"), + .update => |cmd| cmd.execute(ctx) catch |e| fatal_err(e, "failed to update port"), + .bump_version => |cmd| cmd.execute(ctx) catch |e| fatal_err(e, "failed to bump the port version"), + .history => |cmd| cmd.execute(ctx) catch |e| fatal_err(e, "failed to list distfile history"), + .rollback => |cmd| cmd.execute(ctx) catch |e| fatal_err(e, "failed to roll back the port version"), + .rm => |cmd| cmd.execute(ctx) catch |e| fatal_err(e, "failed to delete port"), } } @@ -680,7 +681,7 @@ const Port_Command = struct { }; fn execute(self: Port_Info, ctx: port.Ctx) !void { _ = self; - const port_name = ctx.env.args.next_positional() orelse { + const port_name = cli.next_positional() orelse { cli.fatal_with_usage(@This(), "missing port name", .{}); }; const p = try port.must_get(ctx, port_name); @@ -707,6 +708,32 @@ const Port_Command = struct { } }; + const Port_Version = struct { + pub const help = cli.Command_Help{ + .name = "porteur port version", + .synopsis = &.{ + "porteur port [options] version ⟨port⟩", + }, + .short_description = "Show the current version of a port.", + .long_description = + \\This command prints the current version of the specified port of a + \\ports tree. If the port cannot be found in the ports tree an error + \\will be reported. + , + }; + fn execute(self: Port_Version, ctx: port.Ctx) !void { + _ = self; + const port_name = cli.next_positional() orelse { + cli.fatal_with_usage(@This(), "missing port name", .{}); + }; + const p = try port.must_get(ctx, port_name); + defer p.deinit(); + const version = try port.read_version(ctx, p); + cli.stdout.printf_line("{f}", .{version}, .{}); + cli.stdout.flush(); + } + }; + const Port_Add = struct { pub const help = cli.Command_Help{ .name = "porteur port add", @@ -730,11 +757,22 @@ const Port_Command = struct { fn execute(self: Port_Add, ctx: port.Ctx) !void { _ = self; - if (!try tmpl.exists_any(ctx.env)) { - const tmpl_dir = tmpl.etc_dir(ctx.env); - cli.stderr.print_line("No templates found.", .{}); - cli.fatal("Please define a template in `{s}`.", .{tmpl_dir.name()}); - } + + var templatebuf: [std.posix.NAME_MAX]u8 = undefined; + const the_only_template = blk: { + var iter = try tmpl.iterate(ctx.env); + defer iter.deinit(); + const first_tmpl = try iter.next() orelse { + const tmpl_dir = tmpl.etc_dir(ctx.env); + cli.stderr.print_line("No templates found.", .{}); + cli.fatal("Please define a template in `{s}`.", .{tmpl_dir.name()}); + }; + const first_tmpl_name = templatebuf[0..first_tmpl.name.len]; + @memcpy(first_tmpl_name, first_tmpl.name); + + _ = try iter.next() orelse break :blk first_tmpl_name; + break :blk null; + }; const namebuf = try ctx.env.allocator.alloc(u8, 64); defer ctx.env.allocator.free(namebuf); @@ -744,7 +782,7 @@ const Port_Command = struct { cli.stderr.flush(); continue; }; - if (std.mem.indexOfAny(u8, input, "/\\\t ")) |_| { + if (std.mem.findAny(u8, input, "/\\\t ")) |_| { cli.stderr.print_line("ERROR: The port name must not contain slash, backslash, or space.", .{}); cli.stderr.flush(); continue; @@ -765,7 +803,7 @@ const Port_Command = struct { cli.stderr.flush(); continue; }; - if (std.mem.indexOfAny(u8, input, "/\\\t ")) |_| { + if (std.mem.findAny(u8, input, "/\\\t ")) |_| { cli.stderr.print_line("ERROR: The port category must not contain slash, backslash, or space.", .{}); cli.stderr.flush(); continue; @@ -784,7 +822,7 @@ const Port_Command = struct { break ver; }; - var repourlbuf: [std.fs.max_path_bytes]u8 = undefined; + var repourlbuf: [fs.max_path_bytes]u8 = undefined; const repo_url = while (true) { const input = cli.ask("Git Repository: ", .{}, &repourlbuf) orelse { cli.stderr.print_line("ERROR: The Git repository is required.", .{}); @@ -806,8 +844,7 @@ const Port_Command = struct { break input; }; - var templatebuf: [64]u8 = undefined; - const template = while (true) { + const template = the_only_template orelse while (true) { const input = cli.ask("Template: ", .{}, &templatebuf) orelse { cli.stderr.print_line("ERROR: The template is required.", .{}); cli.stderr.flush(); @@ -894,7 +931,7 @@ const Port_Command = struct { }, fn execute(self: Port_Vars, ctx: port.Ctx) !void { - const port_name = ctx.env.args.next_positional() orelse { + const port_name = cli.next_positional() orelse { cli.fatal_with_usage(@This(), "missing port name", .{}); }; @@ -907,7 +944,8 @@ const Port_Command = struct { if (edit) |_| { try port.edit_vars(ctx, port_name); } else if (import) |filename| { - try port.import_vars(ctx, port_name, .init(filename)); + const path: fs.Path = .init(filename); + try port.import_vars(ctx, port_name, &path); } else { const p = try port.must_get(ctx, port_name); defer p.deinit(); @@ -959,7 +997,7 @@ const Port_Command = struct { .force = self.options.force.value_or_default(), }; var count: usize = 0; - while (ctx.env.args.next_positional()) |port_name| { + while (cli.next_positional()) |port_name| { count += 1; try update_single_port(ctx, port_name, opts); } @@ -1035,7 +1073,7 @@ const Port_Command = struct { else => cli.fatal("can either bump major, minor, or patch version", .{}), } - const port_name = ctx.env.args.next_positional() orelse { + const port_name = cli.next_positional() orelse { cli.fatal_with_usage(@This(), "missing port name", .{}); }; @@ -1075,7 +1113,7 @@ const Port_Command = struct { fn execute(self: Port_History, ctx: port.Ctx) !void { const long = self.options.long.value_or_default(); - const port_name = ctx.env.args.next_positional() orelse { + const port_name = cli.next_positional() orelse { cli.fatal_with_usage(@This(), "missing port name", .{}); }; var distfiles = try port.list_distfiles(ctx, port_name); @@ -1120,10 +1158,10 @@ const Port_Command = struct { fn execute(self: Port_Rollback, ctx: port.Ctx) !void { _ = self; - const port_name = ctx.env.args.next_positional() orelse { + const port_name = cli.next_positional() orelse { cli.fatal_with_usage(@This(), "missing port name", .{}); }; - const version_str = ctx.env.args.next_positional() orelse { + const version_str = cli.next_positional() orelse { cli.fatal_with_usage(@This(), "missing version", .{}); }; const version = port.Version.parse(version_str) catch { @@ -1164,7 +1202,7 @@ const Port_Command = struct { var count: usize = 0; var exit_code: u8 = 0; - while (ctx.env.args.next_positional()) |port_name| { + while (cli.next_positional()) |port_name| { count += 1; if (!force) { const confirmed = cli.ask_confirmation("Remove port {s}?", .{port_name}, .no); |