Skip to content

RFC: native Windows bottles — cross-compile pilot + architecture sketch #346

Description

@tannevaled

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

  1. Cross-compile from Linux vs. native Windows runners: I'm leaning hard on cross-compile (CI cost + speed). Any blocker you can think of?
  2. MinGW vs MSVC ABI: target both (max distrib compat) or pick one (start with clang-cl/MSVC ABI)?
  3. UCRT: rely on Win10+ shipping it (no bundle), or bundle vcruntime140.dll in each bottle for older targets?
  4. pkgx integrate on Windows: PowerShell profile primary, cmd.exe secondary? Or other dispatch?
  5. 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:

  1. Open a follow-up PR to brewkit adding Layer 1 + Layer 4 scaffolding (toolchain symlinks + PE audit hook)
  2. Add an llvm.org/mingw-w64 recipe to pantry (cross-compile toolchain bottle)
  3. Open a pantry PR with a jq Windows pilot
  4. 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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions