Cheviiot 25f751e874 Build old (pre-v145) PlatformToolset-pinned .vcxproj files
vintner only ever downloads one compiler generation, but real-world
.vcxproj files are pinned to whichever PlatformToolset they were last
saved under - v142 (VS2019) for anything not actively maintained is
extremely common. MSBuild checks toolset "installed-ness" (MSB8020) by
testing whether MSBuild/Microsoft/VC/v<schema>/Platforms/<arch>/
PlatformToolsets/<toolset>/ exists on disk - a plain file lookup our
downloaded MSBuild package only satisfies for the exact generation it
shipped. `install` now symlinks every historical numeric PlatformToolset
name (v90 through v143) onto whichever real toolset directory is
actually present, so any of them resolves transparently; Toolset.props/
.targets don't hardcode a version number, so aliasing is correct, not
just a workaround.

Three more MSBuild property/environment issues came with it, all found
building a real years-old project against the one modern toolchain
vintner installs:

- VCInstallDir_<N>/VCToolsInstallDir_<N> needed a third numbering
  source (PlatformToolset short names from Microsoft.VCToolsVersion.
  v<N>.default.props) alongside the existing MSBuild schema-version and
  known-toolset lists, so the env-var-driven half of toolset resolution
  covers the same names the on-disk alias does.
- VCToolsVersion must be a real version string: left unset, it falls
  back to a literal placeholder that then hits an unconditional
  version-string comparison elsewhere in Microsoft.CppBuild.targets
  (MSB4184). Setting it to the real installed version in turn requires
  CheckMSVCComponents=false, since CheckVCToolsetVersion (MSB8052)
  otherwise rejects an aliased PlatformToolset whenever its numeric
  generation doesn't match VCToolsVersion's - exactly the case aliasing
  creates on purpose. Everything else CheckMSVCComponents gates is
  diagnostic-only (MFC/ATL/Spectre presence warnings), so disabling it
  costs nothing else.
- WindowsTargetPlatformVersion needed to become an explicit /p: global
  property on the msbuild command line, not just an env var: legacy
  .vcxproj files commonly hardcode this in a PropertyGroup, and an
  explicit project assignment always wins over an inherited environment
  variable of the same name. A command-line global property is the one
  thing a project file can't override. Only injected when the caller
  hasn't already pinned it themselves.
2026-07-25 15:41:09 +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

CI Release License: MIT

vintner cross-compiles with the real MSVC toolchain on Linux, using Wine. One Go binary drops in as cl, link, lib, rc, midl, mc, mt, dumpbin, msbuild, nmake, ml, ml64, armasm, armasm64, plus cmd/findstr shims, so once installed you invoke the real Microsoft tools exactly like on Windows. It handles full MSBuild projects, with --with-wdk real KMDF/UMDF Windows drivers, and with --with-dxsdk the real D3DX9 headers/libs.

Inspired by mstorsjo/msvc-wine's approach: download the real MSVC/WinSDK, wrap the compiler under Wine.

Contents

How it works

vintner is a multi-call binary, like busybox: it behaves differently depending on the name it's invoked as.

  • As cl, link, lib, and the rest: it loads a per-architecture env.json, sets INCLUDE/LIB/WINEPATH, and rewrites absolute Unix paths in the arguments to Wine's z:\... form (Wine and cl.exe otherwise mishandle relative includes — see winehq bug 55200). It then runs the real .exe under wine/wine64, and rewrites z:\... paths back to Unix paths in the output, so your build system's error parsing keeps working.
  • As vintner: it exposes the download, install, env, version and completion subcommands below (short aliases: dl, i, e, v; help/h prints usage).

Installation

On ALT Linux, via Nivora:

stplr install nivora/vintner

Prebuilt binary, from the latest release:

curl -fLo vintner "https://github.com/Cheviiot/vintner/releases/latest/download/vintner-linux-$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')"
chmod +x vintner
sudo install vintner /usr/local/bin/vintner

From source: see Building from source.

Either way, wine/wine64, msitools (for msiextract) and git need to be on PATH at run time. Nivora installs pull these in automatically as package dependencies.

Prerequisites

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

On ALT Linux:

pkcon install wine msitools git

Quick start

# 1. Download and unpack MSVC + Windows SDK into ~/.vintner (accepts
#    Microsoft's Visual Studio Build Tools license). 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

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
vintner completion bash|zsh                                       print a shell completion script

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

download's main options: --msvc-version, --sdk-version, --architecture (repeatable: x86/x64/arm/arm64/host), --host-arch, --only-host, --with-wdk (see below), --with-dxsdk (see below), --ignore (repeatable), --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 and its human-readable title from the fetched manifest, then exit without downloading anything. Useful for finding what to pass as a bare package id or through --with-*. --print-deps-tree prints the dependency tree of whatever would actually be selected — honoring every other flag — without downloading anything.

Building drivers (WDK)

--with-wdk also fetches the Windows Driver Kit: headers, import libs, and the MSBuild WindowsKernelModeDriver10.0/WindowsUserModeDriver10.0 PlatformToolsets.

vintner download --accept-license --with-wdk

With it, msbuild builds real KMDF/UMDF drivers — compiling, linking, INF stamping, and the Inf2Cat signability check (SignMode=off) all work under Wine. Tested against a real sample driver from microsoft/Windows-driver-samples. Only x64 and arm64 targets have a WDK package upstream; there's no x86 or arm one.

Building against D3DX9 (DirectX SDK)

--with-dxsdk fetches the DirectX SDK (June 2010) — the last standalone release of D3DX9/10/11, XInput and XAudio2, dropped from the Windows SDK entirely once D3DX was deprecated. It unpacks the real headers and x86/x64 import libs (d3dx9.h/d3dx9.lib included) to <dest>/DXSDK.

vintner download --accept-license --with-dxsdk

Point your project's IncludePath/LibraryPath at <dest>/DXSDK/Include and <dest>/DXSDK/Lib/x86 or <dest>/DXSDK/Lib/x64. Requires cabextract on PATH (the installer is a self-extracting CAB archive).

Language

CLI text (usage, progress lines, prompts) defaults to English. Set VINTNER_LANG=ru (or a ru-prefixed LC_ALL/LC_MESSAGES/LANG, e.g. ru_RU.UTF-8) for Russian:

VINTNER_LANG=ru vintner help

Error text from internal packages stays in English regardless.

Shell completion

Already set up if you installed via Nivora. Otherwise:

source <(vintner completion bash)   # or add to ~/.bashrc
source <(vintner completion zsh)    # or add to ~/.zshrc

Completes subcommands, including the short aliases, download's flags, and directory arguments for install/env --bin.

The MSVC/WinSDK headers and libraries work directly with Clang/LLD in MSVC-compatible mode. No Wine needed:

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

toolrelay.exe

install compiles assets/vendor/toolrelay.cpp, a small native Windows launcher, with the freshly-installed host-arch cl.exe. This is best-effort: if wine isn't available yet, or the compile fails, install still succeeds, and tool invocations just skip it. When present, every non-MSBuild tool call is routed through it via two named FIFOs.

That's what lets mt.exe's CMake-compatibility exit code (0x410200010xbb) survive Wine's own exit-code truncation: a native Windows process can read the real 32-bit exit code via GetExitCodeProcess() before Wine collapses it to a single byte on the way back to Unix.

Compatibility patches

download applies a few small patches (assets/patches) to the downloaded MSVC/WinSDK tree, so VsDevCmd.bat and MSBuild's SDK-detection props work without a Windows Registry, which doesn't exist under Wine. They look up the SDK directly under the VS install root instead of querying the registry, skip telemetry, and don't fail devcmd setup when an optional component (ConnectionManagerExe, bundled CMake/Ninja) is missing.

Building from source

go build -o vintner ./cmd/vintner
go vet ./...
go test ./...

Go 1.23+ builds it. wine/msitools are only needed at run time, for install/tool invocation and download respectively.

License

MIT (see LICENSE.txt) for vintner's own source. The MSVC Build Tools, Windows SDK, and WDK that download fetches stay under 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%