build: add static library target for libghostty-vt
Refactor GhosttyLibVt to support both shared and static library builds via a shared initLib helper that accepts a LinkMode. The shared and static entry points (initShared, initStatic) delegate to this common path. For static builds, compiler_rt and ubsan_rt are bundled to avoid undefined symbol errors. Debug symbols (dsymutil) are skipped for static libs since they are not linked. The install artifact uses a "-static" suffix internally but installs as "libghostty-vt.a" via a new installLib method. Wasm is excluded from static builds since it has no meaningful static vs shared distinction.pull/11732/head
parent
1438a2fe4b
commit
8d6be5a3dd
23
build.zig
23
build.zig
|
|
@ -106,6 +106,29 @@ pub fn build(b: *std.Build) !void {
|
||||||
};
|
};
|
||||||
libghostty_vt_shared.install(b.getInstallStep());
|
libghostty_vt_shared.install(b.getInstallStep());
|
||||||
|
|
||||||
|
// libghostty-vt static lib. We don't build this for wasm since wasm has
|
||||||
|
// no concept of static vs shared and we put the wasm binary up in
|
||||||
|
// our shared handling.
|
||||||
|
if (!config.target.result.cpu.arch.isWasm()) {
|
||||||
|
const libghostty_vt_static = try buildpkg.GhosttyLibVt.initStatic(
|
||||||
|
b,
|
||||||
|
&mod,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (config.is_dep) {
|
||||||
|
// If we're a dependency, we need to install everything as-is
|
||||||
|
// so that dep.artifact("ghostty-vt-static") works.
|
||||||
|
libghostty_vt_static.install(b.getInstallStep());
|
||||||
|
} else {
|
||||||
|
// If we're not a dependency, we rename the static lib to
|
||||||
|
// be idiomatic.
|
||||||
|
b.getInstallStep().dependOn(&b.addInstallLibFile(
|
||||||
|
libghostty_vt_static.output,
|
||||||
|
"libghostty-vt.a",
|
||||||
|
).step);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Helpgen
|
// Helpgen
|
||||||
if (config.emit_helpgen) deps.help_strings.install();
|
if (config.emit_helpgen) deps.help_strings.install();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Example: `ghostty-vt` Static Linking
|
||||||
|
|
||||||
|
This contains a simple example of how to statically link the `ghostty-vt`
|
||||||
|
C library with a C program using the `ghostty-vt-static` artifact. It is
|
||||||
|
otherwise identical to the `c-vt` example.
|
||||||
|
|
||||||
|
This uses a `build.zig` and `Zig` to build the C program so that we
|
||||||
|
can reuse a lot of our build logic and depend directly on our source
|
||||||
|
tree, but Ghostty emits a standard C library that can be used with any
|
||||||
|
C tooling.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Run the program:
|
||||||
|
|
||||||
|
```shell-session
|
||||||
|
zig build run
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn build(b: *std.Build) void {
|
||||||
|
const target = b.standardTargetOptions(.{});
|
||||||
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
|
|
||||||
|
const run_step = b.step("run", "Run the app");
|
||||||
|
|
||||||
|
const exe_mod = b.createModule(.{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
exe_mod.addCSourceFiles(.{
|
||||||
|
.root = b.path("src"),
|
||||||
|
.files = &.{"main.c"},
|
||||||
|
});
|
||||||
|
|
||||||
|
// You'll want to use a lazy dependency here so that ghostty is only
|
||||||
|
// downloaded if you actually need it.
|
||||||
|
if (b.lazyDependency("ghostty", .{
|
||||||
|
// Setting simd to false will force a pure static build that
|
||||||
|
// doesn't even require libc, but it has a significant performance
|
||||||
|
// penalty. If your embedding app requires libc anyway, you should
|
||||||
|
// always keep simd enabled.
|
||||||
|
// .simd = false,
|
||||||
|
})) |dep| {
|
||||||
|
// Use "ghostty-vt-static" for static linking instead of
|
||||||
|
// "ghostty-vt" which provides a shared library.
|
||||||
|
exe_mod.linkLibrary(dep.artifact("ghostty-vt-static"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exe
|
||||||
|
const exe = b.addExecutable(.{
|
||||||
|
.name = "c_vt_static",
|
||||||
|
.root_module = exe_mod,
|
||||||
|
});
|
||||||
|
b.installArtifact(exe);
|
||||||
|
|
||||||
|
// Run
|
||||||
|
const run_cmd = b.addRunArtifact(exe);
|
||||||
|
run_cmd.step.dependOn(b.getInstallStep());
|
||||||
|
if (b.args) |args| run_cmd.addArgs(args);
|
||||||
|
run_step.dependOn(&run_cmd.step);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
.{
|
||||||
|
.name = .c_vt_static,
|
||||||
|
.version = "0.0.0",
|
||||||
|
.fingerprint = 0xa592a9fdd5d87ed2,
|
||||||
|
.minimum_zig_version = "0.15.1",
|
||||||
|
.dependencies = .{
|
||||||
|
// Ghostty dependency. In reality, you'd probably use a URL-based
|
||||||
|
// dependency like the one showed (and commented out) below this one.
|
||||||
|
// We use a path dependency here for simplicity and to ensure our
|
||||||
|
// examples always test against the source they're bundled with.
|
||||||
|
.ghostty = .{ .path = "../../" },
|
||||||
|
|
||||||
|
// Example of what a URL-based dependency looks like:
|
||||||
|
// .ghostty = .{
|
||||||
|
// .url = "https://github.com/ghostty-org/ghostty/archive/COMMIT.tar.gz",
|
||||||
|
// .hash = "N-V-__8AAMVLTABmYkLqhZPLXnMl-KyN38R8UVYqGrxqO36s",
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
.paths = .{
|
||||||
|
"build.zig",
|
||||||
|
"build.zig.zon",
|
||||||
|
"src",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <ghostty/vt.h>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
GhosttyOscParser parser;
|
||||||
|
if (ghostty_osc_new(NULL, &parser) != GHOSTTY_SUCCESS) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup change window title command to change the title to "hello"
|
||||||
|
ghostty_osc_next(parser, '0');
|
||||||
|
ghostty_osc_next(parser, ';');
|
||||||
|
const char *title = "hello";
|
||||||
|
for (size_t i = 0; i < strlen(title); i++) {
|
||||||
|
ghostty_osc_next(parser, title[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// End parsing and get command
|
||||||
|
GhosttyOscCommand command = ghostty_osc_end(parser, 0);
|
||||||
|
|
||||||
|
// Get and print command type
|
||||||
|
GhosttyOscCommandType type = ghostty_osc_command_type(command);
|
||||||
|
printf("Command type: %d\n", type);
|
||||||
|
|
||||||
|
// Extract and print the title
|
||||||
|
if (ghostty_osc_command_data(command, GHOSTTY_OSC_DATA_CHANGE_WINDOW_TITLE_STR, &title)) {
|
||||||
|
printf("Extracted title: %s\n", title);
|
||||||
|
} else {
|
||||||
|
printf("Failed to extract title\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
ghostty_osc_free(parser);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
@ -61,6 +61,10 @@ emit_xcframework: bool = false,
|
||||||
emit_webdata: bool = false,
|
emit_webdata: bool = false,
|
||||||
emit_unicode_table_gen: bool = false,
|
emit_unicode_table_gen: bool = false,
|
||||||
|
|
||||||
|
/// True when Ghostty is being built as a dependency of another project
|
||||||
|
/// rather than as the root project.
|
||||||
|
is_dep: bool = false,
|
||||||
|
|
||||||
/// Environmental properties
|
/// Environmental properties
|
||||||
env: std.process.EnvMap,
|
env: std.process.EnvMap,
|
||||||
|
|
||||||
|
|
@ -88,6 +92,10 @@ pub fn init(b: *std.Build, appVersion: []const u8) !Config {
|
||||||
break :target result;
|
break :target result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Detect if Ghostty is a dependency of another project.
|
||||||
|
// dep_prefix is non-empty when this build is running as a dependency.
|
||||||
|
const is_dep = b.dep_prefix.len > 0;
|
||||||
|
|
||||||
// This is set to true when we're building a system package. For now
|
// This is set to true when we're building a system package. For now
|
||||||
// this is trivially detected using the "system_package_mode" bool
|
// this is trivially detected using the "system_package_mode" bool
|
||||||
// but we may want to make this more sophisticated in the future.
|
// but we may want to make this more sophisticated in the future.
|
||||||
|
|
@ -110,6 +118,7 @@ pub fn init(b: *std.Build, appVersion: []const u8) !Config {
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
.target = target,
|
.target = target,
|
||||||
.wasm_target = wasm_target,
|
.wasm_target = wasm_target,
|
||||||
|
.is_dep = is_dep,
|
||||||
.env = env,
|
.env = env,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -221,9 +230,7 @@ pub fn init(b: *std.Build, appVersion: []const u8) !Config {
|
||||||
const app_version = try std.SemanticVersion.parse(appVersion);
|
const app_version = try std.SemanticVersion.parse(appVersion);
|
||||||
|
|
||||||
// Is ghostty a dependency? If so, skip git detection.
|
// Is ghostty a dependency? If so, skip git detection.
|
||||||
// @src().file won't resolve from b.build_root unless ghostty
|
if (is_dep) break :version .{
|
||||||
// is the project being built.
|
|
||||||
b.build_root.handle.access(@src().file, .{}) catch break :version .{
|
|
||||||
.major = app_version.major,
|
.major = app_version.major,
|
||||||
.minor = app_version.minor,
|
.minor = app_version.minor,
|
||||||
.patch = app_version.patch,
|
.patch = app_version.patch,
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,22 @@ step: *std.Build.Step,
|
||||||
/// The artifact result
|
/// The artifact result
|
||||||
artifact: *std.Build.Step.InstallArtifact,
|
artifact: *std.Build.Step.InstallArtifact,
|
||||||
|
|
||||||
|
/// The kind of library
|
||||||
|
kind: Kind,
|
||||||
|
|
||||||
/// The final library file
|
/// The final library file
|
||||||
output: std.Build.LazyPath,
|
output: std.Build.LazyPath,
|
||||||
dsym: ?std.Build.LazyPath,
|
dsym: ?std.Build.LazyPath,
|
||||||
pkg_config: ?std.Build.LazyPath,
|
pkg_config: ?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.
|
||||||
|
const Kind = enum {
|
||||||
|
wasm,
|
||||||
|
shared,
|
||||||
|
static,
|
||||||
|
};
|
||||||
|
|
||||||
pub fn initWasm(
|
pub fn initWasm(
|
||||||
b: *std.Build,
|
b: *std.Build,
|
||||||
zig: *const GhosttyZig,
|
zig: *const GhosttyZig,
|
||||||
|
|
@ -39,20 +50,40 @@ pub fn initWasm(
|
||||||
return .{
|
return .{
|
||||||
.step = &exe.step,
|
.step = &exe.step,
|
||||||
.artifact = b.addInstallArtifact(exe, .{}),
|
.artifact = b.addInstallArtifact(exe, .{}),
|
||||||
|
.kind = .wasm,
|
||||||
.output = exe.getEmittedBin(),
|
.output = exe.getEmittedBin(),
|
||||||
.dsym = null,
|
.dsym = null,
|
||||||
.pkg_config = null,
|
.pkg_config = null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn initStatic(
|
||||||
|
b: *std.Build,
|
||||||
|
zig: *const GhosttyZig,
|
||||||
|
) !GhosttyLibVt {
|
||||||
|
return initLib(b, zig, .static);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn initShared(
|
pub fn initShared(
|
||||||
b: *std.Build,
|
b: *std.Build,
|
||||||
zig: *const GhosttyZig,
|
zig: *const GhosttyZig,
|
||||||
) !GhosttyLibVt {
|
) !GhosttyLibVt {
|
||||||
|
return initLib(b, zig, .dynamic);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initLib(
|
||||||
|
b: *std.Build,
|
||||||
|
zig: *const GhosttyZig,
|
||||||
|
linkage: std.builtin.LinkMode,
|
||||||
|
) !GhosttyLibVt {
|
||||||
|
const kind: Kind = switch (linkage) {
|
||||||
|
.static => .static,
|
||||||
|
.dynamic => .shared,
|
||||||
|
};
|
||||||
const target = zig.vt.resolved_target.?;
|
const target = zig.vt.resolved_target.?;
|
||||||
const lib = b.addLibrary(.{
|
const lib = b.addLibrary(.{
|
||||||
.name = "ghostty-vt",
|
.name = if (kind == .static) "ghostty-vt-static" else "ghostty-vt",
|
||||||
.linkage = .dynamic,
|
.linkage = linkage,
|
||||||
.root_module = zig.vt_c,
|
.root_module = zig.vt_c,
|
||||||
.version = std.SemanticVersion{ .major = 0, .minor = 1, .patch = 0 },
|
.version = std.SemanticVersion{ .major = 0, .minor = 1, .patch = 0 },
|
||||||
});
|
});
|
||||||
|
|
@ -62,6 +93,15 @@ pub fn initShared(
|
||||||
.{ .include_extensions = &.{".h"} },
|
.{ .include_extensions = &.{".h"} },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (kind == .static) {
|
||||||
|
// These must be bundled since we're compiling into a static lib.
|
||||||
|
// Otherwise, you get undefined symbol errors. This could cause
|
||||||
|
// problems if you're linking multiple static Zig libraries but
|
||||||
|
// we'll cross that bridge when we get to it.
|
||||||
|
lib.bundle_compiler_rt = true;
|
||||||
|
lib.bundle_ubsan_rt = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (lib.rootModuleTarget().abi.isAndroid()) {
|
if (lib.rootModuleTarget().abi.isAndroid()) {
|
||||||
// Support 16kb page sizes, required for Android 15+.
|
// Support 16kb page sizes, required for Android 15+.
|
||||||
lib.link_z_max_page_size = 16384; // 16kb
|
lib.link_z_max_page_size = 16384; // 16kb
|
||||||
|
|
@ -82,11 +122,10 @@ pub fn initShared(
|
||||||
if (builtin.os.tag.isDarwin()) try @import("apple_sdk").addPaths(b, lib);
|
if (builtin.os.tag.isDarwin()) try @import("apple_sdk").addPaths(b, lib);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get our debug symbols
|
// Get our debug symbols (only for shared libs; static libs aren't linked)
|
||||||
const dsymutil: ?std.Build.LazyPath = dsymutil: {
|
const dsymutil: ?std.Build.LazyPath = dsymutil: {
|
||||||
if (!target.result.os.tag.isDarwin()) {
|
if (kind != .shared) break :dsymutil null;
|
||||||
break :dsymutil null;
|
if (!target.result.os.tag.isDarwin()) break :dsymutil null;
|
||||||
}
|
|
||||||
|
|
||||||
const dsymutil = RunStep.create(b, "dsymutil");
|
const dsymutil = RunStep.create(b, "dsymutil");
|
||||||
dsymutil.addArgs(&.{"dsymutil"});
|
dsymutil.addArgs(&.{"dsymutil"});
|
||||||
|
|
@ -116,6 +155,7 @@ pub fn initShared(
|
||||||
return .{
|
return .{
|
||||||
.step = &lib.step,
|
.step = &lib.step,
|
||||||
.artifact = b.addInstallArtifact(lib, .{}),
|
.artifact = b.addInstallArtifact(lib, .{}),
|
||||||
|
.kind = kind,
|
||||||
.output = lib.getEmittedBin(),
|
.output = lib.getEmittedBin(),
|
||||||
.dsym = dsymutil,
|
.dsym = dsymutil,
|
||||||
.pkg_config = pc,
|
.pkg_config = pc,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue