mirror of
https://github.com/Cheviiot/Vintner.git
synced 2026-08-03 15:57:24 +00:00
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.
132 lines
3.4 KiB
Go
132 lines
3.4 KiB
Go
package install
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// LowercaseOptions controls how Lowercase renames entries.
|
|
type LowercaseOptions struct {
|
|
// Symlink adds lowercase-named symlinks alongside the original entries
|
|
// instead of renaming them in place (used for WinSDK/MSVC headers so
|
|
// both casings stay available; renaming would break other packages
|
|
// that reference the original casing).
|
|
Symlink bool
|
|
// MapWinSDK is shorthand for path-keyed overrides that keep the "GL"
|
|
// header directory's canonical uppercase spelling.
|
|
MapWinSDK bool
|
|
}
|
|
|
|
// Lowercase recursively lowercases every file/dir name under root, merging
|
|
// into an existing same-named lowercase directory on collision.
|
|
func Lowercase(root string, opts LowercaseOptions) error {
|
|
mapPaths := map[string]string{}
|
|
if opts.MapWinSDK {
|
|
mapPaths["gl"] = "GL"
|
|
}
|
|
|
|
remap := func(relPath string) string {
|
|
rp := strings.TrimSuffix(relPath, "/")
|
|
base := rp
|
|
if idx := strings.LastIndexByte(rp, '/'); idx >= 0 {
|
|
base = rp[idx+1:]
|
|
}
|
|
if opts.MapWinSDK {
|
|
if v, ok := mapPaths[strings.ToLower(rp)]; ok {
|
|
return v
|
|
}
|
|
}
|
|
return strings.ToLower(base)
|
|
}
|
|
|
|
var doDir func(dir, relPath string) error
|
|
doDir = func(dir, relPath string) error {
|
|
entries, err := os.ReadDir(dir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, e := range entries {
|
|
name := e.Name()
|
|
childPath := filepath.Join(dir, name)
|
|
if e.IsDir() {
|
|
if err := doDir(childPath, relPath+name+"/"); err != nil {
|
|
return err
|
|
}
|
|
continue
|
|
}
|
|
newName := remap(relPath + name)
|
|
if newName != name {
|
|
if err := renameOrSymlink(childPath, dir, newName, opts.Symlink); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
var newName string
|
|
if relPath == "" {
|
|
newName = strings.ToLower(filepath.Base(dir))
|
|
} else {
|
|
newName = remap(relPath)
|
|
}
|
|
oldName := filepath.Base(dir)
|
|
if oldName == newName {
|
|
return nil
|
|
}
|
|
parent := filepath.Dir(dir)
|
|
newPath := filepath.Join(parent, newName)
|
|
if fi, err := os.Stat(newPath); err == nil && fi.IsDir() {
|
|
return combineIntoDir(dir, newPath, opts.Symlink)
|
|
}
|
|
return renameOrSymlink(dir, parent, newName, opts.Symlink)
|
|
}
|
|
|
|
return doDir(root, "")
|
|
}
|
|
|
|
func renameOrSymlink(src, destDir, destName string, symlink bool) error {
|
|
dest := filepath.Join(destDir, destName)
|
|
if symlink {
|
|
if _, err := os.Lstat(dest); err == nil {
|
|
// A conflicting entry already exists at dest - this happens on
|
|
// case-insensitive filesystems where dest and src are the same
|
|
// path, so treat it as already done rather than failing.
|
|
return nil
|
|
}
|
|
rel, err := filepath.Rel(destDir, src)
|
|
if err != nil {
|
|
rel = src
|
|
}
|
|
return os.Symlink(rel, dest)
|
|
}
|
|
return os.Rename(src, dest)
|
|
}
|
|
|
|
// combineIntoDir moves (or symlinks) every entry of src into the already-existing
|
|
// dest, recursing into same-named subdirectories, then removes src (unless
|
|
// symlink mode, where src's real content must be left in place).
|
|
func combineIntoDir(src, dest string, symlink bool) error {
|
|
entries, err := os.ReadDir(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, e := range entries {
|
|
name := e.Name()
|
|
srcChild := filepath.Join(src, name)
|
|
destChild := filepath.Join(dest, name)
|
|
if fi, err := os.Stat(destChild); err == nil && fi.IsDir() && e.IsDir() {
|
|
if err := combineIntoDir(srcChild, destChild, symlink); err != nil {
|
|
return err
|
|
}
|
|
continue
|
|
}
|
|
if err := renameOrSymlink(srcChild, dest, name, symlink); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if !symlink {
|
|
return os.Remove(src)
|
|
}
|
|
return nil
|
|
}
|