mirror of
https://github.com/Cheviiot/Vintner.git
synced 2026-08-03 15:57:24 +00:00
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:
@@ -0,0 +1,131 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user