build: produce fat static archive on all platforms

The static libghostty archive previously only bundled vendored
dependencies on macOS (via libtool). On Windows and Linux the
archive contained only the Zig-compiled code, leaving consumers
to discover and link freetype, harfbuzz, glslang, spirv-cross,
simdutf, oniguruma, and other vendored deps separately.

Now all platforms produce a single fat archive:
- macOS: libtool (unchanged)
- Windows: zig ar qcL --format=coff (LLVM archiver with the L
  flag to flatten nested archives; MSVC's lib.exe cannot read
  Zig-produced GNU-format archives)
- Linux: ar -M with MRI scripts (same as libghostty-vt)

This makes the static library self-contained for consumers like
.NET NativeAOT that link via the platform linker (MSVC link.exe)
and need all symbols resolved from a single archive.
This commit is contained in:
Alessandro De Blasis
2026-04-10 08:20:40 +02:00
parent e88c6c0991
commit 94e638d084

View File

@@ -48,29 +48,18 @@ pub fn initStatic(
}
// Add our dependencies. Get the list of all static deps so we can
// build a combined archive if necessary.
// build a combined archive.
var lib_list = try deps.add(lib);
try lib_list.append(b.allocator, lib.getEmittedBin());
if (!deps.config.target.result.os.tag.isDarwin()) return .{
.step = &lib.step,
.output = lib.getEmittedBin(),
.dsym = null,
.pkg_config = null,
.pkg_config_static = null,
};
// Create a static lib that contains all our dependencies.
const libtool = LibtoolStep.create(b, .{
.name = "ghostty",
.out_name = "libghostty-fat.a",
.sources = lib_list.items,
});
libtool.step.dependOn(&lib.step);
// Combine all archives into a single fat static library so
// consumers only need to link one file.
const combined = combineArchives(b, deps.config.target, lib_list.items);
combined.step.dependOn(&lib.step);
return .{
.step = libtool.step,
.output = libtool.output,
.step = combined.step,
.output = combined.output,
// Static libraries cannot have dSYMs because they aren't linked.
.dsym = null,
@@ -232,6 +221,61 @@ pub fn installHeader(self: *const GhosttyLib) void {
b.getInstallStep().dependOn(&header_install.step);
}
/// Combine multiple static archives into a single fat archive.
/// Uses libtool on Darwin, lib.exe on Windows, and ar MRI scripts
/// on other platforms.
fn combineArchives(
b: *std.Build,
target: std.Build.ResolvedTarget,
sources: []const std.Build.LazyPath,
) struct { step: *std.Build.Step, output: std.Build.LazyPath } {
const os_tag = target.result.os.tag;
if (os_tag.isDarwin()) {
const libtool = LibtoolStep.create(b, .{
.name = "ghostty",
.out_name = "libghostty-fat.a",
.sources = @constCast(sources),
});
return .{ .step = libtool.step, .output = libtool.output };
}
if (os_tag == .windows) {
// Zig's bundled LLVM archiver can flatten COFF archives with
// the L modifier. MSVC's lib.exe cannot read Zig-produced
// GNU-format archives, so we use zig ar instead.
const run = RunStep.create(b, "combine-archives ghostty");
run.addArgs(&.{ b.graph.zig_exe, "ar", "qcL", "--format=coff" });
const output = run.addOutputFileArg("ghostty-fat.lib");
for (sources) |source| run.addFileArg(source);
return .{ .step = &run.step, .output = output };
}
// On Linux and other platforms, use an MRI script with ar -M to
// combine archives directly without extracting.
const run = RunStep.create(b, "combine-archives ghostty");
run.addArgs(&.{
"/bin/sh", "-c",
\\set -e
\\out="$1"; shift
\\script="CREATE $out"
\\for a in "$@"; do
\\ script="$script
\\ADDLIB $a"
\\done
\\script="$script
\\SAVE
\\END"
\\echo "$script" | ar -M
,
"_",
});
const output = run.addOutputFileArg("libghostty-fat.a");
for (sources) |source| run.addFileArg(source);
return .{ .step = &run.step, .output = output };
}
const PkgConfigFiles = struct {
shared: std.Build.LazyPath,
static: std.Build.LazyPath,