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.
171 lines
3.8 KiB
Go
171 lines
3.8 KiB
Go
package download
|
|
|
|
import (
|
|
"regexp"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// Index maps a lowercased package id to every variant sharing that id,
|
|
// sorted by arch/language priority (best match first).
|
|
type Index map[string][]*Package
|
|
|
|
// BuildIndex indexes every package in m, prioritizing variants matching
|
|
// hostArch and language ("" language means no language preference beyond
|
|
// the default "en").
|
|
func BuildIndex(m *Manifest, hostArch, language string) Index {
|
|
if language == "" {
|
|
language = "en"
|
|
}
|
|
idx := Index{}
|
|
for i := range m.Packages {
|
|
p := &m.Packages[i]
|
|
key := strings.ToLower(p.ID)
|
|
idx[key] = append(idx[key], p)
|
|
}
|
|
for key, list := range idx {
|
|
list := list
|
|
sort.SliceStable(list, func(i, j int) bool {
|
|
return comparePackages(hostArch, language, list[i], list[j]) < 0
|
|
})
|
|
idx[key] = list
|
|
}
|
|
return idx
|
|
}
|
|
|
|
// comparePackages ranks arch match first, then language match.
|
|
func comparePackages(arch, language string, a, b *Package) int {
|
|
archOrd := func(field string, x *Package) int {
|
|
if arch == "" {
|
|
return 0
|
|
}
|
|
var v string
|
|
switch field {
|
|
case "chip":
|
|
v = x.Chip
|
|
case "machineArch":
|
|
v = x.MachineArch
|
|
case "productArch":
|
|
v = x.ProductArch
|
|
}
|
|
v = strings.ToLower(v)
|
|
if v == "" || v == "neutral" {
|
|
return 0
|
|
}
|
|
if v == arch {
|
|
return -1
|
|
}
|
|
return 1
|
|
}
|
|
for _, field := range []string{"chip", "machineArch", "productArch"} {
|
|
if r := archOrd(field, a) - archOrd(field, b); r != 0 {
|
|
return r
|
|
}
|
|
}
|
|
|
|
lang := strings.ToLower(language)
|
|
countryLang := strings.Contains(lang, "-")
|
|
langPriority := func(x *Package) int {
|
|
xl := strings.ToLower(x.Language)
|
|
if xl == "" {
|
|
return 0
|
|
}
|
|
if (countryLang && xl == lang) || (!countryLang && strings.HasPrefix(xl, lang+"-")) {
|
|
return 2
|
|
}
|
|
if strings.HasPrefix(xl, "en-") {
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
ap, bp := langPriority(a), langPriority(b)
|
|
if ap > bp {
|
|
return -1
|
|
}
|
|
if ap < bp {
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// Find looks up id (case-insensitive), preferring a candidate matching
|
|
// constraints' chip/machineArch, falling back to the highest-priority
|
|
// variant. Returns nil if id isn't in the index at all.
|
|
func (idx Index) Find(id string, constraints map[string]string) *Package {
|
|
candidates, ok := idx[strings.ToLower(id)]
|
|
if !ok || len(candidates) == 0 {
|
|
return nil
|
|
}
|
|
for _, p := range candidates {
|
|
matched := true
|
|
for _, k := range []string{"chip", "machineArch"} {
|
|
want, has := constraints[k]
|
|
if !has {
|
|
continue
|
|
}
|
|
var got string
|
|
if k == "chip" {
|
|
got = p.Chip
|
|
} else {
|
|
got = p.MachineArch
|
|
}
|
|
if !strings.EqualFold(got, want) {
|
|
matched = false
|
|
break
|
|
}
|
|
}
|
|
if matched {
|
|
return p
|
|
}
|
|
}
|
|
return candidates[0]
|
|
}
|
|
|
|
var knownHostArchs = []string{"x86", "x64", "arm64"}
|
|
|
|
// HostArchCompatible reports whether p can run on host: some packages
|
|
// encode their host arch in the id itself (e.g.
|
|
// Microsoft.VisualCpp.Tools.HostARM64.*).
|
|
func HostArchCompatible(p *Package, host string) bool {
|
|
if host == "" {
|
|
return true
|
|
}
|
|
id := strings.ToLower(p.ID)
|
|
for _, a := range knownHostArchs {
|
|
if strings.Contains(id, "host"+a) {
|
|
return a == host
|
|
}
|
|
}
|
|
for _, v := range []string{p.Chip, p.MachineArch, p.ProductArch} {
|
|
v = strings.ToLower(v)
|
|
if v == "" || v == "neutral" {
|
|
continue
|
|
}
|
|
if v != host {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
var reTargetArch = regexp.MustCompile(`\.target(x86|x64|arm64|arm)(\W|$)`)
|
|
|
|
// TargetArchCompatible reports whether p is wanted for archs: packages
|
|
// naming a target arch in their id (e.g. ...HostX64.TargetX64) are only
|
|
// wanted when that arch was requested.
|
|
func TargetArchCompatible(p *Package, archs []string) bool {
|
|
if archs == nil {
|
|
return true
|
|
}
|
|
m := reTargetArch.FindStringSubmatch(strings.ToLower(p.ID))
|
|
if m == nil {
|
|
return true
|
|
}
|
|
for _, a := range archs {
|
|
if a == m[1] {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|