aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/build
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-05-07 18:29:21 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-05-07 18:29:21 +0300
commitdcfe11a7588fcd42471159453810abc9b7720a57 (patch)
treec32a77266028e73ca2382b96c96f8438a5fcd621 /vendor/libghostty-vt/src/build
parent2dfed860103c4e4505ac7f25def9fe37da1e1e6a (diff)
build: update libghostty-vt vendor
vendor ghostty 063ac3e and remove the musl libc++ link workaround. expose the new selection formatter and hyperlink uri apis for follow-up rendering work. add manual artifact validation for static linux builds.
Diffstat (limited to 'vendor/libghostty-vt/src/build')
-rw-r--r--vendor/libghostty-vt/src/build/CombineArchivesStep.zig50
-rw-r--r--vendor/libghostty-vt/src/build/Config.zig60
-rw-r--r--vendor/libghostty-vt/src/build/GhosttyLib.zig119
-rw-r--r--vendor/libghostty-vt/src/build/GhosttyLibVt.zig306
-rw-r--r--vendor/libghostty-vt/src/build/GhosttyXCFramework.zig18
-rw-r--r--vendor/libghostty-vt/src/build/GhosttyZig.zig62
-rw-r--r--vendor/libghostty-vt/src/build/GitVersion.zig13
-rw-r--r--vendor/libghostty-vt/src/build/SharedDeps.zig54
-rw-r--r--vendor/libghostty-vt/src/build/combine_archives.zig55
9 files changed, 595 insertions, 142 deletions
diff --git a/vendor/libghostty-vt/src/build/CombineArchivesStep.zig b/vendor/libghostty-vt/src/build/CombineArchivesStep.zig
new file mode 100644
index 00000000..cebd8e9f
--- /dev/null
+++ b/vendor/libghostty-vt/src/build/CombineArchivesStep.zig
@@ -0,0 +1,50 @@
+//! Combines multiple static archives into a single fat archive.
+//! Uses libtool on Darwin and a cross-platform MRI-script build tool
+//! on all other platforms (including Windows).
+const std = @import("std");
+const builtin = @import("builtin");
+const LibtoolStep = @import("LibtoolStep.zig");
+
+/// Combine multiple static archives into a single fat archive.
+///
+/// `name` identifies the library (e.g. "ghostty-internal", "ghostty-vt").
+/// Output uses a `-fat` suffix to distinguish the combined archive from
+/// the single-library archive in the build cache.
+pub fn create(
+ b: *std.Build,
+ target: std.Build.ResolvedTarget,
+ name: []const u8,
+ sources: []const std.Build.LazyPath,
+) struct { step: *std.Build.Step, output: std.Build.LazyPath } {
+ if (target.result.os.tag.isDarwin() and
+ comptime builtin.os.tag.isDarwin())
+ {
+ const libtool = LibtoolStep.create(b, .{
+ .name = name,
+ .out_name = b.fmt("lib{s}-fat.a", .{name}),
+ .sources = @constCast(sources),
+ });
+ return .{ .step = libtool.step, .output = libtool.output };
+ }
+
+ // 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);
+ run.addArg(b.graph.zig_exe);
+ const out_name = if (target.result.os.tag == .windows)
+ b.fmt("{s}-fat.lib", .{name})
+ else
+ b.fmt("lib{s}-fat.a", .{name});
+ const output = run.addOutputFileArg(out_name);
+ for (sources) |source| run.addFileArg(source);
+
+ return .{ .step = &run.step, .output = output };
+}
diff --git a/vendor/libghostty-vt/src/build/Config.zig b/vendor/libghostty-vt/src/build/Config.zig
index 88968aab..0a994731 100644
--- a/vendor/libghostty-vt/src/build/Config.zig
+++ b/vendor/libghostty-vt/src/build/Config.zig
@@ -38,6 +38,7 @@ wasm_shared: bool = true,
/// Ghostty exe properties
exe_entrypoint: ExeEntrypoint = .ghostty,
version: std.SemanticVersion = .{ .major = 0, .minor = 0, .patch = 0 },
+lib_version: std.SemanticVersion = .{ .major = 0, .minor = 0, .patch = 0 },
/// Binary properties
pie: bool = false,
@@ -68,7 +69,7 @@ is_dep: bool = false,
/// Environmental properties
env: std.process.EnvMap,
-pub fn init(b: *std.Build, appVersion: []const u8) !Config {
+pub fn init(b: *std.Build, appVersion: []const u8, libVersion: []const u8) !Config {
// Setup our standard Zig target and optimize options, i.e.
// `-Doptimize` and `-Dtarget`.
const optimize = b.standardOptimizeOption(.{});
@@ -294,6 +295,20 @@ pub fn init(b: *std.Build, appVersion: []const u8) !Config {
};
};
+ // libghostty-vt properties
+
+ const lib_version_string = b.option(
+ []const u8,
+ "lib-version-string",
+ "A specific version string to use for the build of libghostty-vt. " ++
+ "If not specified, git will be used. This must be a semantic version.",
+ );
+
+ config.lib_version = if (lib_version_string) |v|
+ try std.SemanticVersion.parse(v)
+ else
+ try std.SemanticVersion.parse(libVersion);
+
//---------------------------------------------------------------
// Binary Properties
@@ -429,13 +444,22 @@ pub fn init(b: *std.Build, appVersion: []const u8) !Config {
bool,
"emit-xcframework",
"Build and install the xcframework for the macOS library.",
- ) orelse !config.emit_lib_vt and
- builtin.target.os.tag.isDarwin() and
- target.result.os.tag == .macos and
- config.app_runtime == .none and
- (!config.emit_bench and
- !config.emit_test_exe and
- !config.emit_helpgen);
+ ) orelse emit_xcfw: {
+ if (!builtin.target.os.tag.isDarwin() or target.result.os.tag != .macos)
+ break :emit_xcfw false;
+ if (config.emit_lib_vt) {
+ // In lib-vt mode default to whether xcodebuild is available,
+ // since xcodebuild is required to produce the XCFramework.
+ const path = expandPath(b.allocator, "xcodebuild") catch
+ break :emit_xcfw false;
+ defer if (path) |p| b.allocator.free(p);
+ break :emit_xcfw path != null;
+ }
+ break :emit_xcfw config.app_runtime == .none and
+ (!config.emit_bench and
+ !config.emit_test_exe and
+ !config.emit_helpgen);
+ };
config.emit_macos_app = b.option(
bool,
@@ -519,13 +543,20 @@ pub fn addOptions(self: *const Config, step: *std.Build.Step.Options) !void {
// Our version. We also add the string version so we don't need
// to do any allocations at runtime. This has to be long enough to
// accommodate realistic large branch names for dev versions.
- var buf: [1024]u8 = undefined;
+ var app_version_buf: [1024]u8 = undefined;
step.addOption(std.SemanticVersion, "app_version", self.version);
step.addOption([:0]const u8, "app_version_string", try std.fmt.bufPrintZ(
- &buf,
+ &app_version_buf,
"{f}",
.{self.version},
));
+ var lib_version_buf: [1024]u8 = undefined;
+ step.addOption(std.SemanticVersion, "lib_version", self.lib_version);
+ step.addOption([:0]const u8, "lib_version_string", try std.fmt.bufPrintZ(
+ &lib_version_buf,
+ "{f}",
+ .{self.lib_version},
+ ));
step.addOption(
ReleaseChannel,
"release_channel",
@@ -539,13 +570,16 @@ pub fn addOptions(self: *const Config, step: *std.Build.Step.Options) !void {
/// Returns the build options for the terminal module. This assumes a
/// Ghostty executable being built. Callers should modify this as needed.
-pub fn terminalOptions(self: *const Config) TerminalBuildOptions {
+pub fn terminalOptions(self: *const Config, artifact: TerminalBuildOptions.Artifact) TerminalBuildOptions {
return .{
- .artifact = .ghostty,
+ .artifact = artifact,
.simd = self.simd,
.oniguruma = true,
.c_abi = false,
- .version = self.version,
+ .version = switch (artifact) {
+ .ghostty => self.version,
+ .lib => self.lib_version,
+ },
.slow_runtime_safety = switch (self.optimize) {
.Debug => true,
.ReleaseSafe,
diff --git a/vendor/libghostty-vt/src/build/GhosttyLib.zig b/vendor/libghostty-vt/src/build/GhosttyLib.zig
index 4e15fbbf..b762da8b 100644
--- a/vendor/libghostty-vt/src/build/GhosttyLib.zig
+++ b/vendor/libghostty-vt/src/build/GhosttyLib.zig
@@ -2,9 +2,9 @@ const GhosttyLib = @This();
const std = @import("std");
const RunStep = std.Build.Step.Run;
+const CombineArchivesStep = @import("CombineArchivesStep.zig");
const Config = @import("Config.zig");
const SharedDeps = @import("SharedDeps.zig");
-const LibtoolStep = @import("LibtoolStep.zig");
const LipoStep = @import("LipoStep.zig");
/// The step that generates the file.
@@ -13,6 +13,8 @@ step: *std.Build.Step,
/// The final static library file
output: std.Build.LazyPath,
dsym: ?std.Build.LazyPath,
+pkg_config: ?std.Build.LazyPath,
+pkg_config_static: ?std.Build.LazyPath,
pub fn initStatic(
b: *std.Build,
@@ -46,30 +48,23 @@ 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,
- };
-
- // 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 = CombineArchivesStep.create(b, deps.config.target, "ghostty-internal", 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,
+ .pkg_config = null,
+ .pkg_config_static = null,
};
}
@@ -146,10 +141,20 @@ pub fn initShared(
break :dsymutil output;
};
+ // pkg-config
+ //
+ // pkg-config's --static only expands Libs.private / Requires.private;
+ // it doesn't rewrite Libs: 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(b, deps);
+
return .{
.step = &lib.step,
.output = lib.getEmittedBin(),
.dsym = dsymutil,
+ .pkg_config = pcs.shared,
+ .pkg_config_static = pcs.static,
};
}
@@ -168,7 +173,7 @@ pub fn initMacOSUniversal(
const universal = LipoStep.create(b, .{
.name = "ghostty",
- .out_name = "libghostty.a",
+ .out_name = "ghostty-internal.a",
.input_a = aarch64.output,
.input_b = x86_64.output,
});
@@ -180,13 +185,31 @@ pub fn initMacOSUniversal(
// You can't run dsymutil on a universal binary, you have to
// do it on the individual binaries.
.dsym = null,
+ .pkg_config = null,
+ .pkg_config_static = null,
};
}
pub fn install(self: *const GhosttyLib, name: []const u8) void {
const b = self.step.owner;
+ const step = b.getInstallStep();
const lib_install = b.addInstallLibFile(self.output, name);
- b.getInstallStep().dependOn(&lib_install.step);
+ step.dependOn(&lib_install.step);
+
+ if (self.pkg_config) |pc| {
+ step.dependOn(&b.addInstallFileWithDir(
+ pc,
+ .prefix,
+ "share/pkgconfig/ghostty-internal.pc",
+ ).step);
+ }
+ if (self.pkg_config_static) |pc| {
+ step.dependOn(&b.addInstallFileWithDir(
+ pc,
+ .prefix,
+ "share/pkgconfig/ghostty-internal-static.pc",
+ ).step);
+ }
}
pub fn installHeader(self: *const GhosttyLib) void {
@@ -197,3 +220,61 @@ pub fn installHeader(self: *const GhosttyLib) void {
);
b.getInstallStep().dependOn(&header_install.step);
}
+
+const PkgConfigFiles = struct {
+ shared: std.Build.LazyPath,
+ static: std.Build.LazyPath,
+};
+
+fn pkgConfigFiles(
+ b: *std.Build,
+ deps: *const SharedDeps,
+) PkgConfigFiles {
+ const os_tag = deps.config.target.result.os.tag;
+ const wf = b.addWriteFiles();
+
+ return .{
+ .shared = wf.add("ghostty-internal.pc", b.fmt(
+ \\prefix={s}
+ \\includedir=${{prefix}}/include
+ \\libdir=${{prefix}}/lib
+ \\
+ \\Name: ghostty-internal
+ \\URL: https://github.com/ghostty-org/ghostty
+ \\Description: Ghostty internal library (not for external use)
+ \\Version: {f}
+ \\Cflags: -I${{includedir}}
+ \\Libs: ${{libdir}}/{s}
+ \\Libs.private:
+ \\Requires.private:
+ , .{ b.install_prefix, deps.config.version, sharedLibraryName(os_tag) })),
+ .static = wf.add("ghostty-internal-static.pc", b.fmt(
+ \\prefix={s}
+ \\includedir=${{prefix}}/include
+ \\libdir=${{prefix}}/lib
+ \\
+ \\Name: ghostty-internal-static
+ \\URL: https://github.com/ghostty-org/ghostty
+ \\Description: Ghostty internal library, static (not for external use)
+ \\Version: {f}
+ \\Cflags: -I${{includedir}}
+ \\Libs: ${{libdir}}/{s}
+ \\Libs.private:
+ \\Requires.private:
+ , .{ b.install_prefix, deps.config.version, staticLibraryName(os_tag) })),
+ };
+}
+
+fn sharedLibraryName(os_tag: std.Target.Os.Tag) []const u8 {
+ return if (os_tag == .windows)
+ "ghostty-internal.dll"
+ else
+ "ghostty-internal.so";
+}
+
+fn staticLibraryName(os_tag: std.Target.Os.Tag) []const u8 {
+ return if (os_tag == .windows)
+ "ghostty-internal-static.lib"
+ else
+ "ghostty-internal.a";
+}
diff --git a/vendor/libghostty-vt/src/build/GhosttyLibVt.zig b/vendor/libghostty-vt/src/build/GhosttyLibVt.zig
index 3e1be477..65f945bf 100644
--- a/vendor/libghostty-vt/src/build/GhosttyLibVt.zig
+++ b/vendor/libghostty-vt/src/build/GhosttyLibVt.zig
@@ -4,9 +4,12 @@ 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 Config = @import("Config.zig");
const GhosttyZig = @import("GhosttyZig.zig");
-const LibtoolStep = @import("LibtoolStep.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,
@@ -21,6 +24,7 @@ kind: Kind,
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.
@@ -40,7 +44,7 @@ pub fn initWasm(
const exe = b.addExecutable(.{
.name = "ghostty-vt",
.root_module = zig.vt_c,
- .version = std.SemanticVersion{ .major = 0, .minor = 1, .patch = 0 },
+ .version = zig.version,
});
// Allow exported symbols to actually be exported.
@@ -82,6 +86,7 @@ pub fn initWasm(
.output = output,
.dsym = null,
.pkg_config = null,
+ .pkg_config_static = null,
};
}
@@ -99,6 +104,94 @@ pub fn initShared(
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.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,
@@ -113,7 +206,7 @@ fn initLib(
.name = if (kind == .static) "ghostty-vt-static" else "ghostty-vt",
.linkage = linkage,
.root_module = zig.vt_c,
- .version = std.SemanticVersion{ .major = 0, .minor = 1, .patch = 0 },
+ .version = zig.version,
});
lib.installHeadersDirectory(
b.path("include/ghostty"),
@@ -174,36 +267,27 @@ fn initLib(
};
// 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: 0.1.0
- \\Cflags: -I${{includedir}}
- \\Libs: -L${{libdir}} -lghostty-vt
- \\Libs.private: {s}
- \\Requires.private: {s}
- , .{ b.install_prefix, libsPrivate(zig), requiresPrivate(b) }));
- };
+ //
+ // 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. 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());
try sources.appendSlice(b.allocator, zig.simd_libs.items);
- const combined = combineArchives(b, target, sources.items);
+ const combined = CombineArchivesStep.create(b, target, "ghostty-vt", sources.items);
combined.step.dependOn(&lib.step);
return .{
@@ -212,7 +296,8 @@ fn initLib(
.kind = kind,
.output = combined.output,
.dsym = dsymutil,
- .pkg_config = pc,
+ .pkg_config = if (pcs) |v| v.shared else null,
+ .pkg_config_static = if (pcs) |v| v.static else null,
};
}
@@ -222,64 +307,79 @@ fn initLib(
.kind = kind,
.output = lib.getEmittedBin(),
.dsym = dsymutil,
- .pkg_config = pc,
+ .pkg_config = if (pcs) |v| v.shared else null,
+ .pkg_config_static = if (pcs) |v| v.static else null,
};
}
-/// 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.
+/// 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
@@ -294,6 +394,59 @@ fn requiresPrivate(b: *std.Build) []const u8 {
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(target: std.Target) bool {
+ _ = std.zig.LibCInstallation.findNative(.{
+ .allocator = std.heap.page_allocator,
+ .target = &target,
+ .verbose = false,
+ }) catch return false;
+ return true;
+}
+
pub fn install(
self: *const GhosttyLibVt,
step: *std.Build.Step,
@@ -307,4 +460,11 @@ pub fn install(
"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);
+ }
}
diff --git a/vendor/libghostty-vt/src/build/GhosttyXCFramework.zig b/vendor/libghostty-vt/src/build/GhosttyXCFramework.zig
index 3afeb907..a19dd18a 100644
--- a/vendor/libghostty-vt/src/build/GhosttyXCFramework.zig
+++ b/vendor/libghostty-vt/src/build/GhosttyXCFramework.zig
@@ -53,6 +53,16 @@ pub fn init(
}),
));
+ // Generate a headers directory with only ghostty.h and the module
+ // map. We can't use include/ directly because it also contains the
+ // libghostty-vt headers under include/ghostty/, which would trigger
+ // "umbrella header does not include header" warnings from Clang's
+ // module system.
+ const wf = b.addWriteFiles();
+ _ = wf.addCopyFile(b.path("include/ghostty.h"), "ghostty.h");
+ _ = wf.addCopyFile(b.path("include/module.modulemap"), "module.modulemap");
+ const headers = wf.getDirectory();
+
// The xcframework wraps our ghostty library so that we can link
// it to the final app built with Swift.
const xcframework = XCFrameworkStep.create(b, .{
@@ -62,24 +72,24 @@ pub fn init(
.universal => &.{
.{
.library = macos_universal.output,
- .headers = b.path("include"),
+ .headers = headers,
.dsym = macos_universal.dsym,
},
.{
.library = ios.output,
- .headers = b.path("include"),
+ .headers = headers,
.dsym = ios.dsym,
},
.{
.library = ios_sim.output,
- .headers = b.path("include"),
+ .headers = headers,
.dsym = ios_sim.dsym,
},
},
.native => &.{.{
.library = macos_native.output,
- .headers = b.path("include"),
+ .headers = headers,
.dsym = macos_native.dsym,
}},
},
diff --git a/vendor/libghostty-vt/src/build/GhosttyZig.zig b/vendor/libghostty-vt/src/build/GhosttyZig.zig
index 4901180d..44c300e1 100644
--- a/vendor/libghostty-vt/src/build/GhosttyZig.zig
+++ b/vendor/libghostty-vt/src/build/GhosttyZig.zig
@@ -11,6 +11,9 @@ const TerminalBuildOptions = @import("../terminal/build_options.zig").Options;
vt: *std.Build.Module,
vt_c: *std.Build.Module,
+/// The libghostty-vt version
+version: std.SemanticVersion,
+
/// Static library paths for vendored SIMD dependencies. Populated
/// only when the dependencies are built from source (not provided
/// by the system via -Dsystem-integration). Used to produce a
@@ -22,8 +25,46 @@ pub fn init(
cfg: *const Config,
deps: *const SharedDeps,
) !GhosttyZig {
+ return initInner(b, cfg, deps, "ghostty-vt", "ghostty-vt-c");
+}
+
+/// Create a new GhosttyZig with modules retargeted to a different
+/// architecture. Used to produce universal (fat) binaries on macOS.
+pub fn retarget(
+ self: *const GhosttyZig,
+ b: *std.Build,
+ cfg: *const Config,
+ deps: *const SharedDeps,
+ target: std.Build.ResolvedTarget,
+) !GhosttyZig {
+ _ = self;
+ const retargeted_config = try b.allocator.create(Config);
+ retargeted_config.* = cfg.*;
+ retargeted_config.target = target;
+
+ const retargeted_deps = try b.allocator.create(SharedDeps);
+ retargeted_deps.* = try deps.retarget(b, target);
+
+ // Use unique module names to avoid collisions with the original target.
+ const arch_name = @tagName(target.result.cpu.arch);
+ return initInner(
+ b,
+ retargeted_config,
+ retargeted_deps,
+ b.fmt("ghostty-vt-{s}", .{arch_name}),
+ b.fmt("ghostty-vt-c-{s}", .{arch_name}),
+ );
+}
+
+fn initInner(
+ b: *std.Build,
+ cfg: *const Config,
+ deps: *const SharedDeps,
+ vt_name: []const u8,
+ vt_c_name: []const u8,
+) !GhosttyZig {
// Terminal module build options
- var vt_options = cfg.terminalOptions();
+ var vt_options = cfg.terminalOptions(.lib);
vt_options.artifact = .lib;
// We presently don't allow Oniguruma in our Zig module at all.
// We should expose this as a build option in the future so we can
@@ -34,7 +75,7 @@ pub fn init(
return .{
.vt = try initVt(
- "ghostty-vt",
+ vt_name,
b,
cfg,
deps,
@@ -43,7 +84,7 @@ pub fn init(
),
.vt_c = try initVt(
- "ghostty-vt-c",
+ vt_c_name,
b,
cfg,
deps,
@@ -55,6 +96,8 @@ pub fn init(
&simd_libs,
),
+ .version = cfg.lib_version,
+
.simd_libs = simd_libs,
};
}
@@ -76,13 +119,14 @@ fn initVt(
.target = cfg.target,
.optimize = cfg.optimize,
- // SIMD require libc/libcpp (both) but otherwise we don't care.
- // On MSVC, we must not use linkLibCpp because Zig passes
- // -nostdinc++ and adds its bundled libc++/libc++abi headers
- // which conflict with MSVC's C++ runtime. The MSVC SDK dirs
- // added via link_libc contain both C and C++ headers.
+ // SIMD requires libc. Vendored C++ dependencies are built with
+ // no-libcxx mode (HWY_NO_LIBCXX / SIMDUTF_NO_LIBCXX) so we
+ // don't need libcpp. System-provided simdutf headers still
+ // use C++ stdlib headers, so we need libcpp in that case.
.link_libc = if (cfg.simd) true else null,
- .link_libcpp = if (cfg.simd and cfg.target.result.abi != .msvc) true else null,
+ .link_libcpp = if (cfg.simd and
+ b.systemIntegrationOption("simdutf", .{}) and
+ cfg.target.result.abi != .msvc) true else null,
});
vt.addOptions("build_options", general_options);
vt_options.add(b, vt);
diff --git a/vendor/libghostty-vt/src/build/GitVersion.zig b/vendor/libghostty-vt/src/build/GitVersion.zig
index 8b368d2c..41cc7f84 100644
--- a/vendor/libghostty-vt/src/build/GitVersion.zig
+++ b/vendor/libghostty-vt/src/build/GitVersion.zig
@@ -29,11 +29,14 @@ pub fn detect(b: *std.Build) !Version {
error.ExitCodeFailure => return error.GitNotRepository,
else => return err,
};
- // Replace any '/' with '-' as including slashes will mess up building
- // the dist tarball - the tarball uses the branch as part of the
- // name and including slashes means that the tarball will end up in
- // subdirectories instead of where it's supposed to be.
- std.mem.replaceScalar(u8, tmp, '/', '-');
+
+ // Replace characters that are not valid in semantic version
+ // pre-release identifiers (which only allow [0-9A-Za-z-]).
+ // Slashes would also mess up dist tarball paths.
+ for (tmp) |*c| {
+ if (!std.ascii.isAlphanumeric(c.*) and c.* != '-') c.* = '-';
+ }
+
break :b tmp;
};
diff --git a/vendor/libghostty-vt/src/build/SharedDeps.zig b/vendor/libghostty-vt/src/build/SharedDeps.zig
index aa63c082..b68be92d 100644
--- a/vendor/libghostty-vt/src/build/SharedDeps.zig
+++ b/vendor/libghostty-vt/src/build/SharedDeps.zig
@@ -133,7 +133,7 @@ pub fn add(
step.root_module.addOptions("build_options", self.options);
// Every exe needs the terminal options
- self.config.terminalOptions().add(b, step.root_module);
+ self.config.terminalOptions(.ghostty).add(b, step.root_module);
// C imports for locale constants and functions
{
@@ -412,7 +412,16 @@ pub fn add(
// C files
step.linkLibC();
step.addIncludePath(b.path("src/stb"));
- step.addCSourceFiles(.{ .files = &.{"src/stb/stb.c"} });
+ // Disable ubsan for MSVC: Zig's ubsan runtime cannot be bundled
+ // on Windows (LNK4229), leaving __ubsan_handle_* unresolved when
+ // the static archive is consumed by an external linker.
+ step.addCSourceFiles(.{
+ .files = &.{"src/stb/stb.c"},
+ .flags = if (step.rootModuleTarget().abi == .msvc)
+ &.{ "-fno-sanitize=undefined", "-fno-sanitize-trap=undefined" }
+ else
+ &.{},
+ });
if (step.rootModuleTarget().os.tag == .linux) {
step.addIncludePath(b.path("src/apprt/gtk"));
}
@@ -753,6 +762,7 @@ pub fn addSimd(
) !void {
const target = m.resolved_target.?;
const optimize = m.optimize.?;
+ const system_highway = b.systemIntegrationOption("highway", .{ .default = false });
// Simdutf
if (b.systemIntegrationOption("simdutf", .{})) {
@@ -761,6 +771,7 @@ pub fn addSimd(
if (b.lazyDependency("simdutf", .{
.target = target,
.optimize = optimize,
+ .no_libcxx = true,
})) |simdutf_dep| {
m.linkLibrary(simdutf_dep.artifact("simdutf"));
if (static_libs) |v| try v.append(
@@ -771,7 +782,7 @@ pub fn addSimd(
}
// Highway
- if (b.systemIntegrationOption("highway", .{ .default = false })) {
+ if (system_highway) {
m.linkSystemLibrary("libhwy", dynamic_link_opts);
} else {
if (b.lazyDependency("highway", .{
@@ -786,18 +797,6 @@ pub fn addSimd(
}
}
- // utfcpp - This is used as a dependency on our hand-written C++ code
- if (b.lazyDependency("utfcpp", .{
- .target = target,
- .optimize = optimize,
- })) |utfcpp_dep| {
- m.linkLibrary(utfcpp_dep.artifact("utfcpp"));
- if (static_libs) |v| try v.append(
- b.allocator,
- utfcpp_dep.artifact("utfcpp").getEmittedBin(),
- );
- }
-
// SIMD C++ files
m.addIncludePath(b.path("src"));
{
@@ -830,10 +829,27 @@ pub fn addSimd(
"-std=c++17",
);
- // Disable ubsan for MSVC to avoid undefined references to
- // __ubsan_handle_* symbols that require a runtime we don't link
- // and bundle. Hopefully we can fix this one day since ubsan is nice!
- if (target.result.abi == .msvc) try flags.appendSlice(b.allocator, &.{
+ // Keep our SIMD sources in the same Highway header mode as the
+ // vendored package build so HWY's inline dispatch/runtime helpers
+ // have a consistent ABI.
+ if (!system_highway) try flags.append(
+ b.allocator,
+ "-DHWY_NO_LIBCXX",
+ );
+
+ // When using the vendored simdutf, build its headers in no-libcxx
+ // mode so we don't need C++ standard library headers at all.
+ // System simdutf headers may not support this define.
+ if (!b.systemIntegrationOption("simdutf", .{})) try flags.append(
+ b.allocator,
+ "-DSIMDUTF_NO_LIBCXX",
+ );
+
+ // Disable ubsan for Windows C/C++ objects to avoid undefined
+ // __ubsan_handle_* references. The Zig libraries on Windows don't
+ // currently bundle a matching UBSan runtime for these objects in
+ // our build configurations (this affects both MSVC and GNU ABIs).
+ if (target.result.os.tag == .windows) try flags.appendSlice(b.allocator, &.{
"-fno-sanitize=undefined",
"-fno-sanitize-trap=undefined",
});
diff --git a/vendor/libghostty-vt/src/build/combine_archives.zig b/vendor/libghostty-vt/src/build/combine_archives.zig
new file mode 100644
index 00000000..04ec8e97
--- /dev/null
+++ b/vendor/libghostty-vt/src/build/combine_archives.zig
@@ -0,0 +1,55 @@
+//! 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 <zig_exe> <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 < 4) {
+ std.log.err("usage: combine_archives <zig_exe> <output> <input...>", .{});
+ std.process.exit(1);
+ }
+
+ const zig_exe = args[1];
+ const output_path = args[2];
+ const inputs = args[3..];
+
+ // 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_exe, "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);
+ }
+}