summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMitchell Hashimoto <m@mitchellh.com>2025-09-28 14:22:36 -0700
committerGitHub <noreply@github.com>2025-09-28 14:22:36 -0700
commitc5145d552e22afb3f657ddb13d7ec3b5e7ecea54 (patch)
treeaabf264dedfe54f323409d150a234c61f9b89f68
parent08ecbe328a2ef1feec00f0aee0b47755c22a81a7 (diff)
parentf614fb7c1b0bcf0f7256aea0040ec5425be09929 (diff)
build: use build options to configure terminal C ABI mode (#8945)
Fixes various issues: - C ABI detection was faulty, which caused some Zig programs to use the C ABI mode and some C programs not to. Let's be explicit. - Unit tests now tests C ABI mode. - Build binary no longer rebuilds on any terminal change (a regression). - Zig programs can choose to depend on the C ABI version of the terminal lib by using the `ghostty-vt-c` module.
-rw-r--r--build.zig9
-rw-r--r--src/build/Config.zig1
-rw-r--r--src/build/GhosttyLibVt.zig2
-rw-r--r--src/build/GhosttyZig.zig45
-rw-r--r--src/lib_vt.zig9
-rw-r--r--src/terminal/build_options.zig9
-rw-r--r--src/terminal/c/osc.zig8
-rw-r--r--src/terminal/main.zig9
-rw-r--r--src/terminal/osc.zig4
9 files changed, 72 insertions, 24 deletions
diff --git a/build.zig b/build.zig
index 008fc849e..62fa77511 100644
--- a/build.zig
+++ b/build.zig
@@ -255,6 +255,15 @@ pub fn build(b: *std.Build) !void {
});
const mod_vt_test_run = b.addRunArtifact(mod_vt_test);
test_lib_vt_step.dependOn(&mod_vt_test_run.step);
+
+ const mod_vt_c_test = b.addTest(.{
+ .root_module = mod.vt_c,
+ .target = config.target,
+ .optimize = config.optimize,
+ .filters = test_filters,
+ });
+ const mod_vt_c_test_run = b.addRunArtifact(mod_vt_c_test);
+ test_lib_vt_step.dependOn(&mod_vt_c_test_run.step);
}
// Tests
diff --git a/src/build/Config.zig b/src/build/Config.zig
index 474674d3a..0b7dae14d 100644
--- a/src/build/Config.zig
+++ b/src/build/Config.zig
@@ -498,6 +498,7 @@ pub fn terminalOptions(self: *const Config) TerminalBuildOptions {
.artifact = .ghostty,
.simd = self.simd,
.oniguruma = true,
+ .c_abi = false,
.slow_runtime_safety = switch (self.optimize) {
.Debug => true,
.ReleaseSafe,
diff --git a/src/build/GhosttyLibVt.zig b/src/build/GhosttyLibVt.zig
index 0029d6756..80f2bf9ad 100644
--- a/src/build/GhosttyLibVt.zig
+++ b/src/build/GhosttyLibVt.zig
@@ -26,7 +26,7 @@ pub fn initShared(
const target = zig.vt.resolved_target.?;
const lib = b.addSharedLibrary(.{
.name = "ghostty-vt",
- .root_module = zig.vt,
+ .root_module = zig.vt_c,
});
lib.installHeader(
b.path("include/ghostty/vt.h"),
diff --git a/src/build/GhosttyZig.zig b/src/build/GhosttyZig.zig
index f175eb957..a8d2726bc 100644
--- a/src/build/GhosttyZig.zig
+++ b/src/build/GhosttyZig.zig
@@ -5,18 +5,17 @@ const GhosttyZig = @This();
const std = @import("std");
const Config = @import("Config.zig");
const SharedDeps = @import("SharedDeps.zig");
+const TerminalBuildOptions = @import("../terminal/build_options.zig").Options;
+/// The `_c`-suffixed modules are built with the C ABI enabled.
vt: *std.Build.Module,
+vt_c: *std.Build.Module,
pub fn init(
b: *std.Build,
cfg: *const Config,
deps: *const SharedDeps,
) !GhosttyZig {
- // General build options
- const general_options = b.addOptions();
- try cfg.addOptions(general_options);
-
// Terminal module build options
var vt_options = cfg.terminalOptions();
vt_options.artifact = .lib;
@@ -25,7 +24,41 @@ pub fn init(
// conditionally do this.
vt_options.oniguruma = false;
- const vt = b.addModule("ghostty-vt", .{
+ return .{
+ .vt = try initVt(
+ "ghostty-vt",
+ b,
+ cfg,
+ deps,
+ vt_options,
+ ),
+
+ .vt_c = try initVt(
+ "ghostty-vt-c",
+ b,
+ cfg,
+ deps,
+ options: {
+ var dup = vt_options;
+ dup.c_abi = true;
+ break :options dup;
+ },
+ ),
+ };
+}
+
+fn initVt(
+ name: []const u8,
+ b: *std.Build,
+ cfg: *const Config,
+ deps: *const SharedDeps,
+ vt_options: TerminalBuildOptions,
+) !*std.Build.Module {
+ // General build options
+ const general_options = b.addOptions();
+ try cfg.addOptions(general_options);
+
+ const vt = b.addModule(name, .{
.root_source_file = b.path("src/lib_vt.zig"),
.target = cfg.target,
.optimize = cfg.optimize,
@@ -45,5 +78,5 @@ pub fn init(
try SharedDeps.addSimd(b, vt, null);
}
- return .{ .vt = vt };
+ return vt;
}
diff --git a/src/lib_vt.zig b/src/lib_vt.zig
index 37ab7ae68..b7ef9459a 100644
--- a/src/lib_vt.zig
+++ b/src/lib_vt.zig
@@ -7,6 +7,7 @@
//! by thousands of users for years. However, the API itself (functions,
//! types, etc.) may change without warning. We're working on stabilizing
//! this in the future.
+const lib = @This();
// The public API below reproduces a lot of terminal/main.zig but
// is separate because (1) we need our root file to be in `src/`
@@ -68,7 +69,7 @@ pub const Attribute = terminal.Attribute;
comptime {
// If we're building the C library (vs. the Zig module) then
// we want to reference the C API so that it gets exported.
- if (terminal.is_c_lib) {
+ if (@import("root") == lib) {
const c = terminal.c_api;
@export(&c.osc_new, .{ .name = "ghostty_osc_new" });
@export(&c.osc_free, .{ .name = "ghostty_osc_free" });
@@ -81,8 +82,8 @@ comptime {
test {
_ = terminal;
-
- // Tests always test the C API and shared C functions
- _ = terminal.c_api;
_ = @import("lib/main.zig");
+ if (comptime terminal.options.c_abi) {
+ _ = terminal.c_api;
+ }
}
diff --git a/src/terminal/build_options.zig b/src/terminal/build_options.zig
index 2085e2243..e209a56fa 100644
--- a/src/terminal/build_options.zig
+++ b/src/terminal/build_options.zig
@@ -1,8 +1,6 @@
const std = @import("std");
-/// True if we're building the C library libghostty-vt.
-pub const is_c_lib = @import("root") == @import("../lib_vt.zig");
-
+/// Options set by Zig build.zig and exposed via `terminal_options`.
pub const Options = struct {
/// The target artifact to build. This will gate some functionality.
artifact: Artifact,
@@ -26,6 +24,10 @@ pub const Options = struct {
/// generally be disabled in production builds.
slow_runtime_safety: bool,
+ /// Force C ABI mode on or off. If not set, then it will be set based on
+ /// Options.
+ c_abi: bool,
+
/// Add the required build options for the terminal module.
pub fn add(
self: Options,
@@ -34,6 +36,7 @@ pub const Options = struct {
) void {
const opts = b.addOptions();
opts.addOption(Artifact, "artifact", self.artifact);
+ opts.addOption(bool, "c_abi", self.c_abi);
opts.addOption(bool, "oniguruma", self.oniguruma);
opts.addOption(bool, "simd", self.simd);
opts.addOption(bool, "slow_runtime_safety", self.slow_runtime_safety);
diff --git a/src/terminal/c/osc.zig b/src/terminal/c/osc.zig
index c04626b69..d1998f4e1 100644
--- a/src/terminal/c/osc.zig
+++ b/src/terminal/c/osc.zig
@@ -73,9 +73,9 @@ test "command type" {
));
defer free(p);
- p.next('0');
- p.next(';');
- p.next('a');
- const cmd = p.end(0);
+ next(p, '0');
+ next(p, ';');
+ next(p, 'a');
+ const cmd = end(p, 0);
try testing.expectEqual(.change_window_title, commandType(cmd));
}
diff --git a/src/terminal/main.zig b/src/terminal/main.zig
index 7403ff309..6875ba89d 100644
--- a/src/terminal/main.zig
+++ b/src/terminal/main.zig
@@ -1,5 +1,4 @@
const builtin = @import("builtin");
-const build_options = @import("terminal_options");
const charsets = @import("charsets.zig");
const sanitize = @import("sanitize.zig");
@@ -21,7 +20,7 @@ pub const page = @import("page.zig");
pub const parse_table = @import("parse_table.zig");
pub const search = @import("search.zig");
pub const size = @import("size.zig");
-pub const tmux = if (build_options.tmux_control_mode) @import("tmux.zig") else struct {};
+pub const tmux = if (options.tmux_control_mode) @import("tmux.zig") else struct {};
pub const x11_color = @import("x11_color.zig");
pub const Charset = charsets.Charset;
@@ -62,9 +61,11 @@ pub const Attribute = sgr.Attribute;
pub const isSafePaste = sanitize.isSafePaste;
+pub const Options = @import("build_options.zig").Options;
+pub const options = @import("terminal_options");
+
/// This is set to true when we're building the C library.
-pub const is_c_lib = @import("build_options.zig").is_c_lib;
-pub const c_api = if (is_c_lib) @import("c/main.zig") else void;
+pub const c_api = if (options.c_abi) @import("c/main.zig") else void;
test {
@import("std").testing.refAllDecls(@This());
diff --git a/src/terminal/osc.zig b/src/terminal/osc.zig
index 71d2f8598..20b22d1ef 100644
--- a/src/terminal/osc.zig
+++ b/src/terminal/osc.zig
@@ -7,11 +7,11 @@ const osc = @This();
const std = @import("std");
const builtin = @import("builtin");
+const build_options = @import("terminal_options");
const mem = std.mem;
const assert = std.debug.assert;
const Allocator = mem.Allocator;
const LibEnum = @import("../lib/enum.zig").Enum;
-const is_c_lib = @import("build_options.zig").is_c_lib;
const RGB = @import("color.zig").RGB;
const kitty_color = @import("kitty/color.zig");
const osc_color = @import("osc/color.zig");
@@ -175,7 +175,7 @@ pub const Command = union(Key) {
conemu_guimacro: []const u8,
pub const Key = LibEnum(
- if (is_c_lib) .c else .zig,
+ if (build_options.c_abi) .c else .zig,
// NOTE: Order matters, see LibEnum documentation.
&.{
"invalid",