Ghostty 实战:通过 XCFramework 在 Swift Package 中消费 libghostty-vt 终端引擎
本文基于 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 原生开发生态 的示例,其目标正是演示三件事:
- 用预构建的 XCFramework(而非源码或动态库)作为 Swift Package 的二进制依赖;
- 创建一个 80×24 的虚拟终端,并向其中写入带 ANSI 粗体(
\e[1m...\e[0m)转义序列的 VT 内容; - 通过 formatter 把终端屏幕内容输出为纯文本。
示例的完整目录结构非常小,仅三个文件:
- Package.swift —— 声明对 XCFramework 的
binaryTarget依赖; - Sources/main.swift —— 完整的 C API 调用演示;
- README.md —— 两步构建说明。
第一步:在仓库根目录构建 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.zig 中if (!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);
}
从这段代码可以确认两个适用前提:
- 必须在 macOS 宿主机上构建(
builtin.os.tag.isDarwin()),交叉编译到 Darwin 目标不会触发 XCFramework 构建; - 产物是 universal binary(
initStaticAppleUniversal),即同时包含 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.h、formatter.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 调用(buf、formatter、terminal)恰好与前面创建的三个对象一一对应,展示了 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.h、formatter.h。
综上,swift-vt-xcframework 虽然只有约 40 行可执行代码,但它完整示范了 libghostty-vt 从 Zig 构建产物到 Apple 原生生态消费的整条链路:一条 zig build -Demit-lib-vt 命令生成 universal XCFramework,一个 binaryTarget 完成 SwiftPM 集成,一段标准 C ABI 调用即得到可运行的终端引擎实例。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00