aboutsummaryrefslogtreecommitdiff
path: root/src/main.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.zig')
-rw-r--r--src/main.zig256
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);