Something went wrong. Try again.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701/// 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 interfacesapp_runtime: ApprtRuntime = .none,renderer: RendererBackend = .opengl,font_backend: FontBackend = .freetype,
/// Feature flagsx11: bool = false,wayland: bool = false,sentry: bool = true,simd: bool = true,i18n: bool = true,wasm_shared: bool = true,
/// Ghostty exe propertiesexe_entrypoint: ExeEntrypoint = .ghostty,version: std.SemanticVersion = .{ .major = 0, .minor = 0, .patch = 0 },lib_version: std.SemanticVersion = .{ .major = 0, .minor = 0, .patch = 0 },
/// Binary propertiespie: bool = false,strip: bool = false,patch_rpath: ?[]const u8 = null,
/// Artifactsflatpak: 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 propertiesenv: std.process.EnvMap,
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(.{}); 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, }; };
// 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
// 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 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, "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 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( &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", 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, artifact: TerminalBuildOptions.Artifact) TerminalBuildOptions { return .{ .artifact = artifact, .simd = self.simd, .oniguruma = true, .c_abi = false, .version = switch (artifact) { .ghostty => self.version, .lib => self.lib_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,};