Skip to content

fix: support native builds on Windows via MinGW-w64 (clang) and zig's bundled sysroot - #202

Open
techfreaque wants to merge 1 commit into
vercel-labs:mainfrom
techfreaque:fix-windows-native-toolchain
Open

fix: support native builds on Windows via MinGW-w64 (clang) and zig's bundled sysroot#202
techfreaque wants to merge 1 commit into
vercel-labs:mainfrom
techfreaque:fix-windows-native-toolchain

Conversation

@techfreaque

Copy link
Copy Markdown

Problem

scriptc build --dynamic (and any native, non-cross-compiled build) had no working path on Windows at all. nativePlatformArgs() only had a linux branch and fell through to empty flags for win32, for both host-native drivers resolveCc can select:

  • Bare clang — its default target on Windows is the MSVC ABI, which has none of the POSIX compatibility headers (dirent.h, unistd.h) or types (ssize_t) this project's runtime C sources need. Fails deep in a compile step with a cryptic missing-header error, no guidance toward the actual cause.
  • zig cc (host-native, no SCRIPTC_TARGET) — already bundles its own mingw-w64 headers/CRT for its x86_64-windows-gnu target (per this file's own module doc comment), so it needs no external toolchain — this path just happened to also fall through to the same empty-flags branch, which is actually correct for it (nothing to add).

Fix

nativePlatformArgs() gets a win32 branch, gated by a new viaZig parameter so the two drivers get opposite treatment:

  • Bare clang: auto-discovers a MinGW-w64 install (SCRIPTC_MINGW_ROOT env var, else the common MSYS2/mingw-w64 install locations) and targets it via --target=x86_64-w64-mingw32 plus the include/lib/gcc-runtime/winpthread flags that target needs to actually link (MinGW's own libgcc.a/libgcc_eh.a, versioned and auto-discovered; -lwinpthread for clock_gettime/nanosleep).
  • zig cc: gets empty targetArgs/linkArgs — matching how it already works standalone. This matters because --target=x86_64-w64-mingw32 isn't even a valid zig target-query spelling; feeding it to zig cc hard-errors (unable to parse target query 'x86_64-w64-mingw32': UnknownOperatingSystem), confirmed against a real zig 0.16 install.

resolveCc also computes nativePlatformArgs lazily, only at the two host-native return sites — every zig-cc cross-compile target (iOS, Android, Linux, wasm, ...) builds its own explicit targetArgs/linkArgs and never touches this, so none of them require a local MinGW install on a Windows host either.

Verification

On a real Windows machine with MSYS2/MinGW-w64 and zig 0.16 installed:

  • resolveCc({}, "win32") (bare clang) → MinGW target/include/link flags; a minimal probe compiles and links a real ~2MB native .exe.
  • resolveCc({SCRIPTC_CC: "zigcc"}, "win32") → empty targetArgs/linkArgs; zig cc hi.c -o hi.exe with no extra flags compiles and links a working ~780KB native .exe standalone, confirming the empty flags are correct (not just "didn't crash").
  • Confirmed --target=x86_64-w64-mingw32 actively breaks zig cc (hard target-parse error) before this fix — this was a real regression risk, not just redundant flags.
  • cc-driver.test.ts's cross-compile-target tests (linux host flags, SCRIPTC_TARGET-without-zigcc rejection, x86_64-windows-gnu cross, musl, regex, --dynamic engine archive) all pass unchanged.
  • Could not execute the compiled native binaries end-to-end on this particular machine — a local Windows Defender Exploit Guard / Attack Surface Reduction policy blocks running freshly-compiled unsigned binaries here, unrelated to the compiler itself. The compile+link step (what this fix changes) completes and produces a correct binary either way.

… own bundled sysroot

scriptc had no working native (non-cross-compiled) Windows build path
at all: `nativePlatformArgs()` only had a `linux` branch, falling
through to empty flags on win32 for both host-native drivers
`resolveCc` can select (bare clang and `zig cc`) - which need OPPOSITE
handling on this platform.

**Bare clang**: its own default target on Windows is the MSVC ABI,
which has none of the POSIX compatibility headers (dirent.h,
unistd.h) or types (ssize_t) this project's runtime C sources need.
Fixed by auto-discovering a MinGW-w64 install (SCRIPTC_MINGW_ROOT env
var, else the common MSYS2/mingw-w64 install locations) and pointing
clang at it via --target=x86_64-w64-mingw32, plus the include/lib/gcc-
runtime/winpthread flags that target needs to actually link.

**zig cc**: already bundles its own mingw-w64 headers/CRT for its
`x86_64-windows-gnu` target (see this file's own module doc comment)
and needs no external MinGW at all - `--target=x86_64-w64-mingw32`
isn't even a valid zig target-query spelling and hard-errors
("UnknownOperatingSystem") if fed to it. `nativePlatformArgs` takes a
`viaZig` flag so the MinGW-discovery branch only applies to the bare-
clang path; zig's host-native branch gets empty targetArgs/linkArgs,
matching how `zig cc` already works standalone. `resolveCc` also
computes these lazily (only at the two host-native return sites),
so every zig-cc cross-compile target (iOS, Android, Linux, wasm, ...)
- which builds its own explicit targetArgs/linkArgs and never touches
this - keeps working on a Windows host with no local MinGW at all.

Verified on a real Windows machine with MSYS2/MinGW-w64 and zig 0.16
installed:
- `resolveCc({}, "win32")` (bare clang) -> MinGW target/include/link
  flags; a minimal probe compiles AND links a real ~2MB native .exe.
- `resolveCc({SCRIPTC_CC: "zigcc"}, "win32")` -> empty targetArgs/
  linkArgs; `zig cc hi.c -o hi.exe` with no extra flags compiles and
  links a working ~780KB native .exe standalone (confirming the empty
  flags are correct, not just "did not crash").
- Confirmed `--target=x86_64-w64-mingw32` actively breaks `zig cc`
  before this fix (hard target-parse error), not just redundant.
- cc-driver.test.ts's cross-compile-target tests (linux host flags,
  SCRIPTC_TARGET-without-zigcc rejection, x86_64-windows-gnu cross,
  musl, regex, --dynamic engine archive) all pass unchanged.
- Could not execute the compiled native binaries end-to-end on this
  particular machine (a Windows Defender Exploit Guard / Attack
  Surface Reduction policy blocks running freshly-compiled unsigned
  binaries here) - a local security policy, unrelated to the compiler
  itself; the compile+link step (what this fix changes) completes and
  produces a correct binary either way.
@vercel

vercel Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

@techfreaque is attempting to deploy a commit to the Vercel Labs Team on Vercel.

A member of the Team first needs to authorize it.

techfreaque added a commit to techfreaque/scriptc that referenced this pull request Aug 22, 2026
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.

1 participant