aboutsummaryrefslogtreecommitdiff
path: root/toolchain/akibacompile/main.zig
blob: 9a52c1c6954fc46de07d571dee8b679dff3fa1d7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
const std = @import("std");

const MAX_LIBS = 32;
const MAX_NAME = 64;

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    const args = try std.process.argsAlloc(allocator);
    defer std.process.argsFree(allocator, args);

    if (args.len < 4) {
        std.debug.print("Usage: akibacompile <binary_dir> <output> <libraries_dir>\n", .{});
        return error.InvalidArgs;
    }

    const binary_dir = args[1];
    const output_path = args[2];
    const libraries_dir = args[3];

    const bin_name = std.fs.path.basename(binary_dir);
    const zig_file = try std.fmt.allocPrint(allocator, "{s}/{s}.zig", .{ binary_dir, bin_name });
    defer allocator.free(zig_file);

    const zon_file = try std.fmt.allocPrint(allocator, "{s}/{s}.zon", .{ binary_dir, bin_name });
    defer allocator.free(zon_file);

    const zon_content = std.fs.cwd().readFileAlloc(allocator, zon_file, 1024) catch {
        std.debug.print("Error: No .zon file for {s}\n", .{bin_name});
        return error.MissingZon;
    };
    defer allocator.free(zon_content);

    // Get all available library names
    var available: [MAX_LIBS][MAX_NAME]u8 = undefined;
    var available_len: [MAX_LIBS]usize = undefined;
    var available_count: usize = 0;

    var lib_dir = try std.fs.cwd().openDir(libraries_dir, .{ .iterate = true });
    var lib_iter = lib_dir.iterate();
    while (try lib_iter.next()) |entry| {
        if (entry.kind != .directory) continue;
        if (entry.name.len < MAX_NAME and available_count < MAX_LIBS) {
            @memcpy(available[available_count][0..entry.name.len], entry.name);
            available_len[available_count] = entry.name.len;
            available_count += 1;
        }
    }
    lib_dir.close();

    // Track which libs are needed
    var needed: [MAX_LIBS]bool = [_]bool{false} ** MAX_LIBS;
    var deps: [MAX_LIBS][256]u8 = undefined;
    var deps_len: [MAX_LIBS]usize = [_]usize{0} ** MAX_LIBS;

    // Mark direct dependencies from binary's zon
    for (0..available_count) |i| {
        const name = available[i][0..available_len[i]];
        if (std.mem.indexOf(u8, zon_content, name) != null) {
            needed[i] = true;
        }
    }

    // Iteratively find transitive dependencies (max 10 passes)
    for (0..10) |_| {
        var changed = false;
        for (0..available_count) |i| {
            if (!needed[i]) continue;

            // Read this lib's zon
            const lib_zon_path = try std.fmt.allocPrint(allocator, "{s}/{s}/{s}.zon", .{
                libraries_dir,
                available[i][0..available_len[i]],
                available[i][0..available_len[i]],
            });
            defer allocator.free(lib_zon_path);

            const lib_zon = std.fs.cwd().readFileAlloc(allocator, lib_zon_path, 4096) catch continue;
            defer allocator.free(lib_zon);

            // Check for dependencies on other libs
            for (0..available_count) |j| {
                if (i == j) continue;
                const dep_name = available[j][0..available_len[j]];
                if (std.mem.indexOf(u8, lib_zon, dep_name) != null) {
                    if (!needed[j]) {
                        needed[j] = true;
                        changed = true;
                    }
                    // Record dependency
                    if (deps_len[i] > 0) {
                        deps[i][deps_len[i]] = ',';
                        deps_len[i] += 1;
                    }
                    // Check if already in deps
                    var already = false;
                    var check_iter = std.mem.splitScalar(u8, deps[i][0..deps_len[i]], ',');
                    while (check_iter.next()) |existing| {
                        if (std.mem.eql(u8, existing, dep_name)) {
                            already = true;
                            break;
                        }
                    }
                    if (!already) {
                        @memcpy(deps[i][deps_len[i]..][0..dep_name.len], dep_name);
                        deps_len[i] += dep_name.len;
                    } else if (deps_len[i] > 0) {
                        deps_len[i] -= 1; // Remove the comma we added
                    }
                }
            }
        }
        if (!changed) break;
    }

    // Build the build.zig content
    var content = std.ArrayListUnmanaged(u8){};
    defer content.deinit(allocator);

    try content.appendSlice(allocator,
        \\const std = @import("std");
        \\pub fn build(b: *std.Build) void {
        \\    const target = b.resolveTargetQuery(.{
        \\        .cpu_arch = .x86_64,
        \\        .os_tag = .freestanding,
        \\        .abi = .none,
        \\    });
        \\
    );

    // Create modules for needed libs
    for (0..available_count) |i| {
        if (!needed[i]) continue;
        const name = available[i][0..available_len[i]];
        const module_decl = try std.fmt.allocPrint(allocator,
            \\    const {s}_module = b.addModule("{s}", .{{
            \\        .root_source_file = b.path("../{s}/{s}/{s}.zig"),
            \\        .target = target,
            \\        .optimize = .ReleaseSmall,
            \\    }});
            \\
        , .{ name, name, libraries_dir, name, name });
        defer allocator.free(module_decl);
        try content.appendSlice(allocator, module_decl);
        std.debug.print("  + Library: {s}\n", .{name});
    }

    // Add inter-library dependencies
    for (0..available_count) |i| {
        if (!needed[i]) continue;
        const name = available[i][0..available_len[i]];
        var dep_iter = std.mem.splitScalar(u8, deps[i][0..deps_len[i]], ',');
        while (dep_iter.next()) |dep| {
            if (dep.len > 0) {
                const import_stmt = try std.fmt.allocPrint(allocator,
                    \\    {s}_module.addImport("{s}", {s}_module);
                    \\
                , .{ name, dep, dep });
                defer allocator.free(import_stmt);
                try content.appendSlice(allocator, import_stmt);
            }
        }
    }

    // Create executable
    const exe_decl = try std.fmt.allocPrint(allocator,
        \\    const exe = b.addExecutable(.{{
        \\        .name = "{s}",
        \\        .root_module = b.createModule(.{{
        \\            .root_source_file = b.path("../{s}"),
        \\            .target = target,
        \\            .optimize = .ReleaseSmall,
        \\        }}),
        \\    }});
        \\    exe.setLinkerScript(b.path("../toolchain/linker/akiba.binary.linker"));
        \\
    , .{ bin_name, zig_file });
    defer allocator.free(exe_decl);
    try content.appendSlice(allocator, exe_decl);

    // Add library imports to exe
    for (0..available_count) |i| {
        if (!needed[i]) continue;
        const name = available[i][0..available_len[i]];
        const import_stmt = try std.fmt.allocPrint(allocator,
            \\    exe.root_module.addImport("{s}", {s}_module);
            \\
        , .{ name, name });
        defer allocator.free(import_stmt);
        try content.appendSlice(allocator, import_stmt);
    }

    try content.appendSlice(allocator,
        \\    b.installArtifact(exe);
        \\}
        \\
    );

    // Create temp directory and build
    const temp_dir = try std.fmt.allocPrint(allocator, ".akiba-build-{s}", .{bin_name});
    defer allocator.free(temp_dir);

    std.fs.cwd().deleteTree(temp_dir) catch {};
    try std.fs.cwd().makeDir(temp_dir);
    defer std.fs.cwd().deleteTree(temp_dir) catch {};

    const build_path = try std.fmt.allocPrint(allocator, "{s}/build.zig", .{temp_dir});
    defer allocator.free(build_path);
    try std.fs.cwd().writeFile(.{ .sub_path = build_path, .data = content.items });

    const result = try std.process.Child.run(.{
        .allocator = allocator,
        .argv = &[_][]const u8{ "zig", "build" },
        .cwd = temp_dir,
    });
    defer allocator.free(result.stdout);
    defer allocator.free(result.stderr);

    if (result.term.Exited != 0) {
        std.debug.print("{s}\n", .{result.stderr});
        return error.CompileFailed;
    }

    const built_exe = try std.fmt.allocPrint(allocator, "{s}/zig-out/bin/{s}", .{ temp_dir, bin_name });
    defer allocator.free(built_exe);

    try std.fs.cwd().rename(built_exe, output_path);
    std.debug.print("✓ Compiled {s}\n", .{bin_name});
}