// Copyright 2018 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 implements the module-aware ``go get'' command. package modget // The arguments to 'go get' are patterns with optional version queries, with // the version queries defaulting to "upgrade". // // The patterns are normally interpreted as package patterns. However, if a // pattern cannot match a package, it is instead interpreted as a *module* // pattern. For version queries such as "upgrade" and "patch" that depend on the // selected version of a module (or of the module containing a package), // whether a pattern denotes a package or module may change as updates are // applied (see the example in mod_get_patchmod.txt). // // There are a few other ambiguous cases to resolve, too. A package can exist in // two different modules at the same version: for example, the package // example.com/foo might be found in module example.com and also in module // example.com/foo, and those modules may have independent v0.1.0 tags — so the // input 'example.com/foo@v0.1.0' could syntactically refer to the variant of // the package loaded from either module! (See mod_get_ambiguous_pkg.txt.) // If the argument is ambiguous, the user can often disambiguate by specifying // explicit versions for *all* of the potential module paths involved. import ( "context" "errors" "fmt" "os" "path/filepath" "reflect" "runtime" "sort" "strings" "sync" "cmd/go/internal/base" "cmd/go/internal/cfg" "cmd/go/internal/imports" "cmd/go/internal/load" "cmd/go/internal/modload" "cmd/go/internal/par" "cmd/go/internal/search" "cmd/go/internal/work" "golang.org/x/mod/modfile" "golang.org/x/mod/module" "golang.org/x/mod/semver" ) var CmdGet = &base.Command{ // Note: -d -u are listed explicitly because they are the most common get flags. // Do not send CLs removing them because they're covered by [get flags]. UsageLine: "go get [-d] [-t] [-u] [-v] [-insecure] [build flags] [packages]", Short: "add dependencies to current module and install them", Long: ` Get resolves its command-line arguments to packages at specific module versions, updates go.mod to require those versions, downloads source code into the module cache, then builds and installs the named packages. To add a dependency for a package or upgrade it to its latest version: go get example.com/pkg To upgrade or downgrade a package to a specific version: go get example.com/pkg@v1.2.3 To remove a dependency on a module and downgrade modules that require it: go get example.com/mod@none See https://golang.org/ref/mod#go-get for details. The 'go install' command may be used to build and install packages. When a version is specified, 'go install' runs in module-aware mode and ignores the go.mod file in the current directory. For example: go install example.com/pkg@v1.2.3 go install example.com/pkg@latest See 'go help install' or https://golang.org/ref/mod#go-install for details. In addition to build flags (listed in 'go help build') 'go get' accepts the following flags. The -t flag instructs get to consider modules needed to build tests of packages specified on the command line. The -u flag instructs get to update modules providing dependencies of packages named on the command line to use newer minor or patch releases when available. The -u=patch flag (not -u patch) also instructs get to update dependencies, but changes the default to select patch releases. When the -t and -u flags are used together, get will update test dependencies as well. The -insecure flag permits fetching from repositories and resolving custom domains using insecure schemes such as HTTP, and also bypassess module sum validation using the checksum database. Use with caution. This flag is deprecated and will be removed in a future version of go. To permit the use of insecure schemes, use the GOINSECURE environment variable instead. To bypass module sum validation, use GOPRIVATE or GONOSUMDB. See 'go help environment' for details. The -d flag instructs get not to build or install packages. get will only update go.mod and download source code needed to build packages. Building and installing packages with get is deprecated. In a future release, the -d flag will be enabled by default, and 'go get' will be only be used to adjust dependencies of the current module. To install a package using dependencies from the current module, use 'go install'. To install a package ignoring the current module, use 'go install' with an @version suffix like "@latest" after each argument. For more about modules, see https://golang.org/ref/mod. For more about specifying packages, see 'go help packages'. This text describes the behavior of get using modules to manage source code and dependencies. If instead the go command is running in GOPATH mode, the details of get's flags and effects change, as does 'go help get'. See 'go help gopath-get'. See also: go build, go install, go clean, go mod. `, } // Note that this help text is a stopgap to make the module-aware get help text // available even in non-module settings. It should be deleted when the old get // is deleted. It should NOT be considered to set a precedent of having hierarchical // help names with dashes. var HelpModuleGet = &base.Command{ UsageLine: "module-get", Short: "module-aware go get", Long: ` The 'go get' command changes behavior depending on whether the go command is running in module-aware mode or legacy GOPATH mode. This help text, accessible as 'go help module-get' even in legacy GOPATH mode, describes 'go get' as it operates in module-aware mode. Usage: ` + CmdGet.UsageLine + ` ` + CmdGet.Long, } var HelpVCS = &base.Command{ UsageLine: "vcs", Short: "controlling version control with GOVCS", Long: ` The 'go get' command can run version control commands like git to download imported code. This functionality is critical to the decentralized Go package ecosystem, in which code can be imported from any server, but it is also a potential security problem, if a malicious server finds a way to cause the invoked version control command to run unintended code. To balance the functionality and security concerns, the 'go get' command by default will only use git and hg to download code from public servers. But it will use any known version control system (bzr, fossil, git, hg, svn) to download code from private servers, defined as those hosting packages matching the GOPRIVATE variable (see 'go help private'). The rationale behind allowing only Git and Mercurial is that these two systems have had the most attention to issues of being run as clients of untrusted servers. In contrast, Bazaar, Fossil, and Subversion have primarily been used in trusted, authenticated environments and are not as well scrutinized as attack surfaces. The version control command restrictions only apply when using direct version control access to download code. When downloading modules from a proxy, 'go get' uses the proxy protocol instead, which is always permitted. By default, the 'go get' command uses the Go module mirror (proxy.golang.org) for public packages and only falls back to version control for private packages or when the mirror refuses to serve a public package (typically for legal reasons). Therefore, clients can still access public code served from Bazaar, Fossil, or Subversion repositories by default, because those downloads use the Go module mirror, which takes on the security risk of running the version control commands using a custom sandbox. The GOVCS variable can be used to change the allowed version control systems for specific packages (identified by a module or import path). The GOVCS variable applies when building package in both module-aware mode and GOPATH mode. When using modules, the patterns match against the module path. When using GOPATH, the patterns match against the import path corresponding to the root of the version control repository. The general form of the GOVCS setting is a comma-separated list of pattern:vcslist rules. The pattern is a glob pattern that must match one or more leading elements of the module or import path. The vcslist is a pipe-separated list of allowed version control commands, or "all" to allow use of any known command, or "off" to disallow all commands. Note that if a module matches a pattern with vcslist "off", it may still be downloaded if the origin server uses the "mod" scheme, which instructs the go command to download the module using the GOPROXY protocol. The earliest matching pattern in the list applies, even if later patterns might also match. For example, consider: GOVCS=github.com:git,evil.com:off,*:git|hg With this setting, code with a module or import path beginning with github.com/ can only use git; paths on evil.com cannot use any version control command, and all other paths (* matches everything) can use only git or hg. The special patterns "public" and "private" match public and private module or import paths. A path is private if it matches the GOPRIVATE variable; otherwise it is public. If no rules in the GOVCS variable match a particular module or import path, the 'go get' command applies its default rule, which can now be summarized in GOVCS notation as 'public:git|hg,private:all'. To allow unfettered use of any version control system for any package, use: GOVCS=*:all To disable all use of version control, use: GOVCS=*:off The 'go env -w' command (see 'go help env') can be used to set the GOVCS variable for future go command invocations. `, } var ( getD = CmdGet.Flag.Bool("d", false, "") getF = CmdGet.Flag.Bool("f", false, "") getFix = CmdGet.Flag.Bool("fix", false, "") getM = CmdGet.Flag.Bool("m", false, "") getT = CmdGet.Flag.Bool("t", false, "") getU upgradeFlag // -insecure is cfg.Insecure // -v is cfg.BuildV ) // upgradeFlag is a custom flag.Value for -u. type upgradeFlag struct { rawVersion string version string } func (*upgradeFlag) IsBoolFlag() bool { return true } // allow -u func (v *upgradeFlag) Set(s string) error { if s == "false" { v.version = "" v.rawVersion = "" } else if s == "true" { v.version = "upgrade" v.rawVersion = "" } else { v.version = s v.rawVersion = s } return nil } func (v *upgradeFlag) String() string { return "" } func init() { work.AddBuildFlags(CmdGet, work.OmitModFlag) CmdGet.Run = runGet // break init loop CmdGet.Flag.BoolVar(&cfg.Insecure, "insecure", cfg.Insecure, "") CmdGet.Flag.Var(&getU, "u", "") } func runGet(ctx context.Context, cmd *base.Command, args []string) { switch getU.version { case "", "upgrade", "patch": // ok default: base.Fatalf("go get: unknown upgrade flag -u=%s", getU.rawVersion) } if *getF { fmt.Fprintf(os.Stderr, "go get: -f flag is a no-op when using modules\n") } if *getFix { fmt.Fprintf(os.Stderr, "go get: -fix flag is a no-op when using modules\n") } if *getM { base.Fatalf("go get: -m flag is no longer supported; consider -d to skip building packages") } if cfg.Insecure { fmt.Fprintf(os.Stderr, "go get: -insecure flag is deprecated; see 'go help get' for details\n") } load.ModResolveTests = *getT // Do not allow any updating of go.mod until we've applied // all the requested changes and checked that the result matches // what was requested. modload.DisallowWriteGoMod() // Allow looking up modules for import paths when outside of a module. // 'go get' is expected to do this, unlike other commands. modload.AllowMissingModuleImports() modload.LoadModFile(ctx) // Initializes modload.Target. queries := parseArgs(ctx, args) r := newResolver(ctx, queries) r.performLocalQueries(ctx) r.performPathQueries(ctx) for { r.performWildcardQueries(ctx) r.performPatternAllQueries(ctx) if changed := r.resolveCandidates(ctx, queries, nil); changed { // 'go get' arguments can be (and often are) package patterns rather than // (just) modules. A package can be provided by any module with a prefix // of its import path, and a wildcard can even match packages in modules // with totally different paths. Because of these effects, and because any // change to the selected version of a module can bring in entirely new // module paths as dependencies, we need to reissue queries whenever we // change the build list. // // The result of any version query for a given module — even "upgrade" or // "patch" — is always relative to the build list at the start of // the 'go get' command, not an intermediate state, and is therefore // dederministic and therefore cachable, and the constraints on the // selected version of each module can only narrow as we iterate. // // "all" is functionally very similar to a wildcard pattern. The set of // packages imported by the main module does not change, and the query // result for the module containing each such package also does not change // (it is always relative to the initial build list, before applying // queries). So the only way that the result of an "all" query can change // is if some matching package moves from one module in the build list // to another, which should not happen very often. continue } // When we load imports, we detect the following conditions: // // - missing transitive depencies that need to be resolved from outside the // current build list (note that these may add new matches for existing // pattern queries!) // // - transitive dependencies that didn't match any other query, // but need to be upgraded due to the -u flag // // - ambiguous import errors. // TODO(#27899): Try to resolve ambiguous import errors automatically. upgrades := r.findAndUpgradeImports(ctx, queries) if changed := r.resolveCandidates(ctx, nil, upgrades); changed { continue } r.findMissingWildcards(ctx) if changed := r.resolveCandidates(ctx, r.wildcardQueries, nil); changed { continue } break } r.checkWildcardVersions(ctx) var pkgPatterns []string for _, q := range queries { if q.matchesPackages { pkgPatterns = append(pkgPatterns, q.pattern) } } r.checkPackagesAndRetractions(ctx, pkgPatterns) // We've already downloaded modules (and identified direct and indirect // dependencies) by loading packages in findAndUpgradeImports. // So if -d is set, we're done after the module work. // // Otherwise, we need to build and install the packages matched by // command line arguments. // Note that 'go get -u' without arguments is equivalent to // 'go get -u .', so we'll typically build the package in the current // directory. if !*getD && len(pkgPatterns) > 0 { work.BuildInit() pkgs := load.PackagesAndErrors(ctx, pkgPatterns) load.CheckPackageErrors(pkgs) work.InstallPackages(ctx, pkgPatterns, pkgs) // TODO(#40276): After Go 1.16, print a deprecation notice when building and // installing main packages. 'go install pkg' or 'go install pkg@version' // should be used instead. Give the specific argument to use if possible. } if !modload.HasModRoot() { return } // Everything succeeded. Update go.mod. oldReqs := reqsFromGoMod(modload.ModFile()) modload.AllowWriteGoMod() modload.WriteGoMod() modload.DisallowWriteGoMod() newReqs := reqsFromGoMod(modload.ModFile()) r.reportChanges(oldReqs, newReqs) } // parseArgs parses command-line arguments and reports errors. // // The command-line arguments are of the form path@version or simply path, with // implicit @upgrade. path@none is "downgrade away". func parseArgs(ctx context.Context, rawArgs []string) []*query { defer base.ExitIfErrors() var queries []*query for _, arg := range search.CleanPatterns(rawArgs) { q, err := newQuery(arg) if err != nil { base.Errorf("go get: %v", err) continue } // If there were no arguments, CleanPatterns returns ".". Set the raw // string back to "" for better errors. if len(rawArgs) == 0 { q.raw = "" } // Guard against 'go get x.go', a common mistake. // Note that package and module paths may end with '.go', so only print an error // if the argument has no version and either has no slash or refers to an existing file. if strings.HasSuffix(q.raw, ".go") && q.rawVersion == "" { if !strings.Contains(q.raw, "/") { base.Errorf("go get %s: arguments must be package or module paths", q.raw) continue } if fi, err := os.Stat(q.raw); err == nil && !fi.IsDir() { base.Errorf("go get: %s exists as a file, but 'go get' requires package arguments", q.raw) continue } } queries = append(queries, q) } return queries } type resolver struct { localQueries []*query // queries for absolute or relative paths pathQueries []*query // package path literal queries in original order wildcardQueries []*query // path wildcard queries in original order patternAllQueries []*query // queries with the pattern "all" // Indexed "none" queries. These are also included in the slices above; // they are indexed here to speed up noneForPath. nonesByPath map[string]*query // path-literal "@none" queries indexed by path wildcardNones []*query // wildcard "@none" queries // resolvedVersion maps each module path to the version of that module that // must be selected in the final build list, along with the first query // that resolved the module to that version (the “reason”). resolvedVersion map[string]versionReason buildList []module.Version buildListResolvedVersions int // len(resolvedVersion) when buildList was computed buildListVersion map[string]string // index of buildList (module path → version) initialVersion map[string]string // index of the initial build list at the start of 'go get' missing []pathSet // candidates for missing transitive dependencies work *par.Queue matchInModuleCache par.Cache } type versionReason struct { version string reason *query } func newResolver(ctx context.Context, queries []*query) *resolver { buildList := modload.LoadAllModules(ctx) initialVersion := make(map[string]string, len(buildList)) for _, m := range buildList { initialVersion[m.Path] = m.Version } r := &resolver{ work: par.NewQueue(runtime.GOMAXPROCS(0)), resolvedVersion: map[string]versionReason{}, buildList: buildList, buildListVersion: initialVersion, initialVersion: initialVersion, nonesByPath: map[string]*query{}, } for _, q := range queries { if q.pattern == "all" { r.patternAllQueries = append(r.patternAllQueries, q) } else if q.patternIsLocal { r.localQueries = append(r.localQueries, q) } else if q.isWildcard() { r.wildcardQueries = append(r.wildcardQueries, q) } else { r.pathQueries = append(r.pathQueries, q) } if q.version == "none" { // Index "none" queries to make noneForPath more efficient. if q.isWildcard() { r.wildcardNones = append(r.wildcardNones, q) } else { // All "@none" queries for the same path are identical; we only // need to index one copy. r.nonesByPath[q.pattern] = q } } } return r } // initialSelected returns the version of the module with the given path that // was selected at the start of this 'go get' invocation. func (r *resolver) initialSelected(mPath string) (version string) { v, ok := r.initialVersion[mPath] if !ok { return "none" } return v } // selected returns the version of the module with the given path that is // selected in the resolver's current build list. func (r *resolver) selected(mPath string) (version string) { v, ok := r.buildListVersion[mPath] if !ok { return "none" } return v } // noneForPath returns a "none" query matching the given module path, // or found == false if no such query exists. func (r *resolver) noneForPath(mPath string) (nq *query, found bool) { if nq = r.nonesByPath[mPath]; nq != nil { return nq, true } for _, nq := range r.wildcardNones { if nq.matchesPath(mPath) { return nq, true } } return nil, false } // queryModule wraps modload.Query, substituting r.checkAllowedOr to decide // allowed versions. func (r *resolver) queryModule(ctx context.Context, mPath, query string, selected func(string) string) (module.Version, error) { current := r.initialSelected(mPath) rev, err := modload.Query(ctx, mPath, query, current, r.checkAllowedOr(query, selected)) if err != nil { return module.Version{}, err } return module.Version{Path: mPath, Version: rev.Version}, nil } // queryPackage wraps modload.QueryPackage, substituting r.checkAllowedOr to // decide allowed versions. func (r *resolver) queryPackages(ctx context.Context, pattern, query string, selected func(string) string) (pkgMods []module.Version, err error) { results, err := modload.QueryPackages(ctx, pattern, query, selected, r.checkAllowedOr(query, selected)) if len(results) > 0 { pkgMods = make([]module.Version, 0, len(results)) for _, qr := range results { pkgMods = append(pkgMods, qr.Mod) } } return pkgMods, err } // queryPattern wraps modload.QueryPattern, substituting r.checkAllowedOr to // decide allowed versions. func (r *resolver) queryPattern(ctx context.Context, pattern, query string, selected func(string) string) (pkgMods []module.Version, mod module.Version, err error) { results, modOnly, err := modload.QueryPattern(ctx, pattern, query, selected, r.checkAllowedOr(query, selected)) if len(results) > 0 { pkgMods = make([]module.Version, 0, len(results)) for _, qr := range results { pkgMods = append(pkgMods, qr.Mod) } } if modOnly != nil { mod = modOnly.Mod } return pkgMods, mod, err } // checkAllowedOr is like modload.CheckAllowed, but it always allows the requested // and current versions (even if they are retracted or otherwise excluded). func (r *resolver) checkAllowedOr(requested string, selected func(string) string) modload.AllowedFunc { return func(ctx context.Context, m module.Version) error { if m.Version == requested { return modload.CheckExclusions(ctx, m) } if (requested == "upgrade" || requested == "patch") && m.Version == selected(m.Path) { return nil } return modload.CheckAllowed(ctx, m) } } // matchInModule is a caching wrapper around modload.MatchInModule. func (r *resolver) matchInModule(ctx context.Context, pattern string, m module.Version) (packages []string, err error) { type key struct { pattern string m module.Version } type entry struct { packages []string err error } e := r.matchInModuleCache.Do(key{pattern, m}, func() interface{} { match := modload.MatchInModule(ctx, pattern, m, imports.AnyTags()) if len(match.Errs) > 0 { return entry{match.Pkgs, match.Errs[0]} } return entry{match.Pkgs, nil} }).(entry) return e.packages, e.err } // queryNone adds a candidate set to q for each module matching q.pattern. // Each candidate set has only one possible module version: the matched // module at version "none". // // We interpret arguments to 'go get' as packages first, and fall back to // modules second. However, no module exists at version "none", and therefore no // package exists at that version either: we know that the argument cannot match // any packages, and thus it must match modules instead. func (r *resolver) queryNone(ctx context.Context, q *query) { if search.IsMetaPackage(q.pattern) { panic(fmt.Sprintf("internal error: queryNone called with pattern %q", q.pattern)) } if !q.isWildcard() { q.pathOnce(q.pattern, func() pathSet { if modload.HasModRoot() && q.pattern == modload.Target.Path { // The user has explicitly requested to downgrade their own module to // version "none". This is not an entirely unreasonable request: it // could plausibly mean “downgrade away everything that depends on any // explicit version of the main module”, or “downgrade away the // package with the same path as the main module, found in a module // with a prefix of the main module's path”. // // However, neither of those behaviors would be consistent with the // plain meaning of the query. To try to reduce confusion, reject the // query explicitly. return errSet(&modload.QueryMatchesMainModuleError{Pattern: q.pattern, Query: q.version}) } return pathSet{mod: module.Version{Path: q.pattern, Version: "none"}} }) } for _, curM := range r.buildList { if !q.matchesPath(curM.Path) { continue } q.pathOnce(curM.Path, func() pathSet { if modload.HasModRoot() && curM == modload.Target { return errSet(&modload.QueryMatchesMainModuleError{Pattern: q.pattern, Query: q.version}) } return pathSet{mod: module.Version{Path: curM.Path, Version: "none"}} }) } } func (r *resolver) performLocalQueries(ctx context.Context) { for _, q := range r.localQueries { q.pathOnce(q.pattern, func() pathSet { absDetail := "" if !filepath.IsAbs(q.pattern) { if absPath, err := filepath.Abs(q.pattern); err == nil { absDetail = fmt.Sprintf(" (%s)", absPath) } } // Absolute paths like C:\foo and relative paths like ../foo... are // restricted to matching packages in the main module. pkgPattern := modload.DirImportPath(q.pattern) if pkgPattern == "." { return errSet(fmt.Errorf("%s%s is not within module rooted at %s", q.pattern, absDetail, modload.ModRoot())) } match := modload.MatchInModule(ctx, pkgPattern, modload.Target, imports.AnyTags()) if len(match.Errs) > 0 { return pathSet{err: match.Errs[0]} } if len(match.Pkgs) == 0 { if q.raw == "" || q.raw == "." { return errSet(fmt.Errorf("no package in current directory")) } if !q.isWildcard() { return errSet(fmt.Errorf("%s%s is not a package in module rooted at %s", q.pattern, absDetail, modload.ModRoot())) } search.WarnUnmatched([]*search.Match{match}) return pathSet{} } return pathSet{pkgMods: []module.Version{modload.Target}} }) } } // performWildcardQueries populates the candidates for each query whose pattern // is a wildcard. // // The candidates for a given module path matching (or containing a package // matching) a wildcard query depend only on the initial build list, but the set // of modules may be expanded by other queries, so wildcard queries need to be // re-evaluated whenever a potentially-matching module path is added to the // build list. func (r *resolver) performWildcardQueries(ctx context.Context) { for _, q := range r.wildcardQueries { q := q r.work.Add(func() { if q.version == "none" { r.queryNone(ctx, q) } else { r.queryWildcard(ctx, q) } }) } <-r.work.Idle() } // queryWildcard adds a candidate set to q for each module for which: // - some version of the module is already in the build list, and // - that module exists at some version matching q.version, and // - either the module path itself matches q.pattern, or some package within // the module at q.version matches q.pattern. func (r *resolver) queryWildcard(ctx context.Context, q *query) { // For wildcard patterns, modload.QueryPattern only identifies modules // matching the prefix of the path before the wildcard. However, the build // list may already contain other modules with matching packages, and we // should consider those modules to satisfy the query too. // We want to match any packages in existing dependencies, but we only want to // resolve new dependencies if nothing else turns up. for _, curM := range r.buildList { if !q.canMatchInModule(curM.Path) { continue } q.pathOnce(curM.Path, func() pathSet { if _, hit := r.noneForPath(curM.Path); hit { // This module is being removed, so it will no longer be in the build list // (and thus will no longer match the pattern). return pathSet{} } if curM.Path == modload.Target.Path && !versionOkForMainModule(q.version) { if q.matchesPath(curM.Path) { return errSet(&modload.QueryMatchesMainModuleError{ Pattern: q.pattern, Query: q.version, }) } packages, err := r.matchInModule(ctx, q.pattern, curM) if err != nil { return errSet(err) } if len(packages) > 0 { return errSet(&modload.QueryMatchesPackagesInMainModuleError{ Pattern: q.pattern, Query: q.version, Packages: packages, }) } return r.tryWildcard(ctx, q, curM) } m, err := r.queryModule(ctx, curM.Path, q.version, r.initialSelected) if err != nil { if !isNoSuchModuleVersion(err) { // We can't tell whether a matching version exists. return errSet(err) } // There is no version of curM.Path matching the query. // We haven't checked whether curM contains any matching packages at its // currently-selected version, or whether curM.Path itself matches q. If // either of those conditions holds, *and* no other query changes the // selected version of curM, then we will fail in checkWildcardVersions. // (This could be an error, but it's too soon to tell.) // // However, even then the transitive requirements of some other query // may downgrade this module out of the build list entirely, in which // case the pattern will no longer include it and it won't be an error. // // Either way, punt on the query rather than erroring out just yet. return pathSet{} } return r.tryWildcard(ctx, q, m) }) } // Even if no modules matched, we shouldn't query for a new module to provide // the pattern yet: some other query may yet induce a new requirement that // will match the wildcard. Instead, we'll check in findMissingWildcards. } // tryWildcard returns a pathSet for module m matching query q. // If m does not actually match q, tryWildcard returns an empty pathSet. func (r *resolver) tryWildcard(ctx context.Context, q *query, m module.Version) pathSet { mMatches := q.matchesPath(m.Path) packages, err := r.matchInModule(ctx, q.pattern, m) if err != nil { return errSet(err) } if len(packages) > 0 { return pathSet{pkgMods: []module.Version{m}} } if mMatches { return pathSet{mod: m} } return pathSet{} } // findMissingWildcards adds a candidate set for each query in r.wildcardQueries // that has not yet resolved to any version containing packages. func (r *resolver) findMissingWildcards(ctx context.Context) { for _, q := range r.wildcardQueries { if q.version == "none" || q.matchesPackages { continue // q is not “missing” } r.work.Add(func() { q.pathOnce(q.pattern, func() pathSet { pkgMods, mod, err := r.queryPattern(ctx, q.pattern, q.version, r.initialSelected) if err != nil { if isNoSuchPackageVersion(err) && len(q.resolved) > 0 { // q already resolved one or more modules but matches no packages. // That's ok: this pattern is just a module pattern, and we don't // need to add any more modules to satisfy it. return pathSet{} } return errSet(err) } return pathSet{pkgMods: pkgMods, mod: mod} }) }) } <-r.work.Idle() } // checkWildcardVersions reports an error if any module in the build list has a // path (or contains a package) matching a query with a wildcard pattern, but // has a selected version that does *not* match the query. func (r *resolver) checkWildcardVersions(ctx context.Context) { defer base.ExitIfErrors() for _, q := range r.wildcardQueries { for _, curM := range r.buildList { if !q.canMatchInModule(curM.Path) { continue } if !q.matchesPath(curM.Path) { packages, err := r.matchInModule(ctx, q.pattern, curM) if len(packages) == 0 { if err != nil { reportError(q, err) } continue // curM is not relevant to q. } } rev, err := r.queryModule(ctx, curM.Path, q.version, r.initialSelected) if err != nil { reportError(q, err) continue } if rev.Version == curM.Version { continue // curM already matches q. } if !q.matchesPath(curM.Path) { m := module.Version{Path: curM.Path, Version: rev.Version} packages, err := r.matchInModule(ctx, q.pattern, m) if err != nil { reportError(q, err) continue } if len(packages) == 0 { // curM at its original version contains a path matching q.pattern, // but at rev.Version it does not, so (somewhat paradoxically) if // we changed the version of curM it would no longer match the query. var version interface{} = m if rev.Version != q.version { version = fmt.Sprintf("%s@%s (%s)", m.Path, q.version, m.Version) } reportError(q, fmt.Errorf("%v matches packages in %v but not %v: specify a different version for module %s", q, curM, version, m.Path)) continue } } // Since queryModule succeeded and either curM or one of the packages it // contains matches q.pattern, we should have either selected the version // of curM matching q, or reported a conflict error (and exited). // If we're still here and the version doesn't match, // something has gone very wrong. reportError(q, fmt.Errorf("internal error: selected %v instead of %v", curM, rev.Version)) } } } // performPathQueries populates the candidates for each query whose pattern is // a path literal. // // The candidate packages and modules for path literals depend only on the // initial build list, not the current build list, so we only need to query path // literals once. func (r *resolver) performPathQueries(ctx context.Context) { for _, q := range r.pathQueries { q := q r.work.Add(func() { if q.version == "none" { r.queryNone(ctx, q) } else { r.queryPath(ctx, q) } }) } <-r.work.Idle() } // queryPath adds a candidate set to q for the package with path q.pattern. // The candidate set consists of all modules that could provide q.pattern // and have a version matching q, plus (if it exists) the module whose path // is itself q.pattern (at a matching version). func (r *resolver) queryPath(ctx context.Context, q *query) { q.pathOnce(q.pattern, func() pathSet { if search.IsMetaPackage(q.pattern) || q.isWildcard() { panic(fmt.Sprintf("internal error: queryPath called with pattern %q", q.pattern)) } if q.version == "none" { panic(`internal error: queryPath called with version "none"`) } if search.IsStandardImportPath(q.pattern) { stdOnly := module.Version{} packages, _ := r.matchInModule(ctx, q.pattern, stdOnly) if len(packages) > 0 { if q.rawVersion != "" { return errSet(fmt.Errorf("can't request explicit version %q of standard library package %s", q.version, q.pattern)) } q.matchesPackages = true return pathSet{} // No module needed for standard library. } } pkgMods, mod, err := r.queryPattern(ctx, q.pattern, q.version, r.initialSelected) if err != nil { return errSet(err) } return pathSet{pkgMods: pkgMods, mod: mod} }) } // performPatternAllQueries populates the candidates for each query whose // pattern is "all". // // The candidate modules for a given package in "all" depend only on the initial // build list, but we cannot follow the dependencies of a given package until we // know which candidate is selected — and that selection may depend on the // results of other queries. We need to re-evaluate the "all" queries whenever // the module for one or more packages in "all" are resolved. func (r *resolver) performPatternAllQueries(ctx context.Context) { if len(r.patternAllQueries) == 0 { return } findPackage := func(ctx context.Context, path string, m module.Version) (versionOk bool) { versionOk = true for _, q := range r.patternAllQueries { q.pathOnce(path, func() pathSet { pkgMods, err := r.queryPackages(ctx, path, q.version, r.initialSelected) if len(pkgMods) != 1 || pkgMods[0] != m { // There are candidates other than m for the given path, so we can't // be certain that m will actually be the module selected to provide // the package. Don't load its dependencies just yet, because they // might no longer be dependencies after we resolve the correct // version. versionOk = false } return pathSet{pkgMods: pkgMods, err: err} }) } return versionOk } r.loadPackages(ctx, []string{"all"}, findPackage) // Since we built up the candidate lists concurrently, they may be in a // nondeterministic order. We want 'go get' to be fully deterministic, // including in which errors it chooses to report, so sort the candidates // into a deterministic-but-arbitrary order. for _, q := range r.patternAllQueries { sort.Slice(q.candidates, func(i, j int) bool { return q.candidates[i].path < q.candidates[j].path }) } } // findAndUpgradeImports returns a pathSet for each package that is not yet // in the build list but is transitively imported by the packages matching the // given queries (which must already have been resolved). // // If the getU flag ("-u") is set, findAndUpgradeImports also returns a // pathSet for each module that is not constrained by any other // command-line argument and has an available matching upgrade. func (r *resolver) findAndUpgradeImports(ctx context.Context, queries []*query) (upgrades []pathSet) { patterns := make([]string, 0, len(queries)) for _, q := range queries { if q.matchesPackages { patterns = append(patterns, q.pattern) } } if len(patterns) == 0 { return nil } // mu guards concurrent writes to upgrades, which will be sorted // (to restore determinism) after loading. var mu sync.Mutex findPackage := func(ctx context.Context, path string, m module.Version) (versionOk bool) { version := "latest" if m.Path != "" { if getU.version == "" { // The user did not request that we upgrade transitive dependencies. return true } if _, ok := r.resolvedVersion[m.Path]; ok { // We cannot upgrade m implicitly because its version is determined by // an explicit pattern argument. return true } version = getU.version } // Unlike other queries, the "-u" flag upgrades relative to the build list // after applying changes so far, not the initial build list. // This is for two reasons: // // - The "-u" flag intentionally applies to transitive dependencies, // which may not be known or even resolved in advance of applying // other version changes. // // - The "-u" flag, unlike other arguments, does not cause version // conflicts with other queries. (The other query always wins.) pkgMods, err := r.queryPackages(ctx, path, version, r.selected) for _, u := range pkgMods { if u == m { // The selected package version is already upgraded appropriately; there // is no need to change it. return true } } if err != nil { if isNoSuchPackageVersion(err) || (m.Path == "" && module.CheckPath(path) != nil) { // We can't find the package because it doesn't — or can't — even exist // in any module at the latest version. (Note that invalid module paths // could in general exist due to replacements, so we at least need to // run the query to check those.) // // There is no version change we can make to fix the package, so leave // it unresolved. Either some other query (perhaps a wildcard matching a // newly-added dependency for some other missing package) will fill in // the gaps, or we will report an error (with a better import stack) in // the final LoadPackages call. return true } } mu.Lock() upgrades = append(upgrades, pathSet{path: path, pkgMods: pkgMods, err: err}) mu.Unlock() return false } r.loadPackages(ctx, patterns, findPackage) // Since we built up the candidate lists concurrently, they may be in a // nondeterministic order. We want 'go get' to be fully deterministic, // including in which errors it chooses to report, so sort the candidates // into a deterministic-but-arbitrary order. sort.Slice(upgrades, func(i, j int) bool { return upgrades[i].path < upgrades[j].path }) return upgrades } // loadPackages loads the packages matching the given patterns, invoking the // findPackage function for each package that may require a change to the // build list. // // loadPackages invokes the findPackage function for each package loaded from a // module outside the main module. If the module or version that supplies that // package needs to be changed due to a query, findPackage may return false // and the imports of that package will not be loaded. // // loadPackages also invokes the findPackage function for each imported package // that is neither present in the standard library nor in any module in the // build list. func (r *resolver) loadPackages(ctx context.Context, patterns []string, findPackage func(ctx context.Context, path string, m module.Version) (versionOk bool)) { opts := modload.PackageOpts{ Tags: imports.AnyTags(), LoadTests: *getT, SilenceErrors: true, // May be fixed by subsequent upgrades or downgrades. } opts.AllowPackage = func(ctx context.Context, path string, m module.Version) error { if m.Path == "" || m == modload.Target { // Packages in the standard library and main module are already at their // latest (and only) available versions. return nil } if ok := findPackage(ctx, path, m); !ok { return errVersionChange } return nil } _, pkgs := modload.LoadPackages(ctx, opts, patterns...) for _, path := range pkgs { const ( parentPath = "" parentIsStd = false ) _, _, err := modload.Lookup(parentPath, parentIsStd, path) if err == nil { continue } if errors.Is(err, errVersionChange) { // We already added candidates during loading. continue } var ( importMissing *modload.ImportMissingError ambiguous *modload.AmbiguousImportError ) if !errors.As(err, &importMissing) && !errors.As(err, &ambiguous) { // The package, which is a dependency of something we care about, has some // problem that we can't resolve with a version change. // Leave the error for the final LoadPackages call. continue } path := path r.work.Add(func() { findPackage(ctx, path, module.Version{}) }) } <-r.work.Idle() } // errVersionChange is a sentinel error indicating that a module's version needs // to be updated before its dependencies can be loaded. var errVersionChange = errors.New("version change needed") // resolveCandidates resolves candidates sets that are attached to the given // queries and/or needed to provide the given missing-package dependencies. // // resolveCandidates starts by resolving one module version from each // unambiguous pathSet attached to the given queries. // // If no unambiguous query results in a change to the build list, // resolveCandidates modifies the build list by adding one module version from // each pathSet in missing, but does not mark those versions as resolved // (so they can still be modified by other queries). // // If that still does not result in any changes to the build list, // resolveCandidates revisits the ambiguous query candidates and resolves them // arbitrarily in order to guarantee forward progress. // // If all pathSets are resolved without any changes to the build list, // resolveCandidates returns with changed=false. func (r *resolver) resolveCandidates(ctx context.Context, queries []*query, upgrades []pathSet) (changed bool) { defer base.ExitIfErrors() // Note: this is O(N²) with the number of pathSets in the worst case. // // We could perhaps get it down to O(N) if we were to index the pathSets // by module path, so that we only revisit a given pathSet when the // version of some module in its containingPackage list has been determined. // // However, N tends to be small, and most candidate sets will include only one // candidate module (so they will be resolved in the first iteration), so for // now we'll stick to the simple O(N²) approach. resolved := 0 for { prevResolved := resolved for _, q := range queries { unresolved := q.candidates[:0] for _, cs := range q.candidates { if cs.err != nil { reportError(q, cs.err) resolved++ continue } filtered, isPackage, m, unique := r.disambiguate(cs) if !unique { unresolved = append(unresolved, filtered) continue } if m.Path == "" { // The query is not viable. Choose an arbitrary candidate from // before filtering and “resolve” it to report a conflict. isPackage, m = r.chooseArbitrarily(cs) } if isPackage { q.matchesPackages = true } r.resolve(q, m) resolved++ } q.candidates = unresolved } base.ExitIfErrors() if resolved == prevResolved { break // No unambiguous candidate remains. } } if changed := r.updateBuildList(ctx, nil); changed { // The build list has changed, so disregard any missing packages: they might // now be determined by requirements in the build list, which we would // prefer to use instead of arbitrary "latest" versions. return true } // Arbitrarily add a "latest" version that provides each missing package, but // do not mark the version as resolved: we still want to allow the explicit // queries to modify the resulting versions. var tentative []module.Version for _, cs := range upgrades { if cs.err != nil { base.Errorf("go get: %v", cs.err) continue } filtered, _, m, unique := r.disambiguate(cs) if !unique { _, m = r.chooseArbitrarily(filtered) } if m.Path == "" { // There is no viable candidate for the missing package. // Leave it unresolved. continue } tentative = append(tentative, m) } base.ExitIfErrors() if changed := r.updateBuildList(ctx, tentative); changed { return true } // The build list will be the same on the next iteration as it was on this // iteration, so any ambiguous queries will remain so. In order to make // progress, resolve them arbitrarily but deterministically. // // If that results in conflicting versions, the user can re-run 'go get' // with additional explicit versions for the conflicting packages or // modules. for _, q := range queries { for _, cs := range q.candidates { isPackage, m := r.chooseArbitrarily(cs) if isPackage { q.matchesPackages = true } r.resolve(q, m) } } return r.updateBuildList(ctx, nil) } // disambiguate eliminates candidates from cs that conflict with other module // versions that have already been resolved. If there is only one (unique) // remaining candidate, disambiguate returns that candidate, along with // an indication of whether that result interprets cs.path as a package // // Note: we're only doing very simple disambiguation here. The goal is to // reproduce the user's intent, not to find a solution that a human couldn't. // In the vast majority of cases, we expect only one module per pathSet, // but we want to give some minimal additional tools so that users can add an // extra argument or two on the command line to resolve simple ambiguities. func (r *resolver) disambiguate(cs pathSet) (filtered pathSet, isPackage bool, m module.Version, unique bool) { if len(cs.pkgMods) == 0 && cs.mod.Path == "" { panic("internal error: resolveIfUnambiguous called with empty pathSet") } for _, m := range cs.pkgMods { if _, ok := r.noneForPath(m.Path); ok { // A query with version "none" forces the candidate module to version // "none", so we cannot use any other version for that module. continue } if m.Path == modload.Target.Path { if m.Version == modload.Target.Version { return pathSet{}, true, m, true } // The main module can only be set to its own version. continue } vr, ok := r.resolvedVersion[m.Path] if !ok { // m is a viable answer to the query, but other answers may also // still be viable. filtered.pkgMods = append(filtered.pkgMods, m) continue } if vr.version != m.Version { // Some query forces the candidate module to a version other than this // one. // // The command could be something like // // go get example.com/foo/bar@none example.com/foo/bar/baz@latest // // in which case we *cannot* resolve the package from // example.com/foo/bar (because it is constrained to version // "none") and must fall through to module example.com/foo@latest. continue } // Some query forces the candidate module *to* the candidate version. // As a result, this candidate is the only viable choice to provide // its package(s): any other choice would result in an ambiguous import // for this path. // // For example, consider the command // // go get example.com/foo@latest example.com/foo/bar/baz@latest // // If modules example.com/foo and example.com/foo/bar both provide // package example.com/foo/bar/baz, then we *must* resolve the package // from example.com/foo: if we instead resolved it from // example.com/foo/bar, we would have two copies of the package. return pathSet{}, true, m, true } if cs.mod.Path != "" { vr, ok := r.resolvedVersion[cs.mod.Path] if !ok || vr.version == cs.mod.Version { filtered.mod = cs.mod } } if len(filtered.pkgMods) == 1 && (filtered.mod.Path == "" || filtered.mod == filtered.pkgMods[0]) { // Exactly one viable module contains the package with the given path // (by far the common case), so we can resolve it unambiguously. return pathSet{}, true, filtered.pkgMods[0], true } if len(filtered.pkgMods) == 0 { // All modules that could provide the path as a package conflict with other // resolved arguments. If it can refer to a module instead, return that; // otherwise, this pathSet cannot be resolved (and we will return the // zero module.Version). return pathSet{}, false, filtered.mod, true } // The query remains ambiguous: there are at least two different modules // to which cs.path could refer. return filtered, false, module.Version{}, false } // chooseArbitrarily returns an arbitrary (but deterministic) module version // from among those in the given set. // // chooseArbitrarily prefers module paths that were already in the build list at // the start of 'go get', prefers modules that provide packages over those that // do not, and chooses the first module meeting those criteria (so biases toward // longer paths). func (r *resolver) chooseArbitrarily(cs pathSet) (isPackage bool, m module.Version) { // Prefer to upgrade some module that was already in the build list. for _, m := range cs.pkgMods { if r.initialSelected(m.Path) != "none" { return true, m } } // Otherwise, arbitrarily choose the first module that provides the package. if len(cs.pkgMods) > 0 { return true, cs.pkgMods[0] } return false, cs.mod } // checkPackagesAndRetractions reloads packages for the given patterns and // reports missing and ambiguous package errors. It also reports loads and // reports retractions for resolved modules and modules needed to build // named packages. // // We skip missing-package errors earlier in the process, since we want to // resolve pathSets ourselves, but at that point, we don't have enough context // to log the package-import chains leading to each error. func (r *resolver) checkPackagesAndRetractions(ctx context.Context, pkgPatterns []string) { defer base.ExitIfErrors() // Build a list of modules to load retractions for. Start with versions // selected based on command line queries. // // This is a subset of the build list. If the main module has a lot of // dependencies, loading retractions for the entire build list would be slow. relevantMods := make(map[module.Version]struct{}) for path, reason := range r.resolvedVersion { relevantMods[module.Version{Path: path, Version: reason.version}] = struct{}{} } // Reload packages, reporting errors for missing and ambiguous imports. if len(pkgPatterns) > 0 { // LoadPackages will print errors (since it has more context) but will not // exit, since we need to load retractions later. pkgOpts := modload.PackageOpts{ LoadTests: *getT, ResolveMissingImports: false, AllowErrors: true, } matches, pkgs := modload.LoadPackages(ctx, pkgOpts, pkgPatterns...) for _, m := range matches { if len(m.Errs) > 0 { base.SetExitStatus(1) break } } for _, pkg := range pkgs { if dir, _, err := modload.Lookup("", false, pkg); err != nil { if dir != "" && errors.Is(err, imports.ErrNoGo) { // Since dir is non-empty, we must have located source files // associated with either the package or its test — ErrNoGo must // indicate that none of those source files happen to apply in this // configuration. If we are actually building the package (no -d // flag), the compiler will report the problem; otherwise, assume that // the user is going to build or test it in some other configuration // and suppress the error. continue } base.SetExitStatus(1) if ambiguousErr := (*modload.AmbiguousImportError)(nil); errors.As(err, &ambiguousErr) { for _, m := range ambiguousErr.Modules { relevantMods[m] = struct{}{} } } } if m := modload.PackageModule(pkg); m.Path != "" { relevantMods[m] = struct{}{} } } } // Load and report retractions. type retraction struct { m module.Version err error } retractions := make([]retraction, 0, len(relevantMods)) for m := range relevantMods { retractions = append(retractions, retraction{m: m}) } sort.Slice(retractions, func(i, j int) bool { return retractions[i].m.Path < retractions[j].m.Path }) for i := 0; i < len(retractions); i++ { i := i r.work.Add(func() { err := modload.CheckRetractions(ctx, retractions[i].m) if retractErr := (*modload.ModuleRetractedError)(nil); errors.As(err, &retractErr) { retractions[i].err = err } }) } <-r.work.Idle() var retractPath string for _, r := range retractions { if r.err != nil { fmt.Fprintf(os.Stderr, "go: warning: %v\n", r.err) if retractPath == "" { retractPath = r.m.Path } else { retractPath = "" } } } if retractPath != "" { fmt.Fprintf(os.Stderr, "go: to switch to the latest unretracted version, run:\n\tgo get %s@latest\n", retractPath) } } // reportChanges logs version changes to os.Stderr. // // reportChanges only logs changes to modules named on the command line and to // explicitly required modules in go.mod. Most changes to indirect requirements // are not relevant to the user and are not logged. // // reportChanges should be called after WriteGoMod. func (r *resolver) reportChanges(oldReqs, newReqs []module.Version) { type change struct { path, old, new string } changes := make(map[string]change) // Collect changes in modules matched by command line arguments. for path, reason := range r.resolvedVersion { old := r.initialVersion[path] new := reason.version if old != new && (old != "" || new != "none") { changes[path] = change{path, old, new} } } // Collect changes to explicit requirements in go.mod. for _, req := range oldReqs { path := req.Path old := req.Version new := r.buildListVersion[path] if old != new { changes[path] = change{path, old, new} } } for _, req := range newReqs { path := req.Path old := r.initialVersion[path] new := req.Version if old != new { changes[path] = change{path, old, new} } } sortedChanges := make([]change, 0, len(changes)) for _, c := range changes { sortedChanges = append(sortedChanges, c) } sort.Slice(sortedChanges, func(i, j int) bool { return sortedChanges[i].path < sortedChanges[j].path }) for _, c := range sortedChanges { if c.old == "" { fmt.Fprintf(os.Stderr, "go get: added %s %s\n", c.path, c.new) } else if c.new == "none" || c.new == "" { fmt.Fprintf(os.Stderr, "go get: removed %s %s\n", c.path, c.old) } else if semver.Compare(c.new, c.old) > 0 { fmt.Fprintf(os.Stderr, "go get: upgraded %s %s => %s\n", c.path, c.old, c.new) } else { fmt.Fprintf(os.Stderr, "go get: downgraded %s %s => %s\n", c.path, c.old, c.new) } } // TODO(golang.org/issue/33284): attribute changes to command line arguments. // For modules matched by command line arguments, this probably isn't // necessary, but it would be useful for unmatched direct dependencies of // the main module. } // resolve records that module m must be at its indicated version (which may be // "none") due to query q. If some other query forces module m to be at a // different version, resolve reports a conflict error. func (r *resolver) resolve(q *query, m module.Version) { if m.Path == "" { panic("internal error: resolving a module.Version with an empty path") } if m.Path == modload.Target.Path && m.Version != modload.Target.Version { reportError(q, &modload.QueryMatchesMainModuleError{ Pattern: q.pattern, Query: q.version, }) return } vr, ok := r.resolvedVersion[m.Path] if ok && vr.version != m.Version { reportConflict(q, m, vr) return } r.resolvedVersion[m.Path] = versionReason{m.Version, q} q.resolved = append(q.resolved, m) } // updateBuildList updates the module loader's global build list to be // consistent with r.resolvedVersion, and to include additional modules // provided that they do not conflict with the resolved versions. // // If the additional modules conflict with the resolved versions, they will be // downgraded to a non-conflicting version (possibly "none"). func (r *resolver) updateBuildList(ctx context.Context, additions []module.Version) (changed bool) { if len(additions) == 0 && len(r.resolvedVersion) == r.buildListResolvedVersions { return false } defer base.ExitIfErrors() resolved := make([]module.Version, 0, len(r.resolvedVersion)) for mPath, rv := range r.resolvedVersion { if mPath != modload.Target.Path { resolved = append(resolved, module.Version{Path: mPath, Version: rv.version}) } } if err := modload.EditBuildList(ctx, additions, resolved); err != nil { var constraint *modload.ConstraintError if !errors.As(err, &constraint) { base.Errorf("go get: %v", err) return false } reason := func(m module.Version) string { rv, ok := r.resolvedVersion[m.Path] if !ok { panic(fmt.Sprintf("internal error: can't find reason for requirement on %v", m)) } return rv.reason.ResolvedString(module.Version{Path: m.Path, Version: rv.version}) } for _, c := range constraint.Conflicts { base.Errorf("go get: %v requires %v, not %v", reason(c.Source), c.Dep, reason(c.Constraint)) } return false } buildList := modload.LoadAllModules(ctx) r.buildListResolvedVersions = len(r.resolvedVersion) if reflect.DeepEqual(r.buildList, buildList) { return false } r.buildList = buildList r.buildListVersion = make(map[string]string, len(r.buildList)) for _, m := range r.buildList { r.buildListVersion[m.Path] = m.Version } return true } func reqsFromGoMod(f *modfile.File) []module.Version { reqs := make([]module.Version, len(f.Require)) for i, r := range f.Require { reqs[i] = r.Mod } return reqs } // isNoSuchModuleVersion reports whether err indicates that the requested module // does not exist at the requested version, either because the module does not // exist at all or because it does not include that specific version. func isNoSuchModuleVersion(err error) bool { var noMatch *modload.NoMatchingVersionError return errors.Is(err, os.ErrNotExist) || errors.As(err, &noMatch) } // isNoSuchPackageVersion reports whether err indicates that the requested // package does not exist at the requested version, either because no module // that could contain it exists at that version, or because every such module // that does exist does not actually contain the package. func isNoSuchPackageVersion(err error) bool { var noPackage *modload.PackageNotInModuleError return isNoSuchModuleVersion(err) || errors.As(err, &noPackage) }