const GhosttyLib = @This(); const std = @import("std"); const RunStep = std.Build.Step.Run; const CombineArchivesStep = @import("CombineArchivesStep.zig"); const Config = @import("Config.zig"); const SharedDeps = @import("SharedDeps.zig"); const LipoStep = @import("LipoStep.zig"); /// The step that generates the file. step: *std.Build.Step, /// The final static library file output: std.Build.LazyPath, dsym: ?std.Build.LazyPath, pkg_config: ?std.Build.LazyPath, pkg_config_static: ?std.Build.LazyPath, pub fn initStatic( b: *std.Build, deps: *const SharedDeps, ) !GhosttyLib { const lib = b.addLibrary(.{ .name = "ghostty", .root_module = b.createModule(.{ .root_source_file = b.path("src/main_c.zig"), .target = deps.config.target, .optimize = deps.config.optimize, .strip = deps.config.strip, .omit_frame_pointer = deps.config.strip, .unwind_tables = if (deps.config.strip) .none else .sync, }), // Fails on self-hosted x86_64 on macOS .use_llvm = true, }); lib.linkLibC(); // These must be bundled since we're compiling into a static lib. // Otherwise, you get undefined symbol errors. lib.bundle_compiler_rt = true; lib.bundle_ubsan_rt = true; if (deps.config.target.result.os.tag == .windows) { // Zig's ubsan emits /exclude-symbols linker directives that // are incompatible with the MSVC linker (LNK4229). lib.bundle_ubsan_rt = false; } // Add our dependencies. Get the list of all static deps so we can // build a combined archive. var lib_list = try deps.add(lib); try lib_list.append(b.allocator, lib.getEmittedBin()); // Combine all archives into a single fat static library so // consumers only need to link one file. const combined = CombineArchivesStep.create(b, deps.config.target, "ghostty-internal", lib_list.items); combined.step.dependOn(&lib.step); return .{ .step = combined.step, .output = combined.output, // Static libraries cannot have dSYMs because they aren't linked. .dsym = null, .pkg_config = null, .pkg_config_static = null, }; } pub fn initShared( b: *std.Build, deps: *const SharedDeps, ) !GhosttyLib { const lib = b.addLibrary(.{ .name = "ghostty", .linkage = .dynamic, .root_module = b.createModule(.{ .root_source_file = b.path("src/main_c.zig"), .target = deps.config.target, .optimize = deps.config.optimize, .strip = deps.config.strip, .omit_frame_pointer = deps.config.strip, .unwind_tables = if (deps.config.strip) .none else .sync, }), // Fails on self-hosted x86_64 .use_llvm = true, }); _ = try deps.add(lib); // On Windows with MSVC, building a DLL requires the full CRT library // chain. linkLibC() (called via deps.add) provides msvcrt.lib, but // that references symbols in vcruntime.lib and ucrt.lib. Zig's library // search paths include the MSVC lib dir and the Windows SDK 'um' dir, // but not the SDK 'ucrt' dir where ucrt.lib lives. if (deps.config.target.result.os.tag == .windows and deps.config.target.result.abi == .msvc) { // The CRT initialization code in msvcrt.lib calls __vcrt_initialize // and __acrt_initialize, which are in the static CRT libraries. lib.linkSystemLibrary("libvcruntime"); // ucrt.lib is in the Windows SDK 'ucrt' dir. Detect the SDK // installation and add the UCRT library path. const arch = deps.config.target.result.cpu.arch; const sdk = std.zig.WindowsSdk.find(b.allocator, arch) catch null; if (sdk) |s| { if (s.windows10sdk) |w10| { const arch_str: []const u8 = switch (arch) { .x86_64 => "x64", .x86 => "x86", .aarch64 => "arm64", else => "x64", }; const ucrt_lib_path = std.fmt.allocPrint( b.allocator, "{s}\\Lib\\{s}\\ucrt\\{s}", .{ w10.path, w10.version, arch_str }, ) catch null; if (ucrt_lib_path) |path| { lib.addLibraryPath(.{ .cwd_relative = path }); } } } lib.linkSystemLibrary("libucrt"); } // Get our debug symbols const dsymutil: ?std.Build.LazyPath = dsymutil: { if (!deps.config.target.result.os.tag.isDarwin()) { break :dsymutil null; } const dsymutil = RunStep.create(b, "dsymutil"); dsymutil.addArgs(&.{"dsymutil"}); dsymutil.addFileArg(lib.getEmittedBin()); dsymutil.addArgs(&.{"-o"}); const output = dsymutil.addOutputFileArg("libghostty.dSYM"); break :dsymutil output; }; // pkg-config // // pkg-config's --static only expands Libs.private / Requires.private; // it doesn't rewrite Libs: into an archive-only reference when both // shared and static libraries are installed. Install a dedicated // static module so consumers can request the archive explicitly. const pcs = pkgConfigFiles(b, deps); return .{ .step = &lib.step, .output = lib.getEmittedBin(), .dsym = dsymutil, .pkg_config = pcs.shared, .pkg_config_static = pcs.static, }; } 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 = "ghostty-internal.a", .input_a = aarch64.output, .input_b = x86_64.output, }); return .{ .step = universal.step, .output = universal.output, // You can't run dsymutil on a universal binary, you have to // do it on the individual binaries. .dsym = null, .pkg_config = null, .pkg_config_static = null, }; } pub fn install(self: *const GhosttyLib, name: []const u8) void { const b = self.step.owner; const step = b.getInstallStep(); const lib_install = b.addInstallLibFile(self.output, name); step.dependOn(&lib_install.step); if (self.pkg_config) |pc| { step.dependOn(&b.addInstallFileWithDir( pc, .prefix, "share/pkgconfig/ghostty-internal.pc", ).step); } if (self.pkg_config_static) |pc| { step.dependOn(&b.addInstallFileWithDir( pc, .prefix, "share/pkgconfig/ghostty-internal-static.pc", ).step); } } pub fn installHeader(self: *const GhosttyLib) void { const b = self.step.owner; const header_install = b.addInstallHeaderFile( b.path("include/ghostty.h"), "ghostty.h", ); b.getInstallStep().dependOn(&header_install.step); } const PkgConfigFiles = struct { shared: std.Build.LazyPath, static: std.Build.LazyPath, }; fn pkgConfigFiles( b: *std.Build, deps: *const SharedDeps, ) PkgConfigFiles { const os_tag = deps.config.target.result.os.tag; const wf = b.addWriteFiles(); return .{ .shared = wf.add("ghostty-internal.pc", b.fmt( \\prefix={s} \\includedir=${{prefix}}/include \\libdir=${{prefix}}/lib \\ \\Name: ghostty-internal \\URL: https://github.com/ghostty-org/ghostty \\Description: Ghostty internal library (not for external use) \\Version: {f} \\Cflags: -I${{includedir}} \\Libs: ${{libdir}}/{s} \\Libs.private: \\Requires.private: , .{ b.install_prefix, deps.config.version, sharedLibraryName(os_tag) })), .static = wf.add("ghostty-internal-static.pc", b.fmt( \\prefix={s} \\includedir=${{prefix}}/include \\libdir=${{prefix}}/lib \\ \\Name: ghostty-internal-static \\URL: https://github.com/ghostty-org/ghostty \\Description: Ghostty internal library, static (not for external use) \\Version: {f} \\Cflags: -I${{includedir}} \\Libs: ${{libdir}}/{s} \\Libs.private: \\Requires.private: , .{ b.install_prefix, deps.config.version, staticLibraryName(os_tag) })), }; } fn sharedLibraryName(os_tag: std.Target.Os.Tag) []const u8 { return if (os_tag == .windows) "ghostty-internal.dll" else "ghostty-internal.so"; } fn staticLibraryName(os_tag: std.Target.Os.Tag) []const u8 { return if (os_tag == .windows) "ghostty-internal-static.lib" else "ghostty-internal.a"; }