diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-04-04 19:22:04 +0300 |
|---|---|---|
| committer | Can Celik <ogulcancelik@gmail.com> | 2026-04-05 01:23:06 +0300 |
| commit | 41dd733ca7123c9ac4bfc6c781697a660e95b052 (patch) | |
| tree | 3fc91815ca139ef2e0b281bba0abda4ef15175f4 /vendor/libghostty-vt/src/build/Config.zig | |
| parent | 55c138263e190a1e79d4b997bfb9397306c0ec1f (diff) | |
feat: finish ghostty pane backend integration
Diffstat (limited to 'vendor/libghostty-vt/src/build/Config.zig')
| -rw-r--r-- | vendor/libghostty-vt/src/build/Config.zig | 666 |
1 files changed, 666 insertions, 0 deletions
diff --git a/vendor/libghostty-vt/src/build/Config.zig b/vendor/libghostty-vt/src/build/Config.zig new file mode 100644 index 00000000..88968aab --- /dev/null +++ b/vendor/libghostty-vt/src/build/Config.zig @@ -0,0 +1,666 @@ +/// Build configuration. This is the configuration that is populated +/// during `zig build` to control the rest of the build process. +const Config = @This(); + +const std = @import("std"); +const builtin = @import("builtin"); + +const ApprtRuntime = @import("../apprt/runtime.zig").Runtime; +const FontBackend = @import("../font/backend.zig").Backend; +const RendererBackend = @import("../renderer/backend.zig").Backend; +const TerminalBuildOptions = @import("../terminal/build_options.zig").Options; +const XCFrameworkTarget = @import("xcframework.zig").Target; +const WasmTarget = @import("../os/wasm/target.zig").Target; +const expandPath = @import("../os/path.zig").expand; + +const gtk = @import("gtk.zig"); +const GitVersion = @import("GitVersion.zig"); + +/// Standard build configuration options. +optimize: std.builtin.OptimizeMode, +target: std.Build.ResolvedTarget, +xcframework_target: XCFrameworkTarget = .universal, +wasm_target: WasmTarget, + +/// Comptime interfaces +app_runtime: ApprtRuntime = .none, +renderer: RendererBackend = .opengl, +font_backend: FontBackend = .freetype, + +/// Feature flags +x11: bool = false, +wayland: bool = false, +sentry: bool = true, +simd: bool = true, +i18n: bool = true, +wasm_shared: bool = true, + +/// Ghostty exe properties +exe_entrypoint: ExeEntrypoint = .ghostty, +version: std.SemanticVersion = .{ .major = 0, .minor = 0, .patch = 0 }, + +/// Binary properties +pie: bool = false, +strip: bool = false, +patch_rpath: ?[]const u8 = null, + +/// Artifacts +flatpak: bool = false, +snap: bool = false, +emit_bench: bool = false, +emit_docs: bool = false, +emit_exe: bool = false, +emit_helpgen: bool = false, +emit_lib_vt: bool = false, +emit_macos_app: bool = false, +emit_terminfo: bool = false, +emit_termcap: bool = false, +emit_test_exe: bool = false, +emit_themes: bool = false, +emit_xcframework: bool = false, +emit_webdata: bool = false, +emit_unicode_table_gen: bool = false, + +/// True when Ghostty is being built as a dependency of another project +/// rather than as the root project. +is_dep: bool = false, + +/// Environmental properties +env: std.process.EnvMap, + +pub fn init(b: *std.Build, appVersion: []const u8) !Config { + // Setup our standard Zig target and optimize options, i.e. + // `-Doptimize` and `-Dtarget`. + const optimize = b.standardOptimizeOption(.{}); + const target = target: { + var result = b.standardTargetOptions(.{}); + + // If we're building for macOS and we're on macOS, we need to + // use a generic target to workaround compilation issues. + if (result.result.os.tag == .macos and + builtin.target.os.tag.isDarwin()) + { + result = genericMacOSTarget(b, result.query.cpu_arch); + } + + // On Windows, default to the MSVC ABI so that produced COFF + // objects (including compiler_rt) are compatible with the MSVC + // linker. Zig defaults to the GNU ABI which produces objects + // with invalid COMDAT sections that MSVC rejects (LNK1143). + // Only override when no explicit ABI was requested. + if (result.result.os.tag == .windows and + result.query.abi == null) + { + var query = result.query; + query.abi = .msvc; + result = b.resolveTargetQuery(query); + } + + // If we have no minimum OS version, we set the default based on + // our tag. Not all tags have a minimum so this may be null. + if (result.query.os_version_min == null) { + result.query.os_version_min = osVersionMin(result.result.os.tag); + } + + break :target result; + }; + + // Detect if Ghostty is a dependency of another project. + // dep_prefix is non-empty when this build is running as a dependency. + const is_dep = b.dep_prefix.len > 0; + + // This is set to true when we're building a system package. For now + // this is trivially detected using the "system_package_mode" bool + // but we may want to make this more sophisticated in the future. + const system_package = b.graph.system_package_mode; + + // This specifies our target wasm runtime. For now only one semi-usable + // one exists so this is hardcoded. + const wasm_target: WasmTarget = .browser; + + // Determine whether GTK supports X11 and Wayland. This is always safe + // to run even on non-Linux platforms because any failures result in + // defaults. + const gtk_targets = gtk.targets(b); + + // We use env vars throughout the build so we grab them immediately here. + var env = try std.process.getEnvMap(b.allocator); + errdefer env.deinit(); + + var config: Config = .{ + .optimize = optimize, + .target = target, + .wasm_target = wasm_target, + .is_dep = is_dep, + .env = env, + }; + + //--------------------------------------------------------------- + // Target-specific properties + config.xcframework_target = b.option( + XCFrameworkTarget, + "xcframework-target", + "The target for the xcframework.", + ) orelse .universal; + + //--------------------------------------------------------------- + // Comptime Interfaces + config.font_backend = b.option( + FontBackend, + "font-backend", + "The font backend to use for discovery and rasterization.", + ) orelse FontBackend.default(target.result, wasm_target); + + config.app_runtime = b.option( + ApprtRuntime, + "app-runtime", + "The app runtime to use. Not all values supported on all platforms.", + ) orelse ApprtRuntime.default(target.result); + + config.renderer = b.option( + RendererBackend, + "renderer", + "The app runtime to use. Not all values supported on all platforms.", + ) orelse RendererBackend.default(target.result, wasm_target); + + //--------------------------------------------------------------- + // Feature Flags + + config.flatpak = b.option( + bool, + "flatpak", + "Build for Flatpak (integrates with Flatpak APIs). Only has an effect targeting Linux.", + ) orelse false; + + config.snap = b.option( + bool, + "snap", + "Build for Snap (do specific Snap operations). Only has an effect targeting Linux.", + ) orelse false; + + config.sentry = b.option( + bool, + "sentry", + "Build with Sentry crash reporting. Default for macOS is true, false for any other system.", + ) orelse sentry: { + switch (target.result.os.tag) { + .macos, .ios => break :sentry true, + + // Note its false for linux because the crash reports on Linux + // don't have much useful information. + else => break :sentry false, + } + }; + + config.simd = b.option( + bool, + "simd", + "Build with SIMD-accelerated code paths. Results in significant performance improvements.", + ) orelse simd: { + // We can't build our SIMD dependencies for Wasm. Note that we may + // still use SIMD features in the Wasm-builds. + if (target.result.cpu.arch.isWasm()) break :simd false; + + break :simd true; + }; + + config.wayland = b.option( + bool, + "gtk-wayland", + "Enables linking against Wayland libraries when using the GTK rendering backend.", + ) orelse gtk_targets.wayland; + + config.x11 = b.option( + bool, + "gtk-x11", + "Enables linking against X11 libraries when using the GTK rendering backend.", + ) orelse gtk_targets.x11; + + config.i18n = b.option( + bool, + "i18n", + "Enables gettext-based internationalization. Enabled by default only for macOS, and other Unix-like systems like Linux and FreeBSD when using glibc.", + ) orelse switch (target.result.os.tag) { + .macos, .ios => true, + .linux, .freebsd => target.result.isGnuLibC(), + else => false, + }; + + //--------------------------------------------------------------- + // Ghostty Exe Properties + + const version_string = b.option( + []const u8, + "version-string", + "A specific version string to use for the build. " ++ + "If not specified, git will be used. This must be a semantic version.", + ); + + config.version = if (version_string) |v| + // If an explicit version is given, we always use it. + try std.SemanticVersion.parse(v) + else version: { + const app_version = try std.SemanticVersion.parse(appVersion); + + // Is ghostty a dependency? If so, skip git detection. + if (is_dep) break :version .{ + .major = app_version.major, + .minor = app_version.minor, + .patch = app_version.patch, + }; + + // If no explicit version is given, we try to detect it from git. + const vsn = GitVersion.detect(b) catch |err| switch (err) { + // If Git isn't available we just make an unknown dev version. + error.GitNotFound, + error.GitNotRepository, + => break :version .{ + .major = app_version.major, + .minor = app_version.minor, + .patch = app_version.patch, + .pre = "dev", + .build = "0000000", + }, + + else => return err, + }; + if (vsn.tag) |tag| { + // Tip releases behave just like any other pre-release so we skip. + if (!std.mem.eql(u8, tag, "tip")) { + const expected = b.fmt("v{d}.{d}.{d}", .{ + app_version.major, + app_version.minor, + app_version.patch, + }); + + if (!std.mem.eql(u8, tag, expected)) { + @panic("tagged releases must be in vX.Y.Z format matching build.zig"); + } + + break :version .{ + .major = app_version.major, + .minor = app_version.minor, + .patch = app_version.patch, + }; + } + } + + break :version .{ + .major = app_version.major, + .minor = app_version.minor, + .patch = app_version.patch, + .pre = vsn.branch, + .build = vsn.short_hash, + }; + }; + + //--------------------------------------------------------------- + // Binary Properties + + // On NixOS, the built binary from `zig build` needs to patch the rpath + // into the built binary for it to be portable across the NixOS system + // it was built for. We default this to true if we can detect we're in + // a Nix shell and have LD_LIBRARY_PATH set. + config.patch_rpath = b.option( + []const u8, + "patch-rpath", + "Inject the LD_LIBRARY_PATH as the rpath in the built binary. " ++ + "This defaults to LD_LIBRARY_PATH if we're in a Nix shell environment on NixOS.", + ) orelse patch_rpath: { + // We only do the patching if we're targeting our own CPU and its Linux. + if (!(target.result.os.tag == .linux) or !target.query.isNativeCpu()) break :patch_rpath null; + + // If we're in a nix shell we default to doing this. + // Note: we purposely never deinit envmap because we leak the strings + if (env.get("IN_NIX_SHELL") == null) break :patch_rpath null; + break :patch_rpath env.get("LD_LIBRARY_PATH"); + }; + + config.pie = b.option( + bool, + "pie", + "Build a Position Independent Executable. Default true for system packages.", + ) orelse system_package; + + config.strip = b.option( + bool, + "strip", + "Strip the final executable. Default true for fast and small releases", + ) orelse switch (optimize) { + .Debug => false, + .ReleaseSafe => false, + .ReleaseFast, .ReleaseSmall => true, + }; + + //--------------------------------------------------------------- + // Artifacts to Emit + + config.emit_lib_vt = b.option( + bool, + "emit-lib-vt", + "Set defaults for a libghostty-vt-only build (disables xcframework, macOS app, and docs).", + ) orelse false; + + config.emit_exe = b.option( + bool, + "emit-exe", + "Build and install main executables with 'build'", + ) orelse !config.emit_lib_vt; + + config.emit_test_exe = b.option( + bool, + "emit-test-exe", + "Build and install test executables with 'build'", + ) orelse false; + + config.emit_unicode_table_gen = b.option( + bool, + "emit-unicode-table-gen", + "Build and install executables that generate unicode tables with 'build'", + ) orelse false; + + config.emit_bench = b.option( + bool, + "emit-bench", + "Build and install the benchmark executables.", + ) orelse false; + + config.emit_helpgen = b.option( + bool, + "emit-helpgen", + "Build and install the helpgen executable.", + ) orelse false; + + config.emit_docs = b.option( + bool, + "emit-docs", + "Build and install auto-generated documentation (requires pandoc)", + ) orelse emit_docs: { + // If we are emitting any other artifacts then we default to false. + if (config.emit_bench or + config.emit_test_exe or + config.emit_helpgen or + config.emit_lib_vt) break :emit_docs false; + + // We always emit docs in system package mode. + if (system_package) break :emit_docs true; + + // We only default to true if we can find pandoc. + const path = expandPath(b.allocator, "pandoc") catch + break :emit_docs false; + defer if (path) |p| b.allocator.free(p); + break :emit_docs path != null; + }; + + config.emit_terminfo = b.option( + bool, + "emit-terminfo", + "Install Ghostty terminfo source file", + ) orelse switch (target.result.os.tag) { + .windows => true, + else => switch (optimize) { + .Debug => true, + .ReleaseSafe, .ReleaseFast, .ReleaseSmall => false, + }, + }; + + config.emit_termcap = b.option( + bool, + "emit-termcap", + "Install Ghostty termcap file", + ) orelse switch (optimize) { + .Debug => true, + .ReleaseSafe, .ReleaseFast, .ReleaseSmall => false, + }; + + config.emit_themes = b.option( + bool, + "emit-themes", + "Install bundled iTerm2-Color-Schemes Ghostty themes", + ) orelse true; + + config.emit_webdata = b.option( + bool, + "emit-webdata", + "Build the website data for the website.", + ) orelse false; + + config.emit_xcframework = b.option( + 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); + + config.emit_macos_app = b.option( + bool, + "emit-macos-app", + "Build and install the macOS app bundle.", + ) orelse !config.emit_lib_vt and config.emit_xcframework; + + //--------------------------------------------------------------- + // System Packages + + // These are all our dependencies that can be used with system + // packages if they exist. We set them up here so that we can set + // their defaults early. The first call configures the integration and + // subsequent calls just return the configured value. This lets them + // show up properly in `--help`. + + { + // These dependencies we want to default false if we're on macOS. + // On macOS we don't want to use system libraries because we + // generally want a fat binary. This can be overridden with the + // `-fsys` flag. + for (&[_][]const u8{ + "freetype", + "harfbuzz", + "fontconfig", + "libpng", + "zlib", + "oniguruma", + }) |dep| { + _ = b.systemIntegrationOption( + dep, + .{ + // If we're not on darwin we want to use whatever the + // default is via the system package mode + .default = if (target.result.os.tag.isDarwin()) false else null, + }, + ); + } + + // These default to false because they're rarely available as + // system packages so we usually want to statically link them. + for (&[_][]const u8{ + "glslang", + "spirv-cross", + "simdutf", + }) |dep| { + _ = b.systemIntegrationOption(dep, .{ .default = false }); + } + + // These are dynamic libraries we default to true, preferring + // to use system packages over building and installing libs + // as they require additional ldconfig of library paths or + // patching the rpath of the program to discover the dynamic library + // at runtime + for (&[_][]const u8{"gtk4-layer-shell"}) |dep| { + _ = b.systemIntegrationOption(dep, .{ .default = true }); + } + } + + return config; +} + +/// Configure the build options with our values. +pub fn addOptions(self: *const Config, step: *std.Build.Step.Options) !void { + // We need to break these down individual because addOption doesn't + // support all types. + step.addOption(bool, "flatpak", self.flatpak); + step.addOption(bool, "snap", self.snap); + step.addOption(bool, "x11", self.x11); + step.addOption(bool, "wayland", self.wayland); + step.addOption(bool, "sentry", self.sentry); + step.addOption(bool, "simd", self.simd); + step.addOption(bool, "i18n", self.i18n); + step.addOption(ApprtRuntime, "app_runtime", self.app_runtime); + step.addOption(FontBackend, "font_backend", self.font_backend); + step.addOption(RendererBackend, "renderer", self.renderer); + step.addOption(ExeEntrypoint, "exe_entrypoint", self.exe_entrypoint); + step.addOption(WasmTarget, "wasm_target", self.wasm_target); + step.addOption(bool, "wasm_shared", self.wasm_shared); + + // 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; + step.addOption(std.SemanticVersion, "app_version", self.version); + step.addOption([:0]const u8, "app_version_string", try std.fmt.bufPrintZ( + &buf, + "{f}", + .{self.version}, + )); + step.addOption( + ReleaseChannel, + "release_channel", + channel: { + const pre = self.version.pre orelse break :channel .stable; + if (pre.len == 0) break :channel .stable; + break :channel .tip; + }, + ); +} + +/// 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 { + return .{ + .artifact = .ghostty, + .simd = self.simd, + .oniguruma = true, + .c_abi = false, + .version = self.version, + .slow_runtime_safety = switch (self.optimize) { + .Debug => true, + .ReleaseSafe, + .ReleaseSmall, + .ReleaseFast, + => false, + }, + }; +} + +/// Returns a baseline CPU target retaining all the other CPU configs. +pub fn baselineTarget(self: *const Config) std.Build.ResolvedTarget { + // Set our cpu model as baseline. There may need to be other modifications + // we need to make such as resetting CPU features but for now this works. + var q = self.target.query; + q.cpu_model = .baseline; + + // Same logic as build.resolveTargetQuery but we don't need to + // handle the native case. + return .{ + .query = q, + .result = std.zig.system.resolveTargetQuery(q) catch + @panic("unable to resolve baseline query"), + }; +} + +/// Rehydrate our Config from the comptime options. Note that not all +/// options are available at comptime, so look closely at this implementation +/// to see what is and isn't available. +pub fn fromOptions() Config { + const options = @import("build_options"); + return .{ + // Unused at runtime. + .optimize = undefined, + .target = undefined, + .env = undefined, + + .version = options.app_version, + .flatpak = options.flatpak, + .app_runtime = std.meta.stringToEnum(ApprtRuntime, @tagName(options.app_runtime)).?, + .font_backend = std.meta.stringToEnum(FontBackend, @tagName(options.font_backend)).?, + .renderer = std.meta.stringToEnum(RendererBackend, @tagName(options.renderer)).?, + .snap = options.snap, + .exe_entrypoint = std.meta.stringToEnum(ExeEntrypoint, @tagName(options.exe_entrypoint)).?, + .wasm_target = std.meta.stringToEnum(WasmTarget, @tagName(options.wasm_target)).?, + .wasm_shared = options.wasm_shared, + .i18n = options.i18n, + }; +} + +/// Returns the minimum OS version for the given OS tag. This shouldn't +/// be used generally, it should only be used for Darwin-based OS currently. +pub fn osVersionMin(tag: std.Target.Os.Tag) ?std.Target.Query.OsVersion { + return switch (tag) { + // We support back to the earliest officially supported version + // of macOS by Apple. EOL versions are not supported. + .macos => .{ .semver = .{ + .major = 13, + .minor = 0, + .patch = 0, + } }, + + // iOS 17 picked arbitrarily + .ios => .{ .semver = .{ + .major = 17, + .minor = 0, + .patch = 0, + } }, + + // This should never happen currently. If we add a new target then + // we should add a new case here. + else => null, + }; +} + +// Returns a ResolvedTarget for a mac with a `target.result.cpu.model.name` of `generic`. +// `b.standardTargetOptions()` returns a more specific cpu like `apple_a15`. +// +// This is used to workaround compilation issues on macOS. +// (see for example https://github.com/mitchellh/ghostty/issues/1640). +pub fn genericMacOSTarget( + b: *std.Build, + arch: ?std.Target.Cpu.Arch, +) std.Build.ResolvedTarget { + return b.resolveTargetQuery(.{ + .cpu_arch = arch orelse builtin.target.cpu.arch, + .os_tag = .macos, + .os_version_min = osVersionMin(.macos), + }); +} + +/// The possible entrypoints for the exe artifact. This has no effect on +/// other artifact types (i.e. lib, wasm_module). +/// +/// The whole existence of this enum is to workaround the fact that Zig +/// doesn't allow the main function to be in a file in a subdirctory +/// from the "root" of the module, and I don't want to pollute our root +/// directory with a bunch of individual zig files for each entrypoint. +/// +/// Therefore, main.zig uses this to switch between the different entrypoints. +pub const ExeEntrypoint = enum { + ghostty, + helpgen, + mdgen_ghostty_1, + mdgen_ghostty_5, + webgen_config, + webgen_actions, + webgen_commands, +}; + +/// The release channel for the build. +pub const ReleaseChannel = enum { + /// Unstable builds on every commit. + tip, + + /// Stable tagged releases. + stable, +}; |
