Goal
Native Windows .exe / .dll bottles, produced from Linux CI runners via cross-compile, distributable through the existing .tar.xz bottle format. Tracked at pkgxdev/pkgx#607 since 2023; the runtime piece was started at pkgxdev/libpkgx#48 (merged) but stalled on the chicken-and-egg of "no Windows packages exist yet to test against".
This RFC proposes a concrete first deliverable and the brewkit-side architecture, leveraging the patterns we converged on in #343 / #344 / #345.
Current state in this repo
Brewkit already has three host().platform == 'windows' branches (build/build.ts:248, lib/porcelain/build-script.ts:47, lib/hooks/usePantry.getScript.ts:196) — lld-link symlink, TMP/TEMP env, path-separator normalization. ~30% of the scaffolding is there. Zero pantry recipes target windows/* today.
Why now, why this shape
The architecture we converged on last week composes very well with Windows:
| Pattern (linux) |
Windows mapping |
linux-sysroot: gnu.org/glibc@2.17 (#343) |
windows-sdk: … (or implicit from a toolchain dep) |
gnu.org/glibc bottle (relocatable libc) |
N/A — UCRT ships with Win10+; the "redistributable" bit is vcruntime140.dll + msvcp140.dll, and that's separate from a bottle |
bklibcvenv stage/seal (#344) |
bkwinvenv stage/seal — gather transitive DLLs, move bin/*.exe → libexec/, generate bin/*.cmd wrappers |
build.skip: fix-patchelf (#345) |
build.skip: fix-pe per-recipe escape hatch |
fix-elf rewriting RPATH |
No RPATH on Windows — DLLs found via app-dir → System32 → %PATH%. Hermeticity by layout, not by metadata. |
| Default: PT_INTERP = system, hermeticity opt-in (your call on #12968) |
Default: standard DLL search order. Hermeticity opt-in = bkwinvenv seal + .cmd wrappers. Same separation, gratis. |
The big architectural win: Windows has no PT_INTERP and no RPATH. So "default relocatable, hermeticity opt-in via wrappers" is already the platform's native behaviour — we don't have to fight it like we did with glibc.
Proposed toolchain: llvm-mingw
Three candidates, recommendation = llvm-mingw:
|
Cross-compile from Linux? |
ABI |
Maturity |
MSVC cl.exe |
❌ (native-only) |
MSVC |
Mature, EULA-encumbered |
| MinGW-w64 (gcc) |
✅ |
gcc |
Mature, mismatches with MSVC .lib |
| llvm-mingw (clang) |
✅ |
clang-cl can do both ABIs |
Younger but production-grade (used by Qt, …) |
Cross-compile from Linux reuses pkgx CI infra; GitHub Windows runners are ~10× slower / cost ~10× more.
Brewkit changes (sketched)
Layer 1 — toolchain wiring
build/build.ts already symlinks lld-link on Windows. Extend:
clang-cl, clang++-cl, link.exe → lld-link, rc.exe → llvm-rc
- Add
llvm.org/mingw-w64 recipe to pantry
Layer 2 — fix-pe (the analog of fix-elf, but minimal)
PE doesn't have RPATH — no relocation surgery needed. What fix-pe would actually do:
- Strip
+brewing from any embedded strings (same problem as the libc.so linker scripts in #12968)
- Strip absolute paths from
.pdb debug records
- Audit-only check: every NEEDED DLL is either Windows-shipped (kernel32, ntdll, ucrtbase, …) or in the bottle
Tool: LIEF or llvm-objcopy (already in the clang-mingw bottle).
Layer 3 — bkwinvenv stage/seal
Direct port of #344's pattern, simpler in implementation:
# bkwinvenv seal <prefix>
# - for each bin/*.exe:
# - walk PE imports recursively
# - move dependent DLLs into libexec/
# - move exe → libexec/
# - write bin/foo.cmd:
# @echo off
# "%~dp0\..\libexec\foo.exe" %*
No explicit loader invocation needed (no $LDSO --library-path); the .cmd wrapper does an implicit SetDllDirectory via colocation.
Layer 4 — audit step
PE-aware: llvm-readobj --coff-imports foo.exe to list NEEDED DLLs, verify they're either shipped or system.
Phased plan
Phase 0 — Validation pilot (this RFC's deliverable)
- Bottle
llvm.org/mingw-w64 in pantry
- One pilot recipe:
jq cross-compiled to windows/x86-64
- Produce
.tar.xz containing bin/jq.exe
- Manual test: extract on Win11 VM,
jq.exe --version returns sanely
Phase 1 — Brewkit Windows-aware (~1 week of focus)
- Complete the
platform == 'windows' branches (audit, bottle, env)
- Implement
fix-pe (minimal)
- Add
windows/x86-64, windows/aarch64 to platform schema
Phase 2 — Pantry mass-port (~2-4 weeks)
- 10 "leaf" packages: jq, ripgrep, fd, bat, hyperfine, hexyl, …
- Cascade to C/C++ libs: zlib, openssl, sqlite, …
Phase 3 — bkwinvenv + hermetic mode
- PE-import walker
.cmd wrapper generator
Phase 4 — pkgx CLI integration (follow-through on libpkgx#48)
- PowerShell profile / cmd.exe registry for
pkgx integrate
- Path translation
Questions for maintainers
- Cross-compile from Linux vs. native Windows runners: I'm leaning hard on cross-compile (CI cost + speed). Any blocker you can think of?
- MinGW vs MSVC ABI: target both (max distrib compat) or pick one (start with clang-cl/MSVC ABI)?
- UCRT: rely on Win10+ shipping it (no bundle), or bundle
vcruntime140.dll in each bottle for older targets?
pkgx integrate on Windows: PowerShell profile primary, cmd.exe secondary? Or other dispatch?
- Pantry layout: keep
projects/*/package.yml flat (with platforms: windows/* opt-in), or isolate under projects/windows/*?
What I'd like to do next (with your sign-off)
If the framing here lands well, I'd:
- Open a follow-up PR to brewkit adding Layer 1 + Layer 4 scaffolding (toolchain symlinks + PE audit hook)
- Add an
llvm.org/mingw-w64 recipe to pantry (cross-compile toolchain bottle)
- Open a pantry PR with a
jq Windows pilot
- Produce + post a downloadable
.tar.xz for inspection
If you want me to hold off until #12968 lands or the bklibcvenv shape stabilizes, that's fine too — just want to put the architecture on paper so the existing Windows scaffolding doesn't keep bit-rotting.
cc @mxcl (your message on pkgxdev/pkgx#607 mentioned "we'll need it for Windows native anyway" re. bootstrap procedure — this is that)
Goal
Native Windows
.exe/.dllbottles, produced from Linux CI runners via cross-compile, distributable through the existing.tar.xzbottle format. Tracked at pkgxdev/pkgx#607 since 2023; the runtime piece was started at pkgxdev/libpkgx#48 (merged) but stalled on the chicken-and-egg of "no Windows packages exist yet to test against".This RFC proposes a concrete first deliverable and the brewkit-side architecture, leveraging the patterns we converged on in #343 / #344 / #345.
Current state in this repo
Brewkit already has three
host().platform == 'windows'branches (build/build.ts:248, lib/porcelain/build-script.ts:47, lib/hooks/usePantry.getScript.ts:196) —lld-linksymlink,TMP/TEMPenv, path-separator normalization. ~30% of the scaffolding is there. Zero pantry recipes targetwindows/*today.Why now, why this shape
The architecture we converged on last week composes very well with Windows:
linux-sysroot: gnu.org/glibc@2.17(#343)windows-sdk: …(or implicit from a toolchain dep)gnu.org/glibcbottle (relocatable libc)vcruntime140.dll+msvcp140.dll, and that's separate from a bottlebklibcvenv stage/seal(#344)bkwinvenv stage/seal— gather transitive DLLs, movebin/*.exe → libexec/, generatebin/*.cmdwrappersbuild.skip: fix-patchelf(#345)build.skip: fix-peper-recipe escape hatchfix-elfrewriting RPATH%PATH%. Hermeticity by layout, not by metadata.bkwinvenv seal+.cmdwrappers. Same separation, gratis.The big architectural win: Windows has no PT_INTERP and no RPATH. So "default relocatable, hermeticity opt-in via wrappers" is already the platform's native behaviour — we don't have to fight it like we did with glibc.
Proposed toolchain: llvm-mingw
Three candidates, recommendation = llvm-mingw:
cl.exe.libCross-compile from Linux reuses pkgx CI infra; GitHub Windows runners are ~10× slower / cost ~10× more.
Brewkit changes (sketched)
Layer 1 — toolchain wiring
build/build.tsalready symlinkslld-linkon Windows. Extend:clang-cl,clang++-cl,link.exe → lld-link,rc.exe → llvm-rcllvm.org/mingw-w64recipe to pantryLayer 2 —
fix-pe(the analog of fix-elf, but minimal)PE doesn't have RPATH — no relocation surgery needed. What
fix-pewould actually do:+brewingfrom any embedded strings (same problem as the libc.so linker scripts in #12968).pdbdebug recordsTool: LIEF or
llvm-objcopy(already in the clang-mingw bottle).Layer 3 —
bkwinvenv stage/sealDirect port of #344's pattern, simpler in implementation:
No explicit loader invocation needed (no
$LDSO --library-path); the.cmdwrapper does an implicitSetDllDirectoryvia colocation.Layer 4 — audit step
PE-aware:
llvm-readobj --coff-imports foo.exeto list NEEDED DLLs, verify they're either shipped or system.Phased plan
Phase 0 — Validation pilot (this RFC's deliverable)
llvm.org/mingw-w64in pantryjqcross-compiled towindows/x86-64.tar.xzcontainingbin/jq.exejq.exe --versionreturns sanelyPhase 1 — Brewkit Windows-aware (~1 week of focus)
platform == 'windows'branches (audit, bottle, env)fix-pe(minimal)windows/x86-64,windows/aarch64to platform schemaPhase 2 — Pantry mass-port (~2-4 weeks)
Phase 3 —
bkwinvenv+ hermetic mode.cmdwrapper generatorPhase 4 — pkgx CLI integration (follow-through on libpkgx#48)
pkgx integrateQuestions for maintainers
vcruntime140.dllin each bottle for older targets?pkgx integrateon Windows: PowerShell profile primary, cmd.exe secondary? Or other dispatch?projects/*/package.ymlflat (withplatforms: windows/*opt-in), or isolate underprojects/windows/*?What I'd like to do next (with your sign-off)
If the framing here lands well, I'd:
llvm.org/mingw-w64recipe to pantry (cross-compile toolchain bottle)jqWindows pilot.tar.xzfor inspectionIf you want me to hold off until #12968 lands or the
bklibcvenvshape stabilizes, that's fine too — just want to put the architecture on paper so the existing Windows scaffolding doesn't keep bit-rotting.cc @mxcl (your message on pkgxdev/pkgx#607 mentioned "we'll need it for Windows native anyway" re. bootstrap procedure — this is that)