Cheviiot d11b534fa1 Stability pass: deterministic dependency order, retry backoff, input validation
Found via manual audit plus a staticcheck run:

- collectDependencyClosure iterated a package's dependencies map
  directly, so which package "won" a same-key collision (and the
  order things got downloaded/unpacked in) could vary between runs
  of the exact same download command. Sort the dependency targets
  first, matching what --print-deps-tree's tree-printer already did.
  Verified two consecutive --print-deps-tree runs now produce
  byte-identical output.
- HTTP retry loops (manifest fetch, payload download) retried
  immediately with no backoff, which just hammers a server harder
  during exactly the kind of transient failure retries exist for.
  Added a capped exponential backoff (1s/2s/4s/8s/10s).
- --architecture/--host-arch accepted any string silently; a typo'd
  value matched nothing during package selection and surfaced as a
  confusing downstream failure far from the actual mistake. Now
  rejected up front with a clear error.
- pumpLines' bufio.Scanner silently stops (dropping the rest of a
  tool's output) if a single line ever exceeds its buffer - narrow but
  real for pathological cases like heavily templated C++ diagnostics.
  Now at least reports that truncation happened instead of losing
  output with no trace.
- Removed select.go's unused off() helper (staticcheck U1000).

Re-verified end-to-end after these changes: a real KMDF driver build
and a plain cl/link build both still succeed.
2026-07-25 04:14:34 +10:00
2026-07-25 03:55:22 +10:00
2026-07-25 03:55:22 +10:00
2026-07-25 03:55:22 +10:00
2026-07-25 03:55:22 +10:00
2026-07-25 03:55:22 +10:00

vintner

Cross compile with MSVC on Linux, using Wine — a single-binary Go tool inspired by mstorsjo/msvc-wine's approach (download the real MSVC/WinSDK, wrap the compiler under Wine), implemented independently.

Once installed, you invoke the real Microsoft toolchain exactly like on Windows: cl, link, lib, rc, midl, mc, mt, dumpbin, msbuild, nmake, ml, ml64, armasm, armasm64, plus trivial cmd/findstr shims, all just work from your PATH.

How it works

vintner is one Go binary that behaves differently depending on the name it's invoked as (a "multi-call binary", like busybox):

  • Invoked as cl, link, lib, ... → it loads a small per-architecture env.json, builds the INCLUDE/LIB/WINEPATH environment Wine needs, rewrites absolute unix paths in the arguments into Wine's z:\... form (working around a Wine/cl.exe include-path bug), runs the real .exe under wine/wine64, and rewrites the tool's output back from z:\... paths to plain unix paths so your build system's error parsing keeps working.
  • Invoked as vintner → it exposes the download, install, env and version management subcommands described below (each also has a short alias: dl, i, e, v; help/h prints usage).

Quick start

# 1. Download and unpack MSVC + Windows SDK into ~/.vintner (requires
#    accepting Microsoft's Visual Studio Build Tools license, and msitools
#    for unpacking .msi payloads). Pass --dest <dir> for a different location.
vintner download --accept-license

# 2. Wire up the tool wrappers
vintner install

# 3. Add the toolchain to PATH and build
export PATH=~/.vintner/bin/x64:$PATH
cl /nologo /EHsc hello.cpp

Prerequisites

  • wine (or wine64) — runs the real cl.exe/link.exe/etc.
  • msitools (msiextract) — unpacks the .msi payloads MSVC/WinSDK ship as.
  • git — used to apply the small compatibility patches bundled with download (see Compatibility patches below).

On ALT Linux:

pkcon install wine msitools

Commands

vintner download (dl) --accept-license [--dest <dir>] [options]   fetch and unpack MSVC/WinSDK/WDK
vintner install (i) [dir]                                         wire up wrappers for a downloaded MSVC
vintner env (e) --bin <dir>/bin/<arch>                            print INCLUDE/LIB for native clang-cl/lld-link use
vintner version (v)                                               print the version
vintner help (h)                                                  print usage

--dest/[dir] both default to ~/.vintner when omitted.

download's main options: --msvc-version, --sdk-version, --architecture, --host-arch, --only-host, --with-wdk (also fetch the Windows Driver Kit, for building KMDF/UMDF drivers), --ignore, --only-download, --only-unpack, --keep-unpack, --skip-patch, --cache, --language, --include-optional, --skip-recommended, --major, --preview, --manifest, --list-workloads, --list-components, --print-deps-tree. Run vintner download -h for the full list with descriptions.

--list-workloads/--list-components print every workload/component id (with its human-readable title) available in the fetched manifest and exit without downloading anything - useful for discovering what to pass as a bare package id or via --with-*. --print-deps-tree prints the dependency tree of whatever would actually be selected (honoring every other flag), also without downloading.

Building drivers (WDK)

vintner download --with-wdk additionally fetches the Windows Driver Kit (headers, import libs, and the MSBuild WindowsKernelModeDriver10.0/ WindowsUserModeDriver10.0 PlatformToolsets) so msbuild can build real KMDF/UMDF drivers - compiling, linking, INF stamping and the Inf2Cat signability check (with SignMode=off) all work under Wine. Verified end-to-end against a real sample driver from microsoft/Windows-driver-samples. Only x64 and arm64 targets have a WDK package upstream (no x86/arm).

Language

CLI messages (usage text, progress lines, prompts) are in English by default. Set VINTNER_LANG=ru (or have a ru-prefixed LC_ALL/ LC_MESSAGES/LANG, e.g. ru_RU.UTF-8) for Russian. Deeper error text bubbled up from internal packages stays in English.

You don't need Wine at all if you drive the (nonredistributable) MSVC/WinSDK headers and libraries with Clang/LLD in MSVC-compatible mode:

eval "$(vintner env --bin ~/.vintner/bin/x64)"
clang-cl -c hello.c
lld-link hello.obj -out:hello.exe

Building from source

go build -o vintner ./cmd/vintner

Go 1.23+ is all you need to build it; wine/msitools are only needed at run time (install/tool invocation and download respectively).

toolrelay.exe

install compiles assets/vendor/toolrelay.cpp (a small native Windows launcher, original to this project) with the freshly-installed host-arch cl.exe (best-effort: if wine isn't present yet, or the compile fails, install still succeeds and the wrapper runtime just falls back to invoking tools directly through wine). When present, every non-MSBuild tool invocation is routed through it via two named FIFOs. This is what lets mt.exe's CMake-compatibility exit-code translation (0x410200010xbb) survive Wine's own exit-code truncation: only a native Windows process observing the untranslated code via GetExitCodeProcess() can catch it before Wine marshals the process exit back to Unix and drops everything but the low byte.

Compatibility patches

download applies a handful of small patches (assets/patches) to the downloaded MSVC/WinSDK tree - independently written for this project - that make VsDevCmd.bat and MSBuild's SDK-detection props work without a Windows Registry (which doesn't exist under Wine): they check the SDK directly under the VS install root instead of querying the registry, skip telemetry, and don't hard-fail devcmd setup when an optional component (ConnectionManagerExe, bundled CMake/Ninja) wasn't downloaded.

Known gaps

None currently tracked. Download/select/unpack/install, general MSBuild projects, WDK driver builds, dependency-tree printing, and workload/component listing are all implemented and verified against real projects.

License

MIT, see LICENSE.txt - covers vintner's own source only. The MSVC Build Tools / Windows SDK / WDK that download fetches remain governed by Microsoft's own license (accepted via --accept-license), same as with any other way of obtaining them.

S
Description
Cross compile with MSVC on Linux via Wine
Readme
199 KiB
2026-07-25 08:18:23 +00:00
Languages
Go 100%