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..cae5bdba7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,16 +81,27 @@ 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}") +# 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 @@ -122,7 +133,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}" ) @@ -145,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 ------------------------------------------------------------------ @@ -152,8 +175,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/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); } diff --git a/dist/cmake/ghostty-vt-config.cmake.in b/dist/cmake/ghostty-vt-config.cmake.in index 9e1d65f6c..8e1d75729 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@" ) @@ -42,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/pkg/highway/build.zig b/pkg/highway/build.zig index b6e188b13..49656b93e 100644 --- a/pkg/highway/build.zig +++ b/pkg/highway/build.zig @@ -20,7 +20,15 @@ pub fn build(b: *std.Build) !void { }), .linkage = .static, }); - lib.linkLibCpp(); + 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(); + } 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..e132507a1 100644 --- a/pkg/simdutf/build.zig +++ b/pkg/simdutf/build.zig @@ -12,7 +12,15 @@ pub fn build(b: *std.Build) !void { }), .linkage = .static, }); - lib.linkLibCpp(); + 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(); + } 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..15c652c14 100644 --- a/pkg/utfcpp/build.zig +++ b/pkg/utfcpp/build.zig @@ -12,7 +12,15 @@ pub fn build(b: *std.Build) !void { }), .linkage = .static, }); - lib.linkLibCpp(); + 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(); + } if (target.result.os.tag.isDarwin()) { const apple_sdk = @import("apple_sdk"); 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 1f5f72b84..408f1ebc8 100644 --- a/src/build/GhosttyLibVt.zig +++ b/src/build/GhosttyLibVt.zig @@ -106,6 +106,12 @@ fn initLib( lib.root_module.pic = true; } + if (target.result.os.tag == .windows) { + // Zig's ubsan emits /exclude-symbols linker directives that + // are incompatible with the MSVC linker (LNK4229). + 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 diff --git a/src/build/GhosttyZig.zig b/src/build/GhosttyZig.zig index e63120e74..aabc00d46 100644 --- a/src/build/GhosttyZig.zig +++ b/src/build/GhosttyZig.zig @@ -64,8 +64,12 @@ fn initVt( .optimize = cfg.optimize, // SIMD require libc/libcpp (both) but otherwise we don't care. + // 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) 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..e01adb1fa 100644 --- a/src/build/SharedDeps.zig +++ b/src/build/SharedDeps.zig @@ -399,8 +399,15 @@ 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, 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(); + } // We always require the system SDK so that our system headers are available. // This makes things like `os/log.h` available for cross-compiling. @@ -783,12 +790,35 @@ 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", + ); + + // 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 = &.{ @@ -797,9 +827,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, }); } } 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();