cmake: add ghostty_vt_add_target() for cross-compilation

Add a ghostty_vt_add_target() CMake function that lets downstream
projects build libghostty-vt for a specific Zig target triple. The
function encapsulates zig discovery, build-type-to-optimize mapping,
the zig build invocation, and output path conventions so consumers
do not need to duplicate any of that logic. It creates named IMPORTED
targets (e.g. ghostty-vt-static-linux-amd64) that work alongside the
existing native ghostty-vt and ghostty-vt-static targets.

The build-type mapping is factored into a shared _GHOSTTY_ZIG_OPT_FLAG
variable used by both the native build and the new function.

The static library targets now propagate c++ as a link dependency on
non-Windows platforms, fixing link failures when consumers use static
linking with the default SIMD-enabled build.

A new example/c-vt-cmake-cross/ demonstrates end-to-end cross-
compilation using zig cc as the C compiler, auto-detecting a cross
target based on the host OS.
This commit is contained in:
Mitchell Hashimoto
2026-04-09 21:01:54 -07:00
parent 48a01b8bd5
commit f2e299fb46
9 changed files with 472 additions and 31 deletions

View File

@@ -279,10 +279,9 @@ fn initLib(
// For static libraries with vendored SIMD dependencies, combine
// all archives into a single fat archive so consumers only need
// to link one file. Skip on Windows where ar/libtool aren't available.
// to link one file.
if (kind == .static and
zig.simd_libs.items.len > 0 and
target.result.os.tag != .windows)
zig.simd_libs.items.len > 0)
{
var sources: SharedDeps.LazyPathList = .empty;
try sources.append(b.allocator, lib.getEmittedBin());
@@ -329,26 +328,17 @@ fn combineArchives(
return .{ .step = libtool.step, .output = libtool.output };
}
// On non-Darwin, use an MRI script with ar -M to combine archives
// directly without extracting. This avoids issues with ar x
// producing full-path member names and read-only permissions.
const run = RunStep.create(b, "combine-archives ghostty-vt");
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
,
"_",
// On non-Darwin, use a build tool that generates an MRI script and
// pipes it to `zig ar -M`. This works on all platforms including
// Windows (the previous /bin/sh approach did not).
const tool = b.addExecutable(.{
.name = "combine_archives",
.root_module = b.createModule(.{
.root_source_file = b.path("src/build/combine_archives.zig"),
.target = b.graph.host,
}),
});
const run = b.addRunArtifact(tool);
const output = run.addOutputFileArg("libghostty-vt.a");
for (sources) |source| run.addFileArg(source);

View File

@@ -0,0 +1,54 @@
//! Build tool that combines multiple static archives into a single fat
//! archive using an MRI script piped to `zig ar -M`.
//!
//! MRI scripts require stdin piping (`ar -M < script`), which can't be
//! expressed as a single command in the zig build system's RunStep. The
//! previous approach used `/bin/sh -c` to do the piping, but that isn't
//! available on Windows. This tool handles both the script generation
//! and the piping in a single cross-platform executable.
//!
//! Usage: combine_archives <output.a> <input1.a> [input2.a ...]
const std = @import("std");
pub fn main() !void {
var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
const alloc = gpa.allocator();
const args = try std.process.argsAlloc(alloc);
if (args.len < 3) {
std.log.err("usage: combine_archives <output> <input...>", .{});
std.process.exit(1);
}
const output_path = args[1];
const inputs = args[2..];
// Build the MRI script.
var script: std.ArrayListUnmanaged(u8) = .empty;
try script.appendSlice(alloc, "CREATE ");
try script.appendSlice(alloc, output_path);
try script.append(alloc, '\n');
for (inputs) |input| {
try script.appendSlice(alloc, "ADDLIB ");
try script.appendSlice(alloc, input);
try script.append(alloc, '\n');
}
try script.appendSlice(alloc, "SAVE\nEND\n");
var child: std.process.Child = .init(&.{ "zig", "ar", "-M" }, alloc);
child.stdin_behavior = .Pipe;
child.stdout_behavior = .Inherit;
child.stderr_behavior = .Inherit;
try child.spawn();
try child.stdin.?.writeAll(script.items);
child.stdin.?.close();
child.stdin = null;
const term = try child.wait();
if (term.Exited != 0) {
std.log.err("zig ar -M exited with code {d}", .{term.Exited});
std.process.exit(1);
}
}