Skip to content

build: fix freetype compilation on Windows with MSVC#11807

Merged
mitchellh merged 1 commit intoghostty-org:mainfrom
deblasis:014-windows/fix-freetype-msvc
Mar 24, 2026
Merged

build: fix freetype compilation on Windows with MSVC#11807
mitchellh merged 1 commit intoghostty-org:mainfrom
deblasis:014-windows/fix-freetype-msvc

Conversation

@deblasis
Copy link
Copy Markdown
Contributor

Summary

Getting there! Goal for today/tomorrow is to get it all green.

This one is easy:

  • Gate HAVE_UNISTD_H and HAVE_FCNTL_H behind a non-Windows check since these headers do not exist with MSVC
  • Freetype's gzip module includes zlib headers which conditionally include unistd.h based on this define

Context

Same pattern as the zlib fix (010-* branch from my fork). Freetype passes -DHAVE_UNISTD_H unconditionally, which causes zlib's zconf.h to try including unistd.h when freetype compiles its gzip support. The fix follows the same approach used in pkg/zlib/build.zig (line 36-38).

Stack

Stacked on 013-windows/fix-helpgen-framegen.

Test plan

Cross-platform results (zig build test / zig build -Dapp-runtime=none test on Windows)

Windows Linux Mac
BEFORE (f9d3b1a) FAIL - 44/51 steps, 2 failed PASS - 86/86, 2655/2678 tests, 23 skipped PASS - 160/160, 2655/2662 tests, 7 skipped
AFTER (d5aef6e) FAIL - 47/51 steps, 1 failed PASS - 86/86, 2655/2678 tests, 23 skipped PASS - 160/160, 2655/2662 tests, 7 skipped

Windows: what changed (44 to 47 steps, 2 to 1 failure)

Fixed by this PR:

  • compile lib freetype - was 2 errors (unistd.h/fcntl.h not found) -> success
  • 3 additional steps that depended on freetype now succeed

Remaining failure (pre-existing, tracked separately):

  • translate-c - 3 errors (ssize_t unknown in ghostty.h on MSVC)

Linux/macOS: no regressions

Identical pass counts and test results before and after.

Discussion

Other build files with the same pattern

pkg/fontconfig/build.zig and pkg/harfbuzz/build.zig also pass -DHAVE_UNISTD_H and/or -DHAVE_FCNTL_H unconditionally. They are not in the Windows build path today, but will need the same fix when they are.

What I Learnt

More of the same

Gate HAVE_UNISTD_H and HAVE_FCNTL_H behind a non-Windows check since
these headers do not exist with MSVC. Freetype includes zlib headers
which conditionally include unistd.h based on this define.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@deblasis deblasis requested a review from a team as a code owner March 24, 2026 14:35
@mitchellh mitchellh merged commit d14eab3 into ghostty-org:main Mar 24, 2026
1 check passed
mitchellh added a commit that referenced this pull request Mar 24, 2026
> [!WARNING]
> Review/approve this AFTER #11807 (this PR includes its commits)

92% progress with the fixes!

## Summary
- Add a conditional `ssize_t` typedef for MSVC in `include/ghostty.h`
- MSVC's `<sys/types.h>` does not define `ssize_t` (it is a POSIX type),
which causes the `translate-c` build step to fail when translating
`ghostty.h` on Windows
- Uses `SSIZE_T` from `<BaseTsd.h>`, the standard Windows SDK equivalent

## Context
The `translate-c` step translates `ghostty.h` to Zig for test
compilation. On MSVC, it fails with 3 errors on `ssize_t` (used in
`ghostty_action_move_tab_s`, `ghostty_action_search_total_s`,
`ghostty_action_search_selected_s`).

The `#ifdef _MSC_VER` guard means this only affects MSVC builds.
`BaseTsd.h` is a standard Windows SDK header and `SSIZE_T` is a signed
pointer-sized integer, matching POSIX `ssize_t` and Zig's `isize`. This
pattern is used by libuv, curl, and other cross-platform C projects.

## Test plan

### Cross-platform results (`zig build test` / `zig build
-Dapp-runtime=none test` on Windows)

| | Windows | Linux | Mac |
|---|---|---|---|
| **BEFORE** (d5aef6e) | FAIL - 47/51 steps, 1 failed | PASS - 86/86,
2655/2678 tests, 23 skipped | PASS - 160/160, 2655/2662 tests, 7 skipped
|
| **AFTER** (a35f84d) | FAIL - 48/51 steps, 1 failed | PASS - 86/86,
2655/2678 tests, 23 skipped | PASS - 160/160, 2655/2662 tests, 7 skipped
|

### Windows: what changed (47 -> 48 steps, translate-c fixed)

**Fixed by this PR:**
- `translate-c` - was `3 errors` (unknown type name 'ssize_t' at lines
582, 842, 847) -> `success`

**Remaining failure (pre-existing, unrelated):**
- `compile test ghostty-test` - 3 errors in libcxxabi
(`std::get_new_handler` not found, `type_info` redefinition). This is
Zig's bundled libc++ ABI conflicting with MSVC headers when compiling
the test binary. It was previously masked by the translate-c failure
blocking this step.

### Linux/macOS: no regressions
Identical pass counts and test results before and after.

## What Have I Learnt
- I tried fixing this issue the old way, googling and stuff, I
eventually figured out but it took me way more than I am prepared to
share. Yikes.
@github-actions github-actions bot added this to the 1.3.2 milestone Mar 24, 2026
mitchellh added a commit that referenced this pull request Mar 24, 2026
> [!WARNING]
> Review/approve this AFTER #11807 and #11810 (this PR includes their
commits)

## Summary

### **And `run test ghostty-test` finally runs on Windows! 🎉almost
there!**

- Skip `linkLibCpp()` on MSVC for dcimgui, spirv-cross, and harfbuzz
(same fix already applied upstream to highway, simdutf, utfcpp, glslang,
SharedDeps, GhosttyZig)
- Fix freetype C enum signedness: MSVC translates C enums as signed
`int`, while GCC/Clang uses unsigned `int`. Add `@intCast` at call sites
and `@bitCast` for bit-shift operations on glyph format tags.

## Context
Zig unconditionally passes `-nostdinc++` and adds its bundled
libc++/libc++abi include paths, which conflict with MSVC's own C++
runtime headers. The MSVC SDK directories (added via `linkLibC`) already
contain both C and C++ headers, so `linkLibCpp` is not needed.

The freetype enum issue is a different facet of the same MSVC vs
GCC/Clang divide: `FT_Render_Mode` and `FT_Glyph_Format` are C enums
that get different signedness on different compilers.

## Stack
Stacked on 015-windows/fix-ssize-t-msvc.

## Test plan

### Cross-platform results (`zig build test` / `zig build
-Dapp-runtime=none test` on Windows)

| | Windows | Linux | Mac |
|---|---|---|---|
| **BEFORE** (015, a35f84d) | FAIL - 48/51, 1 failed (compile
ghostty-test) | PASS - 86/86, 2655/2678, 23 skipped | PASS - 160/160,
2655/2662, 7 skipped |
| **AFTER** (016, ce99300) | FAIL - 49/51, 2630/2654 tests passed, 1
failed, 23 skipped | PASS - 86/86, 2655/2678, 23 skipped | PASS -
160/160, 2655/2662, 7 skipped |

### Windows: what changed (48 -> 49 steps, tests now run)

**Fixed by this PR:**
- `compile test ghostty-test` - was `3 errors` (libcxxabi conflicts +
freetype type mismatches) -> `success`
- `run test ghostty-test` - now actually runs: 2630 passed, 23 skipped,
1 failed

**Remaining test failure (pre-existing, unrelated):**
- `ghostty.h MouseShape` - `checkGhosttyHEnum` cannot find
`GHOSTTY_MOUSE_SHAPE_*` constants in the translate-c output. This is a
translate-c issue with how MSVC enum constants are exposed, not related
to C++ linking or enum signedness.

### Linux/macOS: no regressions
Identical pass counts and test results before and after.

## Discussion

### Grep wider: other unconditional linkLibCpp calls
`pkg/breakpad/build.zig` still calls `linkLibCpp()` unconditionally but
is behind sentry and not in the Windows build path. Noted for
completeness.

### Freetype enum signedness
The freetype Zig bindings define `RenderMode = enum(c_uint)` and
`Encoding = enum(u31)`. On MSVC, C enums are `int` (signed), so the
translated C functions expect `c_int` parameters. The fix adds
`@intCast` to convert between signed and unsigned at call sites. This is
safe because the enum values are small positive integers that fit in
both types.

Also, not sure if there's a better way to make this change more
elegantly. The comments are replicated in each instance, probably
overkill but I have seen this same pattern elsewhere in the codebase.

## What I Learnt
- More of the same
mitchellh added a commit that referenced this pull request Mar 27, 2026
## Summary

This PR effectively enables testing for all the Windows related stuff
that is coming soon.

> [!IMPORTANT]
>This PR builds on top of #11782 which fixes the last (as we speak) bug
that we have in the Windows pipeline. So it would be great to review
that PR first and then work on this one. Then we'll have the real
windows testing, basically achieving parity, infrastructurally, with the
other platforms.

What it does:

- Add a `test-windows` job to the CI workflow that runs the full test
suite (`zig build -Dapp-runtime=none test`) on Windows
- Add `test-windows` to the `required` checks list so it gates merges

## Context
The existing `build-libghostty-vt-windows` job only runs `zig build
test-lib-vt` (the VT library subset).
I realized that in c5092b0 we removed the TODO comment in that job:
"Work towards passing the full test suite on Windows."
But effectively we weren't running tests in CI yet! 

The full test suite now passes on Windows (51/51 steps, 2654 tests, 23
skipped). This job mirrors what the other platforms do — Linux runs `zig
build -Dapp-runtime=none test` via Nix, macOS runs `zig build test` via
Nix. Windows runs the same command directly via `setup-zig` since
there's no Nix on Windows.

## How
The new job follows the same pattern as the other Windows CI jobs:
- `runs-on: windows-2025` (same as `build-libghostty-vt-windows` and
`build-examples-cmake-windows`)
- `timeout-minutes: 45` (same as other Windows jobs)
- `needs: skip` so it runs early in parallel (same as `test-macos` and
the main `test` job), not gated behind other jobs
- Uses `mlugg/setup-zig` (same pinned version as other Windows jobs)
- Runs `zig build -Dapp-runtime=none test`

## Dependencies
This job will only pass once the following PRs are merged:
- PR #11782 -> backslash path handling in CommaSplitter/Theme
- PR #11807 -> freetype compilation fix
- PR #11810 -> ssize_t typedef for MSVC
- PR #11812 -> linkLibCpp skip + freetype enum signedness
- Others I have missed probably but they are merged already.

## Test plan
- The workflow YAML is valid (standard GitHub Actions syntax, matches
existing job patterns)
- I will be ready to issue fix PRs if any issue related to this arises.
I cannot reliably test GH actions locally unfortunately.
- Once dependencies land, the job should produce: 51/51 steps, ~2654
tests pass, 23 skipped
- No impact on existing Linux/macOS CI jobs

## What I Learnt
- GitHub Actions Windows runners don't have Nix, so Windows jobs use
`setup-zig` directly while Linux/macOS jobs use `nix develop -c zig
build ...`. The Nix wrapper ensures the exact same environment as the
flake, but on Windows we get that consistency from the `setup-zig`
action which reads the version from `build.zig.zon`.
- The `needs: skip` pattern allows a job to run in parallel with the
main test job rather than waiting for it. The main `test` job is the
gatekeeper for most build jobs (`needs: test`), but platform-specific
test jobs like `test-macos` run in parallel since they're independent.
- The `required` job aggregates all needed jobs and uses a grep-based
check to determine overall pass/fail, so adding a new job there means it
becomes a merge blocker.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants