mirror of
https://github.com/Cheviiot/Vintner.git
synced 2026-08-03 15:57:24 +00:00
Found via manual audit plus a staticcheck run: - collectDependencyClosure iterated a package's dependencies map directly, so which package "won" a same-key collision (and the order things got downloaded/unpacked in) could vary between runs of the exact same download command. Sort the dependency targets first, matching what --print-deps-tree's tree-printer already did. Verified two consecutive --print-deps-tree runs now produce byte-identical output. - HTTP retry loops (manifest fetch, payload download) retried immediately with no backoff, which just hammers a server harder during exactly the kind of transient failure retries exist for. Added a capped exponential backoff (1s/2s/4s/8s/10s). - --architecture/--host-arch accepted any string silently; a typo'd value matched nothing during package selection and surfaced as a confusing downstream failure far from the actual mistake. Now rejected up front with a clear error. - pumpLines' bufio.Scanner silently stops (dropping the rest of a tool's output) if a single line ever exceeds its buffer - narrow but real for pathological cases like heavily templated C++ diagnostics. Now at least reports that truncation happened instead of losing output with no trace. - Removed select.go's unused off() helper (staticcheck U1000). Re-verified end-to-end after these changes: a real KMDF driver build and a plain cl/link build both still succeed.
457 lines
14 KiB
Go
457 lines
14 KiB
Go
package download
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"regexp"
|
|
"sort"
|
|
"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 }
|
|
|
|
// 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
|
|
|
|
// WithWDK selects the PlatformToolset registration
|
|
// (Component.Microsoft.Windows.DriverKit.BuildTools) that lets MSBuild
|
|
// recognize WindowsKernelModeDriver10.0/WindowsUserModeDriver10.0
|
|
// PlatformToolsets. It's not part of the manifest-driven default
|
|
// selection - unlike everything else it depends on, it's opt-in via
|
|
// --with-wdk, since the actual driver headers/libs come from a separate
|
|
// NuGet package download handled outside ExpandSelection entirely (see
|
|
// wdk.go and cmd/vintner's runDownload).
|
|
WithWDK bool
|
|
}
|
|
|
|
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.WithWDK {
|
|
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 {
|
|
sort.Strings(versions)
|
|
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}
|
|
deps := p.Dependencies()
|
|
targets := make([]string, 0, len(deps))
|
|
for target := range deps {
|
|
targets = append(targets, target)
|
|
}
|
|
sort.Strings(targets)
|
|
for _, target := range targets {
|
|
dep := deps[target]
|
|
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
|
|
}
|
|
|
|
// PrintDependencyTree writes an indented tree of opts.Package and everything
|
|
// they transitively depend on to w, applying the exact same
|
|
// arch/--ignore/Optional/Recommended filtering collectDependencyClosure
|
|
// (used by ExpandSelection) does, so what's printed matches what an actual
|
|
// download would select. A package already printed once elsewhere in the
|
|
// tree is shown again as a leaf ("(see above)") rather than re-expanded, to
|
|
// keep the output finite for packages multiple components depend on.
|
|
func PrintDependencyTree(w io.Writer, idx Index, opts *Options) {
|
|
printed := map[string]bool{}
|
|
for _, id := range opts.Package {
|
|
printDepNode(w, idx, id, nil, "", opts, 0, printed)
|
|
}
|
|
}
|
|
|
|
func printDepNode(w io.Writer, idx Index, target string, constraints map[string]string, depType string, opts *Options, depth int, printed map[string]bool) {
|
|
if contains(opts.Ignore, strings.ToLower(target)) {
|
|
return
|
|
}
|
|
indent := strings.Repeat(" ", depth)
|
|
annotation := ""
|
|
if depType != "" {
|
|
annotation = " [" + depType + "]"
|
|
}
|
|
|
|
p := idx.Find(target, constraints)
|
|
if p == nil {
|
|
fmt.Fprintf(w, "%s%s (not found)%s\n", indent, target, annotation)
|
|
return
|
|
}
|
|
if opts.OnlyHost && !HostArchCompatible(p, opts.HostArch) {
|
|
return
|
|
}
|
|
if !TargetArchCompatible(p, opts.Architecture) {
|
|
return
|
|
}
|
|
|
|
key := p.Key()
|
|
if printed[key] {
|
|
fmt.Fprintf(w, "%s%s%s (see above)\n", indent, p.ID, annotation)
|
|
return
|
|
}
|
|
printed[key] = true
|
|
fmt.Fprintf(w, "%s%s@%s%s\n", indent, p.ID, p.Version, annotation)
|
|
|
|
deps := p.Dependencies()
|
|
targets := make([]string, 0, len(deps))
|
|
for t := range deps {
|
|
targets = append(targets, t)
|
|
}
|
|
sort.Strings(targets)
|
|
for _, depTarget := range targets {
|
|
dep := deps[depTarget]
|
|
id := depTarget
|
|
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
|
|
}
|
|
printDepNode(w, idx, id, c, dep.Type, opts, depth+1, printed)
|
|
}
|
|
}
|