首页
/ Ghostty 实战:通过 XCFramework 在 Swift Package 中消费 libghostty-vt 终端引擎

Ghostty 实战:通过 XCFramework 在 Swift Package 中消费 libghostty-vt 终端引擎

2026-09-06 20:04:03作者:宣海椒Queenly

本文基于 Ghostty 仓库中的官方示例 swift-vt-xcframework,讲解如何在 Apple 平台以 Swift Package 的形式引入预构建的 Ghostty XCFramework,完成"创建虚拟终端、写入 VT 转义序列、将屏幕内容格式化为纯文本"的完整闭环。读完后,你将掌握 zig build -Demit-lib-vt 的 XCFramework 产物生成机制、binaryTarget 的本地二进制依赖声明方式,以及 Ghostty C API 在 Swift 中的互操作惯例(不透明指针、size 字段、错误码与内存释放)。

示例定位:libghostty-vt 的 Swift 消费路径

Ghostty 除了完整的终端应用外,还提供独立的核心库 libghostty-vt:它只包含 VT(虚拟终端)解析、终端状态维护、格式化等与 UI 无关的核心,以 C ABI 暴露,供 C、C++、WASM、Swift 等生态嵌入。仓库 example/ 目录下有大量消费示例(如 example/c-vt/example/zig-vt/example/wasm-vt/),而 example/swift-vt-xcframework/ 是其中唯一面向 Apple 原生开发生态 的示例,其目标正是演示三件事:

  1. 用预构建的 XCFramework(而非源码或动态库)作为 Swift Package 的二进制依赖;
  2. 创建一个 80×24 的虚拟终端,并向其中写入带 ANSI 粗体(\e[1m...\e[0m)转义序列的 VT 内容;
  3. 通过 formatter 把终端屏幕内容输出为纯文本。

示例的完整目录结构非常小,仅三个文件:

第一步:在仓库根目录构建 XCFramework

README 给出的前置步骤是:

cd /path/to/ghostty
zig build -Demit-lib-vt

-Demit-lib-vt 是构建系统的核心开关。从 src/build/Config.zig 可以看到它的定义:

const emit_lib_vt = b.option(
    bool,
    "emit-lib-vt",
    "Set defaults for a libghostty-vt-only build (disables xcframework, macOS app, and docs).",
) orelse is_dep;

该选项有两个关键影响:

  • 切换构建模式:构建只产出 libghostty-vt 相关产物(共享库、静态库 libghostty-vt.a 等),不再构建完整的 Ghostty 应用;同时全量应用的测试步骤会被跳过(见 build.zigif (!config.emit_lib_vt) 包裹的 ghostty-test 段落)。
  • XCFramework 自动启用逻辑:在同文件的 emit_xcframework 选项解析中,lib-vt 模式下其默认值取决于当前环境能否找到 xcodebuild——因为生成 XCFramework 必须调用 Xcode 工具链:
if (config.emit_lib_vt) {
    // In lib-vt mode default to whether xcodebuild is available,
    // since xcodebuild is required to produce the XCFramework.
    const path = expandPath(..., "xcodebuild") catch
        break :emit_xcfw false;
    ...
    break :emit_xcfw path != null;
}

也就是说:在装有 Xcode 的 macOS 机器上执行 zig build -Demit-lib-vt 时,XCFramework 会被自动构建并安装,无需额外传参。这正是 README 中只需一条构建命令的原因。

真正的 XCFramework 组装逻辑在 build.zig 中:

// libghostty-vt xcframework (Apple only, universal binary).
// Only when building on macOS (not cross-compiling) since
// xcodebuild is required.
if (config.emit_lib_vt and
    config.emit_xcframework and
    builtin.os.tag.isDarwin() and
    config.target.result.os.tag.isDarwin())
{
    const apple_libs = try buildpkg.GhosttyLibVt.initStaticAppleUniversal(
        b, &config, &deps, &mod);
    const xcframework = buildpkg.GhosttyLibVt.xcframework(&apple_libs, b);
    b.getInstallStep().dependOn(xcframework.step);
}

从这段代码可以确认两个适用前提:

  1. 必须在 macOS 宿主机上构建builtin.os.tag.isDarwin()),交叉编译到 Darwin 目标不会触发 XCFramework 构建;
  2. 产物是 universal binaryinitStaticAppleUniversal),即同时包含 arm64 与 x86_64 切片的静态库,具体实现位于 src/build/GhosttyLibVt.zig

构建完成后,XCFramework 会被安装到 zig-out/lib/ghostty-vt.xcframework——这个路径与 Package.swift 中的引用严格对应。

第二步:以 binaryTarget 声明本地二进制依赖

进入示例目录执行:

cd example/swift-vt-xcframework
swift build
swift run

Package.swift 全文如下,是理解 XCFramework 消费方式的关键:

// swift-tools-version: 5.9
import PackageDescription

let package = Package(
    name: "swift-vt-xcframework",
    platforms: [.macOS(.v13)],
    targets: [
        .executableTarget(
            name: "swift-vt-xcframework",
            dependencies: ["GhosttyVt"],
            path: "Sources"
        ),
        .binaryTarget(
            name: "GhosttyVt",
            path: "../../zig-out/lib/ghostty-vt.xcframework"
        ),
    ]
)

各要素说明:

要素 说明
swift-tools-version: 5.9 要求 Swift 5.9 及以上工具链
platforms: [.macOS(.v13)] 最低部署目标 macOS 13
.binaryTarget(name:path:) 本地文件路径引入预编译二进制,path 相对于包根目录解析,../../zig-out/lib/ghostty-vt.xcframework 即仓库根目录下的 zig 安装输出
dependencies: ["GhosttyVt"] 可执行目标依赖该二进制目标,从而获得自动生成的 GhosttyVt 模块

binaryTarget 是 SwiftPM 消费 Apple 二进制产物(xcframework / framework)的标准机制:包管理器会校验产物切片、按部署目标选取架构,并生成一个与目标同名的模块供 import。由于 path 是指向仓库根 zig-out/ 的相对路径,必须先完成第一步的 zig build,否则 swift build 会因找不到 XCFramework 而失败。

第三步:源码解读——C API 的 Swift 互操作惯例

Sources/main.swift 是一个完整的、可运行的最小终端应用。以下按调用链逐段解析(函数签名与返回码约定可在 include/ghostty/vt/ 的头文件(terminal.hformatter.h 等)中查证):

1. 创建 80×24 的终端

var terminal: GhosttyTerminal?
let result = ghostty_terminal_new(nil, &terminal, 80, 24)
guard result == GHOSTTY_SUCCESS, let terminal else {
    fatalError("Failed to create terminal")
}

Ghostty C API 的统一惯例是:返回 GHOSTTY_SUCCESS 表示成功,其余为错误码;对象通过"出参指针"返回。这里 GhosttyTerminal? 是 XCFramework 模块为 C 侧 GhosttyTerminal* 生成的不透明指针包装类型,nil 作为 allocator 参数表示使用库内部分配策略。80, 24 是终端初始行列数。

2. 写入 VT 转义序列

let text = "Hello from \u{1b}[1mSwift\u{1b}[0m via xcframework!\r\n"
text.withCString { ptr in
    ghostty_terminal_vt_write(terminal, ptr, strlen(ptr))
}

字符串包含一个真实的 ANSI 序列:\u{1b}[1m 开启粗体、\u{1b}[0m 重置属性——也就是说 Swift 这个词会被解析器识别为粗体样式。withCString 是 Swift 把 String 以 NUL 结尾的 C 指针形式交给 C API 的标准写法,配合 strlen 传入长度,对应 C 侧 const char* + size_t 的缓冲区约定。

3. 用 formatter 将屏幕格式化为纯文本

var fmtOpts = GhosttyFormatterTerminalOptions()
fmtOpts.size = MemoryLayout<GhosttyFormatterTerminalOptions>.size
fmtOpts.emit = GHOSTTY_FORMATTER_FORMAT_PLAIN
fmtOpts.trim = true

三处细节值得注意:

  • size 字段:Ghostty C API 要求调用方在 options 结构体中显式填入 sizeof,这是典型的 ABI 兼容设计,便于库在后续版本新增字段时做前向兼容;
  • emit = GHOSTTY_FORMATTER_FORMAT_PLAIN:以纯文本输出(而非 VT/HTML 等格式),常量定义见 include/ghostty/vt/formatter.h
  • trim = true:裁剪输出中的尾部空行。

随后是"创建 → 分配 → 打印 → 释放"的完整生命周期:

var formatter: GhosttyFormatter?
let fmtResult = ghostty_formatter_terminal_new(nil, &formatter, terminal, fmtOpts)
guard fmtResult == GHOSTTY_SUCCESS, let formatter else {
    fatalError("Failed to create formatter")
}

var buf: UnsafeMutablePointer<UInt8>?
var len: Int = 0
let allocResult = ghostty_formatter_format_alloc(formatter, nil, &buf, &len)
guard allocResult == GHOSTTY_SUCCESS, let buf else {
    fatalError("Failed to format")
}

print("Plain text (\(len) bytes):")
let data = Data(bytes: buf, count: len)
print(String(data: data, encoding: .utf8) ?? "<invalid UTF-8>")

ghostty_free(nil, buf, len)
ghostty_formatter_free(formatter)
ghostty_terminal_free(terminal)

ghostty_formatter_format_alloc 由库侧分配输出缓冲区(_alloc 后缀函数),调用方随后必须用配套的 ghostty_free 释放——示例末尾的三个 free 调用(bufformatterterminal)恰好与前面创建的三个对象一一对应,展示了 Ghostty C API "谁分配谁释放、配套成对"的内存管理纪律。

适用前提与扩展方向

  • 平台前提zig build -Demit-lib-vt 需在 macOS 上执行(XCFramework 分支要求宿主与目标均为 Darwin),且环境中存在 xcodebuild;示例包本身声明 macOS 13 最低版本。若在无 Xcode 的 CI 环境构建,emit_xcframework 会自动回退为 false,不会产出 zig-out/lib/ghostty-vt.xcframework,此时 swift build 会失败。
  • iOS 支持:从 src/build/Config.zig 的构建校验可见,"full Ghostty build no longer supports iOS",只有 -Demit-lib-vt 模式支持 iOS 目标——XCFramework 这条路径同样适用于把终端引擎嵌入 iOS 应用(universal binary 的静态切片可被 Xcode 直接链入)。
  • 进一步阅读:想要扩展更多 API 用法(按键编码、鼠标、Kitty 图形协议、快照等),可对照 example/c-vt/ 等 C 示例;所有可链接的函数签名均以 include/ghostty/vt/ 下的头文件为准,例如 terminal.hformatter.h

综上,swift-vt-xcframework 虽然只有约 40 行可执行代码,但它完整示范了 libghostty-vt 从 Zig 构建产物到 Apple 原生生态消费的整条链路:一条 zig build -Demit-lib-vt 命令生成 universal XCFramework,一个 binaryTarget 完成 SwiftPM 集成,一段标准 C ABI 调用即得到可运行的终端引擎实例。

登录后查看全文
热门项目推荐
相关项目推荐