diff --git a/cmd/vintner/download.go b/cmd/vintner/download.go index 57ec428..0985d10 100644 --- a/cmd/vintner/download.go +++ b/cmd/vintner/download.go @@ -45,6 +45,17 @@ func runDownload(args []string) int { } packages := fs.Args() + for _, a := range archsFlag { + if !validArchitectures[a] { + fmt.Fprintf(os.Stderr, "vintner download: invalid --architecture %q (expected one of x86, x64, arm, arm64, host)\n", a) + return 2 + } + } + if *hostArch != "" && !validHostArchs[*hostArch] { + fmt.Fprintf(os.Stderr, "vintner download: invalid --host-arch %q (expected one of x86, x64, arm64)\n", *hostArch) + return 2 + } + opts := &download.Options{ Package: packages, Ignore: []string(ignoreFlag), @@ -267,6 +278,9 @@ func printPackageList(headerKey string, pkgs []*download.Package, language strin } } +var validArchitectures = map[string]bool{"x86": true, "x64": true, "arm": true, "arm64": true, "host": true} +var validHostArchs = map[string]bool{"x86": true, "x64": true, "arm64": true} + func detectHostArch() string { if runtime.GOARCH == "arm64" { return "arm64" diff --git a/internal/download/fetch.go b/internal/download/fetch.go index 34c072f..ec8b127 100644 --- a/internal/download/fetch.go +++ b/internal/download/fetch.go @@ -84,6 +84,9 @@ func FetchPayloads(selected []*Package, cacheDir string, allowHashMismatch bool) func fetchOnePayloadWithRetries(payload Payload, dest, fileID string, allowHashMismatch bool) (int64, error) { var lastErr error for attempt := 0; attempt < maxDownloadAttempts; attempt++ { + if attempt > 0 { + time.Sleep(retryBackoff(attempt)) + } n, err := tryDownloadPayload(payload, dest, fileID, allowHashMismatch) if err == nil { return n, nil @@ -94,6 +97,17 @@ func fetchOnePayloadWithRetries(payload Payload, dest, fileID string, allowHashM return 0, fmt.Errorf("giving up on %s after %d attempts: %w", fileID, maxDownloadAttempts, lastErr) } +// retryBackoff gives a transient failure (network blip, momentary rate +// limiting) a little room to clear before hammering the same URL again: +// 1s, 2s, 4s, 8s, capped at 10s. +func retryBackoff(attempt int) time.Duration { + d := time.Second << uint(attempt-1) + if d > 10*time.Second { + d = 10 * time.Second + } + return d +} + func tryDownloadPayload(payload Payload, dest, fileID string, allowHashMismatch bool) (int64, error) { if fi, err := os.Stat(dest); err == nil && fi.Mode().IsRegular() { if payload.SHA256 != "" { diff --git a/internal/download/manifest.go b/internal/download/manifest.go index 9858d38..9027b4b 100644 --- a/internal/download/manifest.go +++ b/internal/download/manifest.go @@ -198,6 +198,9 @@ const maxManifestAttempts = 5 func httpGet(url string) ([]byte, error) { var lastErr error for attempt := 0; attempt < maxManifestAttempts; attempt++ { + if attempt > 0 { + time.Sleep(retryBackoff(attempt)) + } data, err := tryHTTPGet(url) if err == nil { return data, nil diff --git a/internal/download/select.go b/internal/download/select.go index 2f75435..a4a63f4 100644 --- a/internal/download/select.go +++ b/internal/download/select.go @@ -15,8 +15,7 @@ var reSDKVersion = regexp.MustCompile(`^\d+\.\d+\.\d+`) // 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 } +func on() TriState { v := true; return &v } // Options holds every flag that feeds package selection and download. type Options struct { @@ -303,6 +302,7 @@ func selectSDK(opts *Options, idx Index) error { } } if !found { + sort.Strings(versions) return fmt.Errorf("WinSDK version %s not found (available: %s)", opts.SDKVersion, strings.Join(versions, ", ")) } } @@ -347,7 +347,14 @@ func collectDependencyClosure(idx Index, included map[string]bool, target string included[key] = true ret := []*Package{p} - for target, dep := range p.Dependencies() { + 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 diff --git a/internal/wrapper/run.go b/internal/wrapper/run.go index aa82871..33f40fa 100644 --- a/internal/wrapper/run.go +++ b/internal/wrapper/run.go @@ -310,4 +310,11 @@ func pumpLines(r io.Reader, w *os.File, filter lineFilter) { } fmt.Fprintln(w, line) } + // bufio.Scanner silently stops (dropping the rest of the stream) once a + // single line exceeds its 16MB buffer - surface that rather than letting + // build output vanish without explanation (heavily templated C++ error + // messages are the realistic way to hit this). + if err := scanner.Err(); err != nil { + fmt.Fprintf(w, "vintner: output truncated: %v\n", err) + } }