const std = @import("std"); const fs = std.fs; const Builder = std.build.Builder; const LibExeObjStep = std.build.LibExeObjStep; const glfw = @import("vendor/mach/glfw/build.zig"); const fontconfig = @import("pkg/fontconfig/build.zig"); const freetype = @import("pkg/freetype/build.zig"); const harfbuzz = @import("pkg/harfbuzz/build.zig"); const libxml2 = @import("vendor/zig-libxml2/libxml2.zig"); const libuv = @import("pkg/libuv/build.zig"); const libpng = @import("pkg/libpng/build.zig"); const utf8proc = @import("pkg/utf8proc/build.zig"); const zlib = @import("pkg/zlib/build.zig"); const tracylib = @import("pkg/tracy/build.zig"); const system_sdk = @import("vendor/mach/glfw/system_sdk.zig"); // Build options, see the build options help for more info. var tracy: bool = false; pub fn build(b: *std.build.Builder) !void { const mode = b.standardReleaseOptions(); const target = target: { var result = b.standardTargetOptions(.{}); if (result.isLinux() and result.isGnuLibC()) { // https://github.com/ziglang/zig/issues/9485 result.glibc_version = .{ .major = 2, .minor = 28 }; } break :target result; }; tracy = b.option( bool, "tracy", "Enable Tracy integration (default true in Debug on Linux)", ) orelse (mode == .Debug and target.isLinux()); const static = b.option( bool, "static", "Statically build as much as possible for the exe", ) orelse true; const conformance = b.option( []const u8, "conformance", "Name of the conformance app to run with 'run' option.", ); const exe = b.addExecutable("ghostty", "src/main.zig"); // Exe { const exe_options = b.addOptions(); exe_options.addOption(bool, "tracy_enabled", tracy); exe.setTarget(target); exe.setBuildMode(mode); exe.addOptions("build_options", exe_options); exe.install(); // Add the shared dependencies try addDeps(b, exe, static); } // term.wasm { const wasm = b.addSharedLibrary( "ghostty-term", "src/terminal/main_wasm.zig", .{ .unversioned = {} }, ); wasm.setTarget(.{ .cpu_arch = .wasm32, .os_tag = .freestanding }); wasm.setBuildMode(mode); wasm.setOutputDir("zig-out"); // Wasm-specific deps wasm.addPackage(tracylib.pkg); wasm.addPackage(utf8proc.pkg); _ = try utf8proc.link(b, wasm); const step = b.step("term-wasm", "Build the terminal.wasm library"); step.dependOn(&wasm.step); } // Run { // Build our run step, which runs the main app by default, but will // run a conformance app if `-Dconformance` is set. const run_exe = if (conformance) |name| blk: { var conformance_exes = try conformanceSteps(b, target, mode); defer conformance_exes.deinit(); break :blk conformance_exes.get(name) orelse return error.InvalidConformance; } else exe; const run_cmd = run_exe.run(); run_cmd.step.dependOn(&run_exe.step); if (b.args) |args| { run_cmd.addArgs(args); } const run_step = b.step("run", "Run the app"); run_step.dependOn(&run_cmd.step); } // Tests { const test_step = b.step("test", "Run all tests"); var test_bin_ = b.option([]const u8, "test-bin", "Emit bin to"); var test_filter = b.option([]const u8, "test-filter", "Filter for test"); const main_test = b.addTest("src/main.zig"); { main_test.setFilter(test_filter); if (test_bin_) |test_bin| { main_test.name = std.fs.path.basename(test_bin); if (std.fs.path.dirname(test_bin)) |dir| main_test.setOutputDir(dir); } main_test.setTarget(target); try addDeps(b, main_test, true); var before = b.addLog("\x1b[" ++ color_map.get("cyan").? ++ "\x1b[" ++ color_map.get("b").? ++ "[{s} tests]" ++ "\x1b[" ++ color_map.get("d").? ++ " ----" ++ "\x1b[0m", .{"ghostty"}); var after = b.addLog("\x1b[" ++ color_map.get("d").? ++ "–––---\n\n" ++ "\x1b[0m", .{}); test_step.dependOn(&before.step); test_step.dependOn(&main_test.step); test_step.dependOn(&after.step); } // Named package dependencies don't have their tests run by reference, // so we iterate through them here. We're only interested in dependencies // we wrote (are in the "pkg/" directory). for (main_test.packages.items) |pkg_| { const pkg: std.build.Pkg = pkg_; if (std.mem.eql(u8, pkg.name, "glfw")) continue; var test_ = b.addTestSource(pkg.source); test_.setTarget(target); try addDeps(b, test_, true); if (pkg.dependencies) |children| { test_.packages = std.ArrayList(std.build.Pkg).init(b.allocator); try test_.packages.appendSlice(children); } var before = b.addLog("\x1b[" ++ color_map.get("cyan").? ++ "\x1b[" ++ color_map.get("b").? ++ "[{s} tests]" ++ "\x1b[" ++ color_map.get("d").? ++ " ----" ++ "\x1b[0m", .{pkg.name}); var after = b.addLog("\x1b[" ++ color_map.get("d").? ++ "–––---\n\n" ++ "\x1b[0m", .{}); test_step.dependOn(&before.step); test_step.dependOn(&test_.step); test_step.dependOn(&after.step); } } } /// Adds and links all of the primary dependencies for the exe. fn addDeps( b: *std.build.Builder, step: *std.build.LibExeObjStep, static: bool, ) !void { // We always need the Zig packages step.addPackage(fontconfig.pkg); step.addPackage(freetype.pkg); step.addPackage(harfbuzz.pkg); step.addPackage(glfw.pkg); step.addPackage(libuv.pkg); step.addPackage(utf8proc.pkg); // We always statically compile glad step.addIncludeDir("vendor/glad/include/"); step.addCSourceFile("vendor/glad/src/gl.c", &.{}); // Tracy step.addPackage(tracylib.pkg); if (tracy) { var tracy_step = try tracylib.link(b, step); system_sdk.include(b, tracy_step, .{}); } // utf8proc _ = try utf8proc.link(b, step); // Glfw glfw.link(b, step, .{ .metal = false, .opengl = false, // Found at runtime }); // Dynamic link if (!static) { step.addIncludePath(freetype.include_path_self); step.linkSystemLibrary("bzip2"); step.linkSystemLibrary("freetype2"); step.linkSystemLibrary("harfbuzz"); step.linkSystemLibrary("libpng"); step.linkSystemLibrary("libuv"); step.linkSystemLibrary("zlib"); if (step.target.isLinux()) step.linkSystemLibrary("fontconfig"); } // Other dependencies, we may dynamically link if (static) { const zlib_step = try zlib.link(b, step); const libpng_step = try libpng.link(b, step, .{ .zlib = .{ .step = zlib_step, .include = &zlib.include_paths, }, }); // Freetype const freetype_step = try freetype.link(b, step, .{ .libpng = freetype.Options.Libpng{ .enabled = true, .step = libpng_step, .include = &libpng.include_paths, }, .zlib = .{ .enabled = true, .step = zlib_step, .include = &zlib.include_paths, }, }); // Harfbuzz _ = try harfbuzz.link(b, step, .{ .freetype = .{ .enabled = true, .step = freetype_step, .include = &freetype.include_paths, }, }); // Libuv const libuv_step = try libuv.link(b, step); system_sdk.include(b, libuv_step, .{}); // Only Linux gets fontconfig if (step.target.isLinux()) { // Libxml2 const libxml2_lib = try libxml2.create( b, step.target, step.build_mode, .{ .lzma = false, .zlib = false }, ); libxml2_lib.link(step); // Fontconfig const fontconfig_step = try fontconfig.link(b, step, .{ .freetype = .{ .enabled = true, .step = freetype_step, .include = &freetype.include_paths, }, .libxml2 = true, }); libxml2_lib.link(fontconfig_step); } } } fn conformanceSteps( b: *std.build.Builder, target: std.zig.CrossTarget, mode: std.builtin.Mode, ) !std.StringHashMap(*LibExeObjStep) { var map = std.StringHashMap(*LibExeObjStep).init(b.allocator); // Open the directory ./conformance const c_dir_path = (comptime root()) ++ "/conformance"; var c_dir = try fs.openIterableDirAbsolute(c_dir_path, .{}); defer c_dir.close(); // Go through and add each as a step var c_dir_it = c_dir.iterate(); while (try c_dir_it.next()) |entry| { // Get the index of the last '.' so we can strip the extension. const index = std.mem.lastIndexOfScalar(u8, entry.name, '.') orelse continue; if (index == 0) continue; // Name of the conformance app and full path to the entrypoint. const name = entry.name[0..index]; const path = try fs.path.join(b.allocator, &[_][]const u8{ c_dir_path, entry.name, }); // Executable builder. const c_exe = b.addExecutable(name, path); c_exe.setTarget(target); c_exe.setBuildMode(mode); // Store the mapping try map.put(name, c_exe); } return map; } /// Path to the directory with the build.zig. fn root() []const u8 { return std.fs.path.dirname(@src().file) orelse unreachable; } /// ANSI escape codes for colored log output const color_map = std.ComptimeStringMap([]const u8, .{ &.{ "black", "30m" }, &.{ "blue", "34m" }, &.{ "b", "1m" }, &.{ "d", "2m" }, &.{ "cyan", "36m" }, &.{ "green", "32m" }, &.{ "magenta", "35m" }, &.{ "red", "31m" }, &.{ "white", "37m" }, &.{ "yellow", "33m" }, });