Files
ghostty/src/build/GhosttyLibVt.zig

311 lines
9.8 KiB
Zig

const GhosttyLibVt = @This();
const std = @import("std");
const builtin = @import("builtin");
const assert = std.debug.assert;
const RunStep = std.Build.Step.Run;
const GhosttyZig = @import("GhosttyZig.zig");
const LibtoolStep = @import("LibtoolStep.zig");
const SharedDeps = @import("SharedDeps.zig");
/// The step that generates the file.
step: *std.Build.Step,
/// The install step for the library output.
artifact: *std.Build.Step,
/// The kind of library
kind: Kind,
/// The final library file
output: std.Build.LazyPath,
dsym: ?std.Build.LazyPath,
pkg_config: ?std.Build.LazyPath,
/// The kind of library being built. This is similar to LinkMode but
/// also includes wasm which is an executable, not a library.
const Kind = enum {
wasm,
shared,
static,
};
pub fn initWasm(
b: *std.Build,
zig: *const GhosttyZig,
) !GhosttyLibVt {
const target = zig.vt.resolved_target.?;
assert(target.result.cpu.arch.isWasm());
const exe = b.addExecutable(.{
.name = "ghostty-vt",
.root_module = zig.vt_c,
.version = zig.version,
});
// Allow exported symbols to actually be exported.
exe.rdynamic = true;
// Export the indirect function table so that embedders (e.g. JS in
// a browser) can insert callback entries for terminal effects.
exe.export_table = true;
// There is no entrypoint for this wasm module.
exe.entry = .disabled;
// Zig's WASM linker doesn't support --growable-table, so the table
// is emitted with max == min and can't be grown from JS. Run a
// small Zig build tool that patches the binary's table section to
// remove the max limit.
const patch_run = patch: {
const patcher = b.addExecutable(.{
.name = "wasm_patch_growable_table",
.root_module = b.createModule(.{
.root_source_file = b.path("src/build/wasm_patch_growable_table.zig"),
.target = b.graph.host,
}),
});
break :patch b.addRunArtifact(patcher);
};
patch_run.addFileArg(exe.getEmittedBin());
const output = patch_run.addOutputFileArg("ghostty-vt.wasm");
const artifact_install = b.addInstallFileWithDir(
output,
.bin,
"ghostty-vt.wasm",
);
return .{
.step = &patch_run.step,
.artifact = &artifact_install.step,
.kind = .wasm,
.output = output,
.dsym = null,
.pkg_config = null,
};
}
pub fn initStatic(
b: *std.Build,
zig: *const GhosttyZig,
) !GhosttyLibVt {
return initLib(b, zig, .static);
}
pub fn initShared(
b: *std.Build,
zig: *const GhosttyZig,
) !GhosttyLibVt {
return initLib(b, zig, .dynamic);
}
fn initLib(
b: *std.Build,
zig: *const GhosttyZig,
linkage: std.builtin.LinkMode,
) !GhosttyLibVt {
const kind: Kind = switch (linkage) {
.static => .static,
.dynamic => .shared,
};
const target = zig.vt.resolved_target.?;
const lib = b.addLibrary(.{
.name = if (kind == .static) "ghostty-vt-static" else "ghostty-vt",
.linkage = linkage,
.root_module = zig.vt_c,
.version = zig.version,
});
lib.installHeadersDirectory(
b.path("include/ghostty"),
"ghostty",
.{ .include_extensions = &.{".h"} },
);
if (kind == .static) {
// These must be bundled since we're compiling into a static lib.
// Otherwise, you get undefined symbol errors. This could cause
// problems if you're linking multiple static Zig libraries but
// we'll cross that bridge when we get to it.
lib.bundle_compiler_rt = true;
lib.bundle_ubsan_rt = true;
// Enable PIC so the static library can be linked into PIE
// executables, which is the default on most Linux distributions.
lib.root_module.pic = true;
}
if (target.result.os.tag == .windows) {
// Zig's ubsan emits /exclude-symbols linker directives that
// are incompatible with the MSVC linker (LNK4229).
lib.bundle_ubsan_rt = false;
}
if (lib.rootModuleTarget().abi.isAndroid()) {
// Support 16kb page sizes, required for Android 15+.
lib.link_z_max_page_size = 16384; // 16kb
try @import("android_ndk").addPaths(b, lib);
}
if (lib.rootModuleTarget().os.tag.isDarwin()) {
// Self-hosted x86_64 doesn't work for darwin. It may not work
// for other platforms too but definitely darwin.
lib.use_llvm = true;
// This is required for codesign and dynamic linking to work.
lib.headerpad_max_install_names = true;
// If we're not cross compiling then we try to find the Apple
// SDK using standard Apple tooling.
if (builtin.os.tag.isDarwin()) try @import("apple_sdk").addPaths(b, lib);
}
// Get our debug symbols (only for shared libs; static libs aren't linked)
const dsymutil: ?std.Build.LazyPath = dsymutil: {
if (kind != .shared) break :dsymutil null;
if (!target.result.os.tag.isDarwin()) break :dsymutil null;
const dsymutil = RunStep.create(b, "dsymutil");
dsymutil.addArgs(&.{"dsymutil"});
dsymutil.addFileArg(lib.getEmittedBin());
dsymutil.addArgs(&.{"-o"});
const output = dsymutil.addOutputFileArg("libghostty-vt.dSYM");
break :dsymutil output;
};
// pkg-config
const pc: std.Build.LazyPath = pc: {
const wf = b.addWriteFiles();
break :pc wf.add("libghostty-vt.pc", b.fmt(
\\prefix={s}
\\includedir=${{prefix}}/include
\\libdir=${{prefix}}/lib
\\
\\Name: libghostty-vt
\\URL: https://github.com/ghostty-org/ghostty
\\Description: Ghostty VT library
\\Version: {f}
\\Cflags: -I${{includedir}}
\\Libs: -L${{libdir}} -lghostty-vt
\\Libs.private: {s}
\\Requires.private: {s}
, .{ b.install_prefix, zig.version, libsPrivate(zig), requiresPrivate(b) }));
};
// 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.
if (kind == .static and
zig.simd_libs.items.len > 0 and
target.result.os.tag != .windows)
{
var sources: SharedDeps.LazyPathList = .empty;
try sources.append(b.allocator, lib.getEmittedBin());
try sources.appendSlice(b.allocator, zig.simd_libs.items);
const combined = combineArchives(b, target, sources.items);
combined.step.dependOn(&lib.step);
return .{
.step = combined.step,
.artifact = &b.addInstallArtifact(lib, .{}).step,
.kind = kind,
.output = combined.output,
.dsym = dsymutil,
.pkg_config = pc,
};
}
return .{
.step = &lib.step,
.artifact = &b.addInstallArtifact(lib, .{}).step,
.kind = kind,
.output = lib.getEmittedBin(),
.dsym = dsymutil,
.pkg_config = pc,
};
}
/// Combine multiple static archives into a single fat archive.
/// Uses libtool on Darwin 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 } {
if (target.result.os.tag.isDarwin()) {
const libtool = LibtoolStep.create(b, .{
.name = "ghostty-vt",
.out_name = "libghostty-vt.a",
.sources = @constCast(sources),
});
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
,
"_",
});
const output = run.addOutputFileArg("libghostty-vt.a");
for (sources) |source| run.addFileArg(source);
return .{ .step = &run.step, .output = output };
}
/// Returns the Libs.private value for the pkg-config file.
/// This includes the C++ standard library needed by SIMD code.
///
/// Zig compiles C++ code with LLVM's libc++ (not GNU libstdc++),
/// so consumers linking the static library need a libc++-compatible
/// toolchain: `zig cc`, `clang`, or GCC with `-lc++` installed.
fn libsPrivate(
zig: *const GhosttyZig,
) []const u8 {
return if (zig.vt_c.link_libcpp orelse false) "-lc++" else "";
}
/// Returns the Requires.private value for the pkg-config file.
/// When SIMD dependencies are provided by the system (via
/// -Dsystem-integration), we reference their pkg-config names so
/// that downstream consumers pick them up transitively.
fn requiresPrivate(b: *std.Build) []const u8 {
const system_simdutf = b.systemIntegrationOption("simdutf", .{});
const system_highway = b.systemIntegrationOption("highway", .{ .default = false });
if (system_simdutf and system_highway) return "simdutf, libhwy";
if (system_simdutf) return "simdutf";
if (system_highway) return "libhwy";
return "";
}
pub fn install(
self: *const GhosttyLibVt,
step: *std.Build.Step,
) void {
const b = step.owner;
step.dependOn(self.artifact);
if (self.pkg_config) |pkg_config| {
step.dependOn(&b.addInstallFileWithDir(
pkg_config,
.prefix,
"share/pkgconfig/libghostty-vt.pc",
).step);
}
}