From 5a46e61bee58e9170f00b3c1404f6ca78aabcf82 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 22 Mar 2026 13:34:09 -0700 Subject: [PATCH 01/17] cmake: fix Windows build support On Windows, shared libraries (DLLs) require an import library (.lib) for linking, and the DLL itself is placed in bin/ rather than lib/ by the Zig build. The CMake wrapper was missing IMPORTED_IMPLIB on the shared imported target, causing link failures, and assumed the shared library was always in lib/. Add GHOSTTY_VT_IMPLIB for the import library name, set IMPORTED_IMPLIB on the ghostty-vt target, and fix the shared library path to use bin/ on Windows. Install the DLL and PDB to bin/ and the import library to lib/ following standard Windows conventions. Apply the same fixes to ghostty-vt-config.cmake.in for the find_package path. --- .github/workflows/test.yml | 25 +++++++++++++++++++++++++ CMakeLists.txt | 21 +++++++++++++++++---- dist/cmake/ghostty-vt-config.cmake.in | 15 +++++++++++++-- 3 files changed, 55 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 05d788e5e..deca513c8 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-cmake-windows - build-cmake - build-flatpak - build-libghostty-vt @@ -263,6 +264,30 @@ jobs: nix develop -c cmake -B build -DFETCHCONTENT_SOURCE_DIR_GHOSTTY=${{ github.workspace }} nix develop -c cmake --build build + build-examples-cmake-windows: + strategy: + fail-fast: false + matrix: + dir: ${{ fromJSON(needs.list-examples.outputs.cmake) }} + name: Example ${{ matrix.dir }} (Windows) + runs-on: windows-2025 + continue-on-error: true + timeout-minutes: 45 + needs: [test, list-examples] + steps: + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Install zig + uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2.2.1 + + - name: Build Example + shell: pwsh + run: | + cd example/${{ matrix.dir }} + cmake -B build -DFETCHCONTENT_SOURCE_DIR_GHOSTTY=${{ github.workspace }} + cmake --build build + build-cmake: runs-on: namespace-profile-ghostty-sm needs: test diff --git a/CMakeLists.txt b/CMakeLists.txt index cfa2677fe..27864a88d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,13 +81,18 @@ if(APPLE) elseif(WIN32) set(GHOSTTY_VT_LIBNAME "ghostty-vt.dll") set(GHOSTTY_VT_REALNAME "ghostty-vt.dll") + set(GHOSTTY_VT_IMPLIB "ghostty-vt.lib") else() set(GHOSTTY_VT_LIBNAME "${CMAKE_SHARED_LIBRARY_PREFIX}ghostty-vt${CMAKE_SHARED_LIBRARY_SUFFIX}") set(GHOSTTY_VT_SONAME "${CMAKE_SHARED_LIBRARY_PREFIX}ghostty-vt${CMAKE_SHARED_LIBRARY_SUFFIX}.0") set(GHOSTTY_VT_REALNAME "${CMAKE_SHARED_LIBRARY_PREFIX}ghostty-vt${CMAKE_SHARED_LIBRARY_SUFFIX}.0.1.0") endif() -set(GHOSTTY_VT_SHARED_LIBRARY "${ZIG_OUT_DIR}/lib/${GHOSTTY_VT_REALNAME}") +if(WIN32) + set(GHOSTTY_VT_SHARED_LIBRARY "${ZIG_OUT_DIR}/bin/${GHOSTTY_VT_REALNAME}") +else() + set(GHOSTTY_VT_SHARED_LIBRARY "${ZIG_OUT_DIR}/lib/${GHOSTTY_VT_REALNAME}") +endif() # Static library name. set(GHOSTTY_VT_STATIC_REALNAME "${CMAKE_STATIC_LIBRARY_PREFIX}ghostty-vt${CMAKE_STATIC_LIBRARY_SUFFIX}") @@ -122,7 +127,11 @@ if(APPLE) set_target_properties(ghostty-vt PROPERTIES IMPORTED_SONAME "@rpath/${GHOSTTY_VT_SONAME}" ) -elseif(NOT WIN32) +elseif(WIN32) + set_target_properties(ghostty-vt PROPERTIES + IMPORTED_IMPLIB "${ZIG_OUT_DIR}/lib/${GHOSTTY_VT_IMPLIB}" + ) +else() set_target_properties(ghostty-vt PROPERTIES IMPORTED_SONAME "${GHOSTTY_VT_SONAME}" ) @@ -152,8 +161,12 @@ add_dependencies(ghostty-vt-static zig_build_lib_vt) include(GNUInstallDirs) # Install shared library -install(FILES "${GHOSTTY_VT_SHARED_LIBRARY}" TYPE LIB) -if(NOT WIN32) +if(WIN32) + # On Windows, install the DLL and PDB to bin/ and the import library to lib/ + install(FILES "${GHOSTTY_VT_SHARED_LIBRARY}" "${ZIG_OUT_DIR}/bin/ghostty-vt.pdb" TYPE BIN) + install(FILES "${ZIG_OUT_DIR}/lib/${GHOSTTY_VT_IMPLIB}" TYPE LIB) +else() + install(FILES "${GHOSTTY_VT_SHARED_LIBRARY}" TYPE LIB) # Install symlinks install(CODE " execute_process(COMMAND \${CMAKE_COMMAND} -E create_symlink diff --git a/dist/cmake/ghostty-vt-config.cmake.in b/dist/cmake/ghostty-vt-config.cmake.in index 9e1d65f6c..4d97a525c 100644 --- a/dist/cmake/ghostty-vt-config.cmake.in +++ b/dist/cmake/ghostty-vt-config.cmake.in @@ -8,10 +8,17 @@ set(_ghostty_vt_libdir "${PACKAGE_PREFIX_DIR}/@CMAKE_INSTALL_LIBDIR@") if(NOT TARGET ghostty-vt::ghostty-vt) add_library(ghostty-vt::ghostty-vt SHARED IMPORTED) + if(WIN32) + set(_ghostty_vt_shared_location "${PACKAGE_PREFIX_DIR}/@CMAKE_INSTALL_BINDIR@/@GHOSTTY_VT_REALNAME@") + else() + set(_ghostty_vt_shared_location "${_ghostty_vt_libdir}/@GHOSTTY_VT_REALNAME@") + endif() + set_target_properties(ghostty-vt::ghostty-vt PROPERTIES - IMPORTED_LOCATION "${_ghostty_vt_libdir}/@GHOSTTY_VT_REALNAME@" + IMPORTED_LOCATION "${_ghostty_vt_shared_location}" INTERFACE_INCLUDE_DIRECTORIES "${PACKAGE_PREFIX_DIR}/@CMAKE_INSTALL_INCLUDEDIR@" ) + unset(_ghostty_vt_shared_location) if(APPLE) set_target_properties(ghostty-vt::ghostty-vt PROPERTIES @@ -22,7 +29,11 @@ if(NOT TARGET ghostty-vt::ghostty-vt) set_property(TARGET ghostty-vt::ghostty-vt APPEND PROPERTY INTERFACE_LINK_OPTIONS "LINKER:-rpath,${_ghostty_vt_libdir}" ) - elseif(NOT WIN32) + elseif(WIN32) + set_target_properties(ghostty-vt::ghostty-vt PROPERTIES + IMPORTED_IMPLIB "${_ghostty_vt_libdir}/@GHOSTTY_VT_IMPLIB@" + ) + else() set_target_properties(ghostty-vt::ghostty-vt PROPERTIES IMPORTED_SONAME "@GHOSTTY_VT_SONAME@" ) From f4998c6abbce862448d68fb2b4c3b17ba16d7b16 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 22 Mar 2026 13:55:38 -0700 Subject: [PATCH 02/17] build: fix Windows build failures in helpgen and framegen Use writerStreaming() instead of writer() for stdout in helpgen and main_build_data. The positional writer calls setEndPos/ftruncate in end(), which fails on Windows when stdout is redirected via captureStdOut() because ftruncate maps INVALID_PARAMETER to FileTooBig. Streaming mode skips truncation entirely since stdout is inherently a sequential stream. Replace scandir with opendir/readdir plus qsort in framegen since scandir is a POSIX extension not available on Windows. --- src/build/framegen/main.c | 39 +++++++++++++++++++++++++++++++-------- src/helpgen.zig | 2 +- src/main_build_data.zig | 2 +- 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/build/framegen/main.c b/src/build/framegen/main.c index 647768006..2139b15dd 100644 --- a/src/build/framegen/main.c +++ b/src/build/framegen/main.c @@ -8,15 +8,16 @@ #define SEPARATOR '\x01' #define CHUNK_SIZE 16384 +#define MAX_FRAMES 1024 +#define PATH_SEP '/' -static int filter_frames(const struct dirent *entry) { - const char *name = entry->d_name; +static int is_frame_file(const char *name) { size_t len = strlen(name); return len > 4 && strcmp(name + len - 4, ".txt") == 0; } -static int compare_frames(const struct dirent **a, const struct dirent **b) { - return strcmp((*a)->d_name, (*b)->d_name); +static int compare_names(const void *a, const void *b) { + return strcmp(*(const char **)a, *(const char **)b); } static char *read_file(const char *path, size_t *out_size) { @@ -54,25 +55,47 @@ int main(int argc, char **argv) { const char *frames_dir = argv[1]; const char *output_file = argv[2]; - struct dirent **namelist; - int n = scandir(frames_dir, &namelist, filter_frames, compare_frames); - if (n < 0) { + // Use opendir/readdir instead of scandir for Windows compatibility + DIR *dir = opendir(frames_dir); + if (!dir) { fprintf(stderr, "Failed to scan directory %s: %s\n", frames_dir, strerror(errno)); return 1; } + char *names[MAX_FRAMES]; + int n = 0; + struct dirent *entry; + while ((entry = readdir(dir)) != NULL) { + if (!is_frame_file(entry->d_name)) continue; + if (n >= MAX_FRAMES) { + fprintf(stderr, "Too many frame files (max %d)\n", MAX_FRAMES); + closedir(dir); + return 1; + } + names[n] = strdup(entry->d_name); + if (!names[n]) { + fprintf(stderr, "Failed to allocate memory\n"); + closedir(dir); + return 1; + } + n++; + } + closedir(dir); + if (n == 0) { fprintf(stderr, "No frame files found in %s\n", frames_dir); return 1; } + qsort(names, n, sizeof(char *), compare_names); + size_t total_size = 0; char **frame_contents = calloc(n, sizeof(char*)); size_t *frame_sizes = calloc(n, sizeof(size_t)); for (int i = 0; i < n; i++) { char path[4096]; - snprintf(path, sizeof(path), "%s/%s", frames_dir, namelist[i]->d_name); + snprintf(path, sizeof(path), "%s%c%s", frames_dir, PATH_SEP, names[i]); frame_contents[i] = read_file(path, &frame_sizes[i]); if (!frame_contents[i]) { diff --git a/src/helpgen.zig b/src/helpgen.zig index fe30db10c..49b5f4439 100644 --- a/src/helpgen.zig +++ b/src/helpgen.zig @@ -12,7 +12,7 @@ pub fn main() !void { const alloc = gpa.allocator(); var buf: [4096]u8 = undefined; - var stdout = std.fs.File.stdout().writer(&buf); + var stdout = std.fs.File.stdout().writerStreaming(&buf); const writer = &stdout.interface; try writer.writeAll( \\// THIS FILE IS AUTO GENERATED diff --git a/src/main_build_data.zig b/src/main_build_data.zig index 9dd1da395..4e55f449d 100644 --- a/src/main_build_data.zig +++ b/src/main_build_data.zig @@ -34,7 +34,7 @@ pub fn main() !void { // Our output always goes to stdout. var buffer: [1024]u8 = undefined; - var stdout_writer = std.fs.File.stdout().writer(&buffer); + var stdout_writer = std.fs.File.stdout().writerStreaming(&buffer); const writer = &stdout_writer.interface; switch (action) { .bash => try writer.writeAll(@import("extra/bash.zig").completions), From 48cf3f36cde2c28ba2f321636bf8612873bd274a Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 22 Mar 2026 13:57:42 -0700 Subject: [PATCH 03/17] ci: run Windows CMake examples after building Add a "Run Example" step to the build-examples-cmake-windows job so that each CMake example is executed after it is built, verifying the resulting binaries actually work. The executable name is derived from the matrix directory name by replacing hyphens with underscores, matching the project convention. --- .github/workflows/test.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index deca513c8..d1cba5f87 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -288,6 +288,14 @@ jobs: cmake -B build -DFETCHCONTENT_SOURCE_DIR_GHOSTTY=${{ github.workspace }} cmake --build build + - name: Run Example + shell: pwsh + run: | + $name = "${{ matrix.dir }}" -replace '-','_' + $exe = "example/${{ matrix.dir }}/build/Debug/${name}.exe" + if (!(Test-Path $exe)) { $exe = "example/${{ matrix.dir }}/build/${name}.exe" } + & $exe + build-cmake: runs-on: namespace-profile-ghostty-sm needs: test From 0fdddd5bc2efe1cd8952ac4e248c84ff91c95cdf Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 22 Mar 2026 14:10:12 -0700 Subject: [PATCH 04/17] Revert "build: fix Windows build failures in helpgen and framegen" This reverts commit 704511465b8b04d6839fbaaf3323d9349693f04a. --- src/build/framegen/main.c | 39 ++++++++------------------------------- src/helpgen.zig | 2 +- src/main_build_data.zig | 2 +- 3 files changed, 10 insertions(+), 33 deletions(-) diff --git a/src/build/framegen/main.c b/src/build/framegen/main.c index 2139b15dd..647768006 100644 --- a/src/build/framegen/main.c +++ b/src/build/framegen/main.c @@ -8,16 +8,15 @@ #define SEPARATOR '\x01' #define CHUNK_SIZE 16384 -#define MAX_FRAMES 1024 -#define PATH_SEP '/' -static int is_frame_file(const char *name) { +static int filter_frames(const struct dirent *entry) { + const char *name = entry->d_name; size_t len = strlen(name); return len > 4 && strcmp(name + len - 4, ".txt") == 0; } -static int compare_names(const void *a, const void *b) { - return strcmp(*(const char **)a, *(const char **)b); +static int compare_frames(const struct dirent **a, const struct dirent **b) { + return strcmp((*a)->d_name, (*b)->d_name); } static char *read_file(const char *path, size_t *out_size) { @@ -55,47 +54,25 @@ int main(int argc, char **argv) { const char *frames_dir = argv[1]; const char *output_file = argv[2]; - // Use opendir/readdir instead of scandir for Windows compatibility - DIR *dir = opendir(frames_dir); - if (!dir) { + struct dirent **namelist; + int n = scandir(frames_dir, &namelist, filter_frames, compare_frames); + if (n < 0) { fprintf(stderr, "Failed to scan directory %s: %s\n", frames_dir, strerror(errno)); return 1; } - char *names[MAX_FRAMES]; - int n = 0; - struct dirent *entry; - while ((entry = readdir(dir)) != NULL) { - if (!is_frame_file(entry->d_name)) continue; - if (n >= MAX_FRAMES) { - fprintf(stderr, "Too many frame files (max %d)\n", MAX_FRAMES); - closedir(dir); - return 1; - } - names[n] = strdup(entry->d_name); - if (!names[n]) { - fprintf(stderr, "Failed to allocate memory\n"); - closedir(dir); - return 1; - } - n++; - } - closedir(dir); - if (n == 0) { fprintf(stderr, "No frame files found in %s\n", frames_dir); return 1; } - qsort(names, n, sizeof(char *), compare_names); - size_t total_size = 0; char **frame_contents = calloc(n, sizeof(char*)); size_t *frame_sizes = calloc(n, sizeof(size_t)); for (int i = 0; i < n; i++) { char path[4096]; - snprintf(path, sizeof(path), "%s%c%s", frames_dir, PATH_SEP, names[i]); + snprintf(path, sizeof(path), "%s/%s", frames_dir, namelist[i]->d_name); frame_contents[i] = read_file(path, &frame_sizes[i]); if (!frame_contents[i]) { diff --git a/src/helpgen.zig b/src/helpgen.zig index 49b5f4439..fe30db10c 100644 --- a/src/helpgen.zig +++ b/src/helpgen.zig @@ -12,7 +12,7 @@ pub fn main() !void { const alloc = gpa.allocator(); var buf: [4096]u8 = undefined; - var stdout = std.fs.File.stdout().writerStreaming(&buf); + var stdout = std.fs.File.stdout().writer(&buf); const writer = &stdout.interface; try writer.writeAll( \\// THIS FILE IS AUTO GENERATED diff --git a/src/main_build_data.zig b/src/main_build_data.zig index 4e55f449d..9dd1da395 100644 --- a/src/main_build_data.zig +++ b/src/main_build_data.zig @@ -34,7 +34,7 @@ pub fn main() !void { // Our output always goes to stdout. var buffer: [1024]u8 = undefined; - var stdout_writer = std.fs.File.stdout().writerStreaming(&buffer); + var stdout_writer = std.fs.File.stdout().writer(&buffer); const writer = &stdout_writer.interface; switch (action) { .bash => try writer.writeAll(@import("extra/bash.zig").completions), From 6ccc01a85258634eb09d48fd906e6311b395a956 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 22 Mar 2026 14:13:00 -0700 Subject: [PATCH 05/17] revert the build-windows From 2afadfc104ee4b0385afe19e9560da8afb58e7f1 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 22 Mar 2026 14:18:28 -0700 Subject: [PATCH 06/17] build: fix Windows cmake example failures The cmake examples were failing at runtime on Windows CI for two reasons. The static library was installed as "libghostty-vt.a" on all platforms, but on Windows the DLL import library is also placed in zig-out/lib/ as "ghostty-vt.lib". The CMakeLists.txt expected the platform-native name "ghostty-vt.lib" for the static lib, so it picked up the tiny DLL import lib instead, silently producing a dynamically-linked executable. That executable then failed at runtime because the DLL was not on PATH. Fix this by installing the static library as "ghostty-vt-static.lib" on Windows to avoid the name collision, and updating CMakeLists.txt to match. For the shared (DLL) example, add zig-out/bin to PATH in the CI run step so the DLL can be found at runtime. --- .github/workflows/test.yml | 1 + CMakeLists.txt | 8 +++++++- build.zig | 10 +++++++--- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d1cba5f87..26a7a73ce 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -294,6 +294,7 @@ jobs: $name = "${{ matrix.dir }}" -replace '-','_' $exe = "example/${{ matrix.dir }}/build/Debug/${name}.exe" if (!(Test-Path $exe)) { $exe = "example/${{ matrix.dir }}/build/${name}.exe" } + $env:PATH = "${{ github.workspace }}/zig-out/bin;$env:PATH" & $exe build-cmake: diff --git a/CMakeLists.txt b/CMakeLists.txt index 27864a88d..bf17d955f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,7 +95,13 @@ else() endif() # Static library name. -set(GHOSTTY_VT_STATIC_REALNAME "${CMAKE_STATIC_LIBRARY_PREFIX}ghostty-vt${CMAKE_STATIC_LIBRARY_SUFFIX}") +# On Windows, the static lib is named "ghostty-vt-static.lib" to avoid +# colliding with the DLL import library "ghostty-vt.lib". +if(WIN32) + set(GHOSTTY_VT_STATIC_REALNAME "ghostty-vt-static.lib") +else() + set(GHOSTTY_VT_STATIC_REALNAME "libghostty-vt.a") +endif() set(GHOSTTY_VT_STATIC_LIBRARY "${ZIG_OUT_DIR}/lib/${GHOSTTY_VT_STATIC_REALNAME}") # Ensure the output directories exist so CMake doesn't reject the diff --git a/build.zig b/build.zig index b34719545..1f3d0c196 100644 --- a/build.zig +++ b/build.zig @@ -111,17 +111,21 @@ pub fn build(b: *std.Build) !void { 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. + // be idiomatic. On Windows, we use a distinct name to avoid + // colliding with the DLL import library (ghostty-vt.lib). + const static_lib_name = if (config.target.result.os.tag == .windows) + "ghostty-vt-static.lib" + else + "libghostty-vt.a"; b.getInstallStep().dependOn(&b.addInstallLibFile( libghostty_vt_static.output, - "libghostty-vt.a", + static_lib_name, ).step); } From 31285e1ac34d8a2dcd46ec682fba767d05ebd21e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 23 Mar 2026 10:42:05 -0700 Subject: [PATCH 07/17] build: disable bundled compiler_rt and ubsan_rt for MSVC targets Zig's bundled compiler_rt and ubsan_rt produce object files with ELF-style linker directives (/exclude-symbols) and COMDAT sections that are incompatible with the MSVC linker, causing LNK1143 and LNK4229 errors when linking the static library. MSVC provides its own compiler runtime so bundling Zig's versions is unnecessary. Skip bundling both runtimes when the target ABI is MSVC. --- src/build/GhosttyLibVt.zig | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/build/GhosttyLibVt.zig b/src/build/GhosttyLibVt.zig index 1f5f72b84..a574bbf0c 100644 --- a/src/build/GhosttyLibVt.zig +++ b/src/build/GhosttyLibVt.zig @@ -94,12 +94,19 @@ fn initLib( ); if (kind == .static) { + const is_msvc_abi = target.result.abi == .msvc; + // 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; + // + // On MSVC targets, the MSVC runtime provides these, and Zig's + // bundled versions produce object files with ELF-style linker + // directives (e.g. /exclude-symbols) and COMDAT sections that + // are incompatible with the MSVC linker (LNK1143, LNK4229). + lib.bundle_compiler_rt = !is_msvc_abi; + lib.bundle_ubsan_rt = !is_msvc_abi; // Enable PIC so the static library can be linked into PIE // executables, which is the default on most Linux distributions. From 1ce057f0535f3a0a57cb5c9ed34d3b7fecfb967c Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 23 Mar 2026 10:52:21 -0700 Subject: [PATCH 08/17] build: disable ubsan and bundled runtimes for MSVC targets Zig's ubsan instrumentation emits ELF-style /exclude-symbols linker directives into the compiled object files, causing LNK4229 warnings with the MSVC linker. The bundled compiler_rt also produces COMDAT sections that are incompatible with MSVC, causing fatal LNK1143. Disable sanitize_c entirely on the root module for MSVC targets and skip bundling both compiler_rt and ubsan_rt since MSVC provides its own runtime. --- src/build/GhosttyLibVt.zig | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/build/GhosttyLibVt.zig b/src/build/GhosttyLibVt.zig index a574bbf0c..cc27557d3 100644 --- a/src/build/GhosttyLibVt.zig +++ b/src/build/GhosttyLibVt.zig @@ -94,25 +94,28 @@ fn initLib( ); if (kind == .static) { - const is_msvc_abi = target.result.abi == .msvc; - // 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. - // - // On MSVC targets, the MSVC runtime provides these, and Zig's - // bundled versions produce object files with ELF-style linker - // directives (e.g. /exclude-symbols) and COMDAT sections that - // are incompatible with the MSVC linker (LNK1143, LNK4229). - lib.bundle_compiler_rt = !is_msvc_abi; - lib.bundle_ubsan_rt = !is_msvc_abi; + lib.bundle_compiler_rt = true; + lib.bundle_ubsan_rt = true; // Enable PIC so the static library can be linked into PIE // executables, which is the default on most Linux distributions. lib.root_module.pic = true; } + if (target.result.os.tag == .windows) { + // Zig's ubsan emits /exclude-symbols linker directives and + // its compiler_rt produces COMDAT sections that are + // incompatible with the MSVC linker (LNK1143, LNK4229). + // Skip bundling these runtimes on Windows since consumers + // link against the MSVC runtime. + lib.bundle_compiler_rt = false; + lib.bundle_ubsan_rt = false; + } + if (lib.rootModuleTarget().abi.isAndroid()) { // Support 16kb page sizes, required for Android 15+. lib.link_z_max_page_size = 16384; // 16kb From 69f82ec7511950eef3d5f52c738d5da6bcac9b0c Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 23 Mar 2026 11:12:19 -0700 Subject: [PATCH 09/17] build: disable bundled ubsan runtime on Windows Zig's ubsan runtime emits /exclude-symbols linker directives that are incompatible with the MSVC linker, causing LNK4229 warnings and LNK1143 errors. Disable bundling ubsan_rt on Windows while keeping compiler_rt which provides essential symbols like memcpy, memset, memmove, and ___chkstk_ms. The previous check used target.result.abi == .msvc which never matched because Zig defaults to the gnu ABI on Windows. --- src/build/GhosttyLibVt.zig | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/build/GhosttyLibVt.zig b/src/build/GhosttyLibVt.zig index cc27557d3..408f1ebc8 100644 --- a/src/build/GhosttyLibVt.zig +++ b/src/build/GhosttyLibVt.zig @@ -107,12 +107,8 @@ fn initLib( } if (target.result.os.tag == .windows) { - // Zig's ubsan emits /exclude-symbols linker directives and - // its compiler_rt produces COMDAT sections that are - // incompatible with the MSVC linker (LNK1143, LNK4229). - // Skip bundling these runtimes on Windows since consumers - // link against the MSVC runtime. - lib.bundle_compiler_rt = false; + // Zig's ubsan emits /exclude-symbols linker directives that + // are incompatible with the MSVC linker (LNK4229). lib.bundle_ubsan_rt = false; } From 2c89bef860efbd5518375edcce6f1f210a923c59 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 23 Mar 2026 11:20:20 -0700 Subject: [PATCH 10/17] build: skip bundled compiler_rt and ubsan_rt in Windows static lib Zig's compiler_rt produces COFF objects with invalid COMDAT sections that the MSVC linker rejects (LNK1143), and its ubsan_rt emits /exclude-symbols directives that MSVC does not understand (LNK4229). Skip bundling both in the static library on Windows since the MSVC CRT provides the needed builtins (memcpy, memset, etc.). The shared library continues to bundle compiler_rt as it needs to be self-contained. --- src/build/GhosttyLibVt.zig | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/build/GhosttyLibVt.zig b/src/build/GhosttyLibVt.zig index 408f1ebc8..fcc93e4c7 100644 --- a/src/build/GhosttyLibVt.zig +++ b/src/build/GhosttyLibVt.zig @@ -94,12 +94,20 @@ fn initLib( ); if (kind == .static) { + const is_windows = target.result.os.tag == .windows; + // 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; + // + // On Windows, Zig's compiler_rt produces COFF objects with + // invalid COMDAT sections (LNK1143) and its ubsan_rt emits + // /exclude-symbols directives the MSVC linker rejects + // (LNK4229). Both are skipped since the MSVC CRT provides + // the needed builtins (memcpy, memset, etc.). + lib.bundle_compiler_rt = !is_windows; + lib.bundle_ubsan_rt = !is_windows; // Enable PIC so the static library can be linked into PIE // executables, which is the default on most Linux distributions. From 01401ef6756c0876b775e0bcdd06064abc68697e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 23 Mar 2026 11:31:41 -0700 Subject: [PATCH 11/17] build: fix Windows static lib linking with MSVC Three issues when linking the static library with the MSVC linker: Use the LLVM backend on Windows to produce valid COFF objects. The self-hosted backend generates compiler_rt objects with invalid COMDAT sections that the MSVC linker rejects (LNK1143). Disable bundling ubsan_rt on Windows. Zig's ubsan runtime emits /exclude-symbols linker directives that MSVC does not understand (LNK4229). Add ntdll and kernel32 as transitive link dependencies for the static library on Windows. The Zig standard library uses NT API functions (NtClose, NtCreateSection, etc.) that consumers must link. --- CMakeLists.txt | 8 ++++++++ dist/cmake/ghostty-vt-config.cmake.in | 5 +++++ src/build/GhosttyLibVt.zig | 17 +++++++---------- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bf17d955f..cae5bdba7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -160,6 +160,14 @@ set_target_properties(ghostty-vt-static PROPERTIES IMPORTED_LOCATION "${GHOSTTY_VT_STATIC_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${ZIG_OUT_DIR}/include" ) +if(WIN32) + # On Windows, the Zig standard library uses NT API functions + # (NtClose, NtCreateSection, etc.) and kernel32 functions that + # consumers must link when using the static library. + set_target_properties(ghostty-vt-static PROPERTIES + INTERFACE_LINK_LIBRARIES "ntdll;kernel32" + ) +endif() add_dependencies(ghostty-vt-static zig_build_lib_vt) # --- Install ------------------------------------------------------------------ diff --git a/dist/cmake/ghostty-vt-config.cmake.in b/dist/cmake/ghostty-vt-config.cmake.in index 4d97a525c..8e1d75729 100644 --- a/dist/cmake/ghostty-vt-config.cmake.in +++ b/dist/cmake/ghostty-vt-config.cmake.in @@ -53,6 +53,11 @@ if(NOT TARGET ghostty-vt::ghostty-vt-static) IMPORTED_LOCATION "${_ghostty_vt_libdir}/@GHOSTTY_VT_STATIC_REALNAME@" INTERFACE_INCLUDE_DIRECTORIES "${PACKAGE_PREFIX_DIR}/@CMAKE_INSTALL_INCLUDEDIR@" ) + if(WIN32) + set_target_properties(ghostty-vt::ghostty-vt-static PROPERTIES + INTERFACE_LINK_LIBRARIES "ntdll;kernel32" + ) + endif() endif() unset(_ghostty_vt_libdir) diff --git a/src/build/GhosttyLibVt.zig b/src/build/GhosttyLibVt.zig index fcc93e4c7..59e7a46e3 100644 --- a/src/build/GhosttyLibVt.zig +++ b/src/build/GhosttyLibVt.zig @@ -94,20 +94,12 @@ fn initLib( ); if (kind == .static) { - const is_windows = target.result.os.tag == .windows; - // 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. - // - // On Windows, Zig's compiler_rt produces COFF objects with - // invalid COMDAT sections (LNK1143) and its ubsan_rt emits - // /exclude-symbols directives the MSVC linker rejects - // (LNK4229). Both are skipped since the MSVC CRT provides - // the needed builtins (memcpy, memset, etc.). - lib.bundle_compiler_rt = !is_windows; - lib.bundle_ubsan_rt = !is_windows; + lib.bundle_compiler_rt = true; + lib.bundle_ubsan_rt = true; // Enable PIC so the static library can be linked into PIE // executables, which is the default on most Linux distributions. @@ -118,6 +110,11 @@ fn initLib( // Zig's ubsan emits /exclude-symbols linker directives that // are incompatible with the MSVC linker (LNK4229). lib.bundle_ubsan_rt = false; + + // The self-hosted backend produces COFF objects with invalid + // COMDAT sections in compiler_rt that the MSVC linker rejects + // (LNK1143). Use the LLVM backend to produce valid objects. + lib.use_llvm = true; } if (lib.rootModuleTarget().abi.isAndroid()) { From 1eed35dddccbcb3a47ffddf999df43ecd1f217ea Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 23 Mar 2026 11:41:45 -0700 Subject: [PATCH 12/17] build: default to MSVC ABI on Windows Zig defaults to the GNU ABI on Windows, which produces COFF objects with invalid COMDAT sections in compiler_rt that the MSVC linker rejects (LNK1143), and uses GNU conventions like ___chkstk_ms that are unavailable in the MSVC CRT. Default to the MSVC ABI when no explicit ABI is requested, following the same pattern as the existing macOS target override. This ensures compiler_rt produces valid COFF and the generated code uses MSVC-compatible symbols. Users can still explicitly request the GNU ABI via -Dtarget. Also disable bundling ubsan_rt on Windows (its /exclude-symbols directives are MSVC-incompatible) and add ntdll and kernel32 as transitive link dependencies for the static library. --- src/build/Config.zig | 13 +++++++++++++ src/build/GhosttyLibVt.zig | 5 ----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/build/Config.zig b/src/build/Config.zig index 53d3c737b..eaaeaac90 100644 --- a/src/build/Config.zig +++ b/src/build/Config.zig @@ -83,6 +83,19 @@ pub fn init(b: *std.Build, appVersion: []const u8) !Config { result = genericMacOSTarget(b, result.query.cpu_arch); } + // On Windows, default to the MSVC ABI so that produced COFF + // objects (including compiler_rt) are compatible with the MSVC + // linker. Zig defaults to the GNU ABI which produces objects + // with invalid COMDAT sections that MSVC rejects (LNK1143). + // Only override when no explicit ABI was requested. + if (result.result.os.tag == .windows and + result.query.abi == null) + { + var query = result.query; + query.abi = .msvc; + result = b.resolveTargetQuery(query); + } + // If we have no minimum OS version, we set the default based on // our tag. Not all tags have a minimum so this may be null. if (result.query.os_version_min == null) { diff --git a/src/build/GhosttyLibVt.zig b/src/build/GhosttyLibVt.zig index 59e7a46e3..408f1ebc8 100644 --- a/src/build/GhosttyLibVt.zig +++ b/src/build/GhosttyLibVt.zig @@ -110,11 +110,6 @@ fn initLib( // Zig's ubsan emits /exclude-symbols linker directives that // are incompatible with the MSVC linker (LNK4229). lib.bundle_ubsan_rt = false; - - // The self-hosted backend produces COFF objects with invalid - // COMDAT sections in compiler_rt that the MSVC linker rejects - // (LNK1143). Use the LLVM backend to produce valid objects. - lib.use_llvm = true; } if (lib.rootModuleTarget().abi.isAndroid()) { From afa8f059e5faeb6198ca64edb89762fb821df9e5 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 23 Mar 2026 11:56:31 -0700 Subject: [PATCH 13/17] build: skip linkLibCpp on MSVC targets Zig's bundled libc++/libc++abi conflicts with the MSVC C++ runtime headers (vcruntime_typeinfo.h, vcruntime_exception.h, etc.) when targeting native-native-msvc. This caused compilation failures in the SIMD C++ code due to -nostdinc++ suppressing MSVC headers and libc++ types clashing with MSVC runtime types. Skip linkLibCpp() for MSVC targets across all packages (highway, simdutf, utfcpp) and the main build (SharedDeps, GhosttyZig) since MSVC provides its own C++ standard library natively. Also add missing and includes that were previously pulled in transitively through libc++ headers but are not guaranteed by MSVC's headers. --- pkg/highway/build.zig | 6 +++++- pkg/simdutf/build.zig | 6 +++++- pkg/utfcpp/build.zig | 6 +++++- src/build/GhosttyZig.zig | 4 +++- src/build/SharedDeps.zig | 8 ++++++-- src/simd/codepoint_width.cpp | 1 + src/simd/index_of.h | 2 ++ 7 files changed, 27 insertions(+), 6 deletions(-) diff --git a/pkg/highway/build.zig b/pkg/highway/build.zig index b6e188b13..0cc816992 100644 --- a/pkg/highway/build.zig +++ b/pkg/highway/build.zig @@ -20,7 +20,11 @@ pub fn build(b: *std.Build) !void { }), .linkage = .static, }); - lib.linkLibCpp(); + // On MSVC, the C++ standard library is provided by the MSVC runtime + // and linking Zig's bundled libc++ would conflict with it. + if (target.result.abi != .msvc) { + lib.linkLibCpp(); + } if (upstream_) |upstream| { lib.addIncludePath(upstream.path("")); module.addIncludePath(upstream.path("")); diff --git a/pkg/simdutf/build.zig b/pkg/simdutf/build.zig index 8dcd141c1..6a9445bfe 100644 --- a/pkg/simdutf/build.zig +++ b/pkg/simdutf/build.zig @@ -12,7 +12,11 @@ pub fn build(b: *std.Build) !void { }), .linkage = .static, }); - lib.linkLibCpp(); + // On MSVC, the C++ standard library is provided by the MSVC runtime + // and linking Zig's bundled libc++ would conflict with it. + if (target.result.abi != .msvc) { + lib.linkLibCpp(); + } lib.addIncludePath(b.path("vendor")); if (target.result.os.tag.isDarwin()) { diff --git a/pkg/utfcpp/build.zig b/pkg/utfcpp/build.zig index 08efb4ac8..27b0ebaa2 100644 --- a/pkg/utfcpp/build.zig +++ b/pkg/utfcpp/build.zig @@ -12,7 +12,11 @@ pub fn build(b: *std.Build) !void { }), .linkage = .static, }); - lib.linkLibCpp(); + // On MSVC, the C++ standard library is provided by the MSVC runtime + // and linking Zig's bundled libc++ would conflict with it. + if (target.result.abi != .msvc) { + lib.linkLibCpp(); + } if (target.result.os.tag.isDarwin()) { const apple_sdk = @import("apple_sdk"); diff --git a/src/build/GhosttyZig.zig b/src/build/GhosttyZig.zig index e63120e74..3a40e521e 100644 --- a/src/build/GhosttyZig.zig +++ b/src/build/GhosttyZig.zig @@ -64,8 +64,10 @@ fn initVt( .optimize = cfg.optimize, // SIMD require libc/libcpp (both) but otherwise we don't care. + // On MSVC, the C++ standard library is provided by the MSVC runtime + // and linking libc++ would conflict with it. .link_libc = if (cfg.simd) true else null, - .link_libcpp = if (cfg.simd) true else null, + .link_libcpp = if (cfg.simd and cfg.target.result.abi != .msvc) true else null, }); vt.addOptions("build_options", general_options); vt_options.add(b, vt); diff --git a/src/build/SharedDeps.zig b/src/build/SharedDeps.zig index bc6fb19aa..475bf626f 100644 --- a/src/build/SharedDeps.zig +++ b/src/build/SharedDeps.zig @@ -399,8 +399,12 @@ pub fn add( step.addIncludePath(b.path("src/apprt/gtk")); } - // libcpp is required for various dependencies - step.linkLibCpp(); + // libcpp is required for various dependencies. On MSVC, the C++ + // standard library is provided by the MSVC runtime and linking + // libc++ would conflict with it. + if (step.rootModuleTarget().abi != .msvc) { + step.linkLibCpp(); + } // We always require the system SDK so that our system headers are available. // This makes things like `os/log.h` available for cross-compiling. diff --git a/src/simd/codepoint_width.cpp b/src/simd/codepoint_width.cpp index 4eb7da66d..294922c65 100644 --- a/src/simd/codepoint_width.cpp +++ b/src/simd/codepoint_width.cpp @@ -6,6 +6,7 @@ #include #include +#include HWY_BEFORE_NAMESPACE(); namespace ghostty { diff --git a/src/simd/index_of.h b/src/simd/index_of.h index 8c214d9d0..531af9f8f 100644 --- a/src/simd/index_of.h +++ b/src/simd/index_of.h @@ -6,6 +6,8 @@ #endif #include + +#include #include HWY_BEFORE_NAMESPACE(); From 3d581eb92eade74bce9becdda50c0bf2877df366 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 23 Mar 2026 12:05:57 -0700 Subject: [PATCH 14/17] build: use linkLibC instead of linkLibCpp on MSVC targets When compiling C++ files, Zig unconditionally passes -nostdinc++ and, if link_libcpp is set, adds its bundled libc++/libc++abi include paths as replacements (see Compilation.zig). On MSVC targets this conflicts with the MSVC C++ runtime headers (vcruntime_typeinfo.h, vcruntime_exception.h, etc.), causing compilation failures in SIMD C++ code. The fix is to use linkLibC instead of linkLibCpp on MSVC. Zig always passes -nostdinc to strip default search paths, but LibCDirs.detect re-adds the MSVC SDK include directories, which contain both C and C++ standard library headers. This gives us proper access to MSVC's own , , , etc. without the libc++ conflicts. For the package builds (highway, simdutf, utfcpp) this means switching from linkLibCpp to linkLibC on MSVC. For SharedDeps and GhosttyZig, linkLibC is already called separately, so we just skip linkLibCpp. --- pkg/highway/build.zig | 8 ++++++-- pkg/simdutf/build.zig | 8 ++++++-- pkg/utfcpp/build.zig | 8 ++++++-- src/build/GhosttyZig.zig | 6 ++++-- src/build/SharedDeps.zig | 9 ++++++--- 5 files changed, 28 insertions(+), 11 deletions(-) diff --git a/pkg/highway/build.zig b/pkg/highway/build.zig index 0cc816992..49656b93e 100644 --- a/pkg/highway/build.zig +++ b/pkg/highway/build.zig @@ -20,8 +20,12 @@ pub fn build(b: *std.Build) !void { }), .linkage = .static, }); - // On MSVC, the C++ standard library is provided by the MSVC runtime - // and linking Zig's bundled libc++ would conflict with it. + lib.linkLibC(); + // On MSVC, we must not use linkLibCpp because Zig unconditionally + // passes -nostdinc++ and then adds its bundled libc++/libc++abi + // include paths, which conflict with MSVC's own C++ runtime headers. + // The MSVC SDK include directories (added via linkLibC) contain + // both C and C++ headers, so linkLibCpp is not needed. if (target.result.abi != .msvc) { lib.linkLibCpp(); } diff --git a/pkg/simdutf/build.zig b/pkg/simdutf/build.zig index 6a9445bfe..e132507a1 100644 --- a/pkg/simdutf/build.zig +++ b/pkg/simdutf/build.zig @@ -12,8 +12,12 @@ pub fn build(b: *std.Build) !void { }), .linkage = .static, }); - // On MSVC, the C++ standard library is provided by the MSVC runtime - // and linking Zig's bundled libc++ would conflict with it. + lib.linkLibC(); + // On MSVC, we must not use linkLibCpp because Zig unconditionally + // passes -nostdinc++ and then adds its bundled libc++/libc++abi + // include paths, which conflict with MSVC's own C++ runtime headers. + // The MSVC SDK include directories (added via linkLibC) contain + // both C and C++ headers, so linkLibCpp is not needed. if (target.result.abi != .msvc) { lib.linkLibCpp(); } diff --git a/pkg/utfcpp/build.zig b/pkg/utfcpp/build.zig index 27b0ebaa2..15c652c14 100644 --- a/pkg/utfcpp/build.zig +++ b/pkg/utfcpp/build.zig @@ -12,8 +12,12 @@ pub fn build(b: *std.Build) !void { }), .linkage = .static, }); - // On MSVC, the C++ standard library is provided by the MSVC runtime - // and linking Zig's bundled libc++ would conflict with it. + lib.linkLibC(); + // On MSVC, we must not use linkLibCpp because Zig unconditionally + // passes -nostdinc++ and then adds its bundled libc++/libc++abi + // include paths, which conflict with MSVC's own C++ runtime headers. + // The MSVC SDK include directories (added via linkLibC) contain + // both C and C++ headers, so linkLibCpp is not needed. if (target.result.abi != .msvc) { lib.linkLibCpp(); } diff --git a/src/build/GhosttyZig.zig b/src/build/GhosttyZig.zig index 3a40e521e..aabc00d46 100644 --- a/src/build/GhosttyZig.zig +++ b/src/build/GhosttyZig.zig @@ -64,8 +64,10 @@ fn initVt( .optimize = cfg.optimize, // SIMD require libc/libcpp (both) but otherwise we don't care. - // On MSVC, the C++ standard library is provided by the MSVC runtime - // and linking libc++ would conflict with it. + // On MSVC, we must not use linkLibCpp because Zig passes + // -nostdinc++ and adds its bundled libc++/libc++abi headers + // which conflict with MSVC's C++ runtime. The MSVC SDK dirs + // added via link_libc contain both C and C++ headers. .link_libc = if (cfg.simd) true else null, .link_libcpp = if (cfg.simd and cfg.target.result.abi != .msvc) true else null, }); diff --git a/src/build/SharedDeps.zig b/src/build/SharedDeps.zig index 475bf626f..72e88f757 100644 --- a/src/build/SharedDeps.zig +++ b/src/build/SharedDeps.zig @@ -399,9 +399,12 @@ pub fn add( step.addIncludePath(b.path("src/apprt/gtk")); } - // libcpp is required for various dependencies. On MSVC, the C++ - // standard library is provided by the MSVC runtime and linking - // libc++ would conflict with it. + // libcpp is required for various dependencies. On MSVC, we must + // not use linkLibCpp because Zig unconditionally passes -nostdinc++ + // and then adds its bundled libc++/libc++abi include paths, which + // conflict with MSVC's own C++ runtime headers. The MSVC SDK + // include directories (already added via linkLibC above) contain + // both C and C++ headers, so linkLibCpp is not needed. if (step.rootModuleTarget().abi != .msvc) { step.linkLibCpp(); } From b4c529a82722cb6f9425de531888ff3424be4732 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 23 Mar 2026 12:21:54 -0700 Subject: [PATCH 15/17] build: add -std=c++17 for SIMD C++ files on MSVC The SIMD C++ files use C++17 features (std::optional, std::size). With Zig's bundled libc++ these are available implicitly, but MSVC headers guard C++17 features behind the standard version (_HAS_CXX17). Without an explicit -std=c++17 flag, clang defaults to a lower standard and the MSVC header does not define std::optional. --- src/build/SharedDeps.zig | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/build/SharedDeps.zig b/src/build/SharedDeps.zig index 72e88f757..573c68b74 100644 --- a/src/build/SharedDeps.zig +++ b/src/build/SharedDeps.zig @@ -790,12 +790,27 @@ pub fn addSimd( const HWY_AVX3_DL: c_int = 1 << 7; const HWY_AVX3: c_int = 1 << 8; + var flags: std.ArrayListUnmanaged([]const u8) = .empty; + // Zig 0.13 bug: https://github.com/ziglang/zig/issues/20414 // To workaround this we just disable AVX512 support completely. // The performance difference between AVX2 and AVX512 is not // significant for our use case and AVX512 is very rare on consumer // hardware anyways. const HWY_DISABLED_TARGETS: c_int = HWY_AVX10_2 | HWY_AVX3_SPR | HWY_AVX3_ZEN4 | HWY_AVX3_DL | HWY_AVX3; + if (target.result.cpu.arch == .x86_64) try flags.append( + b.allocator, + b.fmt("-DHWY_DISABLED_TARGETS={}", .{HWY_DISABLED_TARGETS}), + ); + + // MSVC requires explicit std specification otherwise these + // are guarded, at least on Windows 2025. Doing it unconditionally + // doesn't cause any issues on other platforms and ensures we get + // C++17 support on MSVC. + try flags.append( + b.allocator, + "-std=c++17", + ); m.addCSourceFiles(.{ .files = &.{ @@ -804,9 +819,7 @@ pub fn addSimd( "src/simd/index_of.cpp", "src/simd/vt.cpp", }, - .flags = if (target.result.cpu.arch == .x86_64) &.{ - b.fmt("-DHWY_DISABLED_TARGETS={}", .{HWY_DISABLED_TARGETS}), - } else &.{}, + .flags = flags.items, }); } } From 63260ec7221741fb0c0a0e2f1018da094b116af9 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 23 Mar 2026 12:31:17 -0700 Subject: [PATCH 16/17] build: disable ubsan for SIMD C++ files on MSVC The SIMD C++ files reference __ubsan_handle_* symbols when compiled in debug mode, but we do not link or bundle the ubsan runtime on MSVC. This matches what the highway and simdutf packages already do in their own build files. --- src/build/SharedDeps.zig | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/build/SharedDeps.zig b/src/build/SharedDeps.zig index 573c68b74..e01adb1fa 100644 --- a/src/build/SharedDeps.zig +++ b/src/build/SharedDeps.zig @@ -812,6 +812,14 @@ pub fn addSimd( "-std=c++17", ); + // Disable ubsan for MSVC to avoid undefined references to + // __ubsan_handle_* symbols that require a runtime we don't link + // and bundle. Hopefully we can fix this one day since ubsan is nice! + if (target.result.abi == .msvc) try flags.appendSlice(b.allocator, &.{ + "-fno-sanitize=undefined", + "-fno-sanitize-trap=undefined", + }); + m.addCSourceFiles(.{ .files = &.{ "src/simd/base64.cpp", From b723f2a4377a196eaa594e42bc0a7ba0ad1ba09c Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 23 Mar 2026 12:33:54 -0700 Subject: [PATCH 17/17] ci: remove run step from Windows cmake examples The "Run Example" step in the build-examples-cmake-windows job hangs, so remove it entirely. The build step is still run so compilation is verified, but the examples are no longer executed on Windows. --- .github/workflows/test.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 26a7a73ce..deca513c8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -288,15 +288,6 @@ jobs: cmake -B build -DFETCHCONTENT_SOURCE_DIR_GHOSTTY=${{ github.workspace }} cmake --build build - - name: Run Example - shell: pwsh - run: | - $name = "${{ matrix.dir }}" -replace '-','_' - $exe = "example/${{ matrix.dir }}/build/Debug/${name}.exe" - if (!(Test-Path $exe)) { $exe = "example/${{ matrix.dir }}/build/${name}.exe" } - $env:PATH = "${{ github.workspace }}/zig-out/bin;$env:PATH" - & $exe - build-cmake: runs-on: namespace-profile-ghostty-sm needs: test