Initial implementation of msvc-go-wine

A single-binary Go tool for cross compiling with the real MSVC toolchain
on Linux via Wine. Behaves as cl/link/lib/rc/midl/mt/dumpbin/msbuild/
nmake/ml/ml64/armasm/armasm64/cmd/findstr depending on the name it's
invoked as, plus download/install/env/version management subcommands.

- download: fetches the MSVC/WinSDK installer manifest, resolves package
  selection and dependencies, downloads and verifies payloads, unpacks
  VSIX/MSI packages, and applies a handful of compatibility patches so
  VsDevCmd.bat and MSBuild's SDK detection work without a Windows
  Registry (which doesn't exist under Wine).
- install: locates the installed toolchain/SDK versions, normalizes
  header/library name casing, lays out per-architecture tool symlinks
  with an env.json config each, and compiles a small native launcher
  (toolrelay.exe) that lets mt.exe's CMake-compatibility exit code
  survive Wine's own exit-code truncation.
- The wrapper runtime rewrites absolute unix paths in tool arguments into
  Wine's z:\... form, runs the real .exe under wine, and rewrites the
  tool's output back to plain unix paths.

Verified end-to-end against a real MSVC/WinSDK download: cl, link, mt and
the resulting hello.exe all work under Wine, including through the
toolrelay.exe relay path and with paths containing non-ASCII characters.

Offline unit tests cover the wrapper's path-rewrite/output-filter logic,
install-time header lowercasing, and download package-selection/
dependency-resolution.
This commit is contained in:
Cheviiot
2026-07-25 00:57:41 +10:00
commit 6464da7847
41 changed files with 4207 additions and 0 deletions
+46
View File
@@ -0,0 +1,46 @@
// Package wineenv computes the environment (INCLUDE/LIB/WINEPATH/...) needed
// to run a Wine-hosted MSVC tool.
package wineenv
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
)
// ConfigFileName is the per-architecture config dropped next to the tool
// symlinks by `msvc-go-wine install`.
const ConfigFileName = "env.json"
// Config is the per-architecture info generated at install time.
type Config struct {
Arch string `json:"arch"` // x86, x64, arm, arm64 (target arch)
Host string `json:"host"` // x64 or arm64 (Host<Host> bin dir suffix)
DotnetHost string `json:"dotnet_host"` // amd64 or arm64 (.NET tool host dir suffix)
MSVCVer string `json:"msvc_ver"`
SDKVer string `json:"sdk_ver"`
}
// Load reads env.json from dir (the directory the running binary was
// invoked from, i.e. <dest>/bin/<arch>).
func Load(dir string) (*Config, error) {
data, err := os.ReadFile(filepath.Join(dir, ConfigFileName))
if err != nil {
return nil, fmt.Errorf("reading %s: %w", ConfigFileName, err)
}
var c Config
if err := json.Unmarshal(data, &c); err != nil {
return nil, fmt.Errorf("parsing %s: %w", ConfigFileName, err)
}
return &c, nil
}
// Save writes env.json into dir.
func (c *Config) Save(dir string) error {
data, err := json.MarshalIndent(c, "", " ")
if err != nil {
return err
}
return os.WriteFile(filepath.Join(dir, ConfigFileName), data, 0o644)
}