// Copyright 2020 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package modget import ( "context" "errors" "cmd/go/internal/base" "cmd/go/internal/modload" "cmd/go/internal/mvs" "golang.org/x/mod/module" ) // An upgrader adapts an underlying mvs.Reqs to apply an // upgrade policy to a list of targets and their dependencies. type upgrader struct { mvs.Reqs // cmdline maps a module path to a query made for that module at a // specific target version. Each query corresponds to a module // matched by a command line argument. cmdline map[string]*query // upgrade is a set of modules providing dependencies of packages // matched by command line arguments. If -u or -u=patch is set, // these modules are upgraded accordingly. upgrade map[string]bool } // newUpgrader creates an upgrader. cmdline contains queries made at // specific versions for modules matched by command line arguments. pkgs // is the set of packages matched by command line arguments. If -u or -u=patch // is set, modules providing dependencies of pkgs are upgraded accordingly. func newUpgrader(cmdline map[string]*query, pkgs map[string]bool) *upgrader { u := &upgrader{ Reqs: modload.Reqs(), cmdline: cmdline, } if getU != "" { u.upgrade = make(map[string]bool) // Traverse package import graph. // Initialize work queue with root packages. seen := make(map[string]bool) var work []string add := func(path string) { if !seen[path] { seen[path] = true work = append(work, path) } } for pkg := range pkgs { add(pkg) } for len(work) > 0 { pkg := work[0] work = work[1:] m := modload.PackageModule(pkg) u.upgrade[m.Path] = true // testImports is empty unless test imports were actually loaded, // i.e., -t was set or "all" was one of the arguments. imports, testImports := modload.PackageImports(pkg) for _, imp := range imports { add(imp) } for _, imp := range testImports { add(imp) } } } return u } // Required returns the requirement list for m. // For the main module, we override requirements with the modules named // one the command line, and we include new requirements. Otherwise, // we defer to u.Reqs. func (u *upgrader) Required(m module.Version) ([]module.Version, error) { rs, err := u.Reqs.Required(m) if err != nil { return nil, err } if m != modload.Target { return rs, nil } overridden := make(map[string]bool) for i, m := range rs { if q := u.cmdline[m.Path]; q != nil && q.m.Version != "none" { rs[i] = q.m overridden[q.m.Path] = true } } for _, q := range u.cmdline { if !overridden[q.m.Path] && q.m.Path != modload.Target.Path && q.m.Version != "none" { rs = append(rs, q.m) } } return rs, nil } // Upgrade returns the desired upgrade for m. // // If m was requested at a specific version on the command line, then // Upgrade returns that version. // // If -u is set and m provides a dependency of a package matched by // command line arguments, then Upgrade may provider a newer tagged version. // If m is a tagged version, then Upgrade will return the latest tagged // version (with the same minor version number if -u=patch). // If m is a pseudo-version, then Upgrade returns the latest tagged version // only if that version has a time-stamp newer than m. This special case // prevents accidental downgrades when already using a pseudo-version // newer than the latest tagged version. // // If none of the above cases apply, then Upgrade returns m. func (u *upgrader) Upgrade(m module.Version) (module.Version, error) { // Allow pkg@vers on the command line to override the upgrade choice v. // If q's version is < m.Version, then we're going to downgrade anyway, // and it's cleaner to avoid moving back and forth and picking up // extraneous other newer dependencies. // If q's version is > m.Version, then we're going to upgrade past // m.Version anyway, and again it's cleaner to avoid moving back and forth // picking up extraneous other newer dependencies. if q := u.cmdline[m.Path]; q != nil { return q.m, nil } if !u.upgrade[m.Path] { // Not involved in upgrade. Leave alone. return m, nil } // Run query required by upgrade semantics. // Note that Query "latest" is not the same as using repo.Latest, // which may return a pseudoversion for the latest commit. // Query "latest" returns the newest tagged version or the newest // prerelease version if there are no non-prereleases, or repo.Latest // if there aren't any tagged versions. // If we're querying "upgrade" or "patch", Query will compare the current // version against the chosen version and will return the current version // if it is newer. info, err := modload.Query(context.TODO(), m.Path, string(getU), m.Version, checkAllowedOrCurrent(m.Version)) if err != nil { // Report error but return m, to let version selection continue. // (Reporting the error will fail the command at the next base.ExitIfErrors.) // Special case: if the error is for m.Version itself and m.Version has a // replacement, then keep it and don't report the error: the fact that the // version is invalid is likely the reason it was replaced to begin with. var vErr *module.InvalidVersionError if errors.As(err, &vErr) && vErr.Version == m.Version && modload.Replacement(m).Path != "" { return m, nil } // Special case: if the error is "no matching versions" then don't // even report the error. Because Query does not consider pseudo-versions, // it may happen that we have a pseudo-version but during -u=patch // the query v0.0 matches no versions (not even the one we're using). var noMatch *modload.NoMatchingVersionError if !errors.As(err, &noMatch) { base.Errorf("go get: upgrading %s@%s: %v", m.Path, m.Version, err) } return m, nil } if info.Version != m.Version { logOncef("go: %s %s => %s", m.Path, getU, info.Version) } return module.Version{Path: m.Path, Version: info.Version}, nil } // buildListForLostUpgrade returns the build list for the module graph // rooted at lost. Unlike mvs.BuildList, the target module (lost) is not // treated specially. The returned build list may contain a newer version // of lost. // // buildListForLostUpgrade is used after a downgrade has removed a module // requested at a specific version. This helps us understand the requirements // implied by each downgrade. func buildListForLostUpgrade(lost module.Version, reqs mvs.Reqs) ([]module.Version, error) { return mvs.BuildList(lostUpgradeRoot, &lostUpgradeReqs{Reqs: reqs, lost: lost}) } var lostUpgradeRoot = module.Version{Path: "lost-upgrade-root", Version: ""} type lostUpgradeReqs struct { mvs.Reqs lost module.Version } func (r *lostUpgradeReqs) Required(mod module.Version) ([]module.Version, error) { if mod == lostUpgradeRoot { return []module.Version{r.lost}, nil } return r.Reqs.Required(mod) }