- commit
- fac9795
- parent
- 7cdb333
- author
- Eric Bower
- date
- 2026-03-23 14:16:32 -0400 EDT
chore: dockerfile and ci file I'm experimenting with using zmx as a job engine for CI.
5 files changed,
+144,
-89
+2,
-0
1@@ -0,0 +1,2 @@
2+.git
3+zig-out
+20,
-0
1@@ -0,0 +1,20 @@
2+FROM alpine:3.23
3+
4+RUN apk add curl git
5+
6+ARG ZIG_VERSION=0.15.2
7+RUN curl -L -o /tmp/zig.tar.xz https://ziglang.org/download/${ZIG_VERSION}/zig-x86_64-linux-${ZIG_VERSION}.tar.xz && \
8+ cd /tmp && \
9+ tar -xvf zig.tar.xz && \
10+ mv zig-x86_64-linux-${ZIG_VERSION} /usr/local/zig && \
11+ ln -s /usr/local/zig/zig /usr/local/bin/zig
12+
13+ENV PATH=/usr/local/zig:$PATH
14+
15+WORKDIR /app
16+
17+COPY build.zig build.zig.zon src/ /app/
18+
19+RUN zig build
20+
21+CMD ["zig", "build"]
+95,
-87
1@@ -16,9 +16,6 @@ pub fn build(b: *std.Build) void {
2 const version = b.option([]const u8, "version", "Version string for release") orelse
3 @as([]const u8, @import("build.zig.zon").version);
4
5- const run_step = b.step("run", "Run the app");
6- const test_step = b.step("test", "Run unit tests");
7-
8 var code: u8 = 0;
9 const git_sha = std.mem.trim(u8, b.runAllowFail(
10 &.{ "git", "rev-parse", "--short", "HEAD" },
11@@ -29,7 +26,8 @@ pub fn build(b: *std.Build) void {
12 const options = b.addOptions();
13 options.addOption([]const u8, "version", version);
14 options.addOption([]const u8, "git_sha", git_sha);
15- options.addOption([]const u8, "ghostty_version", @import("build.zig.zon").dependencies.ghostty.hash);
16+ const ghostty_ver = @import("build.zig.zon").dependencies.ghostty.hash;
17+ options.addOption([]const u8, "ghostty_version", ghostty_ver);
18
19 const exe_mod = b.createModule(.{
20 .root_source_file = b.path("src/main.zig"),
21@@ -38,8 +36,6 @@ pub fn build(b: *std.Build) void {
22 });
23 exe_mod.addOptions("build_options", options);
24
25- // You'll want to use a lazy dependency here so that ghostty is only
26- // downloaded if you actually need it.
27 if (b.lazyDependency("ghostty", .{
28 .target = target,
29 .optimize = optimize,
30@@ -50,97 +46,109 @@ pub fn build(b: *std.Build) void {
31 );
32 }
33
34- // Exe
35- const exe = b.addExecutable(.{
36- .name = "zmx",
37- .root_module = exe_mod,
38- });
39- exe.linkLibC();
40-
41- b.installArtifact(exe);
42-
43 // Run
44- const run_cmd = b.addRunArtifact(exe);
45- run_cmd.step.dependOn(b.getInstallStep());
46- if (b.args) |args| run_cmd.addArgs(args);
47- run_step.dependOn(&run_cmd.step);
48+ {
49+ const run_step = b.step("run", "Run the app");
50+ const exe = b.addExecutable(.{
51+ .name = "zmx",
52+ .root_module = exe_mod,
53+ });
54+ exe.linkLibC();
55+ b.installArtifact(exe);
56+ const run_cmd = b.addRunArtifact(exe);
57+ run_cmd.step.dependOn(b.getInstallStep());
58+ if (b.args) |args| run_cmd.addArgs(args);
59+ run_step.dependOn(&run_cmd.step);
60+ }
61
62 // Test
63- const exe_unit_tests = b.addTest(.{
64- .root_module = exe_mod,
65- });
66- const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
67- test_step.dependOn(&run_exe_unit_tests.step);
68-
69- // This is where the interesting part begins.
70- // As you can see we are re-defining the same executable but
71- // we're binding it to a dedicated build step.
72- const exe_check = b.addExecutable(.{
73- .name = "zmx",
74- .root_module = exe_mod,
75- });
76- exe_check.linkLibC();
77- // There is no `b.installArtifact(exe_check);` here.
78-
79- // Finally we add the "check" step which will be detected
80- // by ZLS and automatically enable Build-On-Save.
81- // If you copy this into your `build.zig`, make sure to rename 'foo'
82- const check = b.step("check", "Check if foo compiles");
83- check.dependOn(&exe_check.step);
84-
85- // Release step - macOS can cross-compile to Linux, but Linux cannot cross-compile to macOS (needs SDK)
86- const native_os = @import("builtin").os.tag;
87- const release_targets = if (native_os == .macos) linux_targets ++ macos_targets else linux_targets;
88- const release_step = b.step("release", "Build release binaries (macOS builds all, Linux builds Linux only)");
89- for (release_targets) |release_target| {
90- const resolved = b.resolveTargetQuery(release_target);
91- const release_mod = b.createModule(.{
92- .root_source_file = b.path("src/main.zig"),
93- .target = resolved,
94- .optimize = .ReleaseSafe,
95+ {
96+ const test_step = b.step("test", "Run unit tests");
97+ const exe_unit_tests = b.addTest(.{
98+ .root_module = exe_mod,
99 });
100- release_mod.addOptions("build_options", options);
101-
102- if (b.lazyDependency("ghostty", .{
103- .target = resolved,
104- .optimize = .ReleaseSafe,
105- })) |dep| {
106- release_mod.addImport("ghostty-vt", dep.module("ghostty-vt"));
107- }
108+ const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
109+ test_step.dependOn(&run_exe_unit_tests.step);
110+ }
111
112- const release_exe = b.addExecutable(.{
113+ // Check for LSP integration
114+ {
115+ const check = b.step("check", "Check if zmx compiles");
116+ const exe_check = b.addExecutable(.{
117 .name = "zmx",
118- .root_module = release_mod,
119+ .root_module = exe_mod,
120 });
121- release_exe.linkLibC();
122-
123- const os_name = @tagName(release_target.os_tag orelse .linux);
124- const arch_name = @tagName(release_target.cpu_arch orelse .x86_64);
125- const tarball_name = b.fmt("zmx-{s}-{s}-{s}.tar.gz", .{ version, os_name, arch_name });
126-
127- const tar = b.addSystemCommand(&.{ "tar", "--no-xattrs", "-czf" });
128-
129- const tarball = tar.addOutputFileArg(tarball_name);
130- tar.addArg("-C");
131- tar.addDirectoryArg(release_exe.getEmittedBinDirectory());
132- tar.addArg("zmx");
133+ exe_check.linkLibC();
134
135- const shasum = b.addSystemCommand(&.{ "shasum", "-a", "256" });
136- shasum.addFileArg(tarball);
137- const shasum_output = shasum.captureStdOut();
138-
139- const install_tar = b.addInstallFile(tarball, b.fmt("dist/{s}", .{tarball_name}));
140- const install_sha = b.addInstallFile(shasum_output, b.fmt("dist/{s}.sha256", .{tarball_name}));
141- release_step.dependOn(&install_tar.step);
142- release_step.dependOn(&install_sha.step);
143+ // Finally we add the "check" step which will be detected
144+ // by ZLS and automatically enable Build-On-Save.
145+ // If you copy this into your `build.zig`, make sure to rename 'foo'
146+ check.dependOn(&exe_check.step);
147 }
148
149- // Upload step - rsync docs and dist to pgs.sh
150- const upload_step = b.step("upload", "Upload docs and dist to pgs.sh:/zmx");
151+ // Release step - macOS can cross-compile to Linux,
152+ // but Linux cannot cross-compile to macOS (needs SDK)
153+ {
154+ const release_step = b.step(
155+ "release",
156+ "Build release binaries (macOS builds all, Linux builds Linux only)",
157+ );
158+ const native_os = @import("builtin").os.tag;
159+ const release_targets = if (native_os == .macos) linux_targets ++ macos_targets else linux_targets;
160+ for (release_targets) |release_target| {
161+ const resolved = b.resolveTargetQuery(release_target);
162+ const release_mod = b.createModule(.{
163+ .root_source_file = b.path("src/main.zig"),
164+ .target = resolved,
165+ .optimize = .ReleaseSafe,
166+ });
167+ release_mod.addOptions("build_options", options);
168+
169+ if (b.lazyDependency("ghostty", .{
170+ .target = resolved,
171+ .optimize = .ReleaseSafe,
172+ })) |dep| {
173+ release_mod.addImport("ghostty-vt", dep.module("ghostty-vt"));
174+ }
175+
176+ const release_exe = b.addExecutable(.{
177+ .name = "zmx",
178+ .root_module = release_mod,
179+ });
180+ release_exe.linkLibC();
181+
182+ const os_name = @tagName(release_target.os_tag orelse .linux);
183+ const arch_name = @tagName(release_target.cpu_arch orelse .x86_64);
184+ const tarball_name = b.fmt("zmx-{s}-{s}-{s}.tar.gz", .{ version, os_name, arch_name });
185+
186+ const tar = b.addSystemCommand(&.{ "tar", "--no-xattrs", "-czf" });
187+
188+ const tarball = tar.addOutputFileArg(tarball_name);
189+ tar.addArg("-C");
190+ tar.addDirectoryArg(release_exe.getEmittedBinDirectory());
191+ tar.addArg("zmx");
192+
193+ const shasum = b.addSystemCommand(&.{ "shasum", "-a", "256" });
194+ shasum.addFileArg(tarball);
195+ const shasum_output = shasum.captureStdOut();
196+
197+ const install_tar = b.addInstallFile(tarball, b.fmt("dist/{s}", .{tarball_name}));
198+ const install_sha = b.addInstallFile(
199+ shasum_output,
200+ b.fmt("dist/{s}.sha256", .{tarball_name}),
201+ );
202+ release_step.dependOn(&install_tar.step);
203+ release_step.dependOn(&install_sha.step);
204+ }
205+ }
206
207- const rsync_docs = b.addSystemCommand(&.{ "rsync", "-rv", "docs/", "pgs.sh:/zmx" });
208- const rsync_dist = b.addSystemCommand(&.{ "rsync", "-rv", "zig-out/dist/", "pgs.sh:/zmx/a" });
209+ // Upload artifacts to pgs
210+ {
211+ const upload_step = b.step("upload", "Upload docs and dist to pgs.sh:/zmx");
212+ const rsync_docs = b.addSystemCommand(&.{ "rsync", "-rv", "docs/", "pgs.sh:/zmx" });
213+ const rsync_dist = b.addSystemCommand(&.{ "rsync", "-rv", "zig-out/dist/", "pgs.sh:/zmx/a" });
214
215- upload_step.dependOn(&rsync_docs.step);
216- upload_step.dependOn(&rsync_dist.step);
217+ upload_step.dependOn(&rsync_docs.step);
218+ upload_step.dependOn(&rsync_dist.step);
219+ }
220 }
A
pico.sh
+19,
-0
1@@ -0,0 +1,19 @@
2+#!/usr/bin/env bash
3+set -xeo pipefail
4+
5+# This is a little experiement seeing how we could use zmx as a job engine for CI
6+
7+export ZMX_SESSION_PREFIX="ci-"
8+
9+zmx run build podman build -t zig .
10+zmx wait build
11+
12+zmx run fmt podman run --rm -it -v "$(pwd)":/app zig zig fmt --check .
13+zmx run test podman run --rm -it -v "$(pwd)":/app zig zig build test --summary all
14+zmx wait fmt test
15+
16+zmx kill build
17+zmx kill fmt
18+zmx kill test
19+
20+echo "success!"
+8,
-2
1@@ -835,10 +835,12 @@ fn wait(cfg: *Cfg, session_names: std.ArrayList([]const u8)) !void {
2 continue;
3 }
4 if (session.task_ended_at == 0) {
5- try stdout.print("still waiting task={s}\n", .{session.name});
6+ try stdout.print("waiting task={s}\n", .{session.name});
7 try stdout.flush();
8 continue;
9 }
10+ try stdout.print("completed task={s} exit_code={d}\n", .{ session.name, session.task_exit_code.? });
11+ try stdout.flush();
12 if (session.task_exit_code != 0) {
13 agg_exit_code = session.task_exit_code orelse 0;
14 }
15@@ -865,7 +867,11 @@ fn wait(cfg: *Cfg, session_names: std.ArrayList([]const u8)) !void {
16 max_seen = total;
17
18 if (total > 0 and total == done) {
19- try stdout.print("tasks completed!\n", .{});
20+ if (agg_exit_code == 0) {
21+ try stdout.print("task(s) completed!\n", .{});
22+ } else {
23+ try stdout.print("task(s) failed!\n", .{});
24+ }
25 try stdout.flush();
26 std.process.exit(agg_exit_code);
27 return;