Skip to content

Commit 9208108

Browse files
jlongsterkommander
andauthored
build(core): statically link libghostty (#1317)
--------- Co-authored-by: Sebastian Herrlinger <hasta84@gmail.com>
1 parent b2df3d9 commit 9208108

12 files changed

Lines changed: 207 additions & 4 deletions

File tree

.github/workflows/build-core.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ jobs:
5252
cd packages/core
5353
bun run build:native --all
5454
55+
- name: Build custom target without Ghostty VT
56+
working-directory: packages/core/src/zig
57+
run: zig build -Dtarget=x86_64-freebsd -Doptimize=ReleaseFast
58+
5559
- name: Upload native artifacts
5660
uses: actions/upload-artifact@v7
5761
with:

.github/workflows/build-examples.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ jobs:
130130
set -e
131131
mkdir -p artifacts/example-notices
132132
cp LICENSE artifacts/example-notices/LICENSE
133+
cp packages/core/THIRD_PARTY_LICENSES/GHOSTTY artifacts/example-notices/LICENSE-GHOSTTY
133134
cp packages/core/src/zig/vendor/wuffs/LICENSE artifacts/example-notices/LICENSE-WUFFS
134135
cp packages/core/src/zig/vendor/stb/LICENSE artifacts/example-notices/LICENSE-STB
135136
cp packages/core/src/zig/vendor/libwebp/COPYING artifacts/example-notices/LICENSE-LIBWEBP
@@ -195,6 +196,8 @@ jobs:
195196
test -f artifacts/examples-linux-x64.zip || (echo "❌ examples-linux-x64.zip missing!" && exit 1)
196197
test -f artifacts/examples-windows-x64.zip || (echo "❌ examples-windows-x64.zip missing!" && exit 1)
197198
for directory in artifacts/examples-{darwin-x64,darwin-arm64,linux-x64,windows-x64}; do
199+
test -f "$directory/LICENSE" || (echo "❌ OpenTUI license missing from $directory" && exit 1)
200+
test -f "$directory/LICENSE-GHOSTTY" || (echo "❌ Ghostty notices missing from $directory" && exit 1)
198201
test -f "$directory/LICENSE-LIBWEBP" || (echo "❌ libwebp notice missing from $directory" && exit 1)
199202
test -f "$directory/PATENTS-LIBWEBP" || (echo "❌ libwebp patents missing from $directory" && exit 1)
200203
test -f "$directory/AUTHORS-LIBWEBP" || (echo "❌ libwebp authors missing from $directory" && exit 1)

.github/workflows/build-native.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ jobs:
106106
set -e
107107
mkdir -p artifacts/native-notices
108108
cp LICENSE artifacts/native-notices/LICENSE
109+
cp packages/core/THIRD_PARTY_LICENSES/GHOSTTY artifacts/native-notices/LICENSE-GHOSTTY
109110
cp packages/core/src/zig/vendor/wuffs/LICENSE artifacts/native-notices/LICENSE-WUFFS
110111
cp packages/core/src/zig/vendor/stb/LICENSE artifacts/native-notices/LICENSE-STB
111112
cp packages/core/src/zig/vendor/libwebp/COPYING artifacts/native-notices/LICENSE-LIBWEBP
@@ -294,6 +295,8 @@ jobs:
294295
test -f artifacts/native-windows-arm64.zip || (echo "❌ native-windows-arm64.zip missing!" && exit 1)
295296
test -f artifacts/npm-packages.zip || (echo "❌ npm-packages.zip missing!" && exit 1)
296297
for directory in artifacts/native-{darwin-x64,darwin-arm64,linux-x64,linux-arm64,linux-x64-musl,linux-arm64-musl,windows-x64,windows-arm64}; do
298+
test -f "$directory/LICENSE" || (echo "❌ OpenTUI license missing from $directory" && exit 1)
299+
test -f "$directory/LICENSE-GHOSTTY" || (echo "❌ Ghostty notices missing from $directory" && exit 1)
297300
test -f "$directory/LICENSE-LIBWEBP" || (echo "❌ libwebp notice missing from $directory" && exit 1)
298301
test -f "$directory/PATENTS-LIBWEBP" || (echo "❌ libwebp patents missing from $directory" && exit 1)
299302
test -f "$directory/AUTHORS-LIBWEBP" || (echo "❌ libwebp authors missing from $directory" && exit 1)
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
MIT License
2+
3+
Copyright (c) 2024 Mitchell Hashimoto, Ghostty contributors
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.
22+
23+
-------------------------------------------------------------------------------
24+
uucode
25+
-------------------------------------------------------------------------------
26+
27+
MIT License
28+
29+
Copyright (c) 2026 Jacob Sandlund
30+
31+
Permission is hereby granted, free of charge, to any person obtaining a copy of
32+
this software and associated documentation files (the "Software"), to deal in
33+
the Software without restriction, including without limitation the rights to
34+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
35+
of the Software, and to permit persons to whom the Software is furnished to do
36+
so, subject to the following conditions:
37+
38+
The above copyright notice and this permission notice shall be included in all
39+
copies or substantial portions of the Software.
40+
41+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
42+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
43+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
44+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
45+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
46+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
47+
SOFTWARE.
48+
49+
-------------------------------------------------------------------------------
50+
uucode UTF-8 decoder
51+
-------------------------------------------------------------------------------
52+
53+
From https://bjoern.hoehrmann.de/utf-8/decoder/dfa/
54+
55+
Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
56+
57+
Permission is hereby granted, free of charge, to any person obtaining a copy of
58+
this software and associated documentation files (the "Software"), to deal in
59+
the Software without restriction, including without limitation the rights to
60+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
61+
of the Software, and to permit persons to whom the Software is furnished to do
62+
so, subject to the following conditions:
63+
64+
The above copyright notice and this permission notice shall be included in all
65+
copies or substantial portions of the Software.
66+
67+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
68+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
69+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
70+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
71+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
72+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
73+
SOFTWARE.
74+
75+
-------------------------------------------------------------------------------
76+
Unicode data
77+
-------------------------------------------------------------------------------
78+
79+
UNICODE LICENSE V3
80+
81+
COPYRIGHT AND PERMISSION NOTICE
82+
83+
Copyright (c) 1991-2025 Unicode, Inc.
84+
85+
NOTICE TO USER: Carefully read the following legal agreement. BY
86+
DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR
87+
SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE
88+
TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT
89+
DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE.
90+
91+
Permission is hereby granted, free of charge, to any person obtaining a
92+
copy of data files and any associated documentation (the "Data Files") or
93+
software and any associated documentation (the "Software") to deal in the
94+
Data Files or Software without restriction, including without limitation
95+
the rights to use, copy, modify, merge, publish, distribute, and/or sell
96+
copies of the Data Files or Software, and to permit persons to whom the
97+
Data Files or Software are furnished to do so, provided that either (a)
98+
this copyright and permission notice appear with all copies of the Data
99+
Files or Software, or (b) this copyright and permission notice appear in
100+
associated Documentation.
101+
102+
THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
103+
KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
104+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
105+
THIRD PARTY RIGHTS.
106+
107+
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE
108+
BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
109+
OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
110+
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
111+
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA
112+
FILES OR SOFTWARE.
113+
114+
Except as contained in this notice, the name of a copyright holder shall
115+
not be used in advertising or otherwise to promote the sale, use or other
116+
dealings in these Data Files or Software without prior written
117+
authorization of the copyright holder.

packages/core/scripts/build.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ const __filename = fileURLToPath(import.meta.url)
4747
const __dirname = dirname(__filename)
4848
const rootDir = resolve(__dirname, "..")
4949
const licensePath = path.resolve(__dirname, "../../../LICENSE")
50+
const ghosttyLicensePath = path.resolve(rootDir, "THIRD_PARTY_LICENSES/GHOSTTY")
5051
const packageJson: PackageJson = JSON.parse(readFileSync(join(rootDir, "package.json"), "utf8"))
5152

5253
const args = process.argv.slice(2)
@@ -293,14 +294,15 @@ export default module.default
293294

294295
if (existsSync(licensePath)) copyFileSync(licensePath, join(nativeDir, "LICENSE"))
295296
for (const [source, destination] of [
297+
[ghosttyLicensePath, "LICENSE-GHOSTTY"],
296298
[join(rootDir, "src", "zig", "vendor", "wuffs", "LICENSE"), "LICENSE-WUFFS"],
297299
[join(rootDir, "src", "zig", "vendor", "stb", "LICENSE"), "LICENSE-STB"],
298300
[join(rootDir, "src", "zig", "vendor", "libwebp", "COPYING"), "LICENSE-LIBWEBP"],
299301
[join(rootDir, "src", "zig", "vendor", "libwebp", "PATENTS"), "PATENTS-LIBWEBP"],
300302
[join(rootDir, "src", "zig", "vendor", "libwebp", "AUTHORS"), "AUTHORS-LIBWEBP"],
301303
[join(rootDir, "src", "zig", "vendor", "lcms2", "LICENSE"), "LICENSE-LCMS2"],
302304
] as const) {
303-
if (!existsSync(source)) throw new Error(`Required native image license file is missing: ${source}`)
305+
if (!existsSync(source)) throw new Error(`Required native license file is missing: ${source}`)
304306
copyFileSync(source, join(nativeDir, destination))
305307
}
306308
console.log("Built:", nativeName)

packages/core/scripts/dist-test.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,15 @@ function ensureBuildArtifacts(): void {
8585
if (!existsSync(nativePackageDir)) {
8686
throw new Error(`Missing native package directory at ${nativePackageDir}. Run bun run build first.`)
8787
}
88+
if (!existsSync(join(nativePackageDir, "LICENSE-GHOSTTY"))) {
89+
throw new Error("Native package is missing the Ghostty and Unicode license notices")
90+
}
91+
const leakedGhosttyFiles = readdirSync(nativePackageDir).filter(
92+
(name) => name.includes("ghostty-vt") || name.endsWith(".a") || name.endsWith(".lib"),
93+
)
94+
if (leakedGhosttyFiles.length > 0) {
95+
throw new Error(`Native package contains unbundled Ghostty artifacts: ${leakedGhosttyFiles.join(", ")}`)
96+
}
8897
}
8998

9099
function assertPortableDeclarations(): void {
@@ -367,7 +376,6 @@ describe("${packageJson.name} dist smoke test", () => {
367376
expect(typeof parserWorker).toBe("object")
368377
expect(typeof runtimePlugin.createRuntimePlugin).toBe("function")
369378
expect(typeof nativePackage.default).toBe("string")
370-
371379
const image = core.NativeImage.fromRgba(Uint8Array.of(1, 2, 3, 255), 1, 1)
372380
const raw = image.takeRaw()
373381
try {

packages/core/src/zig/build.zig

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ const SUPPORTED_TARGETS = [_]SupportedTarget{
2323
.{ .zig_target = "aarch64-linux-gnu.2.17", .output_name = "aarch64-linux", .description = "Linux aarch64" },
2424
.{ .zig_target = "x86_64-linux-musl", .output_name = "x86_64-linux-musl", .description = "Linux x86_64 (musl)" },
2525
.{ .zig_target = "aarch64-linux-musl", .output_name = "aarch64-linux-musl", .description = "Linux aarch64 (musl)" },
26-
.{ .zig_target = "x86_64-macos", .output_name = "x86_64-macos", .description = "macOS x86_64 (Intel)" },
27-
.{ .zig_target = "aarch64-macos", .output_name = "aarch64-macos", .description = "macOS aarch64 (Apple Silicon)" },
26+
.{ .zig_target = "x86_64-macos.13.0", .output_name = "x86_64-macos", .description = "macOS x86_64 (Intel)" },
27+
.{ .zig_target = "aarch64-macos.13.0", .output_name = "aarch64-macos", .description = "macOS aarch64 (Apple Silicon)" },
2828
.{ .zig_target = "x86_64-windows-gnu", .output_name = "x86_64-windows", .description = "Windows x86_64" },
2929
.{ .zig_target = "aarch64-windows-gnu", .output_name = "aarch64-windows", .description = "Windows aarch64" },
3030
};
@@ -33,6 +33,7 @@ const DEFAULT_MACOS_SDK_PATH = "/Library/Developer/CommandLineTools/SDKs/MacOSX.
3333

3434
const LIB_NAME = "opentui";
3535
const ROOT_SOURCE_FILE = "lib.zig";
36+
const GHOSTTY_VT_VERSION = "0.1.0-dev+74d0c72f";
3637

3738
const YOGA_CXX_FLAGS = [_][]const u8{
3839
"-std=c++20",
@@ -371,6 +372,19 @@ fn addYogaDependencies(b: *std.Build, artifact: *std.Build.Step.Compile) void {
371372
});
372373
}
373374

375+
fn ghosttyVtAvailable(target: std.Build.ResolvedTarget) bool {
376+
switch (target.result.cpu.arch) {
377+
.x86_64, .aarch64 => {},
378+
else => return false,
379+
}
380+
return switch (target.result.os.tag) {
381+
.linux => target.result.abi.isMusl() or target.result.abi == .gnu,
382+
.macos => true,
383+
.windows => target.result.abi == .gnu,
384+
else => false,
385+
};
386+
}
387+
374388
/// Apply dependencies to a module
375389
fn applyDependencies(
376390
b: *std.Build,
@@ -380,6 +394,26 @@ fn applyDependencies(
380394
build_options: *std.Build.Step.Options,
381395
) void {
382396
module.addOptions("build_options", build_options);
397+
const ghostty_vt_available = ghosttyVtAvailable(target);
398+
const ghostty_vt_options = b.addOptions();
399+
ghostty_vt_options.addOption(bool, "available", ghostty_vt_available);
400+
module.addOptions("ghostty_vt_options", ghostty_vt_options);
401+
if (ghostty_vt_available) {
402+
if (b.lazyDependency("ghostty", .{
403+
.target = target,
404+
.optimize = .ReleaseFast,
405+
// Enable once OpenTUI uses Ghostty VT at runtime. Until then,
406+
// Highway and simdutf only add binary size and exported symbols.
407+
.simd = false,
408+
.@"emit-lib-vt" = true,
409+
.@"emit-xcframework" = false,
410+
.@"emit-themes" = false,
411+
.i18n = false,
412+
.@"lib-version-string" = GHOSTTY_VT_VERSION,
413+
})) |ghostty| {
414+
module.addImport("ghostty_vt", ghostty.module("ghostty-vt"));
415+
}
416+
}
383417

384418
// Add uucode for grapheme break detection and width calculation
385419
if (b.lazyDependency("uucode", .{

packages/core/src/zig/build.zig.zon

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@
88
.url = "https://github.com/jacobsandlund/uucode/archive/84ceda8561a17ba4a9b96ac5c583f779660bbd4e.tar.gz",
99
.hash = "uucode-0.1.0-ZZjBPtA_TQCWp5PIKmfm5tu1WOkKWFmBGFEMxircPfkA",
1010
},
11+
.ghostty = .{
12+
.url = "https://github.com/ghostty-org/ghostty/archive/74d0c72fd9318ad3ab95bfb56f6c2d995e267e2e.tar.gz",
13+
.hash = "ghostty-1.3.2-dev-5UdBC48HIgU0iYKZzcmjpJ76Ulg3fI9cIH1hPM1JA6NC",
14+
.lazy = true,
15+
},
1116
.yoga = .{
1217
.url = "git+https://github.com/facebook/yoga#v3.2.1",
1318
.hash = "N-V-__8AAOYl0gAU76B1VRPFD9AWvy2VkOef2jN0B3sISTeO",
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub const vt = @import("ghostty_vt");

packages/core/src/zig/lib.zig

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ const logger = @import("logger.zig");
1919
const event_bus = @import("event-bus.zig");
2020
const native_span_feed = @import("native-span-feed.zig");
2121
const native_audio = @import("audio.zig");
22+
const ghostty_vt_available = @import("ghostty_vt_options").available;
23+
const ghostty_vt = if (ghostty_vt_available) @import("ghostty-vt.zig") else struct {
24+
const vt = struct {};
25+
};
2226
const native_renderable = @import("native-renderable.zig");
2327
const buffer_effects = @import("buffer-methods.zig");
2428
const handles = @import("handles.zig");
@@ -127,6 +131,7 @@ inline fn selectionStyle(bg: ?RGBA, fg: ?RGBA) text_buffer_view.SelectionStyle {
127131
comptime {
128132
_ = native_span_feed;
129133
_ = native_audio;
134+
_ = ghostty_vt.vt;
130135
_ = native_renderable;
131136
_ = native_yoga;
132137
_ = native_image;

0 commit comments

Comments
 (0)