const std = @import("std"); const builtin = @import("builtin"); fn run_process_and_capture_stdout(b: *std.Build, argv: []const []const u8) ![]const u8 { const result = std.process.Child.run(.{ .allocator = b.allocator, .argv = argv, }) catch |err| return err; switch (result.term) { .Exited => |exit_code| { if (exit_code != 0) { return error.SpawnError; } }, else => return error.SpawnError, } return result.stdout; } fn file_find_in_path(allocator: std.mem.Allocator, file_name: []const u8, path_env: []const u8, extension: []const u8) ?[]const u8 { const path_env_separator = switch (builtin.os.tag) { .windows => ';', else => ':', }; const path_separator = switch (builtin.os.tag) { .windows => '\\', else => '/', }; var env_it = std.mem.splitScalar(u8, path_env, path_env_separator); const result: ?[]const u8 = while (env_it.next()) |dir_path| { const full_path = std.mem.concatWithSentinel(allocator, u8, &.{ dir_path, &[1]u8{path_separator}, file_name, extension }, 0) catch unreachable; const file = std.fs.cwd().openFile(full_path, .{}) catch continue; file.close(); break full_path; } else null; return result; } fn executable_find_in_path(allocator: std.mem.Allocator, file_name: []const u8, path_env: []const u8) ?[]const u8 { const extension = switch (builtin.os.tag) { .windows => ".exe", else => "", }; return file_find_in_path(allocator, file_name, path_env, extension); } const LLVM = struct { module: *std.Build.Module, fn setup(b: *std.Build, path: []const u8, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) !LLVM { var llvm_libs = std.ArrayList([]const u8).init(b.allocator); var flags = std.ArrayList([]const u8).init(b.allocator); const llvm_config_path = if (b.option([]const u8, "llvm_prefix", "LLVM prefix")) |llvm_prefix| blk: { const full_path = try std.mem.concat(b.allocator, u8, &.{ llvm_prefix, "/bin/llvm-config" }); const f = std.fs.cwd().openFile(full_path, .{}) catch return error.llvm_not_found; f.close(); break :blk full_path; } else executable_find_in_path(b.allocator, "llvm-config", path) orelse return error.llvm_not_found; const llvm_components_result = try run_process_and_capture_stdout(b, &.{ llvm_config_path, "--components" }); var it = std.mem.splitScalar(u8, llvm_components_result, ' '); var args = std.ArrayList([]const u8).init(b.allocator); try args.append(llvm_config_path); try args.append("--libs"); while (it.next()) |component| { try args.append(std.mem.trimRight(u8, component, "\n")); } const llvm_libs_result = try run_process_and_capture_stdout(b, args.items); it = std.mem.splitScalar(u8, llvm_libs_result, ' '); while (it.next()) |lib| { const llvm_lib = std.mem.trimLeft(u8, std.mem.trimRight(u8, lib, "\n"), "-l"); try llvm_libs.append(llvm_lib); } const llvm_cxx_flags_result = try run_process_and_capture_stdout(b, &.{ llvm_config_path, "--cxxflags" }); it = std.mem.splitScalar(u8, llvm_cxx_flags_result, ' '); while (it.next()) |flag| { const llvm_cxx_flag = std.mem.trimRight(u8, flag, "\n"); try flags.append(llvm_cxx_flag); } const llvm_lib_dir = std.mem.trimRight(u8, try run_process_and_capture_stdout(b, &.{ llvm_config_path, "--libdir" }), "\n"); if (optimize != .ReleaseSmall) { try flags.append("-g"); } try flags.append("-fno-rtti"); const llvm = b.createModule(.{ .target = target, .optimize = optimize, }); llvm.addLibraryPath(.{ .cwd_relative = llvm_lib_dir }); llvm.addCSourceFiles(.{ .files = &.{"src/llvm.cpp"}, .flags = flags.items, }); llvm.addIncludePath(.{ .cwd_relative = "/usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/14.2.1/../../../../include/c++/14.2.1" }); llvm.addIncludePath(.{ .cwd_relative = "/usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/14.2.1/../../../../include/c++/14.2.1/x86_64-pc-linux-gnu" }); const needed_libraries: []const []const u8 = &.{ "unwind", "z" }; llvm.addObjectFile(.{ .cwd_relative = "/usr/lib/libstdc++.so.6" }); const lld_libs: []const []const u8 = &.{ "lldCommon", "lldCOFF", "lldELF", "lldMachO", "lldMinGW", "lldWasm" }; for (needed_libraries) |lib| { llvm.linkSystemLibrary(lib, .{}); } for (llvm_libs.items) |lib| { llvm.linkSystemLibrary(lib, .{}); } for (lld_libs) |lib| { llvm.linkSystemLibrary(lib, .{}); } return LLVM{ .module = llvm, }; } fn link(llvm: LLVM, target: *std.Build.Step.Compile) void { if (target.root_module != llvm.module) { target.root_module.addImport("llvm", llvm.module); } else { // TODO: should we allow this case? unreachable; } } }; pub fn build(b: *std.Build) !void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); const env = try std.process.getEnvMap(b.allocator); const path = env.get("PATH") orelse unreachable; const exe_mod = b.createModule(.{ .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, }); const llvm = try LLVM.setup(b, path, target, optimize); const exe = b.addExecutable(.{ .name = "bloat-buster", .root_module = exe_mod, }); llvm.link(exe); b.installArtifact(exe); const run_cmd = b.addRunArtifact(exe); run_cmd.step.dependOn(b.getInstallStep()); if (b.args) |args| { run_cmd.addArgs(args); } const run_step = b.step("run", "Run the app"); run_step.dependOn(&run_cmd.step); const exe_unit_tests = b.addTest(.{ .root_module = exe_mod, }); llvm.link(exe_unit_tests); const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests); const test_step = b.step("test", "Run unit tests"); test_step.dependOn(&run_exe_unit_tests.step); }