mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-07-31 04:39:01 +00:00
- benchmark: avoid buffers to avoid a memcpy - build: keep frame pointers on macOS. There was some debug changes from Zig 0.15 and this helps. Also, Apple actually requires/expects x29 to always be a frame pointer. - build/macos: force libSystem symbols instead of compiler-rt - global: add InitOpts.tool so that ghostty-gen/bench can parse their own actions in `+action` - quirks: provide our own vectorized memset. see the comment for more details why. - synthetic: fix UB by accessing global.io before it was initialized - terminal/hash_map: force inline for unique repr types. Zig 0.15 inlined and 0.16 doesn't, measured a huge slowdown in hyperlink benchmarks. - terminal: add explicit `@Vector` usage for storing a run of identical cells as well as for scanning printable cells. This auto-vectorized in Zig 0.15 but not in Zig 0.16. This produces the same assembly. - unicode: properties and LUT need power-of-two backing integer to avoid bad LLVM codegen
508 lines
16 KiB
Zig
508 lines
16 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 CombineArchivesStep = @import("CombineArchivesStep.zig");
|
|
const LibsystemOverrideStep = @import("LibsystemOverrideStep.zig");
|
|
const Config = @import("Config.zig");
|
|
const GhosttyZig = @import("GhosttyZig.zig");
|
|
const LipoStep = @import("LipoStep.zig");
|
|
const SharedDeps = @import("SharedDeps.zig");
|
|
const XCFrameworkStep = @import("XCFrameworkStep.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,
|
|
pkg_config_static: ?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,
|
|
.pkg_config_static = 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);
|
|
}
|
|
|
|
/// Apple platform targets for xcframework slices.
|
|
pub const ApplePlatform = enum {
|
|
macos_universal,
|
|
ios,
|
|
ios_simulator,
|
|
// tvOS, watchOS, and visionOS are not yet supported by Zig's
|
|
// standard library (missing PATH_MAX, mcontext fields, etc.).
|
|
|
|
/// Platforms that have device + simulator pairs, gated on SDK detection.
|
|
const sdk_platforms = [_]struct {
|
|
os_tag: std.Target.Os.Tag,
|
|
device: ApplePlatform,
|
|
simulator: ApplePlatform,
|
|
}{
|
|
.{ .os_tag = .ios, .device = .ios, .simulator = .ios_simulator },
|
|
};
|
|
};
|
|
|
|
/// Static libraries for each Apple platform, keyed by `ApplePlatform`.
|
|
pub const AppleLibs = std.EnumMap(ApplePlatform, GhosttyLibVt);
|
|
|
|
/// Build static libraries for all available Apple platforms.
|
|
/// Always builds a macOS universal (arm64 + x86_64) fat binary.
|
|
/// Additional platforms are included if their SDK is detected.
|
|
pub fn initStaticAppleUniversal(
|
|
b: *std.Build,
|
|
cfg: *const Config,
|
|
deps: *const SharedDeps,
|
|
zig: *const GhosttyZig,
|
|
) !AppleLibs {
|
|
var result: AppleLibs = .{};
|
|
|
|
// macOS universal (arm64 + x86_64)
|
|
const aarch64_zig = try zig.retarget(
|
|
b,
|
|
cfg,
|
|
deps,
|
|
Config.genericMacOSTarget(b, .aarch64),
|
|
);
|
|
const x86_64_zig = try zig.retarget(
|
|
b,
|
|
cfg,
|
|
deps,
|
|
Config.genericMacOSTarget(b, .x86_64),
|
|
);
|
|
const aarch64 = try initStatic(b, &aarch64_zig);
|
|
const x86_64 = try initStatic(b, &x86_64_zig);
|
|
const universal = LipoStep.create(b, .{
|
|
.name = "ghostty-vt",
|
|
.out_name = "libghostty-vt.a",
|
|
.input_a = aarch64.output,
|
|
.input_b = x86_64.output,
|
|
});
|
|
result.put(.macos_universal, .{
|
|
.step = universal.step,
|
|
.artifact = universal.step,
|
|
.kind = .static,
|
|
.output = universal.output,
|
|
.dsym = null,
|
|
.pkg_config = null,
|
|
.pkg_config_static = null,
|
|
});
|
|
|
|
// Additional Apple platforms, each gated on SDK availability.
|
|
for (ApplePlatform.sdk_platforms) |p| {
|
|
const target_query: std.Target.Query = .{
|
|
.cpu_arch = .aarch64,
|
|
.os_tag = p.os_tag,
|
|
.os_version_min = Config.osVersionMin(p.os_tag),
|
|
};
|
|
if (detectAppleSDK(
|
|
b.graph.io,
|
|
b.allocator,
|
|
&b.graph.environ_map,
|
|
b.resolveTargetQuery(target_query).result,
|
|
)) {
|
|
const dev_zig = try zig.retarget(b, cfg, deps, b.resolveTargetQuery(target_query));
|
|
result.put(p.device, try initStatic(b, &dev_zig));
|
|
|
|
const sim_zig = try zig.retarget(b, cfg, deps, b.resolveTargetQuery(.{
|
|
.cpu_arch = .aarch64,
|
|
.os_tag = p.os_tag,
|
|
.os_version_min = Config.osVersionMin(p.os_tag),
|
|
.abi = .simulator,
|
|
.cpu_model = .{ .explicit = &std.Target.aarch64.cpu.apple_a17 },
|
|
}));
|
|
result.put(p.simulator, try initStatic(b, &sim_zig));
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
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
|
|
//
|
|
// pkg-config's --static only expands Libs.private / Requires.private;
|
|
// it doesn't change -lghostty-vt into an archive-only reference when
|
|
// both shared and static libraries are installed. Install a dedicated
|
|
// static module so consumers can request the archive explicitly.
|
|
const pcs: ?PkgConfigFiles = if (kind == .shared)
|
|
pkgConfigFiles(b, zig, target.result.os.tag)
|
|
else
|
|
null;
|
|
|
|
// For static libraries with vendored SIMD dependencies, combine
|
|
// all archives into a single fat archive so consumers only need
|
|
// to link one file.
|
|
if (kind == .static and
|
|
zig.simd_libs.items.len > 0)
|
|
{
|
|
var sources: SharedDeps.LazyPathList = .empty;
|
|
try sources.append(b.allocator, lib.getEmittedBin());
|
|
try sources.appendSlice(b.allocator, zig.simd_libs.items);
|
|
|
|
const combined = CombineArchivesStep.create(b, target, "ghostty-vt", sources.items);
|
|
combined.step.dependOn(&lib.step);
|
|
|
|
// On Darwin, prefer libSystem's libc/libm over the bundled
|
|
// compiler-rt for consumers of this archive. See GhosttyLib
|
|
// and libsystem_override.sh for details.
|
|
const override = LibsystemOverrideStep.create(
|
|
b,
|
|
target,
|
|
combined.output,
|
|
"libghostty-vt-fat.a",
|
|
);
|
|
|
|
return .{
|
|
.step = override.step orelse combined.step,
|
|
.artifact = &b.addInstallArtifact(lib, .{}).step,
|
|
.kind = kind,
|
|
.output = override.output,
|
|
.dsym = dsymutil,
|
|
.pkg_config = if (pcs) |v| v.shared else null,
|
|
.pkg_config_static = if (pcs) |v| v.static else null,
|
|
};
|
|
}
|
|
|
|
// Same libSystem preference for the plain (no vendored SIMD)
|
|
// static archive; a no-op off Darwin.
|
|
const override: LibsystemOverrideStep.Result = if (kind == .static) LibsystemOverrideStep.create(
|
|
b,
|
|
target,
|
|
lib.getEmittedBin(),
|
|
"libghostty-vt-static.a",
|
|
) else .{
|
|
.step = null,
|
|
.output = lib.getEmittedBin(),
|
|
};
|
|
|
|
return .{
|
|
.step = override.step orelse &lib.step,
|
|
.artifact = &b.addInstallArtifact(lib, .{}).step,
|
|
.kind = kind,
|
|
.output = override.output,
|
|
.dsym = dsymutil,
|
|
.pkg_config = if (pcs) |v| v.shared else null,
|
|
.pkg_config_static = if (pcs) |v| v.static else null,
|
|
};
|
|
}
|
|
|
|
/// Returns the Libs.private value for the pkg-config file.
|
|
/// Vendored C++ dependencies are built in no-libcxx mode so consumers
|
|
/// don't need libc++. System-provided simdutf still requires it.
|
|
fn libsPrivate(
|
|
zig: *const GhosttyZig,
|
|
) []const u8 {
|
|
return if (zig.vt_c.link_libcpp orelse false) "-lc++" else "";
|
|
}
|
|
|
|
const PkgConfigFiles = struct {
|
|
shared: std.Build.LazyPath,
|
|
static: std.Build.LazyPath,
|
|
};
|
|
|
|
fn pkgConfigFiles(
|
|
b: *std.Build,
|
|
zig: *const GhosttyZig,
|
|
os_tag: std.Target.Os.Tag,
|
|
) PkgConfigFiles {
|
|
const wf = b.addWriteFiles();
|
|
const libs_private = libsPrivate(zig);
|
|
const requires_private = requiresPrivate(b);
|
|
|
|
return .{
|
|
.shared = 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, libs_private, requires_private })),
|
|
.static = wf.add("libghostty-vt-static.pc", b.fmt(
|
|
\\prefix={s}
|
|
\\includedir=${{prefix}}/include
|
|
\\libdir=${{prefix}}/lib
|
|
\\
|
|
\\Name: libghostty-vt-static
|
|
\\URL: https://github.com/ghostty-org/ghostty
|
|
\\Description: Ghostty VT library (static)
|
|
\\Version: {f}
|
|
\\Cflags: -I${{includedir}}
|
|
\\Libs: ${{libdir}}/{s}
|
|
\\Libs.private: {s}
|
|
\\Requires.private: {s}
|
|
, .{
|
|
b.install_prefix,
|
|
zig.version,
|
|
staticLibraryName(os_tag),
|
|
libs_private,
|
|
requires_private,
|
|
})),
|
|
};
|
|
}
|
|
|
|
fn staticLibraryName(os_tag: std.Target.Os.Tag) []const u8 {
|
|
return if (os_tag == .windows)
|
|
"ghostty-vt-static.lib"
|
|
else
|
|
"libghostty-vt.a";
|
|
}
|
|
|
|
/// 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 "";
|
|
}
|
|
|
|
/// Create an XCFramework bundle from Apple platform static libraries.
|
|
pub fn xcframework(
|
|
apple_libs: *const AppleLibs,
|
|
b: *std.Build,
|
|
) *XCFrameworkStep {
|
|
// Generate a headers directory with a module map for Swift PM.
|
|
// We can't use include/ directly because it contains a module map
|
|
// for GhosttyKit (the macOS app library).
|
|
const wf = b.addWriteFiles();
|
|
_ = wf.addCopyDirectory(
|
|
b.path("include/ghostty"),
|
|
"ghostty",
|
|
.{ .include_extensions = &.{".h"} },
|
|
);
|
|
_ = wf.add("module.modulemap",
|
|
\\module GhosttyVt {
|
|
\\ umbrella header "ghostty/vt.h"
|
|
\\ export *
|
|
\\}
|
|
\\
|
|
);
|
|
const headers = wf.getDirectory();
|
|
|
|
var libraries: [AppleLibs.len]XCFrameworkStep.Library = undefined;
|
|
var lib_count: usize = 0;
|
|
for (std.enums.values(ApplePlatform)) |platform| {
|
|
if (apple_libs.get(platform)) |lib| {
|
|
libraries[lib_count] = .{
|
|
.library = lib.output,
|
|
.headers = headers,
|
|
.dsym = null,
|
|
};
|
|
lib_count += 1;
|
|
}
|
|
}
|
|
|
|
return XCFrameworkStep.create(b, .{
|
|
.name = "ghostty-vt",
|
|
.out_path = b.pathJoin(&.{ b.install_prefix, "lib/ghostty-vt.xcframework" }),
|
|
.libraries = libraries[0..lib_count],
|
|
});
|
|
}
|
|
|
|
/// Returns true if the Apple SDK for the given target is installed.
|
|
fn detectAppleSDK(
|
|
io: std.Io,
|
|
alloc: std.mem.Allocator,
|
|
environ_map: *const std.process.Environ.Map,
|
|
target: std.Target,
|
|
) bool {
|
|
_ = std.zig.LibCInstallation.findNative(
|
|
alloc,
|
|
io,
|
|
.{
|
|
.environ_map = environ_map,
|
|
.target = &target,
|
|
.verbose = false,
|
|
},
|
|
) catch return false;
|
|
return true;
|
|
}
|
|
|
|
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);
|
|
}
|
|
if (self.pkg_config_static) |pkg_config_static| {
|
|
step.dependOn(&b.addInstallFileWithDir(
|
|
pkg_config_static,
|
|
.prefix,
|
|
"share/pkgconfig/libghostty-vt-static.pc",
|
|
).step);
|
|
}
|
|
}
|