main build.zig
Eric Bower  ·  2026-10-23
  1const std = @import("std");
  2const Translator = @import("translate_c").Translator;
  3const build_zig_zon = @import("build.zig.zon");
  4
  5const linux_targets: []const std.Target.Query = &.{
  6    .{ .cpu_arch = .x86_64, .os_tag = .linux, .abi = .musl },
  7    .{ .cpu_arch = .aarch64, .os_tag = .linux, .abi = .musl },
  8};
  9
 10const macos_targets: []const std.Target.Query = &.{
 11    .{ .cpu_arch = .x86_64, .os_tag = .macos },
 12    .{ .cpu_arch = .aarch64, .os_tag = .macos },
 13};
 14
 15pub fn build(b: *std.Build) void {
 16    const target = b.standardTargetOptions(.{});
 17    // const is_macos = target.result.os.tag == .macos;
 18    const optimize = b.standardOptimizeOption(.{});
 19    const version = b.option([]const u8, "version", "Version string for release") orelse
 20        @as([]const u8, build_zig_zon.version);
 21
 22    const enable_daemon = b.option(bool, "daemon", "Include daemon support and Ghostty VT in zmx pkg") orelse true;
 23
 24    const options = b.addOptions();
 25    options.addOption([]const u8, "version", version);
 26    const ghostty_ver = build_zig_zon.dependencies.ghostty.hash;
 27    options.addOption([]const u8, "ghostty_version", ghostty_ver);
 28    options.addOption(bool, "has_daemon", enable_daemon);
 29
 30    const translate_c = b.dependency("translate_c", .{});
 31    const translator: Translator = .init(translate_c, .{
 32        .c_source_file = b.path("src/c.h"),
 33        .target = target,
 34        .optimize = optimize,
 35    });
 36
 37    const exe_mod = b.createModule(.{
 38        .root_source_file = b.path("src/main.zig"),
 39        .target = target,
 40        .optimize = optimize,
 41        .link_libc = true,
 42        .imports = &.{
 43            .{
 44                .name = "cross",
 45                .module = translator.mod,
 46            },
 47        },
 48    });
 49    exe_mod.addOptions("build_options", options);
 50
 51    const dep = if (enable_daemon) b.lazyDependency("ghostty", .{
 52        .target = target,
 53        .optimize = optimize,
 54        .@"emit-lib-vt" = true,
 55        // Not redundant: in lib-vt mode emit-xcframework defaults to "xcodebuild
 56        // on PATH" (true even via the CLT stub), which pulls in the iOS SDK at
 57        // configure time and breaks builds without full Xcode.
 58        .@"emit-xcframework" = false,
 59        .@"emit-macos-app" = false,
 60    }) else null;
 61    if (dep) |d| {
 62        const vt_mod = d.module("ghostty-vt");
 63        if (target.result.os.tag == .openbsd) vt_mod.sanitize_c = .off;
 64        exe_mod.addImport("ghostty-vt", vt_mod);
 65    }
 66
 67    // zmx module: programmatic interface for embedding or external tools
 68    const zmx_pkg_mod = b.addModule("zmx", .{
 69        .root_source_file = b.path("src/lib.zig"),
 70        .target = target,
 71        .optimize = optimize,
 72        .link_libc = true,
 73        .imports = &.{
 74            .{
 75                .name = "cross",
 76                .module = translator.mod,
 77            },
 78        },
 79    });
 80
 81    zmx_pkg_mod.addOptions("build_options", options);
 82    if (dep) |d| {
 83        const vt_mod = d.module("ghostty-vt");
 84        if (target.result.os.tag == .openbsd) vt_mod.sanitize_c = .off;
 85        zmx_pkg_mod.addImport("ghostty-vt", vt_mod);
 86    }
 87
 88    // Examples: build the usage examples in examples/ against the zmx module
 89    {
 90        const examples_step = b.step("examples", "Build usage examples");
 91        const Example = struct { name: []const u8, file: []const u8 };
 92        const example_sources: []const Example = &.{
 93            .{ .name = "example-run-and-wait", .file = "examples/run_and_wait.zig" },
 94            .{ .name = "example-list-sessions", .file = "examples/list_sessions.zig" },
 95        };
 96        for (example_sources) |ex| {
 97            const ex_mod = b.createModule(.{
 98                .root_source_file = b.path(ex.file),
 99                .target = target,
100                .optimize = optimize,
101                .link_libc = true,
102                .imports = &.{
103                    .{
104                        .name = "cross",
105                        .module = translator.mod,
106                    },
107                },
108            });
109            ex_mod.addImport("zmx", zmx_pkg_mod);
110            const ex_exe = b.addExecutable(.{ .name = ex.name, .root_module = ex_mod });
111            const install = b.addInstallArtifact(ex_exe, .{});
112            examples_step.dependOn(&install.step);
113        }
114    }
115
116    // Run
117    {
118        const run_step = b.step("run", "Run the app");
119        const exe = b.addExecutable(.{
120            .name = "zmx",
121            // .use_llvm = true,
122            // .use_lld = !is_macos,
123            .root_module = exe_mod,
124        });
125
126        if (enable_daemon) {
127            b.installArtifact(exe);
128            const run_cmd = b.addRunArtifact(exe);
129            run_cmd.step.dependOn(b.getInstallStep());
130            run_cmd.addPassthruArgs();
131            run_step.dependOn(&run_cmd.step);
132        }
133    }
134
135    // Test
136    {
137        const test_step = b.step("test", "Run unit tests");
138        const test_module = b.addModule("test", .{
139            .root_source_file = b.path("src/test.zig"),
140            .target = target,
141            .optimize = optimize,
142            .link_libc = true,
143            .imports = &.{
144                .{
145                    .name = "cross",
146                    .module = translator.mod,
147                },
148            },
149        });
150        test_module.addOptions("build_options", options);
151        const test_dep = b.dependency("ghostty", .{
152            .target = target,
153            .optimize = optimize,
154            .@"emit-lib-vt" = true,
155            .@"emit-xcframework" = false,
156            .@"emit-macos-app" = false,
157        });
158        test_module.addImport(
159            "ghostty-vt",
160            test_dep.module("ghostty-vt"),
161        );
162        const exe_unit_tests = b.addTest(.{
163            .root_module = test_module,
164            // .use_llvm = true,
165            // .use_lld = !is_macos,
166        });
167
168        const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
169        test_step.dependOn(&run_exe_unit_tests.step);
170    }
171
172    // Check for LSP integration
173    {
174        const check = b.step("check", "Check if zmx compiles");
175        if (enable_daemon) {
176            const exe_check = b.addExecutable(.{
177                .name = "zmx",
178                .root_module = exe_mod,
179            });
180            check.dependOn(&exe_check.step);
181        } else {
182            const lib_check = b.addLibrary(.{
183                .name = "zmx",
184                .root_module = zmx_pkg_mod,
185                .linkage = .static,
186            });
187            check.dependOn(&lib_check.step);
188        }
189    }
190
191    // Release step - cross-compile to all targets from any host
192    {
193        const release_step = b.step(
194            "release",
195            "Build release binaries for all platforms",
196        );
197        const release_targets = linux_targets ++ macos_targets;
198        for (release_targets) |release_target| {
199            const resolved = b.resolveTargetQuery(release_target);
200            const release_mod = b.createModule(.{
201                .root_source_file = b.path("src/main.zig"),
202                .target = resolved,
203                .optimize = .ReleaseSafe,
204                .link_libc = true,
205                .imports = &.{
206                    .{
207                        .name = "cross",
208                        .module = translator.mod,
209                    },
210                },
211            });
212            release_mod.addOptions("build_options", options);
213
214            if (b.lazyDependency("ghostty", .{
215                .target = resolved,
216                .optimize = .ReleaseSafe,
217                .@"emit-lib-vt" = true,
218                .@"emit-xcframework" = false,
219                .@"emit-macos-app" = false,
220            })) |release_dep| {
221                release_mod.addImport("ghostty-vt", release_dep.module("ghostty-vt"));
222            }
223
224            // const is_local_macos = resolved.result.os.tag == .macos;
225            const release_exe = b.addExecutable(.{
226                .name = "zmx",
227                // .use_llvm = true,
228                // .use_lld = !is_local_macos,
229                .root_module = release_mod,
230            });
231
232            const os_name = @tagName(release_target.os_tag orelse .linux);
233            const arch_name = @tagName(release_target.cpu_arch orelse .x86_64);
234            const tarball_name = b.fmt("zmx-{s}-{s}-{s}.tar.gz", .{ version, os_name, arch_name });
235
236            const tar = b.addSystemCommand(&.{ "tar", "-czf" });
237
238            const tarball = tar.addOutputFileArg(tarball_name);
239            tar.addArg("-C");
240            tar.addDirectoryArg(release_exe.getEmittedBinDirectory());
241            tar.addArg("zmx");
242
243            const shasum = b.addSystemCommand(&.{"sha256sum"});
244            shasum.addFileArg(tarball);
245            const shasum_output = shasum.captureStdOut(.{});
246
247            const install_tar = b.addInstallFile(tarball, b.fmt("dist/{s}", .{tarball_name}));
248            const install_sha = b.addInstallFile(
249                shasum_output,
250                b.fmt("dist/{s}.sha256", .{tarball_name}),
251            );
252            release_step.dependOn(&install_tar.step);
253            release_step.dependOn(&install_sha.step);
254        }
255    }
256}