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 CmakeBuildType = enum { Debug, RelWithDebInfo, MinSizeRel, Release, fn from_zig_build_type(o: std.builtin.OptimizeMode) CmakeBuildType { return switch (o) { .Debug => .Debug, .ReleaseSafe => .RelWithDebInfo, .ReleaseSmall => .MinSizeRel, .ReleaseFast => .Release, }; } }; var system_llvm: bool = undefined; var target: std.Build.ResolvedTarget = undefined; var optimize: std.builtin.OptimizeMode = undefined; var env: std.process.EnvMap = undefined; const BuildMode = enum { debug_none, debug_fast, debug_size, soft_optimize, optimize_for_speed, optimize_for_size, aggressively_optimize_for_speed, aggressively_optimize_for_size, }; pub fn build(b: *std.Build) !void { env = try std.process.getEnvMap(b.allocator); target = b.standardTargetOptions(.{}); optimize = b.standardOptimizeOption(.{}); system_llvm = b.option(bool, "system_llvm", "Link against system LLVM libraries") orelse false; const c_abi = b.addObject(.{ .name = "c_abi", .link_libc = true, .root_module = b.createModule(.{ .target = target, .optimize = optimize, .link_libc = true, .sanitize_c = false, }), .optimize = optimize, }); c_abi.addCSourceFiles(.{ .files = &.{"tests/c_abi.c"}, .flags = &.{"-g"}, }); const path = env.get("PATH") orelse unreachable; const stack_trace_library = b.addObject(.{ .name = "stack_trace", .root_module = b.createModule(.{ .target = target, .optimize = .ReleaseFast, .root_source_file = b.path("src/stack_trace.zig"), .link_libc = true, }), }); const exe_mod = b.createModule(.{ .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, .link_libc = true, .sanitize_c = false, }); const configuration = b.addOptions(); configuration.addOptionPath("c_abi_object_path", c_abi.getEmittedBin()); exe_mod.addOptions("configuration", configuration); const exe = b.addExecutable(.{ .name = "bloat-buster", .root_module = exe_mod, .link_libc = true, }); exe.addObject(stack_trace_library); 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 if (system_llvm) executable_find_in_path(b.allocator, "llvm-config", path) orelse return error.llvm_not_found else blk: { const home_env = switch (@import("builtin").os.tag) { .windows => "USERPROFILE", else => "HOME", }; const home_path = env.get(home_env) orelse unreachable; const is_ci = std.mem.eql(u8, (env.get("BB_CI") orelse "0"), "1"); const download_dir = try std.mem.concat(b.allocator, u8, &.{ home_path, "/Downloads" }); std.fs.makeDirAbsolute(download_dir) catch {}; const cmake_build_type = if (is_ci) CmakeBuildType.from_zig_build_type(optimize) else CmakeBuildType.Release; const version_string = "20.1.2"; const llvm_base = try std.mem.concat(b.allocator, u8, &.{ "llvm_", version_string, "_", @tagName(target.result.cpu.arch), "-", @tagName(target.result.os.tag), "-", @tagName(cmake_build_type) }); const base = try std.mem.concat(b.allocator, u8, &.{ download_dir, "/", llvm_base }); const full_path = try std.mem.concat(b.allocator, u8, &.{ base, "/bin/llvm-config" }); const f = std.fs.cwd().openFile(full_path, .{}) catch { const url = try std.mem.concat(b.allocator, u8, &.{ "https://github.com/birth-software/llvm/releases/download/v", version_string, "/", llvm_base, ".7z" }); var result = try std.process.Child.run(.{ .allocator = b.allocator, .argv = &.{ "wget", "-P", download_dir, url }, .max_output_bytes = std.math.maxInt(usize), }); var success = false; switch (result.term) { .Exited => |exit_code| { success = exit_code == 0; }, else => {}, } if (!success) { std.debug.print("{s}\n{s}\n", .{ result.stdout, result.stderr }); } if (success) { const file_7z = try std.mem.concat(b.allocator, u8, &.{ base, ".7z" }); result = try std.process.Child.run(.{ .allocator = b.allocator, .argv = &.{ "7z", "x", try std.mem.concat(b.allocator, u8, &.{ "-o", download_dir }), file_7z }, .max_output_bytes = std.math.maxInt(usize), }); success = false; switch (result.term) { .Exited => |exit_code| { success = exit_code == 0; }, else => {}, } if (!success) { std.debug.print("{s}\n{s}\n", .{ result.stdout, result.stderr }); } break :blk full_path; } return error.llvm_not_found; }; f.close(); break :blk full_path; }; 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"); exe.addLibraryPath(.{ .cwd_relative = llvm_lib_dir }); const a = std.fs.cwd().openDir("/usr/lib/x86_64-linux-gnu/", .{}); if (a) |_| { var dir = a catch unreachable; dir.close(); exe.addLibraryPath(.{ .cwd_relative = "/usr/lib/x86_64-linux-gnu/" }); } else |err| { err catch {}; } exe.addCSourceFiles(.{ .files = &.{"src/llvm.cpp"}, .flags = flags.items, }); var dir = try std.fs.cwd().openDir("/usr/include/c++", .{ .iterate = true, }); var iterator = dir.iterate(); const gcc_version = while (try iterator.next()) |entry| { if (entry.kind == .directory) { break entry.name; } } else return error.include_cpp_dir_not_found; dir.close(); const general_cpp_include_dir = try std.mem.concat(b.allocator, u8, &.{ "/usr/include/c++/", gcc_version }); exe.addIncludePath(.{ .cwd_relative = general_cpp_include_dir }); { const arch_cpp_include_dir = try std.mem.concat(b.allocator, u8, &.{ general_cpp_include_dir, "/x86_64-pc-linux-gnu" }); const d2 = std.fs.cwd().openDir(arch_cpp_include_dir, .{}); if (d2) |_| { var d = d2 catch unreachable; d.close(); exe.addIncludePath(.{ .cwd_relative = arch_cpp_include_dir }); } else |err| err catch {}; } { const arch_cpp_include_dir = try std.mem.concat(b.allocator, u8, &.{ "/usr/include/x86_64-linux-gnu/c++/", gcc_version }); const d2 = std.fs.cwd().openDir(arch_cpp_include_dir, .{}); if (d2) |_| { var d = d2 catch unreachable; d.close(); exe.addIncludePath(.{ .cwd_relative = arch_cpp_include_dir }); } else |err| err catch {}; } var found_libcpp = false; if (std.fs.cwd().openFile("/usr/lib/libstdc++.so.6", .{})) |file| { file.close(); found_libcpp = true; exe.addObjectFile(.{ .cwd_relative = "/usr/lib/libstdc++.so.6" }); } else |err| { err catch {}; } if (std.fs.cwd().openFile("/usr/lib/x86_64-linux-gnu/libstdc++.so.6", .{})) |file| { file.close(); found_libcpp = true; exe.addObjectFile(.{ .cwd_relative = "/usr/lib/x86_64-linux-gnu/libstdc++.so.6" }); } else |err| { err catch {}; } if (!found_libcpp) { return error.libcpp_not_found; } const needed_libraries: []const []const u8 = &.{ "unwind", "z", "zstd" }; for (needed_libraries) |lib| { exe.linkSystemLibrary(lib); } for (llvm_libs.items) |lib| { exe.linkSystemLibrary(lib); } const lld_libs: []const []const u8 = &.{ "lldCommon", "lldCOFF", "lldELF", "lldMachO", "lldMinGW", "lldWasm" }; for (lld_libs) |lib| { exe.linkSystemLibrary(lib); } b.installArtifact(exe); for ([_]bool{ false, true }) |is_test| { const run_step_name = switch (is_test) { true => "test", false => "run", }; const debug_step_name = switch (is_test) { true => "debug_test", false => "debug", }; const command = b.addRunArtifact(exe); command.step.dependOn(b.getInstallStep()); if (is_test) { command.addArg("test"); } if (b.args) |args| { command.addArgs(args); } const run_step = b.step(run_step_name, ""); run_step.dependOn(&command.step); const debug_command = std.Build.Step.Run.create(b, b.fmt("{s} {s}", .{ debug_step_name, exe.name })); debug_command.addArg("gdb"); debug_command.addArg("-ex"); debug_command.addArg("r"); debug_command.addArg("--args"); debug_command.addArtifactArg(exe); if (is_test) { debug_command.addArg("test"); } if (b.args) |args| { debug_command.addArgs(args); } const debug_step = b.step(debug_step_name, ""); debug_step.dependOn(&debug_command.step); } }