1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
|
const GhosttyDist = @This();
const std = @import("std");
const Config = @import("Config.zig");
const SharedDeps = @import("SharedDeps.zig");
const GhosttyFrameData = @import("GhosttyFrameData.zig");
/// The final source tarball.
archive: std.Build.LazyPath,
/// The step to install the tarball.
install_step: *std.Build.Step,
/// The step to depend on
archive_step: *std.Build.Step,
/// The step to depend on for checking the dist
check_step: *std.Build.Step,
pub fn init(b: *std.Build, cfg: *const Config) !GhosttyDist {
// Get the resources we're going to inject into the source tarball.
const alloc = b.allocator;
var resources: std.ArrayListUnmanaged(Resource) = .empty;
{
const gtk = SharedDeps.gtkNgDistResources(b);
try resources.append(alloc, gtk.resources_c);
try resources.append(alloc, gtk.resources_h);
}
{
const framedata = GhosttyFrameData.distResources(b);
try resources.append(alloc, framedata.framedata);
}
// git archive to create the final tarball. "git archive" is the
// easiest way I can find to create a tarball that ignores stuff
// from gitignore and also supports adding files as well as removing
// dist-only files (the "export-ignore" git attribute).
const git_archive = b.addSystemCommand(&.{
"git",
"archive",
"--format=tgz",
});
// embed the Ghostty version in the tarball
{
const version = b.addWriteFiles().add("VERSION", b.fmt("{f}", .{cfg.version}));
// --add-file uses the most recent --prefix to determine the path
// in the archive to copy the file (the directory only).
git_archive.addArg(b.fmt("--prefix=ghostty-{f}/", .{
cfg.version,
}));
git_archive.addPrefixedFileArg("--add-file=", version);
}
// Add all of our resources into the tarball.
for (resources.items) |resource| {
// Our dist path basename may not match our generated file basename,
// and git archive requires this. To be safe, we copy the file once
// to ensure the basename matches and then use that as the final
// generated file.
const copied = b.addWriteFiles().addCopyFile(
resource.generated,
std.fs.path.basename(resource.dist),
);
// --add-file uses the most recent --prefix to determine the path
// in the archive to copy the file (the directory only).
git_archive.addArg(b.fmt("--prefix=ghostty-{f}/{s}/", .{
cfg.version,
std.fs.path.dirname(resource.dist).?,
}));
git_archive.addPrefixedFileArg("--add-file=", copied);
}
// Add our output
git_archive.addArgs(&.{
// This is important. Standard source tarballs extract into
// a directory named `project-version`. This is expected by
// standard tooling such as debhelper and rpmbuild.
b.fmt("--prefix=ghostty-{f}/", .{cfg.version}),
"-o",
});
const output = git_archive.addOutputFileArg(b.fmt(
"ghostty-{f}.tar.gz",
.{cfg.version},
));
git_archive.addArg("HEAD");
// The install step to put the dist into the build directory.
const install = b.addInstallFile(
output,
b.fmt("dist/ghostty-{f}.tar.gz", .{cfg.version}),
);
// The check step to ensure the archive works.
const check = b.addSystemCommand(&.{ "tar", "xvzf" });
check.addFileArg(output);
check.addArg("-C");
// This is the root Ghostty source dir of the extracted source tarball.
// i.e. this is way `build.zig` is.
const extract_dir = check
.addOutputDirectoryArg("ghostty")
.path(b, b.fmt("ghostty-{f}", .{cfg.version}));
// Check that tests pass within the extracted directory. This isn't
// a fully hermetic test because we're sharing the Zig cache. In
// the future we could add an option to use a totally new cache but
// in the interest of speed we don't do that for now and hope other
// CI catches any issues.
const check_test = step: {
const step = b.addSystemCommand(&.{ "zig", "build", "test" });
step.setCwd(extract_dir);
// Must be set so that Zig knows that this command doesn't
// have side effects and is being run for its exit code check.
// Zig will cache depending on its extract dir.
step.expectExitCode(0);
// Capture stderr so it doesn't spew into the parent build.
// On the flip side, if the test fails we won't know why so
// that sucks but we should have already ran tests at this point.
// NOTE(mitchellh): temporarily disabled to diagnose heisenbug
//_ = step.captureStdErr();
break :step step;
};
// Check that all our dist resources are at the proper path.
for (resources.items) |resource| {
const path = extract_dir.path(b, resource.dist);
const check_path = b.addCheckFile(path, .{});
check_test.step.dependOn(&check_path.step);
}
return .{
.archive = output,
.install_step = &install.step,
.archive_step = &git_archive.step,
.check_step = &check_test.step,
};
}
/// A dist resource is a resource that is built and distributed as part
/// of the source tarball with Ghostty. These aren't committed to the Git
/// repository but are built as part of the `zig build dist` command.
/// The purpose is to limit the number of build-time dependencies required
/// for downstream users and packagers.
pub const Resource = struct {
/// The relative path in the source tree where the resource will be
/// if it was pre-built. These are not checksummed or anything because the
/// assumption is that the source tarball itself is checksummed and signed.
dist: []const u8,
/// The path to the generated resource in the build system. By depending
/// on this you'll force it to regenerate. This does NOT point to the
/// "path" above.
generated: std.Build.LazyPath,
/// Returns the path to use for this resource.
pub fn path(self: *const Resource, b: *std.Build) std.Build.LazyPath {
// If the dist path exists at build compile time then we use it.
if (self.exists(b)) {
return b.path(self.dist);
}
// Otherwise we use the generated path.
return self.generated;
}
/// Returns true if the dist path exists at build time.
pub fn exists(self: *const Resource, b: *std.Build) bool {
if (std.fs.accessAbsolute(b.pathFromRoot(self.dist), .{})) {
// If we have a ".git" directory then we're a git checkout
// and we never want to use the dist path. This shouldn't happen
// so show a warning to the user.
if (std.fs.accessAbsolute(b.pathFromRoot(".git"), .{})) {
std.log.warn(
"dist resource '{s}' should not be in a git checkout",
.{self.dist},
);
return false;
} else |_| {}
return true;
} else |_| {
return false;
}
}
};
|