From 8bf5c4ed7f8e39ca6dcadd036c8c72924590b200 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 7 Jan 2025 07:14:32 -0800 Subject: [PATCH 1/2] This is a major refactor of `build.zig`. The major idea behind the refactor is to split the `build.zig` file up into distinct `src/build/*.zig` files. By doing so, we can improve readability of the primary `build.zig` while also enabling better reuse of steps. Our `build.zig` is now less than 150 lines of code (of course, it calls into a lot more lines but they're neatly organized now). Improvements: * `build.zig` is less than 150 lines of readable code. * Help strings and unicode table generators are only run once when multiple artifacts are built since the results are the same regardless of target. * Metal lib is only built once per architecture (rather than once per artifact) * Resources (shell integration, terminfo, etc.) and docs are only built/installed for artifacts that need them Breaking changes: * Removed broken wasm build (@gabydd will re-add) * Removed conformance files, shell scripts are better and we don't run these anymore * Removed macOS app bundle creation, we don't use this anymore since we use Xcode ## Some History Our `build.zig` hasn't been significantly refactored since the project started, when Zig was _version 0.10_. Since then, the build system has changed significantly. We've only ever duct taped the `build.zig` as we needed to support new Zig versions, new features, etc. It was a mess. The major improvement is adapting the entire Ghostty `build.zig` to the Step and LazyPath changes introduced way back in Zig 0.12. This lets us better take advantage of parallelism and the dependency graph so that steps are only executed as they're needed. As such, you can see in the build.zig that we initialize a lot of things, but unless a final target (i.e. install, run) references those steps, _they'll never be executed_. This lets us clean up a lot. --- build.zig | 1895 +-------------------- conformance/ansi_ri.zig | 12 - conformance/ansi_ri_top.zig | 23 - conformance/blocks.zig | 99 -- conformance/csi_decstbm.zig | 15 - conformance/csi_dl.zig | 14 - conformance/csi_il.zig | 17 - conformance/esc_decaln.zig | 10 - nix/package.nix | 1 - src/build/Config.zig | 503 ++++++ src/build/GhosttyBench.zig | 69 + src/build/GhosttyDocs.zig | 92 + src/build/GhosttyExe.zig | 120 ++ src/build/GhosttyLib.zig | 110 ++ src/build/GhosttyResources.zig | 257 +++ src/build/GhosttyWebdata.zig | 82 + src/build/GhosttyXCFramework.zig | 68 + src/build/{Version.zig => GitVersion.zig} | 0 src/build/HelpStrings.zig | 46 + src/build/MetallibStep.zig | 4 +- src/build/SharedDeps.zig | 501 ++++++ src/build/UnicodeTables.zig | 43 + src/build/bash_completions.zig | 4 +- src/build/fish_completions.zig | 2 +- src/build/gtk.zig | 24 + src/build/main.zig | 30 + src/build/zsh_completions.zig | 2 +- src/build_config.zig | 117 +- 28 files changed, 2018 insertions(+), 2142 deletions(-) delete mode 100644 conformance/ansi_ri.zig delete mode 100644 conformance/ansi_ri_top.zig delete mode 100644 conformance/blocks.zig delete mode 100644 conformance/csi_decstbm.zig delete mode 100644 conformance/csi_dl.zig delete mode 100644 conformance/csi_il.zig delete mode 100644 conformance/esc_decaln.zig create mode 100644 src/build/Config.zig create mode 100644 src/build/GhosttyBench.zig create mode 100644 src/build/GhosttyDocs.zig create mode 100644 src/build/GhosttyExe.zig create mode 100644 src/build/GhosttyLib.zig create mode 100644 src/build/GhosttyResources.zig create mode 100644 src/build/GhosttyWebdata.zig create mode 100644 src/build/GhosttyXCFramework.zig rename src/build/{Version.zig => GitVersion.zig} (100%) create mode 100644 src/build/HelpStrings.zig create mode 100644 src/build/SharedDeps.zig create mode 100644 src/build/UnicodeTables.zig create mode 100644 src/build/gtk.zig create mode 100644 src/build/main.zig diff --git a/build.zig b/build.zig index 7f0bf84c5..1364745ce 100644 --- a/build.zig +++ b/build.zig @@ -1,30 +1,6 @@ const std = @import("std"); const builtin = @import("builtin"); -const fs = std.fs; -const CompileStep = std.Build.Step.Compile; -const RunStep = std.Build.Step.Run; -const ResolvedTarget = std.Build.ResolvedTarget; - -const apprt = @import("src/apprt.zig"); -const font = @import("src/font/main.zig"); -const renderer = @import("src/renderer.zig"); -const terminfo = @import("src/terminfo/main.zig"); -const config_vim = @import("src/config/vim.zig"); -const config_sublime_syntax = @import("src/config/sublime_syntax.zig"); -const fish_completions = @import("src/build/fish_completions.zig"); -const zsh_completions = @import("src/build/zsh_completions.zig"); -const bash_completions = @import("src/build/bash_completions.zig"); -const build_config = @import("src/build_config.zig"); -const BuildConfig = build_config.BuildConfig; -const WasmTarget = @import("src/os/wasm/target.zig").Target; -const LibtoolStep = @import("src/build/LibtoolStep.zig"); -const LipoStep = @import("src/build/LipoStep.zig"); -const MetallibStep = @import("src/build/MetallibStep.zig"); -const XCFrameworkStep = @import("src/build/XCFrameworkStep.zig"); -const Version = @import("src/build/Version.zig"); -const Command = @import("src/Command.zig"); - -const Scanner = @import("zig_wayland").Scanner; +const buildpkg = @import("src/build/main.zig"); comptime { // This is the required Zig version for building this project. We allow @@ -44,898 +20,80 @@ comptime { } } -/// The version of the next release. -const app_version = std.SemanticVersion{ .major = 1, .minor = 0, .patch = 2 }; - pub fn build(b: *std.Build) !void { - const optimize = b.standardOptimizeOption(.{}); - const target = target: { - var result = b.standardTargetOptions(.{}); + const config = try buildpkg.Config.init(b); - // 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); - } + // Ghostty resources like terminfo, shell integration, themes, etc. + const resources = try buildpkg.GhosttyResources.init(b, &config); - break :target result; - }; + // Ghostty dependencies used by many artifacts. + const deps = try buildpkg.SharedDeps.init(b, &config); + const exe = try buildpkg.GhosttyExe.init(b, &config, &deps); + if (config.emit_helpgen) deps.help_strings.install(); - // 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: bool = b.graph.system_package_mode; - - const wasm_target: WasmTarget = .browser; - - // We use env vars throughout the build so we grab them immediately here. - var env = try std.process.getEnvMap(b.allocator); - defer env.deinit(); - - // Our build configuration. This is all on a struct so that we can easily - // modify it for specific build types (for example, wasm we strictly - // control our backends). - var config: BuildConfig = .{}; - - config.flatpak = b.option( - bool, - "flatpak", - "Build for Flatpak (integrates with Flatpak APIs). Only has an effect targeting Linux.", - ) orelse false; - - config.font_backend = b.option( - font.Backend, - "font-backend", - "The font backend to use for discovery and rasterization.", - ) orelse font.Backend.default(target.result, wasm_target); - - config.app_runtime = b.option( - apprt.Runtime, - "app-runtime", - "The app runtime to use. Not all values supported on all platforms.", - ) orelse apprt.Runtime.default(target.result); - - config.renderer = b.option( - renderer.Impl, - "renderer", - "The app runtime to use. Not all values supported on all platforms.", - ) orelse renderer.Impl.default(target.result, wasm_target); - - config.adwaita = b.option( - bool, - "gtk-adwaita", - "Enables the use of Adwaita when using the GTK rendering backend.", - ) orelse true; - - var x11 = false; - var wayland = false; - - if (target.result.os.tag == .linux) pkgconfig: { - var pkgconfig = std.process.Child.init(&.{ "pkg-config", "--variable=targets", "gtk4" }, b.allocator); - - pkgconfig.stdout_behavior = .Pipe; - pkgconfig.stderr_behavior = .Pipe; - - pkgconfig.spawn() catch |err| { - std.log.warn("failed to spawn pkg-config - disabling X11 and Wayland integrations: {}", .{err}); - break :pkgconfig; - }; - - const output_max_size = 50 * 1024; - - var stdout = std.ArrayList(u8).init(b.allocator); - var stderr = std.ArrayList(u8).init(b.allocator); - defer { - stdout.deinit(); - stderr.deinit(); - } - - try pkgconfig.collectOutput(&stdout, &stderr, output_max_size); - - const term = try pkgconfig.wait(); - - if (stderr.items.len > 0) { - std.log.warn("pkg-config had errors:\n{s}", .{stderr.items}); - } - - switch (term) { - .Exited => |code| { - if (code == 0) { - if (std.mem.indexOf(u8, stdout.items, "x11")) |_| x11 = true; - if (std.mem.indexOf(u8, stdout.items, "wayland")) |_| wayland = true; - } else { - std.log.warn("pkg-config: {s} with code {d}", .{ @tagName(term), code }); - } - }, - inline else => |code| { - std.log.warn("pkg-config: {s} with code {d}", .{ @tagName(term), code }); - return error.Unexpected; - }, - } + // Ghostty docs + if (config.emit_docs) { + const docs = try buildpkg.GhosttyDocs.init(b, &deps); + docs.install(); } - config.x11 = b.option( - bool, - "gtk-x11", - "Enables linking against X11 libraries when using the GTK rendering backend.", - ) orelse x11; + // Ghostty webdata + if (config.emit_webdata) { + const webdata = try buildpkg.GhosttyWebdata.init(b, &deps); + webdata.install(); + } - config.wayland = b.option( - bool, - "gtk-wayland", - "Enables linking against Wayland libraries when using the GTK rendering backend.", - ) orelse wayland; + // Ghostty bench tools + if (config.emit_bench) { + const bench = try buildpkg.GhosttyBench.init(b, &deps); + bench.install(); + } - 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, + // If we're not building libghostty, then install the exe and resources. + if (config.app_runtime != .none) { + exe.install(); + resources.install(); + } - // Note its false for linux because the crash reports on Linux - // don't have much useful information. - else => break :sentry false, - } - }; + // Libghostty + // + // Note: libghostty is not stable for general purpose use. It is used + // heavily by Ghostty on macOS but it isn't built to be reusable yet. + // As such, these build steps are lacking. For example, the Darwin + // build only produces an xcframework. + if (config.app_runtime == .none) { + if (config.target.result.isDarwin()) darwin: { + if (!config.emit_xcframework) break :darwin; - const pie = b.option( - bool, - "pie", - "Build a Position Independent Executable. Default true for system packages.", - ) orelse system_package; + // Build the xcframework + const xcframework = try buildpkg.GhosttyXCFramework.init(b, &deps); + xcframework.install(); - const 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, - }; + // The xcframework build always installs resources because our + // macOS xcode project contains references to them. + resources.install(); - const conformance = b.option( - []const u8, - "conformance", - "Name of the conformance app to run with 'run' option.", - ); - - const emit_test_exe = b.option( - bool, - "emit-test-exe", - "Build and install test executables with 'build'", - ) orelse false; - - const emit_bench = b.option( - bool, - "emit-bench", - "Build and install the benchmark executables.", - ) orelse false; - - const emit_helpgen = b.option( - bool, - "emit-helpgen", - "Build and install the helpgen executable.", - ) orelse false; - - const 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 (emit_bench or emit_test_exe or emit_helpgen) 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 = Command.expandPath(b.allocator, "pandoc") catch - break :emit_docs false; - defer if (path) |p| b.allocator.free(p); - break :emit_docs path != null; - }; - - const emit_webdata = b.option( - bool, - "emit-webdata", - "Build the website data for the website.", - ) orelse false; - - const emit_xcframework = b.option( - bool, - "emit-xcframework", - "Build and install the xcframework for the macOS library.", - ) orelse builtin.target.isDarwin() and - target.result.os.tag == .macos and - config.app_runtime == .none and - (!emit_bench and !emit_test_exe and !emit_helpgen); - - // 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. - const patch_rpath: ?[]const u8 = 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"); - }; - - 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| - try std.SemanticVersion.parse(v) - else version: { - const vsn = Version.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, - }; + // If we aren't emitting docs we need to emit a placeholder so + // our macOS xcodeproject builds. + if (!config.emit_docs) { + var wf = b.addWriteFiles(); + const path = "share/man/.placeholder"; + const placeholder = wf.add(path, "emit-docs not true so no man pages"); + b.getInstallStep().dependOn(&b.addInstallFile(placeholder, path).step); } + } else { + const libghostty_shared = try buildpkg.GhosttyLib.initShared(b, &deps); + const libghostty_static = try buildpkg.GhosttyLib.initStatic(b, &deps); + libghostty_shared.installHeader(); // Only need one header + libghostty_shared.install("libghostty.so"); + libghostty_static.install("libghostty.a"); } + } - break :version .{ - .major = app_version.major, - .minor = app_version.minor, - .patch = app_version.patch, - .pre = vsn.branch, - .build = vsn.short_hash, - }; - }; - - // 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. + // Run runs the Ghostty exe { - // 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.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 }); - } - } - - // We can use wasmtime to test wasm - b.enable_wasmtime = true; - - // Help exe. This must be run before any dependent executables because - // otherwise the build will be cached without emit. That's clunky but meh. - if (emit_helpgen) try addHelp(b, null, config); - - // Add our benchmarks - try benchSteps(b, target, config, emit_bench); - - // We only build an exe if we have a runtime set. - const exe_: ?*std.Build.Step.Compile = if (config.app_runtime != .none) b.addExecutable(.{ - .name = "ghostty", - .root_source_file = b.path("src/main.zig"), - .target = target, - .optimize = optimize, - .strip = strip, - }) else null; - - // Exe - if (exe_) |exe| { - // Set PIE if requested - if (pie) exe.pie = true; - - // Add the shared dependencies - _ = try addDeps(b, exe, config); - - // If we're in NixOS but not in the shell environment then we issue - // a warning because the rpath may not be setup properly. - const is_nixos = is_nixos: { - if (target.result.os.tag != .linux) break :is_nixos false; - if (!target.query.isNativeCpu()) break :is_nixos false; - if (!target.query.isNativeOs()) break :is_nixos false; - break :is_nixos if (std.fs.accessAbsolute("/etc/NIXOS", .{})) true else |_| false; - }; - if (is_nixos and env.get("IN_NIX_SHELL") == null) { - try exe.step.addError( - "\x1b[" ++ color_map.get("yellow").? ++ - "\x1b[" ++ color_map.get("d").? ++ - \\Detected building on and for NixOS outside of the Nix shell environment. - \\ - \\The resulting ghostty binary will likely fail on launch because it is - \\unable to dynamically load the windowing libs (X11, Wayland, etc.). - \\We highly recommend running only within the Nix build environment - \\and the resulting binary will be portable across your system. - \\ - \\To run in the Nix build environment, use the following command. - \\Append any additional options like (`-Doptimize` flags). The resulting - \\binary will be in zig-out as usual. - \\ - \\ nix develop -c zig build - \\ - ++ - "\x1b[0m", - .{}, - ); - } - - if (target.result.os.tag == .windows) { - exe.subsystem = .Windows; - exe.addWin32ResourceFile(.{ - .file = b.path("dist/windows/ghostty.rc"), - }); - } - - // If we're installing, we get the install step so we can add - // additional dependencies to it. - const install_step = if (config.app_runtime != .none) step: { - const step = b.addInstallArtifact(exe, .{}); - b.getInstallStep().dependOn(&step.step); - break :step step; - } else null; - - // Patch our rpath if that option is specified. - if (patch_rpath) |rpath| { - if (rpath.len > 0) { - const run = RunStep.create(b, "patchelf rpath"); - run.addArgs(&.{ "patchelf", "--set-rpath", rpath }); - run.addArtifactArg(exe); - - if (install_step) |step| { - step.step.dependOn(&run.step); - } - } - } - - // App (Mac) - if (target.result.os.tag == .macos) { - const bin_install = b.addInstallFile( - exe.getEmittedBin(), - "Ghostty.app/Contents/MacOS/ghostty", - ); - b.getInstallStep().dependOn(&bin_install.step); - b.installFile("dist/macos/Info.plist", "Ghostty.app/Contents/Info.plist"); - b.installFile("dist/macos/Ghostty.icns", "Ghostty.app/Contents/Resources/Ghostty.icns"); - } - } - - // Shell-integration - { - const install = b.addInstallDirectory(.{ - .source_dir = b.path("src/shell-integration"), - .install_dir = .{ .custom = "share" }, - .install_subdir = b.pathJoin(&.{ "ghostty", "shell-integration" }), - .exclude_extensions = &.{".md"}, - }); - b.getInstallStep().dependOn(&install.step); - - if (target.result.os.tag == .macos and exe_ != null) { - const mac_install = b.addInstallDirectory(options: { - var copy = install.options; - copy.install_dir = .{ - .custom = "Ghostty.app/Contents/Resources", - }; - break :options copy; - }); - b.getInstallStep().dependOn(&mac_install.step); - } - } - - // Themes - { - const upstream = b.dependency("iterm2_themes", .{}); - const install = b.addInstallDirectory(.{ - .source_dir = upstream.path("ghostty"), - .install_dir = .{ .custom = "share" }, - .install_subdir = b.pathJoin(&.{ "ghostty", "themes" }), - .exclude_extensions = &.{".md"}, - }); - b.getInstallStep().dependOn(&install.step); - - if (target.result.os.tag == .macos and exe_ != null) { - const mac_install = b.addInstallDirectory(options: { - var copy = install.options; - copy.install_dir = .{ - .custom = "Ghostty.app/Contents/Resources", - }; - break :options copy; - }); - b.getInstallStep().dependOn(&mac_install.step); - } - } - - // Terminfo - { - // Encode our terminfo - var str = std.ArrayList(u8).init(b.allocator); - defer str.deinit(); - try terminfo.ghostty.encode(str.writer()); - - // Write it - var wf = b.addWriteFiles(); - const src_source = wf.add("share/terminfo/ghostty.terminfo", str.items); - const src_install = b.addInstallFile(src_source, "share/terminfo/ghostty.terminfo"); - b.getInstallStep().dependOn(&src_install.step); - if (target.result.os.tag == .macos and exe_ != null) { - const mac_src_install = b.addInstallFile( - src_source, - "Ghostty.app/Contents/Resources/terminfo/ghostty.terminfo", - ); - b.getInstallStep().dependOn(&mac_src_install.step); - } - - // Convert to termcap source format if thats helpful to people and - // install it. The resulting value here is the termcap source in case - // that is used for other commands. - if (target.result.os.tag != .windows) { - const run_step = RunStep.create(b, "infotocap"); - run_step.addArg("infotocap"); - run_step.addFileArg(src_source); - const out_source = run_step.captureStdOut(); - _ = run_step.captureStdErr(); // so we don't see stderr - - const cap_install = b.addInstallFile(out_source, "share/terminfo/ghostty.termcap"); - b.getInstallStep().dependOn(&cap_install.step); - - if (target.result.os.tag == .macos and exe_ != null) { - const mac_cap_install = b.addInstallFile( - out_source, - "Ghostty.app/Contents/Resources/terminfo/ghostty.termcap", - ); - b.getInstallStep().dependOn(&mac_cap_install.step); - } - } - - // Compile the terminfo source into a terminfo database - if (target.result.os.tag != .windows) { - const run_step = RunStep.create(b, "tic"); - run_step.addArgs(&.{ "tic", "-x", "-o" }); - const path = run_step.addOutputFileArg("terminfo"); - run_step.addFileArg(src_source); - _ = run_step.captureStdErr(); // so we don't see stderr - - // Depend on the terminfo source install step so that Zig build - // creates the "share" directory for us. - run_step.step.dependOn(&src_install.step); - - { - // Use cp -R instead of Step.InstallDir because we need to preserve - // symlinks in the terminfo database. Zig's InstallDir step doesn't - // handle symlinks correctly yet. - const copy_step = RunStep.create(b, "copy terminfo db"); - copy_step.addArgs(&.{ "cp", "-R" }); - copy_step.addFileArg(path); - copy_step.addArg(b.fmt("{s}/share", .{b.install_path})); - b.getInstallStep().dependOn(©_step.step); - } - - if (target.result.os.tag == .macos and exe_ != null) { - // Use cp -R instead of Step.InstallDir because we need to preserve - // symlinks in the terminfo database. Zig's InstallDir step doesn't - // handle symlinks correctly yet. - const copy_step = RunStep.create(b, "copy terminfo db"); - copy_step.addArgs(&.{ "cp", "-R" }); - copy_step.addFileArg(path); - copy_step.addArg( - b.fmt("{s}/Ghostty.app/Contents/Resources", .{b.install_path}), - ); - b.getInstallStep().dependOn(©_step.step); - } - } - } - - // Fish shell completions - { - const wf = b.addWriteFiles(); - _ = wf.add("ghostty.fish", fish_completions.fish_completions); - - b.installDirectory(.{ - .source_dir = wf.getDirectory(), - .install_dir = .prefix, - .install_subdir = "share/fish/vendor_completions.d", - }); - } - - // zsh shell completions - { - const wf = b.addWriteFiles(); - _ = wf.add("_ghostty", zsh_completions.zsh_completions); - - b.installDirectory(.{ - .source_dir = wf.getDirectory(), - .install_dir = .prefix, - .install_subdir = "share/zsh/site-functions", - }); - } - - // bash shell completions - { - const wf = b.addWriteFiles(); - _ = wf.add("ghostty.bash", bash_completions.bash_completions); - - b.installDirectory(.{ - .source_dir = wf.getDirectory(), - .install_dir = .prefix, - .install_subdir = "share/bash-completion/completions", - }); - } - - // Vim plugin - { - const wf = b.addWriteFiles(); - _ = wf.add("syntax/ghostty.vim", config_vim.syntax); - _ = wf.add("ftdetect/ghostty.vim", config_vim.ftdetect); - _ = wf.add("ftplugin/ghostty.vim", config_vim.ftplugin); - _ = wf.add("compiler/ghostty.vim", config_vim.compiler); - b.installDirectory(.{ - .source_dir = wf.getDirectory(), - .install_dir = .prefix, - .install_subdir = "share/vim/vimfiles", - }); - } - - // Neovim plugin - // This is just a copy-paste of the Vim plugin, but using a Neovim subdir. - // By default, Neovim doesn't look inside share/vim/vimfiles. Some distros - // configure it to do that however. Fedora, does not as a counterexample. - { - const wf = b.addWriteFiles(); - _ = wf.add("syntax/ghostty.vim", config_vim.syntax); - _ = wf.add("ftdetect/ghostty.vim", config_vim.ftdetect); - _ = wf.add("ftplugin/ghostty.vim", config_vim.ftplugin); - _ = wf.add("compiler/ghostty.vim", config_vim.compiler); - b.installDirectory(.{ - .source_dir = wf.getDirectory(), - .install_dir = .prefix, - .install_subdir = "share/nvim/site", - }); - } - - // Sublime syntax highlighting for bat cli tool - // NOTE: The current implementation requires symlinking the generated - // 'ghostty.sublime-syntax' file from zig-out to the '~.config/bat/syntaxes' - // directory. The syntax then needs to be mapped to the correct language in - // the config file within the '~.config/bat' directory - // (ex: --map-syntax "/Users/user/.config/ghostty/config:Ghostty Config"). - { - const wf = b.addWriteFiles(); - _ = wf.add("ghostty.sublime-syntax", config_sublime_syntax.syntax); - b.installDirectory(.{ - .source_dir = wf.getDirectory(), - .install_dir = .prefix, - .install_subdir = "share/bat/syntaxes", - }); - } - - // Documentation - if (emit_docs) { - try buildDocumentation(b, config); - } else { - // We need to create the zig-out/share/man directory so that - // macOS builds continue to work even if emit-docs doesn't - // work. - var wf = b.addWriteFiles(); - const path = "share/man/.placeholder"; - const placeholder = wf.add(path, "emit-docs not true so no man pages"); - b.getInstallStep().dependOn(&b.addInstallFile(placeholder, path).step); - } - - // Web data - if (emit_webdata) { - try buildWebData(b, config); - } - - // App (Linux) - if (target.result.os.tag == .linux and config.app_runtime != .none) { - // https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html - - // Desktop file so that we have an icon and other metadata - b.installFile("dist/linux/app.desktop", "share/applications/com.mitchellh.ghostty.desktop"); - - // Right click menu action for Plasma desktop - b.installFile("dist/linux/ghostty_dolphin.desktop", "share/kio/servicemenus/com.mitchellh.ghostty.desktop"); - - // Various icons that our application can use, including the icon - // that will be used for the desktop. - b.installFile("images/icons/icon_16.png", "share/icons/hicolor/16x16/apps/com.mitchellh.ghostty.png"); - b.installFile("images/icons/icon_32.png", "share/icons/hicolor/32x32/apps/com.mitchellh.ghostty.png"); - b.installFile("images/icons/icon_128.png", "share/icons/hicolor/128x128/apps/com.mitchellh.ghostty.png"); - b.installFile("images/icons/icon_256.png", "share/icons/hicolor/256x256/apps/com.mitchellh.ghostty.png"); - b.installFile("images/icons/icon_512.png", "share/icons/hicolor/512x512/apps/com.mitchellh.ghostty.png"); - - // Flatpaks only support icons up to 512x512. - if (!config.flatpak) { - b.installFile("images/icons/icon_1024.png", "share/icons/hicolor/1024x1024/apps/com.mitchellh.ghostty.png"); - } - - b.installFile("images/icons/icon_16@2x.png", "share/icons/hicolor/16x16@2/apps/com.mitchellh.ghostty.png"); - b.installFile("images/icons/icon_32@2x.png", "share/icons/hicolor/32x32@2/apps/com.mitchellh.ghostty.png"); - b.installFile("images/icons/icon_128@2x.png", "share/icons/hicolor/128x128@2/apps/com.mitchellh.ghostty.png"); - b.installFile("images/icons/icon_256@2x.png", "share/icons/hicolor/256x256@2/apps/com.mitchellh.ghostty.png"); - } - - // libghostty (non-Darwin) - if (!builtin.target.isDarwin() and config.app_runtime == .none) { - // Shared - { - const lib = b.addSharedLibrary(.{ - .name = "ghostty", - .root_source_file = b.path("src/main_c.zig"), - .optimize = optimize, - .target = target, - .strip = strip, - }); - _ = try addDeps(b, lib, config); - - const lib_install = b.addInstallLibFile( - lib.getEmittedBin(), - "libghostty.so", - ); - b.getInstallStep().dependOn(&lib_install.step); - } - - // Static - { - const lib = b.addStaticLibrary(.{ - .name = "ghostty", - .root_source_file = b.path("src/main_c.zig"), - .optimize = optimize, - .target = target, - .strip = strip, - }); - _ = try addDeps(b, lib, config); - - const lib_install = b.addInstallLibFile( - lib.getEmittedBin(), - "libghostty.a", - ); - b.getInstallStep().dependOn(&lib_install.step); - } - - // Copy our ghostty.h to include. - const header_install = b.addInstallHeaderFile( - b.path("include/ghostty.h"), - "ghostty.h", - ); - b.getInstallStep().dependOn(&header_install.step); - } - - // On Mac we can build the embedding library. This only handles the macOS lib. - if (emit_xcframework) { - // Create the universal macOS lib. - const macos_lib_step, const macos_lib_path = try createMacOSLib( - b, - optimize, - config, - ); - - // Add our library to zig-out - const lib_install = b.addInstallLibFile( - macos_lib_path, - "libghostty-macos.a", - ); - b.getInstallStep().dependOn(&lib_install.step); - - // Create the universal iOS lib. - const ios_lib_step, const ios_lib_path = try createIOSLib( - b, - null, - optimize, - config, - ); - - // Add our library to zig-out - const ios_lib_install = b.addInstallLibFile( - ios_lib_path, - "libghostty-ios.a", - ); - b.getInstallStep().dependOn(&ios_lib_install.step); - - // Create the iOS simulator lib. - const ios_sim_lib_step, const ios_sim_lib_path = try createIOSLib( - b, - .simulator, - optimize, - config, - ); - - // Add our library to zig-out - const ios_sim_lib_install = b.addInstallLibFile( - ios_sim_lib_path, - "libghostty-ios-simulator.a", - ); - b.getInstallStep().dependOn(&ios_sim_lib_install.step); - - // Copy our ghostty.h to include. The header file is shared by - // all embedded targets. - const header_install = b.addInstallHeaderFile( - b.path("include/ghostty.h"), - "ghostty.h", - ); - b.getInstallStep().dependOn(&header_install.step); - - // The xcframework wraps our ghostty library so that we can link - // it to the final app built with Swift. - const xcframework = XCFrameworkStep.create(b, .{ - .name = "GhosttyKit", - .out_path = "macos/GhosttyKit.xcframework", - .libraries = &.{ - .{ - .library = macos_lib_path, - .headers = b.path("include"), - }, - .{ - .library = ios_lib_path, - .headers = b.path("include"), - }, - .{ - .library = ios_sim_lib_path, - .headers = b.path("include"), - }, - }, - }); - xcframework.step.dependOn(ios_lib_step); - xcframework.step.dependOn(ios_sim_lib_step); - xcframework.step.dependOn(macos_lib_step); - xcframework.step.dependOn(&header_install.step); - b.default_step.dependOn(xcframework.step); - } - - // wasm - { - // Build our Wasm target. - const wasm_crosstarget: std.Target.Query = .{ - .cpu_arch = .wasm32, - .os_tag = .freestanding, - .cpu_model = .{ .explicit = &std.Target.wasm.cpu.mvp }, - .cpu_features_add = std.Target.wasm.featureSet(&.{ - // We use this to explicitly request shared memory. - .atomics, - - // Not explicitly used but compiler could use them if they want. - .bulk_memory, - .reference_types, - .sign_ext, - }), - }; - - // Whether we're using wasm shared memory. Some behaviors change. - // For now we require this but I wanted to make the code handle both - // up front. - const wasm_shared: bool = true; - - // Modify our build configuration for wasm builds. - const wasm_config: BuildConfig = config: { - var copy = config; - - // Backends that are fixed for wasm - copy.font_backend = .web_canvas; - - // Wasm-specific options - copy.wasm_shared = wasm_shared; - copy.wasm_target = wasm_target; - - break :config copy; - }; - - const wasm = b.addSharedLibrary(.{ - .name = "ghostty-wasm", - .root_source_file = b.path("src/main_wasm.zig"), - .target = b.resolveTargetQuery(wasm_crosstarget), - .optimize = optimize, - }); - - // So that we can use web workers with our wasm binary - wasm.import_memory = true; - wasm.initial_memory = 65536 * 25; - wasm.max_memory = 65536 * 65536; // Maximum number of pages in wasm32 - wasm.shared_memory = wasm_shared; - - // Stack protector adds extern requirements that we don't satisfy. - wasm.root_module.stack_protector = false; - - // Wasm-specific deps - _ = try addDeps(b, wasm, wasm_config); - - // Install - const wasm_install = b.addInstallArtifact(wasm, .{}); - wasm_install.dest_dir = .{ .prefix = {} }; - - const step = b.step("wasm", "Build the wasm library"); - step.dependOn(&wasm_install.step); - - // We support tests via wasmtime. wasmtime uses WASI so this - // isn't an exact match to our freestanding target above but - // it lets us test some basic functionality. - const test_step = b.step("test-wasm", "Run all tests for wasm"); - const main_test = b.addTest(.{ - .name = "wasm-test", - .root_source_file = b.path("src/main_wasm.zig"), - .target = b.resolveTargetQuery(wasm_crosstarget), - }); - - _ = try addDeps(b, main_test, wasm_config); - test_step.dependOn(&main_test.step); - } - - // Run - run: { - // Build our run step, which runs the main app by default, but will - // run a conformance app if `-Dconformance` is set. - const run_exe = if (conformance) |name| blk: { - var conformance_exes = try conformanceSteps(b, target, optimize); - defer conformance_exes.deinit(); - break :blk conformance_exes.get(name) orelse return error.InvalidConformance; - } else exe_ orelse break :run; - - const run_cmd = b.addRunArtifact(run_exe); - if (b.args) |args| { - run_cmd.addArgs(args); - } - + const run_cmd = b.addRunArtifact(exe.exe); + if (b.args) |args| run_cmd.addArgs(args); const run_step = b.step("run", "Run the app"); run_step.dependOn(&run_cmd.step); } @@ -945,943 +103,18 @@ pub fn build(b: *std.Build) !void { const test_step = b.step("test", "Run all tests"); const test_filter = b.option([]const u8, "test-filter", "Filter for test"); - // Force all Mac builds to use a `generic` CPU. This avoids - // potential issues with `highway` compile errors due to missing - // `arm_neon` features (see for example https://github.com/mitchellh/ghostty/issues/1640). - const test_target = if (target.result.os.tag == .macos and builtin.target.isDarwin()) - genericMacOSTarget(b, null) - else - target; - - const main_test = b.addTest(.{ + const test_exe = b.addTest(.{ .name = "ghostty-test", .root_source_file = b.path("src/main.zig"), - .target = test_target, + .target = config.target, .filter = test_filter, }); { - if (emit_test_exe) b.installArtifact(main_test); - _ = try addDeps(b, main_test, config); - const test_run = b.addRunArtifact(main_test); + if (config.emit_test_exe) b.installArtifact(test_exe); + _ = try deps.add(test_exe); + const test_run = b.addRunArtifact(test_exe); test_step.dependOn(&test_run.step); } } } - -/// 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. -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`. -fn genericMacOSTarget(b: *std.Build, arch: ?std.Target.Cpu.Arch) ResolvedTarget { - return b.resolveTargetQuery(.{ - .cpu_arch = arch orelse builtin.target.cpu.arch, - .os_tag = .macos, - .os_version_min = osVersionMin(.macos), - }); -} - -/// Creates a universal macOS libghostty library and returns the path -/// to the final library. -/// -/// The library is always a fat static library currently because this is -/// expected to be used directly with Xcode and Swift. In the future, we -/// probably want to change this because it makes it harder to use the -/// library in other contexts. -fn createMacOSLib( - b: *std.Build, - optimize: std.builtin.OptimizeMode, - config: BuildConfig, -) !struct { *std.Build.Step, std.Build.LazyPath } { - const static_lib_aarch64 = lib: { - const lib = b.addStaticLibrary(.{ - .name = "ghostty", - .root_source_file = b.path("src/main_c.zig"), - .target = genericMacOSTarget(b, .aarch64), - .optimize = optimize, - }); - lib.bundle_compiler_rt = true; - lib.linkLibC(); - - // Create a single static lib with all our dependencies merged - var lib_list = try addDeps(b, lib, config); - try lib_list.append(lib.getEmittedBin()); - const libtool = LibtoolStep.create(b, .{ - .name = "ghostty", - .out_name = "libghostty-aarch64-fat.a", - .sources = lib_list.items, - }); - libtool.step.dependOn(&lib.step); - b.default_step.dependOn(libtool.step); - - break :lib libtool; - }; - - const static_lib_x86_64 = lib: { - const lib = b.addStaticLibrary(.{ - .name = "ghostty", - .root_source_file = b.path("src/main_c.zig"), - .target = genericMacOSTarget(b, .x86_64), - .optimize = optimize, - }); - lib.bundle_compiler_rt = true; - lib.linkLibC(); - - // Create a single static lib with all our dependencies merged - var lib_list = try addDeps(b, lib, config); - try lib_list.append(lib.getEmittedBin()); - const libtool = LibtoolStep.create(b, .{ - .name = "ghostty", - .out_name = "libghostty-x86_64-fat.a", - .sources = lib_list.items, - }); - libtool.step.dependOn(&lib.step); - b.default_step.dependOn(libtool.step); - - break :lib libtool; - }; - - const static_lib_universal = LipoStep.create(b, .{ - .name = "ghostty", - .out_name = "libghostty.a", - .input_a = static_lib_aarch64.output, - .input_b = static_lib_x86_64.output, - }); - static_lib_universal.step.dependOn(static_lib_aarch64.step); - static_lib_universal.step.dependOn(static_lib_x86_64.step); - - return .{ - static_lib_universal.step, - static_lib_universal.output, - }; -} - -/// Create an Apple iOS/iPadOS build. -fn createIOSLib( - b: *std.Build, - abi: ?std.Target.Abi, - optimize: std.builtin.OptimizeMode, - config: BuildConfig, -) !struct { *std.Build.Step, std.Build.LazyPath } { - const lib = b.addStaticLibrary(.{ - .name = "ghostty", - .root_source_file = b.path("src/main_c.zig"), - .optimize = optimize, - .target = b.resolveTargetQuery(.{ - .cpu_arch = .aarch64, - .os_tag = .ios, - .os_version_min = osVersionMin(.ios), - .abi = abi, - }), - }); - lib.bundle_compiler_rt = true; - lib.linkLibC(); - - // Create a single static lib with all our dependencies merged - var lib_list = try addDeps(b, lib, config); - try lib_list.append(lib.getEmittedBin()); - const libtool = LibtoolStep.create(b, .{ - .name = "ghostty", - .out_name = "libghostty-ios-fat.a", - .sources = lib_list.items, - }); - libtool.step.dependOn(&lib.step); - - return .{ - libtool.step, - libtool.output, - }; -} - -/// Used to keep track of a list of file sources. -const LazyPathList = std.ArrayList(std.Build.LazyPath); - -/// Adds and links all of the primary dependencies for the exe. -fn addDeps( - b: *std.Build, - step: *std.Build.Step.Compile, - config: BuildConfig, -) !LazyPathList { - // All object targets get access to a standard build_options module - const exe_options = b.addOptions(); - try config.addOptions(exe_options); - step.root_module.addOptions("build_options", exe_options); - - // We maintain a list of our static libraries and return it so that - // we can build a single fat static library for the final app. - var static_libs = LazyPathList.init(b.allocator); - errdefer static_libs.deinit(); - - const target = step.root_module.resolved_target.?; - const optimize = step.root_module.optimize.?; - - // For dynamic linking, we prefer dynamic linking and to search by - // mode first. Mode first will search all paths for a dynamic library - // before falling back to static. - const dynamic_link_opts: std.Build.Module.LinkSystemLibraryOptions = .{ - .preferred_link_mode = .dynamic, - .search_strategy = .mode_first, - }; - - // Freetype - _ = b.systemIntegrationOption("freetype", .{}); // Shows it in help - if (config.font_backend.hasFreetype()) { - const freetype_dep = b.dependency("freetype", .{ - .target = target, - .optimize = optimize, - .@"enable-libpng" = true, - }); - step.root_module.addImport("freetype", freetype_dep.module("freetype")); - - if (b.systemIntegrationOption("freetype", .{})) { - step.linkSystemLibrary2("bzip2", dynamic_link_opts); - step.linkSystemLibrary2("freetype2", dynamic_link_opts); - } else { - step.linkLibrary(freetype_dep.artifact("freetype")); - try static_libs.append(freetype_dep.artifact("freetype").getEmittedBin()); - } - } - - // Harfbuzz - _ = b.systemIntegrationOption("harfbuzz", .{}); // Shows it in help - if (config.font_backend.hasHarfbuzz()) { - const harfbuzz_dep = b.dependency("harfbuzz", .{ - .target = target, - .optimize = optimize, - .@"enable-freetype" = true, - .@"enable-coretext" = config.font_backend.hasCoretext(), - }); - - step.root_module.addImport( - "harfbuzz", - harfbuzz_dep.module("harfbuzz"), - ); - if (b.systemIntegrationOption("harfbuzz", .{})) { - step.linkSystemLibrary2("harfbuzz", dynamic_link_opts); - } else { - step.linkLibrary(harfbuzz_dep.artifact("harfbuzz")); - try static_libs.append(harfbuzz_dep.artifact("harfbuzz").getEmittedBin()); - } - } - - // Fontconfig - _ = b.systemIntegrationOption("fontconfig", .{}); // Shows it in help - if (config.font_backend.hasFontconfig()) { - const fontconfig_dep = b.dependency("fontconfig", .{ - .target = target, - .optimize = optimize, - }); - step.root_module.addImport( - "fontconfig", - fontconfig_dep.module("fontconfig"), - ); - - if (b.systemIntegrationOption("fontconfig", .{})) { - step.linkSystemLibrary2("fontconfig", dynamic_link_opts); - } else { - step.linkLibrary(fontconfig_dep.artifact("fontconfig")); - try static_libs.append(fontconfig_dep.artifact("fontconfig").getEmittedBin()); - } - } - - // Libpng - Ghostty doesn't actually use this directly, its only used - // through dependencies, so we only need to add it to our static - // libs list if we're not using system integration. The dependencies - // will handle linking it. - if (!b.systemIntegrationOption("libpng", .{})) { - const libpng_dep = b.dependency("libpng", .{ - .target = target, - .optimize = optimize, - }); - step.linkLibrary(libpng_dep.artifact("png")); - try static_libs.append(libpng_dep.artifact("png").getEmittedBin()); - } - - // Zlib - same as libpng, only used through dependencies. - if (!b.systemIntegrationOption("zlib", .{})) { - const zlib_dep = b.dependency("zlib", .{ - .target = target, - .optimize = optimize, - }); - step.linkLibrary(zlib_dep.artifact("z")); - try static_libs.append(zlib_dep.artifact("z").getEmittedBin()); - } - - // Oniguruma - const oniguruma_dep = b.dependency("oniguruma", .{ - .target = target, - .optimize = optimize, - }); - step.root_module.addImport("oniguruma", oniguruma_dep.module("oniguruma")); - if (b.systemIntegrationOption("oniguruma", .{})) { - step.linkSystemLibrary2("oniguruma", dynamic_link_opts); - } else { - step.linkLibrary(oniguruma_dep.artifact("oniguruma")); - try static_libs.append(oniguruma_dep.artifact("oniguruma").getEmittedBin()); - } - - // Glslang - const glslang_dep = b.dependency("glslang", .{ - .target = target, - .optimize = optimize, - }); - step.root_module.addImport("glslang", glslang_dep.module("glslang")); - if (b.systemIntegrationOption("glslang", .{})) { - step.linkSystemLibrary2("glslang", dynamic_link_opts); - step.linkSystemLibrary2("glslang-default-resource-limits", dynamic_link_opts); - } else { - step.linkLibrary(glslang_dep.artifact("glslang")); - try static_libs.append(glslang_dep.artifact("glslang").getEmittedBin()); - } - - // Spirv-cross - const spirv_cross_dep = b.dependency("spirv_cross", .{ - .target = target, - .optimize = optimize, - }); - step.root_module.addImport("spirv_cross", spirv_cross_dep.module("spirv_cross")); - if (b.systemIntegrationOption("spirv-cross", .{})) { - step.linkSystemLibrary2("spirv-cross", dynamic_link_opts); - } else { - step.linkLibrary(spirv_cross_dep.artifact("spirv_cross")); - try static_libs.append(spirv_cross_dep.artifact("spirv_cross").getEmittedBin()); - } - - // Simdutf - if (b.systemIntegrationOption("simdutf", .{})) { - step.linkSystemLibrary2("simdutf", dynamic_link_opts); - } else { - const simdutf_dep = b.dependency("simdutf", .{ - .target = target, - .optimize = optimize, - }); - step.linkLibrary(simdutf_dep.artifact("simdutf")); - try static_libs.append(simdutf_dep.artifact("simdutf").getEmittedBin()); - } - - // Sentry - if (config.sentry) { - const sentry_dep = b.dependency("sentry", .{ - .target = target, - .optimize = optimize, - .backend = .breakpad, - }); - - step.root_module.addImport("sentry", sentry_dep.module("sentry")); - - // Sentry - step.linkLibrary(sentry_dep.artifact("sentry")); - try static_libs.append(sentry_dep.artifact("sentry").getEmittedBin()); - - // We also need to include breakpad in the static libs. - const breakpad_dep = sentry_dep.builder.dependency("breakpad", .{ - .target = target, - .optimize = optimize, - }); - try static_libs.append(breakpad_dep.artifact("breakpad").getEmittedBin()); - } - - // Wasm we do manually since it is such a different build. - if (step.rootModuleTarget().cpu.arch == .wasm32) { - const js_dep = b.dependency("zig_js", .{ - .target = target, - .optimize = optimize, - }); - step.root_module.addImport("zig-js", js_dep.module("zig-js")); - - return static_libs; - } - - // On Linux, we need to add a couple common library paths that aren't - // on the standard search list. i.e. GTK is often in /usr/lib/x86_64-linux-gnu - // on x86_64. - if (step.rootModuleTarget().os.tag == .linux) { - const triple = try step.rootModuleTarget().linuxTriple(b.allocator); - step.addLibraryPath(.{ .cwd_relative = b.fmt("/usr/lib/{s}", .{triple}) }); - } - - // C files - step.linkLibC(); - step.addIncludePath(b.path("src/stb")); - step.addCSourceFiles(.{ .files = &.{"src/stb/stb.c"} }); - if (step.rootModuleTarget().os.tag == .linux) { - step.addIncludePath(b.path("src/apprt/gtk")); - } - - // C++ files - step.linkLibCpp(); - step.addIncludePath(b.path("src")); - { - // From hwy/detect_targets.h - const HWY_AVX3_SPR: c_int = 1 << 4; - const HWY_AVX3_ZEN4: c_int = 1 << 6; - const HWY_AVX3_DL: c_int = 1 << 7; - const HWY_AVX3: c_int = 1 << 8; - - // Zig 0.13 bug: https://github.com/ziglang/zig/issues/20414 - // To workaround this we just disable AVX512 support completely. - // The performance difference between AVX2 and AVX512 is not - // significant for our use case and AVX512 is very rare on consumer - // hardware anyways. - const HWY_DISABLED_TARGETS: c_int = HWY_AVX3_SPR | HWY_AVX3_ZEN4 | HWY_AVX3_DL | HWY_AVX3; - - step.addCSourceFiles(.{ - .files = &.{ - "src/simd/base64.cpp", - "src/simd/codepoint_width.cpp", - "src/simd/index_of.cpp", - "src/simd/vt.cpp", - }, - .flags = if (step.rootModuleTarget().cpu.arch == .x86_64) &.{ - b.fmt("-DHWY_DISABLED_TARGETS={}", .{HWY_DISABLED_TARGETS}), - } else &.{}, - }); - } - - // We always require the system SDK so that our system headers are available. - // This makes things like `os/log.h` available for cross-compiling. - if (step.rootModuleTarget().isDarwin()) { - try @import("apple_sdk").addPaths(b, &step.root_module); - try addMetallib(b, step); - } - - // Other dependencies, mostly pure Zig - step.root_module.addImport("opengl", b.dependency( - "opengl", - .{}, - ).module("opengl")); - step.root_module.addImport("vaxis", b.dependency("vaxis", .{ - .target = target, - .optimize = optimize, - }).module("vaxis")); - step.root_module.addImport("wuffs", b.dependency("wuffs", .{ - .target = target, - .optimize = optimize, - }).module("wuffs")); - step.root_module.addImport("xev", b.dependency("libxev", .{ - .target = target, - .optimize = optimize, - }).module("xev")); - step.root_module.addImport("z2d", b.addModule("z2d", .{ - .root_source_file = b.dependency("z2d", .{}).path("src/z2d.zig"), - .target = target, - .optimize = optimize, - })); - step.root_module.addImport("ziglyph", b.dependency("ziglyph", .{ - .target = target, - .optimize = optimize, - }).module("ziglyph")); - step.root_module.addImport("zf", b.dependency("zf", .{ - .target = target, - .optimize = optimize, - .with_tui = false, - }).module("zf")); - - // Mac Stuff - if (step.rootModuleTarget().isDarwin()) { - const objc_dep = b.dependency("zig_objc", .{ - .target = target, - .optimize = optimize, - }); - const macos_dep = b.dependency("macos", .{ - .target = target, - .optimize = optimize, - }); - - step.root_module.addImport("objc", objc_dep.module("objc")); - step.root_module.addImport("macos", macos_dep.module("macos")); - step.linkLibrary(macos_dep.artifact("macos")); - try static_libs.append(macos_dep.artifact("macos").getEmittedBin()); - - if (config.renderer == .opengl) { - step.linkFramework("OpenGL"); - } - } - - // cimgui - const cimgui_dep = b.dependency("cimgui", .{ - .target = target, - .optimize = optimize, - }); - step.root_module.addImport("cimgui", cimgui_dep.module("cimgui")); - step.linkLibrary(cimgui_dep.artifact("cimgui")); - try static_libs.append(cimgui_dep.artifact("cimgui").getEmittedBin()); - - // Highway - const highway_dep = b.dependency("highway", .{ - .target = target, - .optimize = optimize, - }); - step.linkLibrary(highway_dep.artifact("highway")); - try static_libs.append(highway_dep.artifact("highway").getEmittedBin()); - - // utfcpp - This is used as a dependency on our hand-written C++ code - const utfcpp_dep = b.dependency("utfcpp", .{ - .target = target, - .optimize = optimize, - }); - step.linkLibrary(utfcpp_dep.artifact("utfcpp")); - try static_libs.append(utfcpp_dep.artifact("utfcpp").getEmittedBin()); - - // If we're building an exe then we have additional dependencies. - if (step.kind != .lib) { - // We always statically compile glad - step.addIncludePath(b.path("vendor/glad/include/")); - step.addCSourceFile(.{ - .file = b.path("vendor/glad/src/gl.c"), - .flags = &.{}, - }); - - // When we're targeting flatpak we ALWAYS link GTK so we - // get access to glib for dbus. - if (config.flatpak) step.linkSystemLibrary2("gtk4", dynamic_link_opts); - - switch (config.app_runtime) { - .none => {}, - - .glfw => glfw: { - const mach_glfw_dep = b.lazyDependency("mach_glfw", .{ - .target = target, - .optimize = optimize, - }) orelse break :glfw; - step.root_module.addImport("glfw", mach_glfw_dep.module("mach-glfw")); - }, - - .gtk => { - step.linkSystemLibrary2("gtk4", dynamic_link_opts); - if (config.adwaita) step.linkSystemLibrary2("adwaita-1", dynamic_link_opts); - if (config.x11) step.linkSystemLibrary2("X11", dynamic_link_opts); - - if (config.wayland) { - const scanner = Scanner.create(b, .{}); - - const wayland = b.createModule(.{ .root_source_file = scanner.result }); - - const plasma_wayland_protocols = b.dependency("plasma_wayland_protocols", .{ - .target = target, - .optimize = optimize, - }); - scanner.addCustomProtocol(plasma_wayland_protocols.path("src/protocols/blur.xml")); - - scanner.generate("wl_compositor", 1); - scanner.generate("org_kde_kwin_blur_manager", 1); - - step.root_module.addImport("wayland", wayland); - step.linkSystemLibrary2("wayland-client", dynamic_link_opts); - } - - { - const gresource = @import("src/apprt/gtk/gresource.zig"); - - const wf = b.addWriteFiles(); - const gresource_xml = wf.add("gresource.xml", gresource.gresource_xml); - - const generate_resources_c = b.addSystemCommand(&.{ - "glib-compile-resources", - "--c-name", - "ghostty", - "--generate-source", - "--target", - }); - const ghostty_resources_c = generate_resources_c.addOutputFileArg("ghostty_resources.c"); - generate_resources_c.addFileArg(gresource_xml); - generate_resources_c.extra_file_dependencies = &gresource.dependencies; - step.addCSourceFile(.{ .file = ghostty_resources_c, .flags = &.{} }); - - const generate_resources_h = b.addSystemCommand(&.{ - "glib-compile-resources", - "--c-name", - "ghostty", - "--generate-header", - "--target", - }); - const ghostty_resources_h = generate_resources_h.addOutputFileArg("ghostty_resources.h"); - generate_resources_h.addFileArg(gresource_xml); - generate_resources_h.extra_file_dependencies = &gresource.dependencies; - step.addIncludePath(ghostty_resources_h.dirname()); - } - }, - } - } - - try addHelp(b, step, config); - try addUnicodeTables(b, step); - - return static_libs; -} - -/// Generate Metal shader library -fn addMetallib( - b: *std.Build, - step: *std.Build.Step.Compile, -) !void { - const metal_step = MetallibStep.create(b, .{ - .name = "Ghostty", - .target = step.root_module.resolved_target.?, - .sources = &.{b.path("src/renderer/shaders/cell.metal")}, - }); - - metal_step.output.addStepDependencies(&step.step); - step.root_module.addAnonymousImport("ghostty_metallib", .{ - .root_source_file = metal_step.output, - }); -} - -/// Generate help files -fn addHelp( - b: *std.Build, - step_: ?*std.Build.Step.Compile, - config: BuildConfig, -) !void { - // Our static state between runs. We memoize our help strings - // so that we only execute the help generation once. - const HelpState = struct { - var generated: ?std.Build.LazyPath = null; - }; - - const help_output = HelpState.generated orelse strings: { - const help_exe = b.addExecutable(.{ - .name = "helpgen", - .root_source_file = b.path("src/helpgen.zig"), - .target = b.host, - }); - if (step_ == null) b.installArtifact(help_exe); - - const help_config = config: { - var copy = config; - copy.exe_entrypoint = .helpgen; - break :config copy; - }; - const options = b.addOptions(); - try help_config.addOptions(options); - help_exe.root_module.addOptions("build_options", options); - - const help_run = b.addRunArtifact(help_exe); - HelpState.generated = help_run.captureStdOut(); - break :strings HelpState.generated.?; - }; - - if (step_) |step| { - help_output.addStepDependencies(&step.step); - step.root_module.addAnonymousImport("help_strings", .{ - .root_source_file = help_output, - }); - } -} - -/// Generate unicode fast lookup tables -fn addUnicodeTables( - b: *std.Build, - step_: ?*std.Build.Step.Compile, -) !void { - // Our static state between runs. We memoize our output to gen once - const State = struct { - var generated: ?std.Build.LazyPath = null; - }; - - const output = State.generated orelse strings: { - const exe = b.addExecutable(.{ - .name = "unigen", - .root_source_file = b.path("src/unicode/props.zig"), - .target = b.host, - }); - exe.linkLibC(); - if (step_ == null) b.installArtifact(exe); - - const ziglyph_dep = b.dependency("ziglyph", .{ - .target = b.host, - }); - exe.root_module.addImport("ziglyph", ziglyph_dep.module("ziglyph")); - - const help_run = b.addRunArtifact(exe); - State.generated = help_run.captureStdOut(); - break :strings State.generated.?; - }; - - if (step_) |step| { - output.addStepDependencies(&step.step); - step.root_module.addAnonymousImport("unicode_tables", .{ - .root_source_file = output, - }); - } -} - -/// Generate documentation (manpages, etc.) from help strings -fn buildDocumentation( - b: *std.Build, - config: BuildConfig, -) !void { - const manpages = [_]struct { - name: []const u8, - section: []const u8, - }{ - .{ .name = "ghostty", .section = "1" }, - .{ .name = "ghostty", .section = "5" }, - }; - - inline for (manpages) |manpage| { - const generate_markdown = b.addExecutable(.{ - .name = "mdgen_" ++ manpage.name ++ "_" ++ manpage.section, - .root_source_file = b.path("src/main.zig"), - .target = b.host, - }); - try addHelp(b, generate_markdown, config); - - const gen_config = config: { - var copy = config; - copy.exe_entrypoint = @field( - build_config.ExeEntrypoint, - "mdgen_" ++ manpage.name ++ "_" ++ manpage.section, - ); - break :config copy; - }; - - const generate_markdown_options = b.addOptions(); - try gen_config.addOptions(generate_markdown_options); - generate_markdown.root_module.addOptions("build_options", generate_markdown_options); - - const generate_markdown_step = b.addRunArtifact(generate_markdown); - const markdown_output = generate_markdown_step.captureStdOut(); - - b.getInstallStep().dependOn(&b.addInstallFile( - markdown_output, - "share/ghostty/doc/" ++ manpage.name ++ "." ++ manpage.section ++ ".md", - ).step); - - const generate_html = b.addSystemCommand(&.{"pandoc"}); - generate_html.addArgs(&.{ - "--standalone", - "--from", - "markdown", - "--to", - "html", - }); - generate_html.addFileArg(markdown_output); - - b.getInstallStep().dependOn(&b.addInstallFile( - generate_html.captureStdOut(), - "share/ghostty/doc/" ++ manpage.name ++ "." ++ manpage.section ++ ".html", - ).step); - - const generate_manpage = b.addSystemCommand(&.{"pandoc"}); - generate_manpage.addArgs(&.{ - "--standalone", - "--from", - "markdown", - "--to", - "man", - }); - generate_manpage.addFileArg(markdown_output); - - b.getInstallStep().dependOn(&b.addInstallFile( - generate_manpage.captureStdOut(), - "share/man/man" ++ manpage.section ++ "/" ++ manpage.name ++ "." ++ manpage.section, - ).step); - } -} - -/// Generate the website reference data that we merge into the -/// official Ghostty website. This isn't meant to be part of any -/// actual build. -fn buildWebData( - b: *std.Build, - config: BuildConfig, -) !void { - { - const webgen_config = b.addExecutable(.{ - .name = "webgen_config", - .root_source_file = b.path("src/main.zig"), - .target = b.host, - }); - try addHelp(b, webgen_config, config); - - { - const buildconfig = config: { - var copy = config; - copy.exe_entrypoint = .webgen_config; - break :config copy; - }; - - const options = b.addOptions(); - try buildconfig.addOptions(options); - webgen_config.root_module.addOptions("build_options", options); - } - - const webgen_config_step = b.addRunArtifact(webgen_config); - const webgen_config_out = webgen_config_step.captureStdOut(); - - b.getInstallStep().dependOn(&b.addInstallFile( - webgen_config_out, - "share/ghostty/webdata/config.mdx", - ).step); - } - - { - const webgen_actions = b.addExecutable(.{ - .name = "webgen_actions", - .root_source_file = b.path("src/main.zig"), - .target = b.host, - }); - try addHelp(b, webgen_actions, config); - - { - const buildconfig = config: { - var copy = config; - copy.exe_entrypoint = .webgen_actions; - break :config copy; - }; - - const options = b.addOptions(); - try buildconfig.addOptions(options); - webgen_actions.root_module.addOptions("build_options", options); - } - - const webgen_actions_step = b.addRunArtifact(webgen_actions); - const webgen_actions_out = webgen_actions_step.captureStdOut(); - - b.getInstallStep().dependOn(&b.addInstallFile( - webgen_actions_out, - "share/ghostty/webdata/actions.mdx", - ).step); - } -} - -fn benchSteps( - b: *std.Build, - target: std.Build.ResolvedTarget, - config: BuildConfig, - install: bool, -) !void { - // Open the directory ./src/bench - const c_dir_path = (comptime root()) ++ "/src/bench"; - var c_dir = try fs.cwd().openDir(c_dir_path, .{ .iterate = true }); - defer c_dir.close(); - - // Go through and add each as a step - var c_dir_it = c_dir.iterate(); - while (try c_dir_it.next()) |entry| { - // Get the index of the last '.' so we can strip the extension. - const index = std.mem.lastIndexOfScalar(u8, entry.name, '.') orelse continue; - if (index == 0) continue; - - // If it doesn't end in 'zig' then ignore - if (!std.mem.eql(u8, entry.name[index + 1 ..], "zig")) continue; - - // Name of the conformance app and full path to the entrypoint. - const name = entry.name[0..index]; - - // Executable builder. - const bin_name = try std.fmt.allocPrint(b.allocator, "bench-{s}", .{name}); - const c_exe = b.addExecutable(.{ - .name = bin_name, - .root_source_file = b.path("src/main.zig"), - .target = target, - - // We always want our benchmarks to be in release mode. - .optimize = .ReleaseFast, - }); - c_exe.linkLibC(); - if (install) b.installArtifact(c_exe); - _ = try addDeps(b, c_exe, config: { - var copy = config; - var enum_name: [64]u8 = undefined; - @memcpy(enum_name[0..name.len], name); - std.mem.replaceScalar(u8, enum_name[0..name.len], '-', '_'); - - var buf: [64]u8 = undefined; - copy.exe_entrypoint = std.meta.stringToEnum( - build_config.ExeEntrypoint, - try std.fmt.bufPrint(&buf, "bench_{s}", .{enum_name[0..name.len]}), - ).?; - - break :config copy; - }); - } -} - -fn conformanceSteps( - b: *std.Build, - target: std.Build.ResolvedTarget, - optimize: std.builtin.Mode, -) !std.StringHashMap(*CompileStep) { - var map = std.StringHashMap(*CompileStep).init(b.allocator); - - // Open the directory ./conformance - const c_dir_path = (comptime root()) ++ "/conformance"; - var c_dir = try fs.cwd().openDir(c_dir_path, .{ .iterate = true }); - defer c_dir.close(); - - // Go through and add each as a step - var c_dir_it = c_dir.iterate(); - while (try c_dir_it.next()) |entry| { - // Get the index of the last '.' so we can strip the extension. - const index = std.mem.lastIndexOfScalar(u8, entry.name, '.') orelse continue; - if (index == 0) continue; - - // Name of the conformance app and full path to the entrypoint. - const name = try b.allocator.dupe(u8, entry.name[0..index]); - const path = try fs.path.join(b.allocator, &[_][]const u8{ - c_dir_path, - entry.name, - }); - - // Executable builder. - const c_exe = b.addExecutable(.{ - .name = name, - .root_source_file = b.path(path), - .target = target, - .optimize = optimize, - }); - - const install = b.addInstallArtifact(c_exe, .{}); - install.dest_sub_path = "conformance"; - b.getInstallStep().dependOn(&install.step); - - // Store the mapping - try map.put(name, c_exe); - } - - return map; -} - -/// Path to the directory with the build.zig. -fn root() []const u8 { - return std.fs.path.dirname(@src().file) orelse unreachable; -} - -/// ANSI escape codes for colored log output -const color_map = std.StaticStringMap([]const u8).initComptime(.{ - &.{ "black", "30m" }, - &.{ "blue", "34m" }, - &.{ "b", "1m" }, - &.{ "d", "2m" }, - &.{ "cyan", "36m" }, - &.{ "green", "32m" }, - &.{ "magenta", "35m" }, - &.{ "red", "31m" }, - &.{ "white", "37m" }, - &.{ "yellow", "33m" }, -}); diff --git a/conformance/ansi_ri.zig b/conformance/ansi_ri.zig deleted file mode 100644 index 991804295..000000000 --- a/conformance/ansi_ri.zig +++ /dev/null @@ -1,12 +0,0 @@ -//! Reverse Index (RI) - ESC M -const std = @import("std"); - -pub fn main() !void { - const stdout = std.io.getStdOut().writer(); - try stdout.print("A\nB\nC", .{}); - try stdout.print("\x1BM", .{}); - try stdout.print("D\n\n", .{}); - - // const stdin = std.io.getStdIn().reader(); - // _ = try stdin.readByte(); -} diff --git a/conformance/ansi_ri_top.zig b/conformance/ansi_ri_top.zig deleted file mode 100644 index b2258f069..000000000 --- a/conformance/ansi_ri_top.zig +++ /dev/null @@ -1,23 +0,0 @@ -//! Reverse Index (RI) - ESC M -//! Case: test that if the cursor is at the top, it scrolls down. -const std = @import("std"); - -pub fn main() !void { - const stdout = std.io.getStdOut().writer(); - try stdout.print("A\nB\n\n", .{}); - - try stdout.print("\x1B[H", .{}); // Top-left - try stdout.print("\x1BM", .{}); // Reverse-Index - try stdout.print("D", .{}); - - try stdout.print("\x0D", .{}); // CR - try stdout.print("\x0A", .{}); // LF - try stdout.print("\x1B[H", .{}); // Top-left - try stdout.print("\x1BM", .{}); // Reverse-Index - try stdout.print("E", .{}); - - try stdout.print("\n", .{}); - - // const stdin = std.io.getStdIn().reader(); - // _ = try stdin.readByte(); -} diff --git a/conformance/blocks.zig b/conformance/blocks.zig deleted file mode 100644 index a977ca4ea..000000000 --- a/conformance/blocks.zig +++ /dev/null @@ -1,99 +0,0 @@ -//! Outputs various box glyphs for testing. -const std = @import("std"); - -pub fn main() !void { - const stdout = std.io.getStdOut().writer(); - - // Box Drawing - { - try stdout.print("\x1b[4mBox Drawing\x1b[0m\n", .{}); - var i: usize = 0x2500; - const step: usize = 32; - while (i <= 0x257F) : (i += step) { - var j: usize = 0; - while (j < step) : (j += 1) { - try stdout.print("{u} ", .{@as(u21, @intCast(i + j))}); - } - - try stdout.print("\n\n", .{}); - } - } - - // Block Elements - { - try stdout.print("\x1b[4mBlock Elements\x1b[0m\n", .{}); - var i: usize = 0x2580; - const step: usize = 32; - while (i <= 0x259f) : (i += step) { - var j: usize = 0; - while (j < step) : (j += 1) { - try stdout.print("{u} ", .{@as(u21, @intCast(i + j))}); - } - - try stdout.print("\n\n", .{}); - } - } - - // Braille Elements - { - try stdout.print("\x1b[4mBraille\x1b[0m\n", .{}); - var i: usize = 0x2800; - const step: usize = 32; - while (i <= 0x28FF) : (i += step) { - var j: usize = 0; - while (j < step) : (j += 1) { - try stdout.print("{u} ", .{@as(u21, @intCast(i + j))}); - } - - try stdout.print("\n\n", .{}); - } - } - - { - try stdout.print("\x1b[4mSextants\x1b[0m\n", .{}); - var i: usize = 0x1FB00; - const step: usize = 32; - const end = 0x1FB3B; - while (i <= end) : (i += step) { - var j: usize = 0; - while (j < step) : (j += 1) { - const v = i + j; - if (v <= end) try stdout.print("{u} ", .{@as(u21, @intCast(v))}); - } - - try stdout.print("\n\n", .{}); - } - } - - { - try stdout.print("\x1b[4mWedge Triangles\x1b[0m\n", .{}); - var i: usize = 0x1FB3C; - const step: usize = 32; - const end = 0x1FB6B; - while (i <= end) : (i += step) { - var j: usize = 0; - while (j < step) : (j += 1) { - const v = i + j; - if (v <= end) try stdout.print("{u} ", .{@as(u21, @intCast(v))}); - } - - try stdout.print("\n\n", .{}); - } - } - - { - try stdout.print("\x1b[4mOther\x1b[0m\n", .{}); - var i: usize = 0x1FB70; - const step: usize = 32; - const end = 0x1FB8B; - while (i <= end) : (i += step) { - var j: usize = 0; - while (j < step) : (j += 1) { - const v = i + j; - if (v <= end) try stdout.print("{u} ", .{@as(u21, @intCast(v))}); - } - - try stdout.print("\n\n", .{}); - } - } -} diff --git a/conformance/csi_decstbm.zig b/conformance/csi_decstbm.zig deleted file mode 100644 index f8b652427..000000000 --- a/conformance/csi_decstbm.zig +++ /dev/null @@ -1,15 +0,0 @@ -//! Set Top and Bottom Margins (DECSTBM) - ESC [ r -const std = @import("std"); - -pub fn main() !void { - const stdout = std.io.getStdOut().writer(); - try stdout.print("A\nB\nC\nD", .{}); - try stdout.print("\x1B[1;3r", .{}); // cursor up - try stdout.print("\x1B[1;1H", .{}); // top-left - try stdout.print("\x1B[M", .{}); // delete line - try stdout.print("E\n", .{}); - try stdout.print("\x1B[7;1H", .{}); // cursor up - - // const stdin = std.io.getStdIn().reader(); - // _ = try stdin.readByte(); -} diff --git a/conformance/csi_dl.zig b/conformance/csi_dl.zig deleted file mode 100644 index 175637d9b..000000000 --- a/conformance/csi_dl.zig +++ /dev/null @@ -1,14 +0,0 @@ -//! Delete Line (DL) - Esc [ M -const std = @import("std"); - -pub fn main() !void { - const stdout = std.io.getStdOut().writer(); - try stdout.print("A\nB\nC\nD", .{}); - try stdout.print("\x1B[2A", .{}); // cursor up - try stdout.print("\x1B[M", .{}); - try stdout.print("E\n", .{}); - try stdout.print("\x1B[B", .{}); - - // const stdin = std.io.getStdIn().reader(); - // _ = try stdin.readByte(); -} diff --git a/conformance/csi_il.zig b/conformance/csi_il.zig deleted file mode 100644 index 52d2c392f..000000000 --- a/conformance/csi_il.zig +++ /dev/null @@ -1,17 +0,0 @@ -//! Insert Line (IL) - Esc [ L -const std = @import("std"); - -pub fn main() !void { - const stdout = std.io.getStdOut().writer(); - try stdout.print("\x1B[2J", .{}); // clear screen - try stdout.print("\x1B[1;1H", .{}); // set cursor position - try stdout.print("A\nB\nC\nD\nE", .{}); - try stdout.print("\x1B[1;2r", .{}); // set scroll region - try stdout.print("\x1B[1;1H", .{}); // set cursor position - try stdout.print("\x1B[1L", .{}); // insert lines - try stdout.print("X", .{}); - try stdout.print("\x1B[7;1H", .{}); // set cursor position - - // const stdin = std.io.getStdIn().reader(); - // _ = try stdin.readByte(); -} diff --git a/conformance/esc_decaln.zig b/conformance/esc_decaln.zig deleted file mode 100644 index aeb1887a4..000000000 --- a/conformance/esc_decaln.zig +++ /dev/null @@ -1,10 +0,0 @@ -//! DECALN - ESC # 8 -const std = @import("std"); - -pub fn main() !void { - const stdout = std.io.getStdOut().writer(); - try stdout.print("\x1B#8", .{}); - - // const stdin = std.io.getStdIn().reader(); - // _ = try stdin.readByte(); -} diff --git a/nix/package.nix b/nix/package.nix index 166a3c4fb..2f7825a56 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -53,7 +53,6 @@ fileset = lib.fileset.intersection (lib.fileset.fromSource (lib.sources.cleanSource ../.)) ( lib.fileset.unions [ ../dist/linux - ../conformance ../images ../include ../pkg diff --git a/src/build/Config.zig b/src/build/Config.zig new file mode 100644 index 000000000..71dffce4a --- /dev/null +++ b/src/build/Config.zig @@ -0,0 +1,503 @@ +/// 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 apprt = @import("../apprt.zig"); +const font = @import("../font/main.zig"); +const renderer = @import("../renderer.zig"); +const Command = @import("../Command.zig"); +const WasmTarget = @import("../os/wasm/target.zig").Target; + +const gtk = @import("gtk.zig"); +const GitVersion = @import("GitVersion.zig"); + +/// The version of the next release. +/// +/// TODO: When Zig 0.14 is released, derive this from build.zig.zon directly. +/// Until then this MUST match build.zig.zon and should always be the +/// _next_ version to release. +const app_version: std.SemanticVersion = .{ .major = 1, .minor = 0, .patch = 2 }; + +/// Standard build configuration options. +optimize: std.builtin.OptimizeMode, +target: std.Build.ResolvedTarget, +wasm_target: WasmTarget, + +/// Comptime interfaces +app_runtime: apprt.Runtime = .none, +renderer: renderer.Impl = .opengl, +font_backend: font.Backend = .freetype, + +/// Feature flags +adwaita: bool = false, +x11: bool = false, +wayland: bool = false, +sentry: 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, +emit_test_exe: bool = false, +emit_bench: bool = false, +emit_helpgen: bool = false, +emit_docs: bool = false, +emit_webdata: bool = false, +emit_xcframework: bool = false, + +/// Environmental properties +env: std.process.EnvMap, + +pub fn init(b: *std.Build) !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.isDarwin()) { + result = genericMacOSTarget(b, null); + } + + // 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; + }; + + // 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: bool = 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, + .env = env, + }; + + //--------------------------------------------------------------- + // Comptime Interfaces + + config.font_backend = b.option( + font.Backend, + "font-backend", + "The font backend to use for discovery and rasterization.", + ) orelse font.Backend.default(target.result, wasm_target); + + config.app_runtime = b.option( + apprt.Runtime, + "app-runtime", + "The app runtime to use. Not all values supported on all platforms.", + ) orelse apprt.Runtime.default(target.result); + + config.renderer = b.option( + renderer.Impl, + "renderer", + "The app runtime to use. Not all values supported on all platforms.", + ) orelse renderer.Impl.default(target.result, wasm_target); + + //--------------------------------------------------------------- + // Feature Flags + + config.adwaita = b.option( + bool, + "gtk-adwaita", + "Enables the use of Adwaita when using the GTK rendering backend.", + ) orelse true; + + config.flatpak = b.option( + bool, + "flatpak", + "Build for Flatpak (integrates with Flatpak APIs). 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.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; + + //--------------------------------------------------------------- + // 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: { + // 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_test_exe = b.option( + bool, + "emit-test-exe", + "Build and install test executables 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) 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 = Command.expandPath(b.allocator, "pandoc") catch + break :emit_docs false; + defer if (path) |p| b.allocator.free(p); + break :emit_docs path != null; + }; + + 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 builtin.target.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); + + //--------------------------------------------------------------- + // 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.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 }); + } + } + + 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, "adwaita", self.adwaita); + step.addOption(bool, "x11", self.x11); + step.addOption(bool, "wayland", self.wayland); + step.addOption(bool, "sentry", self.sentry); + step.addOption(apprt.Runtime, "app_runtime", self.app_runtime); + step.addOption(font.Backend, "font_backend", self.font_backend); + step.addOption(renderer.Impl, "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, + "{}", + .{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; + }, + ); +} + +/// 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, + .adwaita = options.adwaita, + .app_runtime = std.meta.stringToEnum(apprt.Runtime, @tagName(options.app_runtime)).?, + .font_backend = std.meta.stringToEnum(font.Backend, @tagName(options.font_backend)).?, + .renderer = std.meta.stringToEnum(renderer.Impl, @tagName(options.renderer)).?, + .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, + }; +} + +/// 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, + bench_parser, + bench_stream, + bench_codepoint_width, + bench_grapheme_break, + bench_page_init, +}; + +/// The release channel for the build. +pub const ReleaseChannel = enum { + /// Unstable builds on every commit. + tip, + + /// Stable tagged releases. + stable, +}; diff --git a/src/build/GhosttyBench.zig b/src/build/GhosttyBench.zig new file mode 100644 index 000000000..27f40abff --- /dev/null +++ b/src/build/GhosttyBench.zig @@ -0,0 +1,69 @@ +//! GhosttyBench generates all the Ghostty benchmark helper binaries. +const GhosttyBench = @This(); + +const std = @import("std"); +const Config = @import("Config.zig"); +const SharedDeps = @import("SharedDeps.zig"); + +steps: []*std.Build.Step.Compile, + +pub fn init( + b: *std.Build, + deps: *const SharedDeps, +) !GhosttyBench { + var steps = std.ArrayList(*std.Build.Step.Compile).init(b.allocator); + errdefer steps.deinit(); + + // Open the directory ./src/bench + const c_dir_path = b.pathFromRoot("src/bench"); + var c_dir = try std.fs.cwd().openDir(c_dir_path, .{ .iterate = true }); + defer c_dir.close(); + + // Go through and add each as a step + var c_dir_it = c_dir.iterate(); + while (try c_dir_it.next()) |entry| { + // Get the index of the last '.' so we can strip the extension. + const index = std.mem.lastIndexOfScalar(u8, entry.name, '.') orelse continue; + if (index == 0) continue; + + // If it doesn't end in 'zig' then ignore + if (!std.mem.eql(u8, entry.name[index + 1 ..], "zig")) continue; + + // Name of the conformance app and full path to the entrypoint. + const name = entry.name[0..index]; + + // Executable builder. + const bin_name = try std.fmt.allocPrint(b.allocator, "bench-{s}", .{name}); + const c_exe = b.addExecutable(.{ + .name = bin_name, + .root_source_file = b.path("src/main.zig"), + .target = deps.config.target, + + // We always want our benchmarks to be in release mode. + .optimize = .ReleaseFast, + }); + c_exe.linkLibC(); + + // Update our entrypoint + var enum_name: [64]u8 = undefined; + @memcpy(enum_name[0..name.len], name); + std.mem.replaceScalar(u8, enum_name[0..name.len], '-', '_'); + + var buf: [64]u8 = undefined; + const new_deps = try deps.changeEntrypoint(b, std.meta.stringToEnum( + Config.ExeEntrypoint, + try std.fmt.bufPrint(&buf, "bench_{s}", .{enum_name[0..name.len]}), + ).?); + + _ = try new_deps.add(c_exe); + + try steps.append(c_exe); + } + + return .{ .steps = steps.items }; +} + +pub fn install(self: *const GhosttyBench) void { + const b = self.steps[0].step.owner; + for (self.steps) |step| b.installArtifact(step); +} diff --git a/src/build/GhosttyDocs.zig b/src/build/GhosttyDocs.zig new file mode 100644 index 000000000..28cbea2c9 --- /dev/null +++ b/src/build/GhosttyDocs.zig @@ -0,0 +1,92 @@ +//! GhosttyDocs generates all the on-disk documentation that Ghostty is +//! installed with (man pages, html, markdown, etc.) +const GhosttyDocs = @This(); + +const std = @import("std"); +const Config = @import("Config.zig"); +const SharedDeps = @import("SharedDeps.zig"); + +steps: []*std.Build.Step, + +pub fn init( + b: *std.Build, + deps: *const SharedDeps, +) !GhosttyDocs { + var steps = std.ArrayList(*std.Build.Step).init(b.allocator); + errdefer steps.deinit(); + + const manpages = [_]struct { + name: []const u8, + section: []const u8, + }{ + .{ .name = "ghostty", .section = "1" }, + .{ .name = "ghostty", .section = "5" }, + }; + + inline for (manpages) |manpage| { + const generate_markdown = b.addExecutable(.{ + .name = "mdgen_" ++ manpage.name ++ "_" ++ manpage.section, + .root_source_file = b.path("src/main.zig"), + .target = b.host, + }); + deps.help_strings.addImport(generate_markdown); + + const gen_config = config: { + var copy = deps.config.*; + copy.exe_entrypoint = @field( + Config.ExeEntrypoint, + "mdgen_" ++ manpage.name ++ "_" ++ manpage.section, + ); + break :config copy; + }; + + const generate_markdown_options = b.addOptions(); + try gen_config.addOptions(generate_markdown_options); + generate_markdown.root_module.addOptions("build_options", generate_markdown_options); + + const generate_markdown_step = b.addRunArtifact(generate_markdown); + const markdown_output = generate_markdown_step.captureStdOut(); + + try steps.append(&b.addInstallFile( + markdown_output, + "share/ghostty/doc/" ++ manpage.name ++ "." ++ manpage.section ++ ".md", + ).step); + + const generate_html = b.addSystemCommand(&.{"pandoc"}); + generate_html.addArgs(&.{ + "--standalone", + "--from", + "markdown", + "--to", + "html", + }); + generate_html.addFileArg(markdown_output); + + try steps.append(&b.addInstallFile( + generate_html.captureStdOut(), + "share/ghostty/doc/" ++ manpage.name ++ "." ++ manpage.section ++ ".html", + ).step); + + const generate_manpage = b.addSystemCommand(&.{"pandoc"}); + generate_manpage.addArgs(&.{ + "--standalone", + "--from", + "markdown", + "--to", + "man", + }); + generate_manpage.addFileArg(markdown_output); + + try steps.append(&b.addInstallFile( + generate_manpage.captureStdOut(), + "share/man/man" ++ manpage.section ++ "/" ++ manpage.name ++ "." ++ manpage.section, + ).step); + } + + return .{ .steps = steps.items }; +} + +pub fn install(self: *const GhosttyDocs) void { + const b = self.steps[0].owner; + for (self.steps) |step| b.getInstallStep().dependOn(step); +} diff --git a/src/build/GhosttyExe.zig b/src/build/GhosttyExe.zig new file mode 100644 index 000000000..ef5303baa --- /dev/null +++ b/src/build/GhosttyExe.zig @@ -0,0 +1,120 @@ +const Ghostty = @This(); + +const std = @import("std"); +const Config = @import("Config.zig"); +const SharedDeps = @import("SharedDeps.zig"); + +/// The primary Ghostty executable. +exe: *std.Build.Step.Compile, + +/// The install step for the executable. +install_step: *std.Build.Step.InstallArtifact, + +pub fn init(b: *std.Build, cfg: *const Config, deps: *const SharedDeps) !Ghostty { + const exe: *std.Build.Step.Compile = b.addExecutable(.{ + .name = "ghostty", + .root_source_file = b.path("src/main.zig"), + .target = cfg.target, + .optimize = cfg.optimize, + .strip = cfg.strip, + }); + const install_step = b.addInstallArtifact(exe, .{}); + + // Set PIE if requested + if (cfg.pie) exe.pie = true; + + // Add the shared dependencies + _ = try deps.add(exe); + + // Check for possible issues + try checkNixShell(exe, cfg); + + // Patch our rpath if that option is specified. + if (cfg.patch_rpath) |rpath| { + if (rpath.len > 0) { + const run = std.Build.Step.Run.create(b, "patchelf rpath"); + run.addArgs(&.{ "patchelf", "--set-rpath", rpath }); + run.addArtifactArg(exe); + install_step.step.dependOn(&run.step); + } + } + + // OS-specific + switch (cfg.target.result.os.tag) { + .windows => { + exe.subsystem = .Windows; + exe.addWin32ResourceFile(.{ + .file = b.path("dist/windows/ghostty.rc"), + }); + }, + + else => {}, + } + + return .{ + .exe = exe, + .install_step = install_step, + }; +} + +/// Add the ghostty exe to the install target. +pub fn install(self: *const Ghostty) void { + const b = self.install_step.step.owner; + b.getInstallStep().dependOn(&self.install_step.step); +} + +/// If we're in NixOS but not in the shell environment then we issue +/// a warning because the rpath may not be setup properly. This doesn't modify +/// our build in any way but addresses a common build-from-source issue +/// for a subset of users. +fn checkNixShell(exe: *std.Build.Step.Compile, cfg: *const Config) !void { + // Non-Linux doesn't have rpath issues. + if (cfg.target.result.os.tag != .linux) return; + + // When cross-compiling, we don't need to worry about matching our + // Nix shell rpath since the resulting binary will be run on a + // separate system. + if (!cfg.target.query.isNativeCpu()) return; + if (!cfg.target.query.isNativeOs()) return; + + // Verify we're in NixOS + std.fs.accessAbsolute("/etc/NIXOS", .{}) catch return; + + // If we're in a nix shell, not a problem + if (cfg.env.get("IN_NIX_SHELL") != null) return; + + try exe.step.addError( + "\x1b[" ++ color_map.get("yellow").? ++ + "\x1b[" ++ color_map.get("d").? ++ + \\Detected building on and for NixOS outside of the Nix shell environment. + \\ + \\The resulting ghostty binary will likely fail on launch because it is + \\unable to dynamically load the windowing libs (X11, Wayland, etc.). + \\We highly recommend running only within the Nix build environment + \\and the resulting binary will be portable across your system. + \\ + \\To run in the Nix build environment, use the following command. + \\Append any additional options like (`-Doptimize` flags). The resulting + \\binary will be in zig-out as usual. + \\ + \\ nix develop -c zig build + \\ + ++ + "\x1b[0m", + .{}, + ); +} + +/// ANSI escape codes for colored log output +const color_map = std.StaticStringMap([]const u8).initComptime(.{ + &.{ "black", "30m" }, + &.{ "blue", "34m" }, + &.{ "b", "1m" }, + &.{ "d", "2m" }, + &.{ "cyan", "36m" }, + &.{ "green", "32m" }, + &.{ "magenta", "35m" }, + &.{ "red", "31m" }, + &.{ "white", "37m" }, + &.{ "yellow", "33m" }, +}); diff --git a/src/build/GhosttyLib.zig b/src/build/GhosttyLib.zig new file mode 100644 index 000000000..53aee0e24 --- /dev/null +++ b/src/build/GhosttyLib.zig @@ -0,0 +1,110 @@ +const GhosttyLib = @This(); + +const std = @import("std"); +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. +step: *std.Build.Step, + +/// The final static library file +output: std.Build.LazyPath, + +pub fn initStatic( + b: *std.Build, + deps: *const SharedDeps, +) !GhosttyLib { + const lib = b.addStaticLibrary(.{ + .name = "ghostty", + .root_source_file = b.path("src/main_c.zig"), + .target = deps.config.target, + .optimize = deps.config.optimize, + }); + lib.bundle_compiler_rt = true; + lib.linkLibC(); + + // Add our dependencies. Get the list of all static deps so we can + // build a combined archive if necessary. + var lib_list = try deps.add(lib); + try lib_list.append(lib.getEmittedBin()); + + if (!deps.config.target.result.isDarwin()) return .{ + .step = &lib.step, + .output = lib.getEmittedBin(), + }; + + // 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); + + return .{ + .step = libtool.step, + .output = libtool.output, + }; +} + +pub fn initShared( + b: *std.Build, + deps: *const SharedDeps, +) !GhosttyLib { + const lib = b.addSharedLibrary(.{ + .name = "ghostty", + .root_source_file = b.path("src/main_c.zig"), + .target = deps.config.target, + .optimize = deps.config.optimize, + .strip = deps.config.strip, + }); + _ = try deps.add(lib); + + return .{ + .step = &lib.step, + .output = lib.getEmittedBin(), + }; +} + +pub fn initMacOSUniversal( + b: *std.Build, + original_deps: *const SharedDeps, +) !GhosttyLib { + const aarch64 = try initStatic(b, &try original_deps.retarget( + b, + Config.genericMacOSTarget(b, .aarch64), + )); + const x86_64 = try initStatic(b, &try original_deps.retarget( + b, + Config.genericMacOSTarget(b, .x86_64), + )); + + const universal = LipoStep.create(b, .{ + .name = "ghostty", + .out_name = "libghostty.a", + .input_a = aarch64.output, + .input_b = x86_64.output, + }); + + return .{ + .step = universal.step, + .output = universal.output, + }; +} + +pub fn install(self: *const GhosttyLib, name: []const u8) void { + const b = self.step.owner; + const lib_install = b.addInstallLibFile(self.output, name); + b.getInstallStep().dependOn(&lib_install.step); +} + +pub fn installHeader(self: *const GhosttyLib) void { + const b = self.step.owner; + const header_install = b.addInstallHeaderFile( + b.path("include/ghostty.h"), + "ghostty.h", + ); + b.getInstallStep().dependOn(&header_install.step); +} diff --git a/src/build/GhosttyResources.zig b/src/build/GhosttyResources.zig new file mode 100644 index 000000000..9c5f7f809 --- /dev/null +++ b/src/build/GhosttyResources.zig @@ -0,0 +1,257 @@ +const GhosttyResources = @This(); + +const std = @import("std"); +const buildpkg = @import("main.zig"); +const Config = @import("Config.zig"); +const config_vim = @import("../config/vim.zig"); +const config_sublime_syntax = @import("../config/sublime_syntax.zig"); +const terminfo = @import("../terminfo/main.zig"); +const RunStep = std.Build.Step.Run; + +steps: []*std.Build.Step, + +pub fn init(b: *std.Build, cfg: *const Config) !GhosttyResources { + var steps = std.ArrayList(*std.Build.Step).init(b.allocator); + errdefer steps.deinit(); + + // Terminfo + terminfo: { + // Encode our terminfo + var str = std.ArrayList(u8).init(b.allocator); + defer str.deinit(); + try terminfo.ghostty.encode(str.writer()); + + // Write it + var wf = b.addWriteFiles(); + const src_source = wf.add("share/terminfo/ghostty.terminfo", str.items); + const src_install = b.addInstallFile(src_source, "share/terminfo/ghostty.terminfo"); + try steps.append(&src_install.step); + + // Windows doesn't have the binaries below. + if (cfg.target.result.os.tag == .windows) break :terminfo; + + // Convert to termcap source format if thats helpful to people and + // install it. The resulting value here is the termcap source in case + // that is used for other commands. + { + const run_step = RunStep.create(b, "infotocap"); + run_step.addArg("infotocap"); + run_step.addFileArg(src_source); + const out_source = run_step.captureStdOut(); + _ = run_step.captureStdErr(); // so we don't see stderr + + const cap_install = b.addInstallFile(out_source, "share/terminfo/ghostty.termcap"); + try steps.append(&cap_install.step); + } + + // Compile the terminfo source into a terminfo database + { + const run_step = RunStep.create(b, "tic"); + run_step.addArgs(&.{ "tic", "-x", "-o" }); + const path = run_step.addOutputFileArg("terminfo"); + run_step.addFileArg(src_source); + _ = run_step.captureStdErr(); // so we don't see stderr + + // Depend on the terminfo source install step so that Zig build + // creates the "share" directory for us. + run_step.step.dependOn(&src_install.step); + + { + // Use cp -R instead of Step.InstallDir because we need to preserve + // symlinks in the terminfo database. Zig's InstallDir step doesn't + // handle symlinks correctly yet. + const copy_step = RunStep.create(b, "copy terminfo db"); + copy_step.addArgs(&.{ "cp", "-R" }); + copy_step.addFileArg(path); + copy_step.addArg(b.fmt("{s}/share", .{b.install_path})); + try steps.append(©_step.step); + } + } + } + + // Shell-integration + { + const install_step = b.addInstallDirectory(.{ + .source_dir = b.path("src/shell-integration"), + .install_dir = .{ .custom = "share" }, + .install_subdir = b.pathJoin(&.{ "ghostty", "shell-integration" }), + .exclude_extensions = &.{".md"}, + }); + try steps.append(&install_step.step); + } + + // Themes + { + const upstream = b.dependency("iterm2_themes", .{}); + const install_step = b.addInstallDirectory(.{ + .source_dir = upstream.path("ghostty"), + .install_dir = .{ .custom = "share" }, + .install_subdir = b.pathJoin(&.{ "ghostty", "themes" }), + .exclude_extensions = &.{".md"}, + }); + try steps.append(&install_step.step); + } + + // Fish shell completions + { + const wf = b.addWriteFiles(); + _ = wf.add("ghostty.fish", buildpkg.fish_completions); + + const install_step = b.addInstallDirectory(.{ + .source_dir = wf.getDirectory(), + .install_dir = .prefix, + .install_subdir = "share/fish/vendor_completions.d", + }); + try steps.append(&install_step.step); + } + + // zsh shell completions + { + const wf = b.addWriteFiles(); + _ = wf.add("_ghostty", buildpkg.zsh_completions); + + const install_step = b.addInstallDirectory(.{ + .source_dir = wf.getDirectory(), + .install_dir = .prefix, + .install_subdir = "share/zsh/site-functions", + }); + try steps.append(&install_step.step); + } + + // bash shell completions + { + const wf = b.addWriteFiles(); + _ = wf.add("ghostty.bash", buildpkg.bash_completions); + + const install_step = b.addInstallDirectory(.{ + .source_dir = wf.getDirectory(), + .install_dir = .prefix, + .install_subdir = "share/bash-completion/completions", + }); + try steps.append(&install_step.step); + } + + // Vim plugin + { + const wf = b.addWriteFiles(); + _ = wf.add("syntax/ghostty.vim", config_vim.syntax); + _ = wf.add("ftdetect/ghostty.vim", config_vim.ftdetect); + _ = wf.add("ftplugin/ghostty.vim", config_vim.ftplugin); + _ = wf.add("compiler/ghostty.vim", config_vim.compiler); + + const install_step = b.addInstallDirectory(.{ + .source_dir = wf.getDirectory(), + .install_dir = .prefix, + .install_subdir = "share/vim/vimfiles", + }); + try steps.append(&install_step.step); + } + + // Neovim plugin + // This is just a copy-paste of the Vim plugin, but using a Neovim subdir. + // By default, Neovim doesn't look inside share/vim/vimfiles. Some distros + // configure it to do that however. Fedora, does not as a counterexample. + { + const wf = b.addWriteFiles(); + _ = wf.add("syntax/ghostty.vim", config_vim.syntax); + _ = wf.add("ftdetect/ghostty.vim", config_vim.ftdetect); + _ = wf.add("ftplugin/ghostty.vim", config_vim.ftplugin); + _ = wf.add("compiler/ghostty.vim", config_vim.compiler); + + const install_step = b.addInstallDirectory(.{ + .source_dir = wf.getDirectory(), + .install_dir = .prefix, + .install_subdir = "share/nvim/site", + }); + try steps.append(&install_step.step); + } + + // Sublime syntax highlighting for bat cli tool + // NOTE: The current implementation requires symlinking the generated + // 'ghostty.sublime-syntax' file from zig-out to the '~.config/bat/syntaxes' + // directory. The syntax then needs to be mapped to the correct language in + // the config file within the '~.config/bat' directory + // (ex: --map-syntax "/Users/user/.config/ghostty/config:Ghostty Config"). + { + const wf = b.addWriteFiles(); + _ = wf.add("ghostty.sublime-syntax", config_sublime_syntax.syntax); + + const install_step = b.addInstallDirectory(.{ + .source_dir = wf.getDirectory(), + .install_dir = .prefix, + .install_subdir = "share/bat/syntaxes", + }); + try steps.append(&install_step.step); + } + + // App (Linux) + if (cfg.target.result.os.tag == .linux) { + // https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html + + // Desktop file so that we have an icon and other metadata + try steps.append(&b.addInstallFile( + b.path("dist/linux/app.desktop"), + "share/applications/com.mitchellh.ghostty.desktop", + ).step); + + // Right click menu action for Plasma desktop + try steps.append(&b.addInstallFile( + b.path("dist/linux/ghostty_dolphin.desktop"), + "share/kio/servicemenus/com.mitchellh.ghostty.desktop", + ).step); + + // Various icons that our application can use, including the icon + // that will be used for the desktop. + try steps.append(&b.addInstallFile( + b.path("images/icons/icon_16.png"), + "share/icons/hicolor/16x16/apps/com.mitchellh.ghostty.png", + ).step); + try steps.append(&b.addInstallFile( + b.path("images/icons/icon_32.png"), + "share/icons/hicolor/32x32/apps/com.mitchellh.ghostty.png", + ).step); + try steps.append(&b.addInstallFile( + b.path("images/icons/icon_128.png"), + "share/icons/hicolor/128x128/apps/com.mitchellh.ghostty.png", + ).step); + try steps.append(&b.addInstallFile( + b.path("images/icons/icon_256.png"), + "share/icons/hicolor/256x256/apps/com.mitchellh.ghostty.png", + ).step); + try steps.append(&b.addInstallFile( + b.path("images/icons/icon_512.png"), + "share/icons/hicolor/512x512/apps/com.mitchellh.ghostty.png", + ).step); + // Flatpaks only support icons up to 512x512. + if (!cfg.flatpak) { + try steps.append(&b.addInstallFile( + b.path("images/icons/icon_1024.png"), + "share/icons/hicolor/1024x1024/apps/com.mitchellh.ghostty.png", + ).step); + } + + try steps.append(&b.addInstallFile( + b.path("images/icons/icon_16@2x.png"), + "share/icons/hicolor/16x16@2/apps/com.mitchellh.ghostty.png", + ).step); + try steps.append(&b.addInstallFile( + b.path("images/icons/icon_32@2x.png"), + "share/icons/hicolor/32x32@2/apps/com.mitchellh.ghostty.png", + ).step); + try steps.append(&b.addInstallFile( + b.path("images/icons/icon_128@2x.png"), + "share/icons/hicolor/128x128@2/apps/com.mitchellh.ghostty.png", + ).step); + try steps.append(&b.addInstallFile( + b.path("images/icons/icon_256@2x.png"), + "share/icons/hicolor/256x256@2/apps/com.mitchellh.ghostty.png", + ).step); + } + + return .{ .steps = steps.items }; +} + +pub fn install(self: *const GhosttyResources) void { + const b = self.steps[0].owner; + for (self.steps) |step| b.getInstallStep().dependOn(step); +} diff --git a/src/build/GhosttyWebdata.zig b/src/build/GhosttyWebdata.zig new file mode 100644 index 000000000..6e0acaf17 --- /dev/null +++ b/src/build/GhosttyWebdata.zig @@ -0,0 +1,82 @@ +//! GhosttyWebdata generates all the Ghostty website data that is +//! merged with the website for things like config references. +const GhosttyWebdata = @This(); + +const std = @import("std"); +const Config = @import("Config.zig"); +const SharedDeps = @import("SharedDeps.zig"); + +steps: []*std.Build.Step, + +pub fn init( + b: *std.Build, + deps: *const SharedDeps, +) !GhosttyWebdata { + var steps = std.ArrayList(*std.Build.Step).init(b.allocator); + errdefer steps.deinit(); + + { + const webgen_config = b.addExecutable(.{ + .name = "webgen_config", + .root_source_file = b.path("src/main.zig"), + .target = b.host, + }); + deps.help_strings.addImport(webgen_config); + + { + const buildconfig = config: { + var copy = deps.config.*; + copy.exe_entrypoint = .webgen_config; + break :config copy; + }; + + const options = b.addOptions(); + try buildconfig.addOptions(options); + webgen_config.root_module.addOptions("build_options", options); + } + + const webgen_config_step = b.addRunArtifact(webgen_config); + const webgen_config_out = webgen_config_step.captureStdOut(); + + try steps.append(&b.addInstallFile( + webgen_config_out, + "share/ghostty/webdata/config.mdx", + ).step); + } + + { + const webgen_actions = b.addExecutable(.{ + .name = "webgen_actions", + .root_source_file = b.path("src/main.zig"), + .target = b.host, + }); + deps.help_strings.addImport(webgen_actions); + + { + const buildconfig = config: { + var copy = deps.config.*; + copy.exe_entrypoint = .webgen_actions; + break :config copy; + }; + + const options = b.addOptions(); + try buildconfig.addOptions(options); + webgen_actions.root_module.addOptions("build_options", options); + } + + const webgen_actions_step = b.addRunArtifact(webgen_actions); + const webgen_actions_out = webgen_actions_step.captureStdOut(); + + try steps.append(&b.addInstallFile( + webgen_actions_out, + "share/ghostty/webdata/actions.mdx", + ).step); + } + + return .{ .steps = steps.items }; +} + +pub fn install(self: *const GhosttyWebdata) void { + const b = self.steps[0].owner; + for (self.steps) |step| b.getInstallStep().dependOn(step); +} diff --git a/src/build/GhosttyXCFramework.zig b/src/build/GhosttyXCFramework.zig new file mode 100644 index 000000000..38bc2c43f --- /dev/null +++ b/src/build/GhosttyXCFramework.zig @@ -0,0 +1,68 @@ +const GhosttyXCFramework = @This(); + +const std = @import("std"); +const Config = @import("Config.zig"); +const SharedDeps = @import("SharedDeps.zig"); +const GhosttyLib = @import("GhosttyLib.zig"); +const XCFrameworkStep = @import("XCFrameworkStep.zig"); + +xcframework: *XCFrameworkStep, +macos: GhosttyLib, + +pub fn init(b: *std.Build, deps: *const SharedDeps) !GhosttyXCFramework { + // Create our universal macOS static library. + const macos = try GhosttyLib.initMacOSUniversal(b, deps); + + // iOS + const ios = try GhosttyLib.initStatic(b, &try deps.retarget( + b, + b.resolveTargetQuery(.{ + .cpu_arch = .aarch64, + .os_tag = .ios, + .os_version_min = Config.osVersionMin(.ios), + .abi = null, + }), + )); + + // iOS Simulator + const ios_sim = try GhosttyLib.initStatic(b, &try deps.retarget( + b, + b.resolveTargetQuery(.{ + .cpu_arch = .aarch64, + .os_tag = .ios, + .os_version_min = Config.osVersionMin(.ios), + .abi = .simulator, + }), + )); + + // The xcframework wraps our ghostty library so that we can link + // it to the final app built with Swift. + const xcframework = XCFrameworkStep.create(b, .{ + .name = "GhosttyKit", + .out_path = "macos/GhosttyKit.xcframework", + .libraries = &.{ + .{ + .library = macos.output, + .headers = b.path("include"), + }, + .{ + .library = ios.output, + .headers = b.path("include"), + }, + .{ + .library = ios_sim.output, + .headers = b.path("include"), + }, + }, + }); + + return .{ + .xcframework = xcframework, + .macos = macos, + }; +} + +pub fn install(self: *const GhosttyXCFramework) void { + const b = self.xcframework.step.owner; + b.getInstallStep().dependOn(self.xcframework.step); +} diff --git a/src/build/Version.zig b/src/build/GitVersion.zig similarity index 100% rename from src/build/Version.zig rename to src/build/GitVersion.zig diff --git a/src/build/HelpStrings.zig b/src/build/HelpStrings.zig new file mode 100644 index 000000000..0244670cc --- /dev/null +++ b/src/build/HelpStrings.zig @@ -0,0 +1,46 @@ +const HelpStrings = @This(); + +const std = @import("std"); +const Config = @import("Config.zig"); + +/// The "helpgen" exe. +exe: *std.Build.Step.Compile, + +/// The output path for the help strings. +output: std.Build.LazyPath, + +pub fn init(b: *std.Build, cfg: *const Config) !HelpStrings { + const exe = b.addExecutable(.{ + .name = "helpgen", + .root_source_file = b.path("src/helpgen.zig"), + .target = b.host, + }); + + const help_config = config: { + var copy = cfg.*; + copy.exe_entrypoint = .helpgen; + break :config copy; + }; + const options = b.addOptions(); + try help_config.addOptions(options); + exe.root_module.addOptions("build_options", options); + + const help_run = b.addRunArtifact(exe); + return .{ + .exe = exe, + .output = help_run.captureStdOut(), + }; +} + +/// Add the "help_strings" import. +pub fn addImport(self: *const HelpStrings, step: *std.Build.Step.Compile) void { + self.output.addStepDependencies(&step.step); + step.root_module.addAnonymousImport("help_strings", .{ + .root_source_file = self.output, + }); +} + +/// Install the help exe +pub fn install(self: *const HelpStrings) void { + self.exe.step.owner.installArtifact(self.exe); +} diff --git a/src/build/MetallibStep.zig b/src/build/MetallibStep.zig index 587d276c1..12adf3edb 100644 --- a/src/build/MetallibStep.zig +++ b/src/build/MetallibStep.zig @@ -21,13 +21,13 @@ pub const Options = struct { step: *Step, output: LazyPath, -pub fn create(b: *std.Build, opts: Options) *MetallibStep { +pub fn create(b: *std.Build, opts: Options) ?*MetallibStep { const self = b.allocator.create(MetallibStep) catch @panic("OOM"); const sdk = switch (opts.target.result.os.tag) { .macos => "macosx", .ios => "iphoneos", - else => @panic("unsupported metallib OS"), + else => return null, }; const min_version = if (opts.target.query.os_version_min) |v| diff --git a/src/build/SharedDeps.zig b/src/build/SharedDeps.zig new file mode 100644 index 000000000..792199212 --- /dev/null +++ b/src/build/SharedDeps.zig @@ -0,0 +1,501 @@ +const SharedDeps = @This(); + +const std = @import("std"); +const Scanner = @import("zig_wayland").Scanner; +const Config = @import("Config.zig"); +const HelpStrings = @import("HelpStrings.zig"); +const MetallibStep = @import("MetallibStep.zig"); +const UnicodeTables = @import("UnicodeTables.zig"); + +config: *const Config, + +options: *std.Build.Step.Options, +help_strings: HelpStrings, +metallib: ?*MetallibStep, +unicode_tables: UnicodeTables, + +/// Used to keep track of a list of file sources. +pub const LazyPathList = std.ArrayList(std.Build.LazyPath); + +pub fn init(b: *std.Build, cfg: *const Config) !SharedDeps { + var result: SharedDeps = .{ + .config = cfg, + .help_strings = try HelpStrings.init(b, cfg), + .unicode_tables = try UnicodeTables.init(b), + + // Setup by retarget + .options = undefined, + .metallib = undefined, + }; + try result.initTarget(b, cfg.target); + return result; +} + +/// Retarget our dependencies for another build target. Modifies in-place. +pub fn retarget( + self: *const SharedDeps, + b: *std.Build, + target: std.Build.ResolvedTarget, +) !SharedDeps { + var result = self.*; + try result.initTarget(b, target); + return result; +} + +/// Change the exe entrypoint. +pub fn changeEntrypoint( + self: *const SharedDeps, + b: *std.Build, + entrypoint: Config.ExeEntrypoint, +) !SharedDeps { + // Change our config + const config = try b.allocator.create(Config); + config.* = self.config.*; + config.exe_entrypoint = entrypoint; + + var result = self.*; + result.config = config; + return result; +} + +fn initTarget( + self: *SharedDeps, + b: *std.Build, + target: std.Build.ResolvedTarget, +) !void { + // Update our metallib + self.metallib = MetallibStep.create(b, .{ + .name = "Ghostty", + .target = target, + .sources = &.{b.path("src/renderer/shaders/cell.metal")}, + }); + + // Change our config + const config = try b.allocator.create(Config); + config.* = self.config.*; + config.target = target; + self.config = config; + + // Setup our shared build options + self.options = b.addOptions(); + try self.config.addOptions(self.options); +} + +pub fn add( + self: *const SharedDeps, + step: *std.Build.Step.Compile, +) !LazyPathList { + const b = step.step.owner; + + // We could use our config.target/optimize fields here but its more + // correct to always match our step. + const target = step.root_module.resolved_target.?; + const optimize = step.root_module.optimize.?; + + // We maintain a list of our static libraries and return it so that + // we can build a single fat static library for the final app. + var static_libs = LazyPathList.init(b.allocator); + errdefer static_libs.deinit(); + + // Every exe gets build options populated + step.root_module.addOptions("build_options", self.options); + + // Freetype + _ = b.systemIntegrationOption("freetype", .{}); // Shows it in help + if (self.config.font_backend.hasFreetype()) { + const freetype_dep = b.dependency("freetype", .{ + .target = target, + .optimize = optimize, + .@"enable-libpng" = true, + }); + step.root_module.addImport("freetype", freetype_dep.module("freetype")); + + if (b.systemIntegrationOption("freetype", .{})) { + step.linkSystemLibrary2("bzip2", dynamic_link_opts); + step.linkSystemLibrary2("freetype2", dynamic_link_opts); + } else { + step.linkLibrary(freetype_dep.artifact("freetype")); + try static_libs.append(freetype_dep.artifact("freetype").getEmittedBin()); + } + } + + // Harfbuzz + _ = b.systemIntegrationOption("harfbuzz", .{}); // Shows it in help + if (self.config.font_backend.hasHarfbuzz()) { + const harfbuzz_dep = b.dependency("harfbuzz", .{ + .target = target, + .optimize = optimize, + .@"enable-freetype" = true, + .@"enable-coretext" = self.config.font_backend.hasCoretext(), + }); + + step.root_module.addImport( + "harfbuzz", + harfbuzz_dep.module("harfbuzz"), + ); + if (b.systemIntegrationOption("harfbuzz", .{})) { + step.linkSystemLibrary2("harfbuzz", dynamic_link_opts); + } else { + step.linkLibrary(harfbuzz_dep.artifact("harfbuzz")); + try static_libs.append(harfbuzz_dep.artifact("harfbuzz").getEmittedBin()); + } + } + + // Fontconfig + _ = b.systemIntegrationOption("fontconfig", .{}); // Shows it in help + if (self.config.font_backend.hasFontconfig()) { + const fontconfig_dep = b.dependency("fontconfig", .{ + .target = target, + .optimize = optimize, + }); + step.root_module.addImport( + "fontconfig", + fontconfig_dep.module("fontconfig"), + ); + + if (b.systemIntegrationOption("fontconfig", .{})) { + step.linkSystemLibrary2("fontconfig", dynamic_link_opts); + } else { + step.linkLibrary(fontconfig_dep.artifact("fontconfig")); + try static_libs.append(fontconfig_dep.artifact("fontconfig").getEmittedBin()); + } + } + + // Libpng - Ghostty doesn't actually use this directly, its only used + // through dependencies, so we only need to add it to our static + // libs list if we're not using system integration. The dependencies + // will handle linking it. + if (!b.systemIntegrationOption("libpng", .{})) { + const libpng_dep = b.dependency("libpng", .{ + .target = target, + .optimize = optimize, + }); + step.linkLibrary(libpng_dep.artifact("png")); + try static_libs.append(libpng_dep.artifact("png").getEmittedBin()); + } + + // Zlib - same as libpng, only used through dependencies. + if (!b.systemIntegrationOption("zlib", .{})) { + const zlib_dep = b.dependency("zlib", .{ + .target = target, + .optimize = optimize, + }); + step.linkLibrary(zlib_dep.artifact("z")); + try static_libs.append(zlib_dep.artifact("z").getEmittedBin()); + } + + // Oniguruma + const oniguruma_dep = b.dependency("oniguruma", .{ + .target = target, + .optimize = optimize, + }); + step.root_module.addImport("oniguruma", oniguruma_dep.module("oniguruma")); + if (b.systemIntegrationOption("oniguruma", .{})) { + step.linkSystemLibrary2("oniguruma", dynamic_link_opts); + } else { + step.linkLibrary(oniguruma_dep.artifact("oniguruma")); + try static_libs.append(oniguruma_dep.artifact("oniguruma").getEmittedBin()); + } + + // Glslang + const glslang_dep = b.dependency("glslang", .{ + .target = target, + .optimize = optimize, + }); + step.root_module.addImport("glslang", glslang_dep.module("glslang")); + if (b.systemIntegrationOption("glslang", .{})) { + step.linkSystemLibrary2("glslang", dynamic_link_opts); + step.linkSystemLibrary2("glslang-default-resource-limits", dynamic_link_opts); + } else { + step.linkLibrary(glslang_dep.artifact("glslang")); + try static_libs.append(glslang_dep.artifact("glslang").getEmittedBin()); + } + + // Spirv-cross + const spirv_cross_dep = b.dependency("spirv_cross", .{ + .target = target, + .optimize = optimize, + }); + step.root_module.addImport("spirv_cross", spirv_cross_dep.module("spirv_cross")); + if (b.systemIntegrationOption("spirv-cross", .{})) { + step.linkSystemLibrary2("spirv-cross", dynamic_link_opts); + } else { + step.linkLibrary(spirv_cross_dep.artifact("spirv_cross")); + try static_libs.append(spirv_cross_dep.artifact("spirv_cross").getEmittedBin()); + } + + // Simdutf + if (b.systemIntegrationOption("simdutf", .{})) { + step.linkSystemLibrary2("simdutf", dynamic_link_opts); + } else { + const simdutf_dep = b.dependency("simdutf", .{ + .target = target, + .optimize = optimize, + }); + step.linkLibrary(simdutf_dep.artifact("simdutf")); + try static_libs.append(simdutf_dep.artifact("simdutf").getEmittedBin()); + } + + // Sentry + if (self.config.sentry) { + const sentry_dep = b.dependency("sentry", .{ + .target = target, + .optimize = optimize, + .backend = .breakpad, + }); + + step.root_module.addImport("sentry", sentry_dep.module("sentry")); + + // Sentry + step.linkLibrary(sentry_dep.artifact("sentry")); + try static_libs.append(sentry_dep.artifact("sentry").getEmittedBin()); + + // We also need to include breakpad in the static libs. + const breakpad_dep = sentry_dep.builder.dependency("breakpad", .{ + .target = target, + .optimize = optimize, + }); + try static_libs.append(breakpad_dep.artifact("breakpad").getEmittedBin()); + } + + // Wasm we do manually since it is such a different build. + if (step.rootModuleTarget().cpu.arch == .wasm32) { + const js_dep = b.dependency("zig_js", .{ + .target = target, + .optimize = optimize, + }); + step.root_module.addImport("zig-js", js_dep.module("zig-js")); + + return static_libs; + } + + // On Linux, we need to add a couple common library paths that aren't + // on the standard search list. i.e. GTK is often in /usr/lib/x86_64-linux-gnu + // on x86_64. + if (step.rootModuleTarget().os.tag == .linux) { + const triple = try step.rootModuleTarget().linuxTriple(b.allocator); + step.addLibraryPath(.{ .cwd_relative = b.fmt("/usr/lib/{s}", .{triple}) }); + } + + // C files + step.linkLibC(); + step.addIncludePath(b.path("src/stb")); + step.addCSourceFiles(.{ .files = &.{"src/stb/stb.c"} }); + if (step.rootModuleTarget().os.tag == .linux) { + step.addIncludePath(b.path("src/apprt/gtk")); + } + + // C++ files + step.linkLibCpp(); + step.addIncludePath(b.path("src")); + { + // From hwy/detect_targets.h + const HWY_AVX3_SPR: c_int = 1 << 4; + const HWY_AVX3_ZEN4: c_int = 1 << 6; + const HWY_AVX3_DL: c_int = 1 << 7; + const HWY_AVX3: c_int = 1 << 8; + + // Zig 0.13 bug: https://github.com/ziglang/zig/issues/20414 + // To workaround this we just disable AVX512 support completely. + // The performance difference between AVX2 and AVX512 is not + // significant for our use case and AVX512 is very rare on consumer + // hardware anyways. + const HWY_DISABLED_TARGETS: c_int = HWY_AVX3_SPR | HWY_AVX3_ZEN4 | HWY_AVX3_DL | HWY_AVX3; + + step.addCSourceFiles(.{ + .files = &.{ + "src/simd/base64.cpp", + "src/simd/codepoint_width.cpp", + "src/simd/index_of.cpp", + "src/simd/vt.cpp", + }, + .flags = if (step.rootModuleTarget().cpu.arch == .x86_64) &.{ + b.fmt("-DHWY_DISABLED_TARGETS={}", .{HWY_DISABLED_TARGETS}), + } else &.{}, + }); + } + + // We always require the system SDK so that our system headers are available. + // This makes things like `os/log.h` available for cross-compiling. + if (step.rootModuleTarget().isDarwin()) { + try @import("apple_sdk").addPaths(b, &step.root_module); + + const metallib = self.metallib.?; + metallib.output.addStepDependencies(&step.step); + step.root_module.addAnonymousImport("ghostty_metallib", .{ + .root_source_file = metallib.output, + }); + } + + // Other dependencies, mostly pure Zig + step.root_module.addImport("opengl", b.dependency( + "opengl", + .{}, + ).module("opengl")); + step.root_module.addImport("vaxis", b.dependency("vaxis", .{ + .target = target, + .optimize = optimize, + }).module("vaxis")); + step.root_module.addImport("wuffs", b.dependency("wuffs", .{ + .target = target, + .optimize = optimize, + }).module("wuffs")); + step.root_module.addImport("xev", b.dependency("libxev", .{ + .target = target, + .optimize = optimize, + }).module("xev")); + step.root_module.addImport("z2d", b.addModule("z2d", .{ + .root_source_file = b.dependency("z2d", .{}).path("src/z2d.zig"), + .target = target, + .optimize = optimize, + })); + step.root_module.addImport("ziglyph", b.dependency("ziglyph", .{ + .target = target, + .optimize = optimize, + }).module("ziglyph")); + step.root_module.addImport("zf", b.dependency("zf", .{ + .target = target, + .optimize = optimize, + .with_tui = false, + }).module("zf")); + + // Mac Stuff + if (step.rootModuleTarget().isDarwin()) { + const objc_dep = b.dependency("zig_objc", .{ + .target = target, + .optimize = optimize, + }); + const macos_dep = b.dependency("macos", .{ + .target = target, + .optimize = optimize, + }); + + step.root_module.addImport("objc", objc_dep.module("objc")); + step.root_module.addImport("macos", macos_dep.module("macos")); + step.linkLibrary(macos_dep.artifact("macos")); + try static_libs.append(macos_dep.artifact("macos").getEmittedBin()); + + if (self.config.renderer == .opengl) { + step.linkFramework("OpenGL"); + } + } + + // cimgui + const cimgui_dep = b.dependency("cimgui", .{ + .target = target, + .optimize = optimize, + }); + step.root_module.addImport("cimgui", cimgui_dep.module("cimgui")); + step.linkLibrary(cimgui_dep.artifact("cimgui")); + try static_libs.append(cimgui_dep.artifact("cimgui").getEmittedBin()); + + // Highway + const highway_dep = b.dependency("highway", .{ + .target = target, + .optimize = optimize, + }); + step.linkLibrary(highway_dep.artifact("highway")); + try static_libs.append(highway_dep.artifact("highway").getEmittedBin()); + + // utfcpp - This is used as a dependency on our hand-written C++ code + const utfcpp_dep = b.dependency("utfcpp", .{ + .target = target, + .optimize = optimize, + }); + step.linkLibrary(utfcpp_dep.artifact("utfcpp")); + try static_libs.append(utfcpp_dep.artifact("utfcpp").getEmittedBin()); + + // If we're building an exe then we have additional dependencies. + if (step.kind != .lib) { + // We always statically compile glad + step.addIncludePath(b.path("vendor/glad/include/")); + step.addCSourceFile(.{ + .file = b.path("vendor/glad/src/gl.c"), + .flags = &.{}, + }); + + // When we're targeting flatpak we ALWAYS link GTK so we + // get access to glib for dbus. + if (self.config.flatpak) step.linkSystemLibrary2("gtk4", dynamic_link_opts); + + switch (self.config.app_runtime) { + .none => {}, + + .glfw => glfw: { + const mach_glfw_dep = b.lazyDependency("mach_glfw", .{ + .target = target, + .optimize = optimize, + }) orelse break :glfw; + step.root_module.addImport("glfw", mach_glfw_dep.module("mach-glfw")); + }, + + .gtk => { + step.linkSystemLibrary2("gtk4", dynamic_link_opts); + if (self.config.adwaita) step.linkSystemLibrary2("adwaita-1", dynamic_link_opts); + if (self.config.x11) step.linkSystemLibrary2("X11", dynamic_link_opts); + + if (self.config.wayland) { + const scanner = Scanner.create(b, .{}); + + const wayland = b.createModule(.{ .root_source_file = scanner.result }); + + const plasma_wayland_protocols = b.dependency("plasma_wayland_protocols", .{ + .target = target, + .optimize = optimize, + }); + scanner.addCustomProtocol(plasma_wayland_protocols.path("src/protocols/blur.xml")); + + scanner.generate("wl_compositor", 1); + scanner.generate("org_kde_kwin_blur_manager", 1); + + step.root_module.addImport("wayland", wayland); + step.linkSystemLibrary2("wayland-client", dynamic_link_opts); + } + + { + const gresource = @import("../apprt/gtk/gresource.zig"); + + const wf = b.addWriteFiles(); + const gresource_xml = wf.add("gresource.xml", gresource.gresource_xml); + + const generate_resources_c = b.addSystemCommand(&.{ + "glib-compile-resources", + "--c-name", + "ghostty", + "--generate-source", + "--target", + }); + const ghostty_resources_c = generate_resources_c.addOutputFileArg("ghostty_resources.c"); + generate_resources_c.addFileArg(gresource_xml); + generate_resources_c.extra_file_dependencies = &gresource.dependencies; + step.addCSourceFile(.{ .file = ghostty_resources_c, .flags = &.{} }); + + const generate_resources_h = b.addSystemCommand(&.{ + "glib-compile-resources", + "--c-name", + "ghostty", + "--generate-header", + "--target", + }); + const ghostty_resources_h = generate_resources_h.addOutputFileArg("ghostty_resources.h"); + generate_resources_h.addFileArg(gresource_xml); + generate_resources_h.extra_file_dependencies = &gresource.dependencies; + step.addIncludePath(ghostty_resources_h.dirname()); + } + }, + } + } + + self.help_strings.addImport(step); + self.unicode_tables.addImport(step); + + return static_libs; +} + +// For dynamic linking, we prefer dynamic linking and to search by +// mode first. Mode first will search all paths for a dynamic library +// before falling back to static. +const dynamic_link_opts: std.Build.Module.LinkSystemLibraryOptions = .{ + .preferred_link_mode = .dynamic, + .search_strategy = .mode_first, +}; diff --git a/src/build/UnicodeTables.zig b/src/build/UnicodeTables.zig new file mode 100644 index 000000000..0159de442 --- /dev/null +++ b/src/build/UnicodeTables.zig @@ -0,0 +1,43 @@ +const UnicodeTables = @This(); + +const std = @import("std"); +const Config = @import("Config.zig"); + +/// The exe. +exe: *std.Build.Step.Compile, + +/// The output path for the unicode tables +output: std.Build.LazyPath, + +pub fn init(b: *std.Build) !UnicodeTables { + const exe = b.addExecutable(.{ + .name = "unigen", + .root_source_file = b.path("src/unicode/props.zig"), + .target = b.host, + }); + exe.linkLibC(); + + const ziglyph_dep = b.dependency("ziglyph", .{ + .target = b.host, + }); + exe.root_module.addImport("ziglyph", ziglyph_dep.module("ziglyph")); + + const run = b.addRunArtifact(exe); + return .{ + .exe = exe, + .output = run.captureStdOut(), + }; +} + +/// Add the "unicode_tables" import. +pub fn addImport(self: *const UnicodeTables, step: *std.Build.Step.Compile) void { + self.output.addStepDependencies(&step.step); + step.root_module.addAnonymousImport("unicode_tables", .{ + .root_source_file = self.output, + }); +} + +/// Install the exe +pub fn install(self: *const UnicodeTables, b: *std.Build) void { + b.installArtifact(self.exe); +} diff --git a/src/build/bash_completions.zig b/src/build/bash_completions.zig index 6649bcb01..86c2dc3cf 100644 --- a/src/build/bash_completions.zig +++ b/src/build/bash_completions.zig @@ -14,7 +14,7 @@ const Action = @import("../cli/action.zig").Action; /// it's part of an on going completion like --=. Working around this requires looking /// backward in the command line args to pretend the = is an empty string /// see: https://www.gnu.org/software/gnuastro/manual/html_node/Bash-TAB-completion-tutorial.html -pub const bash_completions = comptimeGenerateBashCompletions(); +pub const completions = comptimeGenerateBashCompletions(); fn comptimeGenerateBashCompletions() []const u8 { comptime { @@ -319,7 +319,7 @@ fn writeBashCompletions(writer: anytype) !void { \\ # clear out prev so we don't run any of the key specific completions \\ prev="" \\ fi - \\ + \\ \\ case "${COMP_WORDS[1]}" in \\ --*) _handle_config ;; \\ +*) _handle_actions ;; diff --git a/src/build/fish_completions.zig b/src/build/fish_completions.zig index b75c4dd16..dca119c6f 100644 --- a/src/build/fish_completions.zig +++ b/src/build/fish_completions.zig @@ -5,7 +5,7 @@ const Action = @import("../cli/action.zig").Action; /// A fish completions configuration that contains all the available commands /// and options. -pub const fish_completions = comptimeGenerateFishCompletions(); +pub const completions = comptimeGenerateFishCompletions(); fn comptimeGenerateFishCompletions() []const u8 { comptime { diff --git a/src/build/gtk.zig b/src/build/gtk.zig new file mode 100644 index 000000000..f33219988 --- /dev/null +++ b/src/build/gtk.zig @@ -0,0 +1,24 @@ +const std = @import("std"); + +pub const Targets = packed struct { + x11: bool = false, + wayland: bool = false, +}; + +/// Returns the targets that GTK4 was compiled with. +pub fn targets(b: *std.Build) Targets { + // Run pkg-config. We allow it to fail so that zig build --help + // works without all dependencies. The build will fail later when + // GTK isn't found anyways. + var code: u8 = undefined; + const output = b.runAllowFail( + &.{ "pkg-config", "--variable=targets", "gtk4" }, + &code, + .Ignore, + ) catch return .{}; + + return .{ + .x11 = std.mem.indexOf(u8, output, "x11") != null, + .wayland = std.mem.indexOf(u8, output, "wayland") != null, + }; +} diff --git a/src/build/main.zig b/src/build/main.zig new file mode 100644 index 000000000..8228abfbf --- /dev/null +++ b/src/build/main.zig @@ -0,0 +1,30 @@ +//! Build logic for Ghostty. A single "build.zig" file became far too complex +//! and spaghetti, so this package extracts the build logic into smaller, +//! more manageable pieces. + +pub const gtk = @import("gtk.zig"); +pub const Config = @import("Config.zig"); +pub const GitVersion = @import("GitVersion.zig"); + +// Artifacts +pub const GhosttyBench = @import("GhosttyBench.zig"); +pub const GhosttyDocs = @import("GhosttyDocs.zig"); +pub const GhosttyExe = @import("GhosttyExe.zig"); +pub const GhosttyLib = @import("GhosttyLib.zig"); +pub const GhosttyResources = @import("GhosttyResources.zig"); +pub const GhosttyXCFramework = @import("GhosttyXCFramework.zig"); +pub const GhosttyWebdata = @import("GhosttyWebdata.zig"); +pub const HelpStrings = @import("HelpStrings.zig"); +pub const SharedDeps = @import("SharedDeps.zig"); +pub const UnicodeTables = @import("UnicodeTables.zig"); + +// Steps +pub const LibtoolStep = @import("LibtoolStep.zig"); +pub const LipoStep = @import("LipoStep.zig"); +pub const MetallibStep = @import("MetallibStep.zig"); +pub const XCFrameworkStep = @import("XCFrameworkStep.zig"); + +// Shell completions +pub const fish_completions = @import("fish_completions.zig").completions; +pub const zsh_completions = @import("zsh_completions.zig").completions; +pub const bash_completions = @import("bash_completions.zig").completions; diff --git a/src/build/zsh_completions.zig b/src/build/zsh_completions.zig index 5c42ea5ab..4114abc63 100644 --- a/src/build/zsh_completions.zig +++ b/src/build/zsh_completions.zig @@ -5,7 +5,7 @@ const Action = @import("../cli/action.zig").Action; /// A zsh completions configuration that contains all the available commands /// and options. -pub const zsh_completions = comptimeGenerateZshCompletions(); +pub const completions = comptimeGenerateZshCompletions(); const equals_required = "=-:::"; diff --git a/src/build_config.zig b/src/build_config.zig index 13131c132..b80247aab 100644 --- a/src/build_config.zig +++ b/src/build_config.zig @@ -10,88 +10,9 @@ const apprt = @import("apprt.zig"); const font = @import("font/main.zig"); const rendererpkg = @import("renderer.zig"); const WasmTarget = @import("os/wasm/target.zig").Target; +const BuildConfig = @import("build/Config.zig"); -/// The build configurations options. This may not be all available options -/// to `zig build` but it contains all the options that the Ghostty source -/// needs to know about at comptime. -/// -/// We put this all in a single struct so that we can check compatibility -/// between options, make it easy to copy and mutate options for different -/// build types, etc. -pub const BuildConfig = struct { - version: std.SemanticVersion = .{ .major = 0, .minor = 0, .patch = 0 }, - flatpak: bool = false, - adwaita: bool = false, - x11: bool = false, - wayland: bool = false, - sentry: bool = true, - app_runtime: apprt.Runtime = .none, - renderer: rendererpkg.Impl = .opengl, - font_backend: font.Backend = .freetype, - - /// The entrypoint for exe targets. - exe_entrypoint: ExeEntrypoint = .ghostty, - - /// The target runtime for the wasm build and whether to use wasm shared - /// memory or not. These are both legacy wasm-specific options that we - /// will probably have to revisit when we get back to work on wasm. - wasm_target: WasmTarget = .browser, - wasm_shared: bool = true, - - /// Configure the build options with our values. - pub fn addOptions(self: BuildConfig, 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, "adwaita", self.adwaita); - step.addOption(bool, "x11", self.x11); - step.addOption(bool, "wayland", self.wayland); - step.addOption(bool, "sentry", self.sentry); - step.addOption(apprt.Runtime, "app_runtime", self.app_runtime); - step.addOption(font.Backend, "font_backend", self.font_backend); - step.addOption(rendererpkg.Impl, "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, - "{}", - .{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; - }, - ); - } - - /// Rehydrate our BuildConfig 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() BuildConfig { - return .{ - .version = options.app_version, - .flatpak = options.flatpak, - .adwaita = options.adwaita, - .app_runtime = std.meta.stringToEnum(apprt.Runtime, @tagName(options.app_runtime)).?, - .font_backend = std.meta.stringToEnum(font.Backend, @tagName(options.font_backend)).?, - .renderer = std.meta.stringToEnum(rendererpkg.Impl, @tagName(options.renderer)).?, - .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, - }; - } -}; +pub const ReleaseChannel = BuildConfig.ReleaseChannel; /// The semantic version of this build. pub const version = options.app_version; @@ -114,7 +35,7 @@ pub const artifact = Artifact.detect(); /// Our build configuration. We re-export a lot of these back at the /// top-level so its a bit cleaner to use throughout the code. See the doc /// comments in BuildConfig for details on each. -pub const config = BuildConfig.fromOptions(); +const config = BuildConfig.fromOptions(); pub const exe_entrypoint = config.exe_entrypoint; pub const flatpak = options.flatpak; pub const app_runtime: apprt.Runtime = config.app_runtime; @@ -175,35 +96,3 @@ pub const Artifact = enum { }; } }; - -/// 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, - bench_parser, - bench_stream, - bench_codepoint_width, - bench_grapheme_break, - bench_page_init, -}; - -/// The release channel for the build. -pub const ReleaseChannel = enum { - /// Unstable builds on every commit. - tip, - - /// Stable tagged releases. - stable, -}; From bade7be021c531a9ca02b8d5ebf2a767a11d5007 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 7 Jan 2025 20:19:08 -0800 Subject: [PATCH 2/2] Use build.zig.zon for Wayland protocols --- build.zig.zon | 42 +++++++++++++++++++++++++--------------- nix/zigCacheHash.nix | 2 +- src/build/SharedDeps.zig | 11 ++++++++++- 3 files changed, 37 insertions(+), 18 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index 33be26193..518022486 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -13,6 +13,14 @@ .hash = "12206ed982e709e565d536ce930701a8c07edfd2cfdce428683f3f2a601d37696a62", .lazy = true, }, + .vaxis = .{ + .url = "git+https://github.com/rockorager/libvaxis/?ref=main#6d729a2dc3b934818dffe06d2ba3ce02841ed74b", + .hash = "12200df4ebeaed45de26cb2c9f3b6f3746d8013b604e035dae658f86f586c8c91d2f", + }, + .z2d = .{ + .url = "git+https://github.com/vancluever/z2d?ref=v0.4.0#4638bb02a9dc41cc2fb811f092811f6a951c752a", + .hash = "12201f0d542e7541cf492a001d4d0d0155c92f58212fbcb0d224e95edeba06b5416a", + }, .zig_objc = .{ .url = "https://github.com/mitchellh/zig-objc/archive/9b8ba849b0f58fe207ecd6ab7c147af55b17556e.tar.gz", .hash = "1220e17e64ef0ef561b3e4b9f3a96a2494285f2ec31c097721bf8c8677ec4415c634", @@ -29,6 +37,10 @@ .url = "https://codeberg.org/ifreund/zig-wayland/archive/a5e2e9b6a6d7fba638ace4d4b24a3b576a02685b.tar.gz", .hash = "1220d41b23ae70e93355bb29dac1c07aa6aeb92427a2dffc4375e94b4de18111248c", }, + .zf = .{ + .url = "git+https://github.com/natecraddock/zf/?ref=main#ed99ca18b02dda052e20ba467e90b623c04690dd", + .hash = "1220edc3b8d8bedbb50555947987e5e8e2f93871ca3c8e8d4cc8f1377c15b5dd35e8", + }, // C libs .cimgui = .{ .path = "./pkg/cimgui" }, @@ -50,27 +62,25 @@ .glslang = .{ .path = "./pkg/glslang" }, .spirv_cross = .{ .path = "./pkg/spirv-cross" }, + // Wayland + .wayland = .{ + .url = "https://deps.files.ghostty.org/wayland-9cb3d7aa9dc995ffafdbdef7ab86a949d0fb0e7d.tar.gz", + .hash = "12202cdac858abc52413a6c6711d5026d2d3c8e13f95ca2c327eade0736298bb021f", + }, + .wayland_protocols = .{ + .url = "https://deps.files.ghostty.org/wayland-protocols-258d8f88f2c8c25a830c6316f87d23ce1a0f12d9.tar.gz", + .hash = "12201a57c6ce0001aa034fa80fba3e1cd2253c560a45748f4f4dd21ff23b491cddef", + }, + .plasma_wayland_protocols = .{ + .url = "git+https://github.com/KDE/plasma-wayland-protocols?ref=main#db525e8f9da548cffa2ac77618dd0fbe7f511b86", + .hash = "12207e0851c12acdeee0991e893e0132fc87bb763969a585dc16ecca33e88334c566", + }, + // Other .apple_sdk = .{ .path = "./pkg/apple-sdk" }, .iterm2_themes = .{ .url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/4762ad5bd6d3906e28babdc2bda8a967d63a63be.tar.gz", .hash = "1220a263b22113273d01bd33e3c06b8119cb2f63b4e5d414a85d88e3aa95bb68a2de", }, - .vaxis = .{ - .url = "git+https://github.com/rockorager/libvaxis/?ref=main#6d729a2dc3b934818dffe06d2ba3ce02841ed74b", - .hash = "12200df4ebeaed45de26cb2c9f3b6f3746d8013b604e035dae658f86f586c8c91d2f", - }, - .zf = .{ - .url = "git+https://github.com/natecraddock/zf/?ref=main#ed99ca18b02dda052e20ba467e90b623c04690dd", - .hash = "1220edc3b8d8bedbb50555947987e5e8e2f93871ca3c8e8d4cc8f1377c15b5dd35e8", - }, - .z2d = .{ - .url = "git+https://github.com/vancluever/z2d?ref=v0.4.0#4638bb02a9dc41cc2fb811f092811f6a951c752a", - .hash = "12201f0d542e7541cf492a001d4d0d0155c92f58212fbcb0d224e95edeba06b5416a", - }, - .plasma_wayland_protocols = .{ - .url = "git+https://invent.kde.org/libraries/plasma-wayland-protocols.git?ref=master#db525e8f9da548cffa2ac77618dd0fbe7f511b86", - .hash = "12207e0851c12acdeee0991e893e0132fc87bb763969a585dc16ecca33e88334c566", - }, }, } diff --git a/nix/zigCacheHash.nix b/nix/zigCacheHash.nix index f2592adf4..3806c64c9 100644 --- a/nix/zigCacheHash.nix +++ b/nix/zigCacheHash.nix @@ -1,3 +1,3 @@ # This file is auto-generated! check build-support/check-zig-cache-hash.sh for # more details. -"sha256-eUY6MS3//r6pA/w9b+E4+YqmqUbzpUfL3afJJlnMhLY=" +"sha256-PnfSy793kcVt85q47kWR0xkivXoMOZAAmuUyKO9vqAI=" diff --git a/src/build/SharedDeps.zig b/src/build/SharedDeps.zig index 792199212..077da96a6 100644 --- a/src/build/SharedDeps.zig +++ b/src/build/SharedDeps.zig @@ -435,7 +435,16 @@ pub fn add( if (self.config.x11) step.linkSystemLibrary2("X11", dynamic_link_opts); if (self.config.wayland) { - const scanner = Scanner.create(b, .{}); + const scanner = Scanner.create(b, .{ + // We shouldn't be using getPath but we need to for now + // https://codeberg.org/ifreund/zig-wayland/issues/66 + .wayland_xml_path = b.dependency("wayland", .{}) + .path("protocol/wayland.xml") + .getPath(b), + .wayland_protocols_path = b.dependency("wayland_protocols", .{}) + .path("") + .getPath(b), + }); const wayland = b.createModule(.{ .root_source_file = scanner.result });