From a82e1569252352bf3875f33a87f5935ddd6fd4a7 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 9 Apr 2026 20:18:54 -0700 Subject: [PATCH] build: add libghostty-vt-static pkg-config module Keep libghostty-vt.pc as the shared/default pkg-config module so `pkg-config --static libghostty-vt` continues to emit the historical `-lghostty-vt` flags. This preserves the old behavior for consumers that still want it, even though that form remains ambiguous on macOS when both the dylib and archive are installed in the same directory. Add a separate libghostty-vt-static.pc module for consumers that need an unambiguous static link. Its `Libs:` entry points directly at the installed archive so macOS does not resolve the request to the dylib. Update the Nix packaging to rewrite the new static module into the `dev` output, use it in the static-link smoke test, and add a compatibility check that covers both pkg-config entry points. --- nix/libghostty-vt.nix | 23 +++++++-- src/build/GhosttyLibVt.zig | 101 ++++++++++++++++++++++++++++++------- 2 files changed, 100 insertions(+), 24 deletions(-) diff --git a/nix/libghostty-vt.nix b/nix/libghostty-vt.nix index 5a819a0cc..35c3cbc36 100644 --- a/nix/libghostty-vt.nix +++ b/nix/libghostty-vt.nix @@ -80,6 +80,8 @@ stdenv.mkDerivation (finalAttrs: { postFixup = '' substituteInPlace "$dev/share/pkgconfig/libghostty-vt.pc" \ --replace-fail "$out" "$dev" + substituteInPlace "$dev/share/pkgconfig/libghostty-vt-static.pc" \ + --replace-fail "$out" "$dev" ''; passthru.tests = { @@ -105,6 +107,17 @@ stdenv.mkDerivation (finalAttrs: { pkg-config = testers.hasPkgConfigModules { package = finalAttrs.finalPackage.dev; }; + pkg-config-libs = + runCommand "pkg-config-libs" { + nativeBuildInputs = [pkg-config]; + } '' + export PKG_CONFIG_PATH="${finalAttrs.finalPackage.dev}/share/pkgconfig" + + pkg-config --libs --static libghostty-vt | grep -q -- '-lghostty-vt' + pkg-config --libs --static libghostty-vt-static | grep -q -- '${finalAttrs.finalPackage.dev}/lib/libghostty-vt.a' + + touch "$out" + ''; build-with-shared = stdenv.mkDerivation { name = "build-with-shared"; src = ./test-src; @@ -154,10 +167,7 @@ stdenv.mkDerivation (finalAttrs: { runHook preBuildHooks cc -o test test_libghostty_vt.c \ - ''$(pkg-config --cflags libghostty-vt) \ - ${finalAttrs.finalPackage.dev}/lib/libghostty-vt.a \ - ''$(pkg-config --libs-only-l --static libghostty-vt | sed 's/-lghostty-vt//') \ - -Wl,-rpath,"${finalAttrs.finalPackage}/lib" + ''$(pkg-config --cflags --libs --static libghostty-vt-static) runHook postBuildHooks ''; @@ -227,6 +237,9 @@ stdenv.mkDerivation (finalAttrs: { homepage = "https://ghostty.org"; license = lib.licenses.mit; platforms = zig_0_15.meta.platforms; - pkgConfigModules = ["libghostty-vt"]; + pkgConfigModules = [ + "libghostty-vt" + "libghostty-vt-static" + ]; }; }) diff --git a/src/build/GhosttyLibVt.zig b/src/build/GhosttyLibVt.zig index e3e6cf8c1..32f2e10a7 100644 --- a/src/build/GhosttyLibVt.zig +++ b/src/build/GhosttyLibVt.zig @@ -24,6 +24,7 @@ kind: Kind, output: std.Build.LazyPath, dsym: ?std.Build.LazyPath, pkg_config: ?std.Build.LazyPath, +pkg_config_static: ?std.Build.LazyPath, /// The kind of library being built. This is similar to LinkMode but /// also includes wasm which is an executable, not a library. @@ -85,6 +86,7 @@ pub fn initWasm( .output = output, .dsym = null, .pkg_config = null, + .pkg_config_static = null, }; } @@ -162,6 +164,7 @@ pub fn initStaticAppleUniversal( .output = universal.output, .dsym = null, .pkg_config = null, + .pkg_config_static = null, }); // Additional Apple platforms, each gated on SDK availability. @@ -264,23 +267,15 @@ fn initLib( }; // pkg-config - const pc: std.Build.LazyPath = pc: { - const wf = b.addWriteFiles(); - break :pc wf.add("libghostty-vt.pc", b.fmt( - \\prefix={s} - \\includedir=${{prefix}}/include - \\libdir=${{prefix}}/lib - \\ - \\Name: libghostty-vt - \\URL: https://github.com/ghostty-org/ghostty - \\Description: Ghostty VT library - \\Version: {f} - \\Cflags: -I${{includedir}} - \\Libs: -L${{libdir}} -lghostty-vt - \\Libs.private: {s} - \\Requires.private: {s} - , .{ b.install_prefix, zig.version, libsPrivate(zig), requiresPrivate(b) })); - }; + // + // pkg-config's --static only expands Libs.private / Requires.private; + // it doesn't change -lghostty-vt 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 = if (kind == .shared) + pkgConfigFiles(b, zig, target.result.os.tag) + else + null; // For static libraries with vendored SIMD dependencies, combine // all archives into a single fat archive so consumers only need @@ -302,7 +297,8 @@ fn initLib( .kind = kind, .output = combined.output, .dsym = dsymutil, - .pkg_config = pc, + .pkg_config = if (pcs) |v| v.shared else null, + .pkg_config_static = if (pcs) |v| v.static else null, }; } @@ -312,7 +308,8 @@ fn initLib( .kind = kind, .output = lib.getEmittedBin(), .dsym = dsymutil, - .pkg_config = pc, + .pkg_config = if (pcs) |v| v.shared else null, + .pkg_config_static = if (pcs) |v| v.static else null, }; } @@ -370,6 +367,65 @@ fn libsPrivate( return if (zig.vt_c.link_libcpp orelse false) "-lc++" else ""; } +const PkgConfigFiles = struct { + shared: std.Build.LazyPath, + static: std.Build.LazyPath, +}; + +fn pkgConfigFiles( + b: *std.Build, + zig: *const GhosttyZig, + os_tag: std.Target.Os.Tag, +) PkgConfigFiles { + const wf = b.addWriteFiles(); + const libs_private = libsPrivate(zig); + const requires_private = requiresPrivate(b); + + return .{ + .shared = wf.add("libghostty-vt.pc", b.fmt( + \\prefix={s} + \\includedir=${{prefix}}/include + \\libdir=${{prefix}}/lib + \\ + \\Name: libghostty-vt + \\URL: https://github.com/ghostty-org/ghostty + \\Description: Ghostty VT library + \\Version: {f} + \\Cflags: -I${{includedir}} + \\Libs: -L${{libdir}} -lghostty-vt + \\Libs.private: {s} + \\Requires.private: {s} + , .{ b.install_prefix, zig.version, libs_private, requires_private })), + .static = wf.add("libghostty-vt-static.pc", b.fmt( + \\prefix={s} + \\includedir=${{prefix}}/include + \\libdir=${{prefix}}/lib + \\ + \\Name: libghostty-vt-static + \\URL: https://github.com/ghostty-org/ghostty + \\Description: Ghostty VT library (static) + \\Version: {f} + \\Cflags: -I${{includedir}} + \\Libs: ${{libdir}}/{s} + \\Libs.private: {s} + \\Requires.private: {s} + , .{ + b.install_prefix, + zig.version, + staticLibraryName(os_tag), + libs_private, + requires_private, + })), + }; +} + +fn staticLibraryName(os_tag: std.Target.Os.Tag) []const u8 { + return if (os_tag == .windows) + "ghostty-vt-static.lib" + else + "libghostty-vt.a"; +} + /// Returns the Requires.private value for the pkg-config file. /// When SIMD dependencies are provided by the system (via /// -Dsystem-integration), we reference their pkg-config names so @@ -450,4 +506,11 @@ pub fn install( "share/pkgconfig/libghostty-vt.pc", ).step); } + if (self.pkg_config_static) |pkg_config_static| { + step.dependOn(&b.addInstallFileWithDir( + pkg_config_static, + .prefix, + "share/pkgconfig/libghostty-vt-static.pc", + ).step); + } }