diff options
Diffstat (limited to 'src/port.zig')
| -rw-r--r-- | src/port.zig | 472 |
1 files changed, 283 insertions, 189 deletions
diff --git a/src/port.zig b/src/port.zig index 81aa034..a7d53e4 100644 --- a/src/port.zig +++ b/src/port.zig @@ -1,5 +1,5 @@ //! A port's configuration is stored file-based with the following -//! directory structure: +//! files: //! //! * info - category, template //! * src - repo, branch, commit hash, commit timestamp @@ -24,7 +24,7 @@ const fatal = cli.fatal; const distname_ext = ".tar.gz"; pub const Ctx = struct { - env: Env, + env: *const Env, tree: tree.Tree, }; @@ -57,9 +57,9 @@ pub const Info = struct { fn from_file(ctx: Ctx, port_name: []const u8) !?Info { const port_root = ctx.tree.port_path(port_name); - const info_file: fs.Path = .join(.{ port_root.name(), "info" }); + const info_path: fs.Path = .join(.{ port_root.name(), "info" }); - const info_res = conf.Config.from_file(ctx.env.allocator, info_file.name()) catch { + const info_res = conf.Config.from_file(ctx.env.allocator, &info_path) catch { port_corrupted(port_name, "invalid info file"); }; const info_conf = switch (info_res) { @@ -88,13 +88,11 @@ pub const Info = struct { }; } - fn to_file(self: Info) !void { - const path: fs.Path = .join(.{ self.root.name(), "info" }); - const file = try std.fs.createFileAbsoluteZ(path.name(), .{ .mode = 0o644 }); - defer file.close(); - + fn to_file(self: Info, path: *const fs.Path) !void { var write_buf: [256]u8 = undefined; - var writer = file.writer(&write_buf); + var writer = try fs.file_writer(path, &write_buf, .{ .create = true }); + defer writer.deinit(); + inline for (&[_]conf.Var{ .{ .key = "category", .val = self.category }, .{ .key = "template", .val = self.template }, @@ -105,6 +103,10 @@ pub const Info = struct { try writer.interface.flush(); } + fn info_file(self: Info) fs.Path { + return .join(.{ self.root.name(), "info" }); + } + fn version_file(self: Info) fs.Path { return .join(.{ self.root.name(), "version" }); } @@ -139,10 +141,10 @@ pub const Version = packed struct { var major: []const u8 = text; var minor: []const u8 = ""; var patch: []const u8 = ""; - if (std.mem.indexOfScalar(u8, major, '.')) |dot1| { + if (std.mem.findScalar(u8, major, '.')) |dot1| { minor = major[dot1 + 1 ..]; major = major[0..dot1]; - if (std.mem.indexOfScalar(u8, minor, '.')) |dot2| { + if (std.mem.findScalar(u8, minor, '.')) |dot2| { patch = minor[dot2 + 1 ..]; minor = minor[0..dot2]; } @@ -155,63 +157,46 @@ pub const Version = packed struct { }; } - /// Parse the version as it is written to a file: 0.0.0[.d00000000[.r0]] + /// Parse the version as it is written to a file: 0.0.0[.00000000r0] pub fn parse(text: []const u8) !Version { - const dot1 = std.mem.indexOfScalar(u8, text, '.') orelse return error.invalid_version; - const dot2 = std.mem.indexOfScalarPos(u8, text, dot1 + 1, '.') orelse return error.invalid_version; - const dot3 = std.mem.indexOfScalarPos(u8, text, dot2 + 1, '.') orelse text.len; + const minor_pos = std.mem.findScalar(u8, text, '.') orelse return error.invalid_version; + const patch_pos = std.mem.findScalarPos(u8, text, minor_pos + 1, '.') orelse return error.invalid_version; + const date_pos = std.mem.findScalarPos(u8, text, patch_pos + 1, '.') orelse text.len; - const major = text[0..dot1]; - const minor = text[dot1 + 1 .. dot2]; - const patch = text[dot2 + 1 .. dot3]; - var date: ?[]const u8 = null; - var revision: ?[]const u8 = null; - if (dot3 < text.len) { - if (dot3 + 1 == text.len or text[dot3 + 1] != 'd') return error.invalid_version; - date = text[dot3 + 2 ..]; - if (std.mem.indexOfScalarPos(u8, text, dot3 + 2, '.')) |dot4| { - if (dot4 + 1 == text.len or text[dot4 + 1] != 'r') return error.invalid_version; - date = text[dot3 + 2 .. dot4]; - revision = text[dot4 + 2 ..]; - } + const major = parse_int(u16, text[0..minor_pos]) orelse return error.invalid_version; + const minor = parse_int(u16, text[minor_pos + 1 .. patch_pos]) orelse return error.invalid_version; + const patch = parse_int(u32, text[patch_pos + 1 .. date_pos]) orelse return error.invalid_version; + var date: u32 = 0; + var revision: u32 = 0; + if (date_pos < text.len) { + const rev_pos = std.mem.findScalarPos(u8, text, date_pos + 1, 'r') orelse return error.invalid_version; + date = parse_int(u32, text[date_pos + 1 .. rev_pos]) orelse return error.invalid_version; + revision = parse_int(u32, text[rev_pos + 1 ..]) orelse return error.invalid_version; } return .{ - .major = parse_int(u16, major) orelse return error.invalid_version, - .minor = parse_int(u16, minor) orelse return error.invalid_version, - .patch = parse_int(u32, patch) orelse return error.invalid_version, - .date = if (date) |d| parse_int(u32, d) orelse return error.invalid_version else 0, - .revision = if (revision) |r| parse_int(u32, r) orelse return error.invalid_version else 0, + .major = major, + .minor = minor, + .patch = patch, + .date = date, + .revision = revision, }; } fn from_file(ctx: Ctx, info: Info) !Version { - const text = try fs.read_file(ctx.env.allocator, info.version_file()) orelse return error.missing_version; + const version_file = info.version_file(); + const text = fs.read_file(ctx.env.allocator, &version_file) catch |err| { + return if (err == error.FileNotFound) error.missing_version else err; + }; defer ctx.env.dealloc(text); - - const s = std.mem.trim(u8, text, " \t\n"); - - var major: []const u8 = s; - var minor: []const u8 = ""; - var patch: []const u8 = ""; - if (std.mem.indexOfScalar(u8, major, '.')) |dot1| { - minor = major[dot1 + 1 ..]; - major = major[0..dot1]; - if (std.mem.indexOfScalar(u8, minor, '.')) |dot2| { - patch = minor[dot2 + 1 ..]; - minor = minor[0..dot2]; - } - } - return try parse(std.mem.trim(u8, text, " \t\n")); } - fn to_file(self: Version, path: fs.Path) !void { - const file = try std.fs.createFileAbsoluteZ(path.name(), .{ .mode = 0o644 }); - defer file.close(); - + fn to_file(self: Version, path: *const fs.Path) !void { var write_buf: [64]u8 = undefined; - var writer = file.writer(&write_buf); + var writer = try fs.file_writer(path, &write_buf, .{ .create = true }); + defer writer.deinit(); + try self.format(&writer.interface); try writer.interface.writeByte('\n'); try writer.interface.flush(); @@ -224,16 +209,17 @@ pub const Version = packed struct { return str; } - pub fn format(self: Version, w: *std.io.Writer) std.io.Writer.Error!void { + pub fn format(self: Version, w: *std.Io.Writer) std.Io.Writer.Error!void { try w.print("{}.{}.{}", .{ self.major, self.minor, self.patch }); if (self.date != 0) { - try w.print(".d{}", .{self.date}); - if (self.revision != 0) { - try w.print(".r{}", .{self.revision}); - } + try w.print(".{}r{}", .{ self.date, self.revision }); } } + fn eq(left: Version, right: Version) bool { + return @as(u128, @bitCast(left)) == @as(u128, @bitCast(right)); + } + fn less(left: Version, right: Version) bool { return @as(u128, @bitCast(left)) < @as(u128, @bitCast(right)); } @@ -255,7 +241,7 @@ pub const Source = struct { fn from_file(ctx: Ctx, info: Info) !Source { const src_file = info.src_file(); - const raw_res = try conf.Config.from_file(ctx.env.allocator, src_file.name()); + const raw_res = try conf.Config.from_file(ctx.env.allocator, &src_file); const raw = switch (raw_res) { .conf => |c| c, .err => port_corrupted(info.name, "invalid source file"), @@ -289,16 +275,15 @@ pub const Source = struct { }; } - fn to_file(self: Source, path: fs.Path) !void { + fn to_file(self: Source, path: *const fs.Path) !void { var commit_buf: [64]u8 = undefined; - var commit_str: std.io.Writer = .fixed(&commit_buf); + var commit_str: std.Io.Writer = .fixed(&commit_buf); try commit_str.print("{f}", .{self.commit}); - const file = try std.fs.createFileAbsoluteZ(path.name(), .{ .mode = 0o644 }); - defer file.close(); - var write_buf: [256]u8 = undefined; - var writer = file.writer(&write_buf); + var writer = try fs.file_writer(path, &write_buf, .{ .create = true }); + defer writer.deinit(); + inline for (&[_]conf.Var{ .{ .key = "repo", .val = self.repo.url }, .{ .key = "branch", .val = self.repo.branch }, @@ -317,12 +302,11 @@ pub const Source = struct { pub const Iterator = struct { ctx: Ctx, - dir: ?std.fs.Dir = null, - iter: ?std.fs.Dir.Iterator = null, + iter: ?fs.Iterator = null, current: ?Info = null, pub fn deinit(self: *Iterator) void { - if (self.dir) |*dir| dir.close(); + if (self.iter) |*it| it.deinit(); if (self.current) |p| p.deinit(); } @@ -333,7 +317,7 @@ pub const Iterator = struct { } if (self.iter) |*iter| { while (try iter.next()) |entry| { - if (entry.kind == .directory) { + if (entry.type == .dir) { self.current = try .from_file(self.ctx, entry.name); return self.current; } @@ -344,17 +328,14 @@ pub const Iterator = struct { }; pub fn iterate(ctx: Ctx) !Iterator { - const dir = std.fs.openDirAbsoluteZ(ctx.tree.root.name(), .{ .iterate = true }) catch |err| { - if (err == error.FileNotFound) { - return .{ .ctx = ctx }; - } + const iter = fs.iterate(&ctx.tree.root) catch |err| { + if (err == error.FileNotFound) return .{ .ctx = ctx }; return err; }; return .{ .ctx = ctx, - .dir = dir, - .iter = dir.iterate(), + .iter = iter, }; } @@ -384,7 +365,7 @@ pub fn read_source(ctx: Ctx, info: Info) !Source { pub fn read_vars(ctx: Ctx, info: Info) !conf.Config { const vars_file = info.vars_file(); - const conf_res = try conf.Config.from_file(ctx.env.allocator, vars_file.name()); + const conf_res = try conf.Config.from_file(ctx.env.allocator, &vars_file); return switch (conf_res) { .conf => |c| c, .err => |e| { @@ -407,8 +388,9 @@ pub fn create(ctx: Ctx, info: Info, opts: Create_Options) !void { .branch = opts.repo_branch, }; + ctx.env.info("===> cloning repository: {s}", .{repo.path.name()}); try repo.clone(ctx.env); - errdefer fs.rm(repo.path) catch {}; + errdefer fs.rm(&repo.path) catch {}; const source: Source = .{ .raw = undefined, @@ -417,23 +399,30 @@ pub fn create(ctx: Ctx, info: Info, opts: Create_Options) !void { }; const port_root = ctx.tree.port_path(info.name); - try fs.mkdir_all(port_root); - try info.to_file(); - try opts.initial_version.to_file(info.version_file()); - try source.to_file(info.src_file()); + const info_file = info.info_file(); + const version_file = info.version_file(); + const src_file = info.src_file(); + + try fs.mkdir_all(&port_root); + ctx.env.info("===> writing port data: {s}", .{info.root.name()}); + try info.to_file(&info_file); + try opts.initial_version.to_file(&version_file); + try source.to_file(&src_file); } pub fn edit_vars(ctx: Ctx, port_name: []const u8) !void { const info = try must_get(ctx, port_name); defer info.deinit(); - try shell.edit_config_file_noverify(ctx.env, info.vars_file()); + + const vars_file = info.vars_file(); + try shell.edit_config_file_noverify(ctx.env, &vars_file); } -pub fn import_vars(ctx: Ctx, port_name: []const u8, new_vars_file: fs.Path) !void { +pub fn import_vars(ctx: Ctx, port_name: []const u8, new_vars_file: *const fs.Path) !void { const info = try must_get(ctx, port_name); defer info.deinit(); - const parsed = try conf.Config.from_file(ctx.env.allocator, new_vars_file.name()); + const parsed = try conf.Config.from_file(ctx.env.allocator, new_vars_file); switch (parsed) { .conf => |c| c.deinit(), .err => |e| { @@ -441,7 +430,8 @@ pub fn import_vars(ctx: Ctx, port_name: []const u8, new_vars_file: fs.Path) !voi }, } - try fs.mv_file(new_vars_file, info.vars_file()); + const vars_file = info.vars_file(); + try fs.mv_file(new_vars_file, &vars_file); } pub const Update_Options = struct { @@ -456,6 +446,7 @@ pub fn update_version(ctx: Ctx, port_name: []const u8, options: Update_Options) var source: Source = try .from_file(ctx, info); defer source.deinit(); + ctx.env.info("===> {s}: updating repository", .{info.name}); const old_version: Version = try .from_file(ctx, info); const old_commit = source.commit; const new_commit = try source.repo.update(ctx.env); @@ -475,13 +466,17 @@ pub fn update_version(ctx: Ctx, port_name: []const u8, options: Update_Options) } source.commit = new_commit; - try source.to_file(info.src_file()); - try new_version.to_file(info.version_file()); + + ctx.env.info("===> {s}: updating port data", .{info.name}); + const src_file = info.src_file(); + const version_file = info.version_file(); + try source.to_file(&src_file); + try new_version.to_file(&version_file); try render_port(ctx, .{ .info = info, .version = new_version, - .source = source, + .source = &source, }); return new_version; } @@ -504,12 +499,13 @@ pub fn bump_version(ctx: Ctx, port_name: []const u8, options: Bump_Options) !Ver .minor => .{ .major = version.major, .minor = version.minor + 1, .patch = 0 }, .patch => .{ .major = version.major, .minor = version.minor, .patch = version.patch + 1 }, }; - try new_version.to_file(info.version_file()); + const version_file = info.version_file(); + try new_version.to_file(&version_file); try render_port(ctx, .{ .info = info, .version = new_version, - .source = source, + .source = &source, }); return new_version; } @@ -520,23 +516,22 @@ pub const Distfile = struct { }; pub fn list_distfiles(ctx: Ctx, port_name: []const u8) !std.ArrayList(Distfile) { - const dist_dir = ctx.env.ports_dist_dir(ctx.tree.name, port_name); - return try list_distfiles_sorted(ctx.env.allocator, port_name, dist_dir); + const distdir = ctx.env.ports_dist_dir(ctx.tree.name, port_name); + return try fetch_port_distfiles_sorted(ctx.env.allocator, port_name, &distdir); } pub fn rollback(ctx: Ctx, port_name: []const u8, old_version: Version) !void { const info = try must_get(ctx, port_name); defer info.deinit(); - var version_buf: [64]u8 = undefined; - const version_str = std.fmt.bufPrint(&version_buf, "{f}", .{old_version}) catch unreachable; - - var distname_buf: [std.fs.max_name_bytes]u8 = undefined; - const distname = assemble_distname(&distname_buf, info.name, version_str); - var dist_file = ctx.env.ports_dist_dir(ctx.tree.name, info.name); - dist_file.append(.{distname}); - if (!try fs.file_exists(dist_file)) { - fatal("distfile {s} cannot be found", .{distname}); + const distdir = ctx.env.ports_dist_dir(ctx.tree.name, port_name); + var distfiles = try fetch_distfiles(ctx.env.allocator, &distdir, .{ + .port_name = port_name, + .version = old_version, + }); + defer distfiles.deinit(ctx.env.allocator); + if (distfiles.items.len == 0) { + fatal("no distfiles found for port {s}", .{port_name}); } try render_port(ctx, .{ @@ -553,19 +548,14 @@ pub fn remove(ctx: Ctx, port_name: []const u8) !bool { } pub inline fn remove_all(ctx: Ctx) !void { - var dir = std.fs.openDirAbsoluteZ(ctx.tree.root.name(), .{ .iterate = true }) catch |err| { - return switch (err) { - error.FileNotFound => return, - error.BadPathName, error.InvalidUtf8 => error.BadPathName, - error.NameTooLong => unreachable, - else => err, - }; + var iter = fs.iterate(&ctx.tree.root) catch |err| { + if (err == error.FileNotFound) return; + return err; }; - defer dir.close(); + defer iter.deinit(); - var iter = dir.iterate(); while (try iter.next()) |entry| { - if (entry.kind == .directory) { + if (entry.type == .dir) { if (try get(ctx, entry.name)) |p| { try remove_existing_port(ctx, p); } @@ -577,16 +567,17 @@ fn remove_existing_port(ctx: Ctx, info: Info) !void { const port_dir = ctx.env.ports_tree_path(ctx.tree.name, .{ info.category, info.name }); const dist_dir = ctx.env.ports_dist_dir(ctx.tree.name, info.name); const src_dir = ctx.env.ports_src_path(ctx.tree.name, .{info.name}); - try fs.rm(port_dir); - try fs.rm(dist_dir); - try fs.rm(src_dir); - try fs.rm(ctx.tree.port_path(info.name)); + const port_path = ctx.tree.port_path(info.name); + try fs.rm(&port_dir); + try fs.rm(&dist_dir); + try fs.rm(&src_dir); + try fs.rm(&port_path); } const Render_Options = struct { info: Info, version: Version, - source: ?Source, // null if it already exists + source: ?*const Source, // null if it already exists }; fn render_port(ctx: Ctx, opts: Render_Options) !void { @@ -600,23 +591,19 @@ fn render_port(ctx: Ctx, opts: Render_Options) !void { var version_buf: [64]u8 = undefined; const version_str = std.fmt.bufPrint(&version_buf, "{f}", .{opts.version}) catch unreachable; - var distname_buf: [std.fs.max_name_bytes]u8 = undefined; - const distname = assemble_distname(&distname_buf, opts.info.name, version_str); - // While generating the port files we write to the following working directories: // // * port files: <wrk>/<tree>/ports/<port>/* - // * dist file: <wrk>/<tree>/dists/<distname> + // * dist files: <wrk>/<tree>/dists/* // const port_dir = ctx.env.ports_tree_path(ctx.tree.name, .{ opts.info.category, opts.info.name }); const dist_dir = ctx.env.ports_dist_dir(ctx.tree.name, opts.info.name); - const dist_file: fs.Path = .join(.{ dist_dir.name(), distname }); const wrk_port_dir = ctx.env.work_path(.{ ctx.tree.name, "ports", opts.info.name }); - const wrk_dist_file = ctx.env.work_path(.{ ctx.tree.name, "dist", distname }); - try fs.rm(wrk_port_dir); - try fs.rm(wrk_dist_file.dir().?); - try fs.mkdir_all(wrk_port_dir); - try fs.mkdir_all(wrk_dist_file.dir().?); + const wrk_dist_dir = ctx.env.work_path(.{ ctx.tree.name, "dist" }); + try fs.rm(&wrk_port_dir); + try fs.rm(&wrk_dist_dir); + try fs.mkdir_all(&wrk_port_dir); + try fs.mkdir_all(&wrk_dist_dir); const template: tmpl.Template = .init(ctx.env, opts.info.template); const template_conf = try tmpl.read_config(ctx.env, template); @@ -631,125 +618,230 @@ fn render_port(ctx: Ctx, opts: Render_Options) !void { try predefined_vars.add_var(".portversion", version_str); try predefined_vars.add_var(".category", opts.info.category); try predefined_vars.add_var(".distdir", dist_dir.name()); - try predefined_vars.add_var(".distname", distname); + for (template_conf.archives[0..template_conf.archives_len]) |*archive| { + var distname_buf: [std.fs.max_name_bytes]u8 = undefined; + const distname = assemble_distname(&distname_buf, .{ + .dist_id = archive.id, + .port_name = opts.info.name, + .version = version_str, + }); + if (archive.id.len > 0) { + var key_buf: [512]u8 = undefined; + try predefined_vars.add_var(concat(&key_buf, &.{ ".distname.", archive.id }), distname); + } else { + try predefined_vars.add_var(".distname", distname); + } + } - var make_portsdir_buf: [std.fs.max_path_bytes]u8 = undefined; - var make_distdir_buf: [std.fs.max_path_bytes]u8 = undefined; + var make_portsdir_buf: [fs.max_path_bytes]u8 = undefined; + var make_distdir_buf: [fs.max_path_bytes]u8 = undefined; const make_portsdir = concat(&make_portsdir_buf, &.{ "PORTSDIR=", ctx.env.ports_dir }); const make_distdir = concat(&make_distdir_buf, &.{ "DISTDIR=", dist_dir.name() }); ctx.env.info("===> {s}: writing port files", .{opts.info.name}); const vars = [_]conf.Config{ predefined_vars, port_vars }; - try template.render(ctx.env, &vars, wrk_port_dir); - try fs.mv_dir(ctx.env.allocator, wrk_port_dir, port_dir); + try template.render(ctx.env, &vars, &wrk_port_dir); + try fs.mv_dir(&wrk_port_dir, &port_dir); + var new_distfiles_created = false; if (opts.source) |src| { var prepared_dist = false; - if (template_conf.prepare_dist_target) |prepare_dist_target| { + defer { + if (prepared_dist) src.repo.reset(ctx.env); + } + + if (template_conf.prepare_target) |prepare_target| { ctx.env.info("===> {s}: prepare distribution", .{opts.info.name}); var make_target_buf: [std.fs.max_name_bytes]u8 = undefined; const res = shell.exec(.{ .env = ctx.env, - .argv = &.{ "make", "-V", concat(&make_target_buf, &.{ "${.ALLTARGETS:M", prepare_dist_target, "}" }), make_portsdir }, - .cwd = port_dir, + .argv = &.{ "make", "-V", concat(&make_target_buf, &.{ "${.ALLTARGETS:M", prepare_target, "}" }), make_portsdir }, + .cwd = &port_dir, }); const has_target = switch (res) { .success => |out| has: { - defer ctx.env.dealloc(out); - const target_found = std.mem.trim(u8, out, " \t\n"); - break :has std.mem.eql(u8, target_found, prepare_dist_target); + const output = out orelse break :has false; + defer ctx.env.dealloc(output); + const target_found = std.mem.trim(u8, output, " \t\n"); + break :has std.mem.eql(u8, target_found, prepare_target); }, .failure => |out| { - ctx.env.err("failed to check for makefile target `{s}`\n", .{prepare_dist_target}); - fatal("{s}", .{out}); + ctx.env.err("failed to check for makefile target `{s}`\n", .{prepare_target}); + fatal("{s}", .{out orelse ""}); }, }; if (has_target) { - var make_wrksrc_buf: [std.fs.max_path_bytes]u8 = undefined; + var make_wrksrc_buf: [fs.max_path_bytes]u8 = undefined; const make_wrksrc = concat(&make_wrksrc_buf, &.{ "WRKSRC=", src.repo.path.name() }); shell.run(.{ .env = ctx.env, - .argv = &.{ "make", make_portsdir, make_distdir, make_wrksrc, prepare_dist_target }, - .cwd = port_dir, + .argv = &.{ "make", make_portsdir, make_distdir, make_wrksrc, prepare_target }, + .cwd = &port_dir, }); prepared_dist = true; } else { - ctx.env.info("Target `{s}` not found (skipping)", .{prepare_dist_target}); + ctx.env.info("Target `{s}` not found (skipping)", .{prepare_target}); } } - ctx.env.info("===> {s}: creating distfiles", .{opts.info.name}); - var tar_rename_buf: [std.fs.max_path_bytes]u8 = undefined; - const tar_rename = concat(&tar_rename_buf, &.{ ",^,", distname[0 .. distname.len - distname_ext.len], "/," }); - shell.run(.{ - .env = ctx.env, - .argv = &.{ "tar", "-czf", wrk_dist_file.name(), "-C", src.repo.path.name(), "--exclude-vcs", "-s", tar_rename, "." }, - }); - if (prepared_dist) src.repo.reset(ctx.env); - try fs.mv_file(wrk_dist_file, dist_file); - ctx.env.info("distfile: {s}", .{dist_file.name()}); + for (template_conf.archives[0..template_conf.archives_len]) |*archive| { + var distname_buf: [std.fs.max_name_bytes]u8 = undefined; + const distname = assemble_distname(&distname_buf, .{ + .dist_id = archive.id, + .port_name = opts.info.name, + .version = version_str, + }); + const wrk_dist_file: fs.Path = .join(.{ wrk_dist_dir.name(), distname }); + const dist_file: fs.Path = .join(.{ dist_dir.name(), distname }); + + if (archive.id.len > 0) { + ctx.env.info("===> {s}: creating distfile [{s}]", .{ opts.info.name, archive.id }); + } else { + ctx.env.info("===> {s}: creating distfile", .{opts.info.name}); + } + var tar_rename_buf: [fs.max_path_bytes]u8 = undefined; + const tar_rename = concat(&tar_rename_buf, &.{ ",^,", distname[0 .. distname.len - distname_ext.len], "/," }); + + var root = src.repo.path; + if (archive.root) |archive_root| root.append(.{archive_root}); + + var tar_cmd: [8 + archive.paths.len][]const u8 = undefined; + var tar_cmd_len: usize = 8; + tar_cmd[0] = "tar"; + tar_cmd[1] = "-czf"; + tar_cmd[2] = wrk_dist_file.name(); + tar_cmd[3] = "-C"; + tar_cmd[4] = root.name(); + tar_cmd[5] = "-s"; + tar_cmd[6] = tar_rename; + tar_cmd[7] = "--exclude-vcs"; + if (archive.paths_len == 0) { + tar_cmd[tar_cmd_len] = "."; + tar_cmd_len += 1; + } else { + var existings_paths: usize = 0; + for (archive.paths[0..archive.paths_len]) |path| { + const abs_path: fs.Path = .join(.{ root.name(), path }); + if (fs.path_exists(&abs_path)) { + tar_cmd[tar_cmd_len] = path; + tar_cmd_len += 1; + existings_paths += 1; + } else { + ctx.env.warn("cannot find path in archive root : {s}", .{path}); + } + } + if (existings_paths == 0) { + tar_cmd[tar_cmd_len] = "--files-from"; + tar_cmd_len += 1; + tar_cmd[tar_cmd_len] = "/dev/null"; + tar_cmd_len += 1; + } + } + + shell.run(.{ + .env = ctx.env, + .argv = tar_cmd[0..tar_cmd_len], + }); + + try fs.mv_file(&wrk_dist_file, &dist_file); + ctx.env.info("distfile: {s}", .{dist_file.name()}); + new_distfiles_created = true; + } } - ctx.env.info("===> {s}: creating distinfo", .{opts.info.name}); + ctx.env.info("===> {s}: creating port's distinfo", .{opts.info.name}); shell.run(.{ .env = ctx.env, .argv = &.{ "make", make_portsdir, make_distdir, "makesum" }, - .cwd = port_dir, + .cwd = &port_dir, }); - if (opts.source != null) { + if (new_distfiles_created) { ctx.env.info("===> {s}: cleaning up old distfiles", .{opts.info.name}); - var history = list_distfiles_sorted(ctx.env.allocator, opts.info.name, dist_dir) catch |err| { - ctx.env.warn("cannot remove old distfiles from {s} ({s})", .{ dist_dir.name(), @errorName(err) }); + var history = fetch_port_distfiles_sorted(ctx.env.allocator, opts.info.name, &dist_dir) catch |err| { + ctx.env.warn("cannot fetch distfiles from {s} ({s})", .{ dist_dir.name(), @errorName(err) }); return; }; defer history.deinit(ctx.env.allocator); - - if (history.items.len > ctx.env.distfiles_history) { - var to_delete: usize = history.items.len - ctx.env.distfiles_history; - var i: usize = history.items.len; - while (i > 0 and to_delete > 0) { - i -= 1; - const dist: *const Distfile = &history.items[i]; - if (!std.mem.endsWith(u8, dist.path.name(), distname)) { - fs.rm(dist.path) catch |err| { - ctx.env.warn("cannot remove {s} ({s})", .{ dist.path.name(), @errorName(err) }); + if (history.items.len > 0) { + var version = history.items[0].version; + var visited: usize = 1; + for (history.items[1..]) |*distfile| { + if (!Version.eq(distfile.version, version)) { + visited += 1; + version = distfile.version; + } + if (visited > ctx.env.distfiles_history) { + fs.rm(&distfile.path) catch |err| { + ctx.env.warn("cannot remove {s} ({s})", .{ distfile.path.name(), @errorName(err) }); }; - to_delete -= 1; } } } } } -/// Get a list of all distfiles sorted from new to old. -fn list_distfiles_sorted(allocator: std.mem.Allocator, port_name: []const u8, dist_dir: fs.Path) !std.ArrayList(Distfile) { - var iter = try fs.iterate_files(dist_dir) orelse return .empty; +/// Get a list of all distfiles of a single port sorted from new to old. +fn fetch_port_distfiles_sorted(allocator: std.mem.Allocator, port_name: []const u8, dist_dir: *const fs.Path) !std.ArrayList(Distfile) { + const distfiles = try fetch_distfiles(allocator, dist_dir, .{ .port_name = port_name }); + std.mem.sortUnstable(Distfile, distfiles.items, {}, struct { + pub fn gt(_: void, left: Distfile, right: Distfile) bool { + return if (Version.eq(right.version, left.version)) + std.mem.lessThan(u8, right.path.name(), left.path.name()) + else + Version.less(right.version, left.version); + } + }.gt); + return distfiles; +} + +const Distfile_Filter = struct { + port_name: []const u8, + version: ?Version = null, +}; + +fn fetch_distfiles(allocator: std.mem.Allocator, dist_dir: *const fs.Path, filter: Distfile_Filter) !std.ArrayList(Distfile) { + var iter = fs.iterate(dist_dir) catch |err| { + if (err == error.FileNotFound) return .empty; + return err; + }; defer iter.deinit(); var distfiles: std.ArrayList(Distfile) = .empty; - while (iter.next()) |distfile| { - if (std.mem.startsWith(u8, distfile, port_name) and std.mem.endsWith(u8, distfile, distname_ext)) { - const version = Version.parse(distfile[port_name.len + 1 .. distfile.len - distname_ext.len]) catch continue; + while (try iter.next()) |entry| { + if (entry.type != .file) continue; + + const distfile = entry.name; + if (std.mem.startsWith(u8, distfile, filter.port_name) and std.mem.endsWith(u8, distfile, distname_ext)) { + const last_dash = std.mem.lastIndexOfScalar(u8, distfile, '-') orelse continue; + const version = Version.parse(distfile[last_dash + 1 .. distfile.len - distname_ext.len]) catch continue; + if (filter.version) |expected_version| { + if (!Version.eq(expected_version, version)) { + continue; + } + } try distfiles.append(allocator, .{ .version = version, .path = .join(.{ dist_dir.name(), distfile }), }); } } - - std.mem.sortUnstable(Distfile, distfiles.items, {}, struct { - pub fn gt(_: void, left: Distfile, right: Distfile) bool { - return Version.less(right.version, left.version); - } - }.gt); return distfiles; } +const Distname_Parts = struct { + dist_id: []const u8, + port_name: []const u8, + version: []const u8, +}; + /// Build a distname that matches FreeBSD's default value for ${DISTNAME}. -fn assemble_distname(buf: []u8, port_name: []const u8, version: []const u8) []const u8 { - return concat(buf, &.{ port_name, "-", version, distname_ext }); +fn assemble_distname(buf: []u8, parts: Distname_Parts) []const u8 { + return if (parts.dist_id.len > 0) + concat(buf, &.{ parts.port_name, "-", parts.dist_id, "-", parts.version, distname_ext }) + else + return concat(buf, &.{ parts.port_name, "-", parts.version, distname_ext }); } fn parse_int(comptime Int: type, text: []const u8) ?Int { @@ -785,19 +877,21 @@ test "parse version" { try std.testing.expectEqual(@as(u32, 0), version.date); try std.testing.expectEqual(@as(u32, 0), version.revision); - version = try .parse("3.2.1.d20180709"); + version = try .parse("3.2.1.20250709r0"); try std.testing.expectEqual(@as(u16, 3), version.major); try std.testing.expectEqual(@as(u16, 2), version.minor); try std.testing.expectEqual(@as(u16, 1), version.patch); - try std.testing.expectEqual(@as(u32, 20180709), version.date); + try std.testing.expectEqual(@as(u32, 20250709), version.date); try std.testing.expectEqual(@as(u32, 0), version.revision); - version = try .parse("2.1.3.d20210429.r7"); + version = try .parse("2.1.3.20250429r7"); try std.testing.expectEqual(@as(u16, 2), version.major); try std.testing.expectEqual(@as(u16, 1), version.minor); try std.testing.expectEqual(@as(u16, 3), version.patch); - try std.testing.expectEqual(@as(u32, 20210429), version.date); + try std.testing.expectEqual(@as(u32, 20250429), version.date); try std.testing.expectEqual(@as(u32, 7), version.revision); + + try std.testing.expectError(error.invalid_version, Version.parse("3.1.2.20250511")); } test "compare version" { |