From 08bbc5b7521a4fb4565bdb572d923cc2273ad200 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 20 Mar 2026 12:04:05 -0700 Subject: [PATCH] build: add Meson wrapper for libghostty-vt Add Meson build system integration mirroring the existing CMake wrapper. The top-level meson.build delegates to zig build lib-vt using --prefix-lib-dir and --prefix-include-dir to place outputs directly in the Meson build directory, avoiding stamp files and source tree pollution. A sentinel .h output ensures Meson orders the zig build before any downstream compiles. Downstream projects can consume the library either as a Meson subproject via a .wrap file or through pkg-config after install. The dist/meson/README.md documents both approaches. Includes a c-vt-meson example, CI job in test.yml for auto-discovered Meson examples, meson and ninja in the nix devShell, and gitignore updates for Meson build artifacts. --- .github/workflows/test.yml | 44 ++++++++++ .gitignore | 4 + dist/meson/README.md | 66 +++++++++++++++ example/.gitignore | 4 + example/c-vt-meson/README.md | 42 +++++++++ example/c-vt-meson/meson.build | 11 +++ example/c-vt-meson/src/main.c | 52 ++++++++++++ meson.build | 150 +++++++++++++++++++++++++++++++++ meson.options | 2 + nix/devShell.nix | 4 + 10 files changed, 379 insertions(+) create mode 100644 dist/meson/README.md create mode 100644 example/c-vt-meson/README.md create mode 100644 example/c-vt-meson/meson.build create mode 100644 example/c-vt-meson/src/main.c create mode 100644 meson.build create mode 100644 meson.options diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e0e13b508..59d39bf7a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -87,6 +87,7 @@ jobs: - build-dist - build-examples-zig - build-examples-cmake + - build-examples-meson - build-flatpak - build-libghostty-vt - build-libghostty-vt-android @@ -175,6 +176,7 @@ jobs: outputs: zig: ${{ steps.list.outputs.zig }} cmake: ${{ steps.list.outputs.cmake }} + meson: ${{ steps.list.outputs.meson }} steps: - name: Checkout code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -187,6 +189,9 @@ jobs: cmake=$(ls example/*/CMakeLists.txt 2>/dev/null | xargs -n1 dirname | xargs -n1 basename | jq -R -s -c 'split("\n") | map(select(. != ""))') echo "$cmake" | jq . echo "cmake=$cmake" >> "$GITHUB_OUTPUT" + meson=$(ls example/*/meson.build 2>/dev/null | xargs -n1 dirname | xargs -n1 basename | jq -R -s -c 'split("\n") | map(select(. != ""))') + echo "$meson" | jq . + echo "meson=$meson" >> "$GITHUB_OUTPUT" build-examples-zig: strategy: @@ -261,6 +266,45 @@ jobs: nix develop -c cmake -B build -DFETCHCONTENT_SOURCE_DIR_GHOSTTY=${{ github.workspace }} nix develop -c cmake --build build + build-examples-meson: + strategy: + fail-fast: false + matrix: + dir: ${{ fromJSON(needs.list-examples.outputs.meson) }} + name: Example ${{ matrix.dir }} + runs-on: namespace-profile-ghostty-xsm + needs: [test, list-examples] + env: + ZIG_LOCAL_CACHE_DIR: /zig/local-cache + ZIG_GLOBAL_CACHE_DIR: /zig/global-cache + steps: + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Setup Cache + uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9 # v1.4.2 + with: + path: | + /nix + /zig + + # Install Nix and use that to run our tests so our environment matches exactly. + - uses: cachix/install-nix-action@1ca7d21a94afc7c957383a2d217460d980de4934 # v31.10.1 + with: + nix_path: nixpkgs=channel:nixos-unstable + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 + with: + name: ghostty + authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" + + - name: Build Example + run: | + cd example/${{ matrix.dir }} + mkdir -p subprojects + ln -s "${{ github.workspace }}" subprojects/ghostty + nix develop -c meson setup build + nix develop -c meson compile -C build + build-flatpak: strategy: fail-fast: false diff --git a/.gitignore b/.gitignore index 699ac9a5f..2480b6230 100644 --- a/.gitignore +++ b/.gitignore @@ -10,9 +10,13 @@ zig-cache/ .zig-cache/ zig-out/ +# CMake build-cmake/ CMakeCache.txt CMakeFiles/ + +# Meson +build-meson/ /build.zig.zon.bak /result* /.nixos-test-history diff --git a/dist/meson/README.md b/dist/meson/README.md new file mode 100644 index 000000000..bda90343c --- /dev/null +++ b/dist/meson/README.md @@ -0,0 +1,66 @@ +# Meson Support for libghostty-vt + +The top-level `meson.build` wraps the Zig build system so that Meson +projects can consume libghostty-vt without invoking `zig build` manually. +Running `meson compile` triggers `zig build lib-vt` automatically. + +This means downstream projects do require a working Zig compiler on +`PATH` to build, but don't need to know any Zig-specific details. + +## Using a subproject (recommended) + +Create `subprojects/ghostty.wrap`: + +```ini +[wrap-git] +url = https://github.com/ghostty-org/ghostty.git +revision = main +depth = 1 +``` + +Then in your project's `meson.build`: + +```meson +ghostty_proj = subproject('ghostty') +ghostty_vt_dep = ghostty_proj.get_variable('ghostty_vt_dep') + +executable('myapp', 'main.c', dependencies: ghostty_vt_dep) +``` + +This fetches the Ghostty source, builds libghostty-vt via Zig during your +Meson build, and links it into your target. Headers are added to the +include path automatically. + +### Using a local checkout + +If you already have the Ghostty source checked out, symlink or copy it +into your `subprojects/` directory: + +```shell-session +ln -s /path/to/ghostty subprojects/ghostty +meson setup build +meson compile -C build +``` + +## Using pkg-config (install-based) + +Build and install libghostty-vt first: + +```shell-session +cd /path/to/ghostty +meson setup build +meson compile -C build +meson install -C build +``` + +Then in your project: + +```meson +ghostty_vt_dep = dependency('libghostty-vt') + +executable('myapp', 'main.c', dependencies: ghostty_vt_dep) +``` + +## Example + +See `example/c-vt-meson/` for a complete working example. diff --git a/example/.gitignore b/example/.gitignore index 6f372bc4d..3d0ef75b3 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -2,4 +2,8 @@ dist/ node_modules/ example.wasm* +# CMake / Meson build/ + +# Meson +subprojects/ diff --git a/example/c-vt-meson/README.md b/example/c-vt-meson/README.md new file mode 100644 index 000000000..173ac8d1e --- /dev/null +++ b/example/c-vt-meson/README.md @@ -0,0 +1,42 @@ +# c-vt-meson + +Demonstrates consuming libghostty-vt from a Meson project using a +subproject. Creates a terminal, writes VT sequences into it, and +formats the screen contents as plain text. + +## Building this example + +Since this example lives inside the Ghostty repo, point the subproject +at the local checkout instead of fetching from GitHub: + +```shell-session +cd example/c-vt-meson +mkdir -p subprojects +ln -s ../../.. subprojects/ghostty +meson setup build +meson compile -C build +./build/c_vt_meson +``` + +## Real World Usage + +Create a `subprojects/ghostty.wrap` file in your project: + +```ini +[wrap-git] +url = https://github.com/ghostty-org/ghostty.git +revision = main +depth = 1 +``` + +Then in your `meson.build`: + +```meson +ghostty_proj = subproject('ghostty') +ghostty_vt_dep = ghostty_proj.get_variable('ghostty_vt_dep') + +executable('myapp', 'src/main.c', dependencies: ghostty_vt_dep) +``` + +Meson will clone the repository into `subprojects/ghostty/` on first +build and invoke `zig build lib-vt` automatically. diff --git a/example/c-vt-meson/meson.build b/example/c-vt-meson/meson.build new file mode 100644 index 000000000..df8b6884c --- /dev/null +++ b/example/c-vt-meson/meson.build @@ -0,0 +1,11 @@ +project('c-vt-meson', 'c', + version: '0.1.0', + meson_version: '>= 1.1.0', +) + +ghostty_proj = subproject('ghostty') +ghostty_vt_dep = ghostty_proj.get_variable('ghostty_vt_dep') + +executable('c_vt_meson', 'src/main.c', + dependencies: ghostty_vt_dep, +) diff --git a/example/c-vt-meson/src/main.c b/example/c-vt-meson/src/main.c new file mode 100644 index 000000000..5c9056da8 --- /dev/null +++ b/example/c-vt-meson/src/main.c @@ -0,0 +1,52 @@ +#include +#include +#include +#include +#include + +int main() { + // Create a terminal with a small grid + GhosttyTerminal terminal; + GhosttyTerminalOptions opts = { + .cols = 80, + .rows = 24, + .max_scrollback = 0, + }; + GhosttyResult result = ghostty_terminal_new(NULL, &terminal, opts); + assert(result == GHOSTTY_SUCCESS); + + // Write some VT-encoded content into the terminal + const char *commands[] = { + "Hello from a \033[1mMeson\033[0m-built program!\r\n", + "Line 2: \033[4munderlined\033[0m text\r\n", + "Line 3: \033[31mred\033[0m \033[32mgreen\033[0m \033[34mblue\033[0m\r\n", + }; + for (size_t i = 0; i < sizeof(commands) / sizeof(commands[0]); i++) { + ghostty_terminal_vt_write(terminal, (const uint8_t *)commands[i], + strlen(commands[i])); + } + + // Format the terminal contents as plain text + GhosttyFormatterTerminalOptions fmt_opts = + GHOSTTY_INIT_SIZED(GhosttyFormatterTerminalOptions); + fmt_opts.emit = GHOSTTY_FORMATTER_FORMAT_PLAIN; + fmt_opts.trim = true; + + GhosttyFormatter formatter; + result = ghostty_formatter_terminal_new(NULL, &formatter, terminal, fmt_opts); + assert(result == GHOSTTY_SUCCESS); + + uint8_t *buf = NULL; + size_t len = 0; + result = ghostty_formatter_format_alloc(formatter, NULL, &buf, &len); + assert(result == GHOSTTY_SUCCESS); + + printf("Plain text (%zu bytes):\n", len); + fwrite(buf, 1, len, stdout); + printf("\n"); + + free(buf); + ghostty_formatter_free(formatter); + ghostty_terminal_free(terminal); + return 0; +} diff --git a/meson.build b/meson.build new file mode 100644 index 000000000..322207e3c --- /dev/null +++ b/meson.build @@ -0,0 +1,150 @@ +# Meson wrapper for libghostty-vt +# +# This file delegates to `zig build lib-vt` to produce the shared library, +# headers, and pkg-config file. It exists so that Meson-based projects can +# consume libghostty-vt without interacting with the Zig build system +# directly. However, downstream users do still require `zig` on the PATH. +# Please consult the Ghostty docs for the required Zig version: +# +# https://ghostty.org/docs/install/build +# +# Building within the Ghostty repo +# --------------------------------- +# +# meson setup build +# meson compile -C build +# meson install -C build +# +# Pass extra flags to the Zig build with -Dzig-build-flags: +# +# meson setup build -Dzig-build-flags='-Demit-macos-app=false' +# +# Integrating into a downstream Meson project +# --------------------------------------------- +# +# Option 1 — Meson subproject with a .wrap file (recommended): +# +# Create subprojects/ghostty.wrap: +# +# [wrap-git] +# url = https://github.com/ghostty-org/ghostty.git +# revision = main +# depth = 1 +# +# Then in your meson.build: +# +# ghostty_proj = subproject('ghostty') +# ghostty_vt_dep = ghostty_proj.get_variable('ghostty_vt_dep') +# executable('myapp', 'main.c', dependencies: ghostty_vt_dep) +# +# Option 2 — pkg-config (after installing to a prefix): +# +# ghostty_vt_dep = dependency('libghostty-vt') +# executable('myapp', 'main.c', dependencies: ghostty_vt_dep) +# +# See dist/meson/README.md for more details and example/c-vt-meson/ for a +# complete working example. + +project('ghostty-vt', 'c', + version: '0.1.0', + meson_version: '>= 1.1.0', +) + +# --- Options ---------------------------------------------------------------- + +zig_build_flags = get_option('zig-build-flags') + +# --- Find Zig ---------------------------------------------------------------- + +zig = find_program('zig', required: true) +message('Found zig: ' + zig.full_path()) + +# --- Build via zig build ----------------------------------------------------- + +# Use --prefix to direct zig build output into the Meson build directory +# so the library is a proper declared output of the custom target. +zig_out_dir = meson.current_build_dir() / 'zig-out' + +# Determine library filenames per platform. +if host_machine.system() == 'darwin' + ghostty_vt_libname = 'libghostty-vt.dylib' + ghostty_vt_soname = 'libghostty-vt.0.dylib' + ghostty_vt_realname = 'libghostty-vt.0.1.0.dylib' +elif host_machine.system() == 'windows' + ghostty_vt_libname = 'ghostty-vt.dll' + ghostty_vt_realname = 'ghostty-vt.dll' + ghostty_vt_soname = '' +else + ghostty_vt_libname = 'libghostty-vt.so' + ghostty_vt_soname = 'libghostty-vt.so.0' + ghostty_vt_realname = 'libghostty-vt.so.0.1.0' +endif + +# Custom target: run zig build lib-vt with --prefix pointing into the build dir. +# Use --prefix-lib-dir to place the library directly in the custom_target +# output directory, and --prefix-include-dir for the headers. +# +# We declare a sentinel .h file as a second output so that Meson creates a +# compile-time ordering dependency (Meson only orders compiles before +# custom_target outputs that look like headers). +zig_build_cmd = [zig, 'build', 'lib-vt', + '--prefix-lib-dir', meson.current_build_dir(), + '--prefix-include-dir', zig_out_dir / 'include', +] +if zig_build_flags != '' + zig_build_cmd += zig_build_flags.split() +endif + +zig_build_lib_vt = custom_target('zig_build_lib_vt', + output: [ghostty_vt_realname, 'ghostty-vt-sentinel.h'], + command: [ + 'sh', '-c', '"$@" && touch @OUTPUT1@', 'sh', + zig_build_cmd, + ], + console: true, + install: true, + install_dir: [get_option('libdir'), false], +) + +# --- Declare dependency for subproject consumers ------------------------------ + +ghostty_vt_lib = declare_dependency( + compile_args: ['-I' + zig_out_dir / 'include'], + sources: zig_build_lib_vt, + link_args: [ + '-L' + meson.current_build_dir(), + '-lghostty-vt', + '-Wl,-rpath,' + meson.current_build_dir(), + ], +) + +# Expose for subproject() consumers via get_variable('ghostty_vt_dep') +ghostty_vt_dep = ghostty_vt_lib + +# --- Install ------------------------------------------------------------------ + +if host_machine.system() != 'windows' + # Install symlinks + meson.add_install_script('sh', '-c', + 'ln -sf "@0@" "$DESTDIR@1@/@2@"'.format( + ghostty_vt_realname, + get_option('prefix') / get_option('libdir'), + ghostty_vt_soname, + ), + ) + meson.add_install_script('sh', '-c', + 'ln -sf "@0@" "$DESTDIR@1@/@2@"'.format( + ghostty_vt_soname, + get_option('prefix') / get_option('libdir'), + ghostty_vt_libname, + ), + ) +endif + +# Install headers +meson.add_install_script('sh', '-c', + 'cp -r "@0@/include/ghostty" "$DESTDIR@1@/"'.format( + zig_out_dir, + get_option('prefix') / get_option('includedir'), + ), +) diff --git a/meson.options b/meson.options new file mode 100644 index 000000000..b251410db --- /dev/null +++ b/meson.options @@ -0,0 +1,2 @@ +option('zig-build-flags', type: 'string', value: '', + description: 'Additional flags to pass to zig build') diff --git a/nix/devShell.nix b/nix/devShell.nix index df08fc204..6b0584fe1 100644 --- a/nix/devShell.nix +++ b/nix/devShell.nix @@ -9,6 +9,8 @@ flatpak-builder, gdb, cmake, + meson, + ninja, #, glxinfo # unused ncurses, nodejs, @@ -93,6 +95,8 @@ in [ # For builds cmake + meson + ninja doxygen jq llvmPackages_latest.llvm