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.
370 lines
11 KiB
Go
370 lines
11 KiB
Go
package download
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
var reSDKVersion = regexp.MustCompile(`^\d+\.\d+\.\d+`)
|
|
|
|
// TriState represents a `--with-*` style flag: nil means "unset, use the
|
|
// applicable default"; a set value means the user (or a higher-level
|
|
// default) explicitly chose to include/exclude the component.
|
|
type TriState = *bool
|
|
|
|
func on() TriState { v := true; return &v }
|
|
func off() TriState { v := false; return &v }
|
|
|
|
// Options holds every flag that feeds package selection and download.
|
|
type Options struct {
|
|
Package []string
|
|
Ignore []string
|
|
Architecture []string // subset of x86,x64,arm,arm64,host
|
|
HostArch string // x86, x64 or arm64
|
|
OnlyHost bool
|
|
|
|
MSVCVersion string // "", "preview", "16.0".."18.0", "15.4".."15.9"
|
|
SDKVersion string
|
|
|
|
WithDefault TriState
|
|
WithWorkload TriState
|
|
WithMSVC TriState
|
|
WithASAN TriState
|
|
WithSDK TriState
|
|
WithATL TriState
|
|
WithDIA TriState
|
|
WithMSBuild TriState
|
|
WithDevCmd TriState
|
|
|
|
IncludeOptional bool
|
|
SkipRecommended bool
|
|
Language string
|
|
|
|
WithWDKInstallers string
|
|
}
|
|
|
|
func addIfWanted(opts *Options, flag TriState, pkg string) {
|
|
if flag == nil {
|
|
return
|
|
}
|
|
if *flag {
|
|
opts.Package = append(opts.Package, pkg)
|
|
} else {
|
|
opts.Ignore = append(opts.Ignore, pkg)
|
|
}
|
|
}
|
|
|
|
type msvcVersionEntry struct {
|
|
gen string // "15" or "16" - which package-name scheme applies
|
|
sdk string
|
|
toolVersion string
|
|
}
|
|
|
|
// msvcVersionTable maps a --msvc-version value to the SDK it pulls in by
|
|
// default and the toolset version fragment used in package ids.
|
|
var msvcVersionTable = map[string]msvcVersionEntry{
|
|
"preview": {"16", "", "Preview"},
|
|
|
|
"16.0": {"16", "10.0.17763", "14.20"},
|
|
"16.1": {"16", "10.0.18362", "14.21"},
|
|
"16.2": {"16", "10.0.18362", "14.22"},
|
|
"16.3": {"16", "10.0.18362", "14.23"},
|
|
"16.4": {"16", "10.0.18362", "14.24"},
|
|
"16.5": {"16", "10.0.18362", "14.25"},
|
|
"16.6": {"16", "10.0.18362", "14.26"},
|
|
"16.7": {"16", "10.0.18362", "14.27"},
|
|
"16.8": {"16", "10.0.18362", "14.28"},
|
|
"16.9": {"16", "10.0.19041", "14.28.16.9"},
|
|
"16.10": {"16", "10.0.19041", "14.29.16.10"},
|
|
"16.11": {"16", "10.0.19041", "14.29.16.11"},
|
|
"17.0": {"16", "10.0.19041", "14.30.17.0"},
|
|
"17.1": {"16", "10.0.19041", "14.31.17.1"},
|
|
"17.2": {"16", "10.0.19041", "14.32.17.2"},
|
|
"17.3": {"16", "10.0.19041", "14.33.17.3"},
|
|
"17.4": {"16", "10.0.22621", "14.34.17.4"},
|
|
"17.5": {"16", "10.0.22621", "14.35.17.5"},
|
|
"17.6": {"16", "10.0.22621", "14.36.17.6"},
|
|
"17.7": {"16", "10.0.22621", "14.37.17.7"},
|
|
"17.8": {"16", "10.0.22621", "14.38.17.8"},
|
|
"17.9": {"16", "10.0.22621", "14.39.17.9"},
|
|
"17.10": {"16", "10.0.22621", "14.40.17.10"},
|
|
"17.11": {"16", "10.0.22621", "14.41.17.11"},
|
|
"17.12": {"16", "10.0.22621", "14.42.17.12"},
|
|
"17.13": {"16", "10.0.22621", "14.43.17.13"},
|
|
"17.14": {"16", "10.0.26100", "14.44.17.14"},
|
|
"18.0": {"16", "10.0.26100", "14.50.18.0"},
|
|
|
|
"15.4": {"15", "10.0.16299", "14.11"},
|
|
"15.5": {"15", "10.0.16299", "14.12"},
|
|
"15.6": {"15", "10.0.16299", "14.13"},
|
|
"15.7": {"15", "10.0.17134", "14.14"},
|
|
"15.8": {"15", "10.0.17134", "14.15"},
|
|
"15.9": {"15", "10.0.17763", "14.16"},
|
|
}
|
|
|
|
func selectToolsetV16(opts *Options, idx Index, userVersion, sdk, toolVersion string, defaultPkgs, defaultIgnores []string) {
|
|
ext := ""
|
|
if toolVersion == "Preview" {
|
|
ext = ".Tools"
|
|
}
|
|
base := "Microsoft.VisualStudio.Component.VC." + toolVersion + ext
|
|
if idx.Find(base+".x86.x64", nil) != nil {
|
|
if contains(opts.Architecture, "x86") || contains(opts.Architecture, "x64") {
|
|
addIfWanted(opts, opts.WithMSVC, base+".x86.x64")
|
|
addIfWanted(opts, opts.WithASAN, "Microsoft.VC."+toolVersion+".ASAN.X86")
|
|
addIfWanted(opts, opts.WithATL, "Microsoft.VisualStudio.Component.VC."+toolVersion+".ATL")
|
|
}
|
|
if contains(opts.Architecture, "arm") {
|
|
addIfWanted(opts, opts.WithMSVC, "Microsoft.VisualStudio.Component.VC."+toolVersion+".ARM")
|
|
addIfWanted(opts, opts.WithATL, "Microsoft.VisualStudio.Component.VC."+toolVersion+".ATL.ARM")
|
|
}
|
|
if contains(opts.Architecture, "arm64") {
|
|
addIfWanted(opts, opts.WithMSVC, "Microsoft.VisualStudio.Component.VC."+toolVersion+".ARM64")
|
|
addIfWanted(opts, opts.WithATL, "Microsoft.VisualStudio.Component.VC."+toolVersion+".ATL.ARM64")
|
|
}
|
|
if opts.SDKVersion == "" {
|
|
opts.SDKVersion = sdk
|
|
}
|
|
} else {
|
|
fmt.Printf("Didn't find exact version packages for %s, assuming this is provided by the default/latest version\n", userVersion)
|
|
opts.Package = append(opts.Package, defaultPkgs...)
|
|
opts.Ignore = append(opts.Ignore, defaultIgnores...)
|
|
}
|
|
}
|
|
|
|
func selectToolsetV15(opts *Options, idx Index, userVersion, sdk, toolVersion string, defaultPkgs, defaultIgnores []string) {
|
|
id := "Microsoft.VisualStudio.Component.VC.Tools." + toolVersion
|
|
if idx.Find(id, nil) != nil {
|
|
addIfWanted(opts, opts.WithMSVC, id)
|
|
if opts.SDKVersion == "" {
|
|
opts.SDKVersion = sdk
|
|
}
|
|
} else {
|
|
fmt.Printf("Didn't find exact version packages for %s, assuming this is provided by the default/latest version\n", userVersion)
|
|
opts.Package = append(opts.Package, defaultPkgs...)
|
|
opts.Ignore = append(opts.Ignore, defaultIgnores...)
|
|
}
|
|
}
|
|
|
|
// ResolveSelection turns Options' high-level flags (--with-*,
|
|
// --msvc-version, --sdk-version, explicit packages) into the final
|
|
// opts.Package/opts.Ignore lists ready for ExpandSelection.
|
|
func ResolveSelection(opts *Options, idx Index) error {
|
|
if len(opts.Architecture) == 0 {
|
|
opts.Architecture = []string{"host", "x86", "x64", "arm", "arm64"}
|
|
}
|
|
if opts.HostArch != "" && contains(opts.Architecture, "host") {
|
|
opts.Architecture = append(opts.Architecture, opts.HostArch)
|
|
}
|
|
|
|
if opts.MSVCVersion != "" {
|
|
if opts.WithMSVC == nil {
|
|
opts.WithMSVC = on()
|
|
}
|
|
if opts.WithASAN == nil {
|
|
opts.WithASAN = on()
|
|
}
|
|
if opts.WithATL == nil {
|
|
opts.WithATL = on()
|
|
}
|
|
if opts.WithSDK == nil {
|
|
opts.WithSDK = on()
|
|
}
|
|
}
|
|
if opts.SDKVersion != "" && opts.WithSDK == nil {
|
|
opts.WithSDK = on()
|
|
}
|
|
|
|
if opts.WithDefault == nil && opts.MSVCVersion == "" && len(opts.Package) == 0 {
|
|
opts.WithDefault = on()
|
|
}
|
|
if opts.WithDefault != nil {
|
|
for _, pair := range []struct {
|
|
flag *TriState
|
|
}{
|
|
{&opts.WithWorkload}, {&opts.WithMSVC}, {&opts.WithASAN}, {&opts.WithSDK},
|
|
{&opts.WithATL}, {&opts.WithDIA}, {&opts.WithMSBuild}, {&opts.WithDevCmd},
|
|
} {
|
|
if *pair.flag == nil {
|
|
v := *opts.WithDefault
|
|
*pair.flag = &v
|
|
}
|
|
}
|
|
}
|
|
|
|
defaultPkgs, defaultIgnores := opts.Package, opts.Ignore
|
|
opts.Package, opts.Ignore = nil, nil
|
|
|
|
addIfWanted(opts, opts.WithWorkload, "Microsoft.VisualStudio.Workload.VCTools")
|
|
if contains(opts.Architecture, "x86") || contains(opts.Architecture, "x64") {
|
|
addIfWanted(opts, opts.WithMSVC, "Microsoft.VisualStudio.Component.VC.Tools.x86.x64")
|
|
addIfWanted(opts, opts.WithASAN, "Microsoft.VisualCpp.ASAN.X86")
|
|
addIfWanted(opts, opts.WithATL, "Microsoft.VisualStudio.Component.VC.ATL")
|
|
}
|
|
if contains(opts.Architecture, "arm") {
|
|
addIfWanted(opts, opts.WithMSVC, "Microsoft.VisualStudio.Component.VC.Tools.ARM")
|
|
addIfWanted(opts, opts.WithATL, "Microsoft.VisualStudio.Component.VC.ATL.ARM")
|
|
}
|
|
if contains(opts.Architecture, "arm64") {
|
|
addIfWanted(opts, opts.WithMSVC, "Microsoft.VisualStudio.Component.VC.Tools.ARM64")
|
|
addIfWanted(opts, opts.WithATL, "Microsoft.VisualStudio.Component.VC.ATL.ARM64")
|
|
}
|
|
|
|
defaultPkgs, opts.Package = opts.Package, defaultPkgs
|
|
defaultIgnores, opts.Ignore = opts.Ignore, defaultIgnores
|
|
|
|
switch {
|
|
case opts.MSVCVersion == "":
|
|
opts.Package = append(opts.Package, defaultPkgs...)
|
|
opts.Ignore = append(opts.Ignore, defaultIgnores...)
|
|
default:
|
|
entry, ok := msvcVersionTable[opts.MSVCVersion]
|
|
if !ok {
|
|
return fmt.Errorf("unsupported MSVC toolchain version %s", opts.MSVCVersion)
|
|
}
|
|
if entry.gen == "15" {
|
|
selectToolsetV15(opts, idx, opts.MSVCVersion, entry.sdk, entry.toolVersion, defaultPkgs, defaultIgnores)
|
|
} else {
|
|
selectToolsetV16(opts, idx, opts.MSVCVersion, entry.sdk, entry.toolVersion, defaultPkgs, defaultIgnores)
|
|
}
|
|
}
|
|
|
|
if err := selectSDK(opts, idx); err != nil {
|
|
return err
|
|
}
|
|
|
|
addIfWanted(opts, opts.WithDIA, "Microsoft.VisualCpp.DIA.SDK")
|
|
addIfWanted(opts, opts.WithMSBuild, "Microsoft.Build")
|
|
addIfWanted(opts, opts.WithMSBuild, "Microsoft.Build.Dependencies")
|
|
addIfWanted(opts, opts.WithDevCmd, "Microsoft.VisualStudio.VC.vcvars")
|
|
addIfWanted(opts, opts.WithDevCmd, "Microsoft.VisualStudio.PackageGroup.VsDevCmd")
|
|
|
|
if opts.WithWDKInstallers != "" {
|
|
opts.Package = append(opts.Package, "Component.Microsoft.Windows.DriverKit.BuildTools")
|
|
}
|
|
|
|
normalizeIgnoreCase(opts)
|
|
return nil
|
|
}
|
|
|
|
func selectSDK(opts *Options, idx Index) error {
|
|
switch {
|
|
case opts.WithSDK == nil:
|
|
return nil
|
|
case !*opts.WithSDK:
|
|
for key := range idx {
|
|
if strings.HasPrefix(key, "win10sdk") || strings.HasPrefix(key, "win11sdk") {
|
|
opts.Ignore = append(opts.Ignore, key)
|
|
}
|
|
}
|
|
case opts.SDKVersion == "":
|
|
saved := *opts
|
|
opts.Package = []string{"Microsoft.VisualStudio.Workload.VCTools"}
|
|
opts.IncludeOptional = false
|
|
opts.SkipRecommended = false
|
|
recommended, err := ExpandSelection(idx, opts)
|
|
*opts = saved
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, p := range recommended {
|
|
key := strings.ToLower(p.ID)
|
|
if strings.HasPrefix(key, "win10sdk") || strings.HasPrefix(key, "win11sdk") {
|
|
opts.Package = append(opts.Package, key)
|
|
}
|
|
}
|
|
default:
|
|
found := false
|
|
var versions []string
|
|
for key := range idx {
|
|
if !strings.HasPrefix(key, "win10sdk") && !strings.HasPrefix(key, "win11sdk") {
|
|
continue
|
|
}
|
|
version := key[9:]
|
|
if reSDKVersion.MatchString(version) {
|
|
versions = append(versions, version)
|
|
}
|
|
if key == key[:8]+"_"+opts.SDKVersion {
|
|
found = true
|
|
opts.Package = append(opts.Package, key)
|
|
} else {
|
|
opts.Ignore = append(opts.Ignore, key)
|
|
}
|
|
}
|
|
if !found {
|
|
return fmt.Errorf("WinSDK version %s not found (available: %s)", opts.SDKVersion, strings.Join(versions, ", "))
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func normalizeIgnoreCase(opts *Options) {
|
|
for i, s := range opts.Ignore {
|
|
opts.Ignore[i] = strings.ToLower(s)
|
|
}
|
|
}
|
|
|
|
func contains(list []string, v string) bool {
|
|
for _, s := range list {
|
|
if s == v {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// collectDependencyClosure recursively resolves target's dependency tree,
|
|
// honoring arch matching, --ignore, and Optional/Recommended filtering.
|
|
func collectDependencyClosure(idx Index, included map[string]bool, target string, constraints map[string]string, opts *Options) []*Package {
|
|
if contains(opts.Ignore, strings.ToLower(target)) {
|
|
return nil
|
|
}
|
|
p := idx.Find(target, constraints)
|
|
if p == nil {
|
|
return nil
|
|
}
|
|
if opts.OnlyHost && !HostArchCompatible(p, opts.HostArch) {
|
|
return nil
|
|
}
|
|
if !TargetArchCompatible(p, opts.Architecture) {
|
|
return nil
|
|
}
|
|
key := p.Key()
|
|
if included[key] {
|
|
return nil
|
|
}
|
|
included[key] = true
|
|
|
|
ret := []*Package{p}
|
|
for target, dep := range p.Dependencies() {
|
|
id := target
|
|
if dep.TargetID != "" {
|
|
id = dep.TargetID
|
|
}
|
|
if dep.Type == "Optional" && !opts.IncludeOptional {
|
|
continue
|
|
}
|
|
if dep.Type == "Recommended" && opts.SkipRecommended {
|
|
continue
|
|
}
|
|
c := map[string]string{}
|
|
if dep.Version != "" {
|
|
c["version"] = dep.Version
|
|
}
|
|
ret = append(ret, collectDependencyClosure(idx, included, id, c, opts)...)
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// ExpandSelection resolves opts.Package (and everything they transitively
|
|
// depend on) into a flat, deduped package list.
|
|
func ExpandSelection(idx Index, opts *Options) ([]*Package, error) {
|
|
included := map[string]bool{}
|
|
var ret []*Package
|
|
for _, id := range opts.Package {
|
|
ret = append(ret, collectDependencyClosure(idx, included, id, nil, opts)...)
|
|
}
|
|
return ret, nil
|
|
}
|