1const Package = @This();
2
3const std = @import("std");
4const fs = std.fs;
5const mem = std.mem;
6const Allocator = mem.Allocator;
7const assert = std.debug.assert;
8
9const Compilation = @import("Compilation.zig");
10const Module = @import("Module.zig");
11
12pub const Table = std.StringHashMapUnmanaged(*Package);
13
14root_src_directory: Compilation.Directory,
15/// Relative to `root_src_directory`. May contain path separators.
16root_src_path: []const u8,
17table: Table = .{},
18parent: ?*Package = null,
19/// Whether to free `root_src_directory` on `destroy`.
20root_src_directory_owned: bool = false,
21
22/// Allocate a Package. No references to the slices passed are kept.
23pub fn create(
24    gpa: Allocator,
25    /// Null indicates the current working directory
26    root_src_dir_path: ?[]const u8,
27    /// Relative to root_src_dir_path
28    root_src_path: []const u8,
29) !*Package {
30    const ptr = try gpa.create(Package);
31    errdefer gpa.destroy(ptr);
32
33    const owned_dir_path = if (root_src_dir_path) |p| try gpa.dupe(u8, p) else null;
34    errdefer if (owned_dir_path) |p| gpa.free(p);
35
36    const owned_src_path = try gpa.dupe(u8, root_src_path);
37    errdefer gpa.free(owned_src_path);
38
39    ptr.* = .{
40        .root_src_directory = .{
41            .path = owned_dir_path,
42            .handle = if (owned_dir_path) |p| try fs.cwd().openDir(p, .{}) else fs.cwd(),
43        },
44        .root_src_path = owned_src_path,
45        .root_src_directory_owned = true,
46    };
47
48    return ptr;
49}
50
51pub fn createWithDir(
52    gpa: Allocator,
53    directory: Compilation.Directory,
54    /// Relative to `directory`. If null, means `directory` is the root src dir
55    /// and is owned externally.
56    root_src_dir_path: ?[]const u8,
57    /// Relative to root_src_dir_path
58    root_src_path: []const u8,
59) !*Package {
60    const ptr = try gpa.create(Package);
61    errdefer gpa.destroy(ptr);
62
63    const owned_src_path = try gpa.dupe(u8, root_src_path);
64    errdefer gpa.free(owned_src_path);
65
66    if (root_src_dir_path) |p| {
67        const owned_dir_path = try directory.join(gpa, &[1][]const u8{p});
68        errdefer gpa.free(owned_dir_path);
69
70        ptr.* = .{
71            .root_src_directory = .{
72                .path = owned_dir_path,
73                .handle = try directory.handle.openDir(p, .{}),
74            },
75            .root_src_directory_owned = true,
76            .root_src_path = owned_src_path,
77        };
78    } else {
79        ptr.* = .{
80            .root_src_directory = directory,
81            .root_src_directory_owned = false,
82            .root_src_path = owned_src_path,
83        };
84    }
85    return ptr;
86}
87
88/// Free all memory associated with this package. It does not destroy any packages
89/// inside its table; the caller is responsible for calling destroy() on them.
90pub fn destroy(pkg: *Package, gpa: Allocator) void {
91    gpa.free(pkg.root_src_path);
92
93    if (pkg.root_src_directory_owned) {
94        // If root_src_directory.path is null then the handle is the cwd()
95        // which shouldn't be closed.
96        if (pkg.root_src_directory.path) |p| {
97            gpa.free(p);
98            pkg.root_src_directory.handle.close();
99        }
100    }
101
102    pkg.deinitTable(gpa);
103    gpa.destroy(pkg);
104}
105
106/// Only frees memory associated with the table.
107pub fn deinitTable(pkg: *Package, gpa: Allocator) void {
108    var it = pkg.table.keyIterator();
109    while (it.next()) |key| {
110        gpa.free(key.*);
111    }
112
113    pkg.table.deinit(gpa);
114}
115
116pub fn add(pkg: *Package, gpa: Allocator, name: []const u8, package: *Package) !void {
117    try pkg.table.ensureUnusedCapacity(gpa, 1);
118    const name_dupe = try gpa.dupe(u8, name);
119    pkg.table.putAssumeCapacityNoClobber(name_dupe, package);
120}
121
122pub fn addAndAdopt(parent: *Package, gpa: Allocator, name: []const u8, child: *Package) !void {
123    assert(child.parent == null); // make up your mind, who is the parent??
124    child.parent = parent;
125    return parent.add(gpa, name, child);
126}
127