Skip to content

cmake: fix Windows libghostty build support#11756

Merged
mitchellh merged 17 commits into
mainfrom
push-xlnoyquvytsy
Mar 23, 2026
Merged

cmake: fix Windows libghostty build support#11756
mitchellh merged 17 commits into
mainfrom
push-xlnoyquvytsy

Conversation

@mitchellh
Copy link
Copy Markdown
Contributor

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.

@mitchellh mitchellh added this to the 1.4.0 milestone Mar 22, 2026
@mitchellh mitchellh force-pushed the push-xlnoyquvytsy branch 3 times, most recently from 89c1cb5 to 6895393 Compare March 22, 2026 20:42
@mitchellh
Copy link
Copy Markdown
Contributor Author

Sorry, added some fat here and I'm fixing our windows build at the same time...

Copy link
Copy Markdown

@abrahamcuenca abrahamcuenca left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@mitchellh
Copy link
Copy Markdown
Contributor Author

Sorry, added some fat here and I'm fixing our windows build at the same time...

There's too much to do, so not doing that. libghostty is fine. I'll keep it tightly scoped to that.

@abrahamcuenca
Copy link
Copy Markdown

Is there a list of tasks somewhere to help divide the work?

@jcollie
Copy link
Copy Markdown
Member

jcollie commented Mar 22, 2026

Is there a list of tasks somewhere to help divide the work?

Not for Windows. I'd suggest diving into it but keep the scope of PRs small to make review easier.

@mitchellh mitchellh changed the title cmake: fix Windows build support cmake: fix Windows libghostty build support Mar 22, 2026
@mitchellh
Copy link
Copy Markdown
Contributor Author

Hm, besides the build issues there MAY be more going here to look into. Step 1 might be getting a passing zig build test-lib-vt on Windows before cmake stuff.

@deblasis
Copy link
Copy Markdown
Contributor

Hi there! I was searching for lib-vt and I bumped onto this.

May I interest you with some comedy and with:

  1. zig build test-lib-vt -> passes with exit 0 on Windows (it was broken on all platforms tbh)
  2. zig build test -> fully green, 0 failures
  3. zig build -> builds successfully, produces libghostty.a, libghostty.so, ghostty-vt.dll

?

It's not free, there's a gate you have to open and there's no turning back. Your choice: #11742 🙈

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.
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.
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.
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.
@mitchellh
Copy link
Copy Markdown
Contributor Author

Rebased onto the various windows fixes on main, hoping that makes this a lot more tractable PR. 😄

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.
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.
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.
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.
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.
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.
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 <iterator> and <cstddef> includes that were previously
pulled in transitively through libc++ headers but are not
guaranteed by MSVC's headers.
@mitchellh mitchellh requested a review from a team as a code owner March 23, 2026 18:57
@mitchellh
Copy link
Copy Markdown
Contributor Author

Actually a lot of issues related to the msvc ABI, so chasing those down now...

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 <optional>, <iterator>, <cstddef>, 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.
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 <optional> header does not define
std::optional.
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.
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.
@mitchellh
Copy link
Copy Markdown
Contributor Author

Okay!! I think we finally made it to a finish line (not the finish line, since I'm sure other issues remain).

This PR fixed a whole trove of build issues we had with the msvc ABI. That was the major blocker. Our lib-vt shared and static builds on Windows now work great with the msvc ABI.

Our examples build but they hang when running. I don't know why they hang but even getting them to build is such a huge milestone I'm willing to look past that and open an issue to track that.

If CI goes green I'll merge this.

@mitchellh
Copy link
Copy Markdown
Contributor Author

Yes, we've done it.

@mitchellh mitchellh merged commit 1213dac into main Mar 23, 2026
160 checks passed
@mitchellh mitchellh deleted the push-xlnoyquvytsy branch March 23, 2026 19:50
mitchellh added a commit that referenced this pull request Mar 24, 2026
# What 

PR #11756 added IMPORTED_IMPLIB pointing to the .lib import library, but
the
import library is not listed in the OUTPUT directive of the
`add_custom_command`
that runs zig build. The file is produced as a side-effect of the build.

This works with the Visual Studio generator (which is lenient about
undeclared outputs) but fails with Ninja:

ninja: error: 'zig-out/lib/ghostty-vt.lib', needed by 'ghostling',
missing and no known rule to make it

The fix adds "${ZIG_OUT_DIR}/lib/${GHOSTTY_VT_IMPLIB}" to the OUTPUT
list. No
behavioral change. The file was already being built, Ninja just needs to
know
about it.

## but_why.gif

I am cleaning up ghostty-org/ghostling#6 and I
realise that in order to get rid of the CMake workarounds we had before
#11756, this change is necessary.

# POC

I set up a branch pointing at my fork with a POC and it builds, this is
the cleaned up CMakeList
https://github.com/deblasis/winghostling/blob/test/cmake-implib-no-workaround/CMakeLists.txt
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants