Eric Bower
·
2025-12-12
build.zig
1const std = @import("std");
2
3const linux_targets: []const std.Target.Query = &.{
4 .{ .cpu_arch = .x86_64, .os_tag = .linux, .abi = .musl },
5 .{ .cpu_arch = .aarch64, .os_tag = .linux, .abi = .musl },
6};
7
8const macos_targets: []const std.Target.Query = &.{
9 .{ .cpu_arch = .x86_64, .os_tag = .macos },
10 .{ .cpu_arch = .aarch64, .os_tag = .macos },
11};
12
13pub fn build(b: *std.Build) void {
14 const target = b.standardTargetOptions(.{});
15 const optimize = b.standardOptimizeOption(.{});
16 const version = b.option([]const u8, "version", "Version string for release") orelse
17 @as([]const u8, @import("build.zig.zon").version);
18
19 const run_step = b.step("run", "Run the app");
20 const test_step = b.step("test", "Run unit tests");
21
22 const options = b.addOptions();
23 options.addOption([]const u8, "version", version);
24 options.addOption([]const u8, "ghostty_version", @import("build.zig.zon").dependencies.ghostty.hash);
25
26 const exe_mod = b.createModule(.{
27 .root_source_file = b.path("src/main.zig"),
28 .target = target,
29 .optimize = optimize,
30 });
31 exe_mod.addOptions("build_options", options);
32
33 // You'll want to use a lazy dependency here so that ghostty is only
34 // downloaded if you actually need it.
35 if (b.lazyDependency("ghostty", .{
36 .target = target,
37 .optimize = optimize,
38 })) |dep| {
39 exe_mod.addImport(
40 "ghostty-vt",
41 dep.module("ghostty-vt"),
42 );
43 }
44
45 // Exe
46 const exe = b.addExecutable(.{
47 .name = "zmx",
48 .root_module = exe_mod,
49 });
50 exe.linkLibC();
51
52 b.installArtifact(exe);
53
54 // Run
55 const run_cmd = b.addRunArtifact(exe);
56 run_cmd.step.dependOn(b.getInstallStep());
57 if (b.args) |args| run_cmd.addArgs(args);
58 run_step.dependOn(&run_cmd.step);
59
60 // Test
61 const exe_unit_tests = b.addTest(.{
62 .root_module = exe_mod,
63 });
64 const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
65 test_step.dependOn(&run_exe_unit_tests.step);
66
67 // This is where the interesting part begins.
68 // As you can see we are re-defining the same executable but
69 // we're binding it to a dedicated build step.
70 const exe_check = b.addExecutable(.{
71 .name = "zmx",
72 .root_module = exe_mod,
73 });
74 exe_check.linkLibC();
75 // There is no `b.installArtifact(exe_check);` here.
76
77 // Finally we add the "check" step which will be detected
78 // by ZLS and automatically enable Build-On-Save.
79 // If you copy this into your `build.zig`, make sure to rename 'foo'
80 const check = b.step("check", "Check if foo compiles");
81 check.dependOn(&exe_check.step);
82
83 // Release step - macOS can cross-compile to Linux, but Linux cannot cross-compile to macOS (needs SDK)
84 const native_os = @import("builtin").os.tag;
85 const release_targets = if (native_os == .macos) linux_targets ++ macos_targets else linux_targets;
86 const release_step = b.step("release", "Build release binaries (macOS builds all, Linux builds Linux only)");
87 for (release_targets) |release_target| {
88 const resolved = b.resolveTargetQuery(release_target);
89 const release_mod = b.createModule(.{
90 .root_source_file = b.path("src/main.zig"),
91 .target = resolved,
92 .optimize = .ReleaseSafe,
93 });
94 release_mod.addOptions("build_options", options);
95
96 if (b.lazyDependency("ghostty", .{
97 .target = resolved,
98 .optimize = .ReleaseSafe,
99 })) |dep| {
100 release_mod.addImport("ghostty-vt", dep.module("ghostty-vt"));
101 }
102
103 const release_exe = b.addExecutable(.{
104 .name = "zmx",
105 .root_module = release_mod,
106 });
107 release_exe.linkLibC();
108
109 const os_name = @tagName(release_target.os_tag orelse .linux);
110 const arch_name = @tagName(release_target.cpu_arch orelse .x86_64);
111 const tarball_name = b.fmt("zmx-{s}-{s}-{s}.tar.gz", .{ version, os_name, arch_name });
112
113 const tar = b.addSystemCommand(&.{ "tar", "--no-xattrs", "-czf" });
114
115 const tarball = tar.addOutputFileArg(tarball_name);
116 tar.addArg("-C");
117 tar.addDirectoryArg(release_exe.getEmittedBinDirectory());
118 tar.addArg("zmx");
119
120 const shasum = b.addSystemCommand(&.{ "shasum", "-a", "256" });
121 shasum.addFileArg(tarball);
122 const shasum_output = shasum.captureStdOut();
123
124 const install_tar = b.addInstallFile(tarball, b.fmt("dist/{s}", .{tarball_name}));
125 const install_sha = b.addInstallFile(shasum_output, b.fmt("dist/{s}.sha256", .{tarball_name}));
126 release_step.dependOn(&install_tar.step);
127 release_step.dependOn(&install_sha.step);
128 }
129
130 // Upload step - rsync docs and dist to pgs.sh
131 const upload_step = b.step("upload", "Upload docs and dist to pgs.sh:/zmx");
132
133 const rsync_docs = b.addSystemCommand(&.{ "rsync", "-rv", "docs/", "pgs.sh:/zmx" });
134 const rsync_dist = b.addSystemCommand(&.{ "rsync", "-rv", "zig-out/dist/", "pgs.sh:/zmx/a" });
135
136 upload_step.dependOn(&rsync_docs.step);
137 upload_step.dependOn(&rsync_dist.step);
138}