diff options
Diffstat (limited to 'src/cmd/go/internal/modget/get.go')
-rw-r--r-- | src/cmd/go/internal/modget/get.go | 418 |
1 files changed, 235 insertions, 183 deletions
diff --git a/src/cmd/go/internal/modget/get.go b/src/cmd/go/internal/modget/get.go index a2a8287d84..f1cf8b17a8 100644 --- a/src/cmd/go/internal/modget/get.go +++ b/src/cmd/go/internal/modget/get.go @@ -17,7 +17,7 @@ import ( "sync" "cmd/go/internal/base" - "cmd/go/internal/get" + "cmd/go/internal/cfg" "cmd/go/internal/imports" "cmd/go/internal/load" "cmd/go/internal/modload" @@ -115,9 +115,12 @@ require downgrading other dependencies, and 'go get' does this automatically as well. The -insecure flag permits fetching from repositories and resolving -custom domains using insecure schemes such as HTTP. Use with caution. The -GOINSECURE environment variable is usually a better alternative, since it -provides control over which modules may be retrieved using an insecure scheme. +custom domains using insecure schemes such as HTTP. Use with caution. +This flag is deprecated and will be removed in a future version of go. +The GOINSECURE environment variable is usually a better alternative, since +it provides control over which modules may be retrieved using an insecure +scheme. It should be noted that the -insecure flag also turns the module +checksum validation off. GOINSECURE does not do that, use GONOSUMDB. See 'go help environment' for details. The second step is to download (if needed), build, and install @@ -181,7 +184,7 @@ var ( getM = CmdGet.Flag.Bool("m", false, "") getT = CmdGet.Flag.Bool("t", false, "") getU upgradeFlag - // -insecure is get.Insecure + // -insecure is cfg.Insecure // -v is cfg.BuildV ) @@ -206,7 +209,7 @@ func (v *upgradeFlag) String() string { return "" } func init() { work.AddBuildFlags(CmdGet, work.OmitModFlag) CmdGet.Run = runGet // break init loop - CmdGet.Flag.BoolVar(&get.Insecure, "insecure", get.Insecure, "") + CmdGet.Flag.BoolVar(&cfg.Insecure, "insecure", cfg.Insecure, "") CmdGet.Flag.Var(&getU, "u", "") } @@ -225,6 +228,8 @@ type getArg struct { vers string } +func (arg getArg) String() string { return arg.raw } + // querySpec describes a query for a specific module. path may be a // module path, package path, or package pattern. vers is a version // query string from a command line argument. @@ -276,14 +281,10 @@ func runGet(ctx context.Context, cmd *base.Command, args []string) { if *getM { base.Fatalf("go get: -m flag is no longer supported; consider -d to skip building packages") } - modload.LoadTests = *getT - - buildList := modload.LoadAllModules(ctx) - buildList = buildList[:len(buildList):len(buildList)] // copy on append - versionByPath := make(map[string]string) - for _, m := range buildList { - versionByPath[m.Path] = m.Version + 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 @@ -294,150 +295,15 @@ func runGet(ctx context.Context, cmd *base.Command, args []string) { // 'go get' is expected to do this, unlike other commands. modload.AllowMissingModuleImports() - // Parse command-line arguments and report errors. The command-line - // arguments are of the form path@version or simply path, with implicit - // @upgrade. path@none is "downgrade away". - var gets []getArg - var queries []*query - for _, arg := range search.CleanPatterns(args) { - // Argument is path or path@vers. - path := arg - vers := "" - if i := strings.Index(arg, "@"); i >= 0 { - path, vers = arg[:i], arg[i+1:] - } - if strings.Contains(vers, "@") || arg != path && vers == "" { - base.Errorf("go get %s: invalid module version syntax", arg) - continue - } - - // 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(arg, ".go") && vers == "" { - if !strings.Contains(arg, "/") { - base.Errorf("go get %s: arguments must be package or module paths", arg) - continue - } - if fi, err := os.Stat(arg); err == nil && !fi.IsDir() { - base.Errorf("go get: %s exists as a file, but 'go get' requires package arguments", arg) - continue - } - } - - // If no version suffix is specified, assume @upgrade. - // If -u=patch was specified, assume @patch instead. - if vers == "" { - if getU != "" { - vers = string(getU) - } else { - vers = "upgrade" - } - } + getArgs := parseArgs(args) - gets = append(gets, getArg{raw: arg, path: path, vers: vers}) - - // Determine the modules that path refers to, and create queries - // to lookup modules at target versions before loading packages. - // This is an imprecise process, but it helps reduce unnecessary - // queries and package loading. It's also necessary for handling - // patterns like golang.org/x/tools/..., which can't be expanded - // during package loading until they're in the build list. - switch { - case filepath.IsAbs(path) || search.IsRelativePath(path): - // Absolute paths like C:\foo and relative paths like ../foo... - // are restricted to matching packages in the main module. If the path - // is explicit and contains no wildcards (...), check that it is a - // package in the main module. If the path contains wildcards but - // matches no packages, we'll warn after package loading. - if !strings.Contains(path, "...") { - m := search.NewMatch(path) - if pkgPath := modload.DirImportPath(path); pkgPath != "." { - m = modload.TargetPackages(ctx, pkgPath) - } - if len(m.Pkgs) == 0 { - for _, err := range m.Errs { - base.Errorf("go get %s: %v", arg, err) - } - - abs, err := filepath.Abs(path) - if err != nil { - abs = path - } - base.Errorf("go get %s: path %s is not a package in module rooted at %s", arg, abs, modload.ModRoot()) - continue - } - } - - if path != arg { - base.Errorf("go get %s: can't request explicit version of path in main module", arg) - continue - } - - case strings.Contains(path, "..."): - // Wait until we load packages to look up modules. - // We don't know yet whether any modules in the build list provide - // packages matching the pattern. For example, suppose - // golang.org/x/tools and golang.org/x/tools/playground are separate - // modules, and only golang.org/x/tools is in the build list. If the - // user runs 'go get golang.org/x/tools/playground/...', we should - // add a requirement for golang.org/x/tools/playground. We should not - // upgrade golang.org/x/tools. - - case path == "all": - // If there is no main module, "all" is not meaningful. - if !modload.HasModRoot() { - base.Errorf(`go get %s: cannot match "all": working directory is not part of a module`, arg) - } - // Don't query modules until we load packages. We'll automatically - // look up any missing modules. - - case search.IsMetaPackage(path): - base.Errorf("go get %s: explicit requirement on standard-library module %s not allowed", path, path) - continue - - default: - // The argument is a package or module path. - if modload.HasModRoot() { - if m := modload.TargetPackages(ctx, path); len(m.Pkgs) != 0 { - // The path is in the main module. Nothing to query. - if vers != "upgrade" && vers != "patch" { - base.Errorf("go get %s: can't request explicit version of path in main module", arg) - } - continue - } - } - - first := path - if i := strings.IndexByte(first, '/'); i >= 0 { - first = path - } - if !strings.Contains(first, ".") { - // The path doesn't have a dot in the first component and cannot be - // queried as a module. It may be a package in the standard library, - // which is fine, so don't report an error unless we encounter - // a problem loading packages below. - continue - } - - // If we're querying "upgrade" or "patch", we need to know the current - // version of the module. For "upgrade", we want to avoid accidentally - // downgrading from a newer prerelease. For "patch", we need to query - // the correct minor version. - // Here, we check if "path" is the name of a module in the build list - // (other than the main module) and set prevM if so. If "path" isn't - // a module in the build list, the current version doesn't matter - // since it's either an unknown module or a package within a module - // that we'll discover later. - q := &query{querySpec: querySpec{path: path, vers: vers}, arg: arg} - if v, ok := versionByPath[path]; ok && path != modload.Target.Path { - q.prevM = module.Version{Path: path, Version: v} - q.forceModulePath = true - } - queries = append(queries, q) - } + buildList := modload.LoadAllModules(ctx) + buildList = buildList[:len(buildList):len(buildList)] // copy on append + selectedVersion := make(map[string]string) + for _, m := range buildList { + selectedVersion[m.Path] = m.Version } - base.ExitIfErrors() + queries := classifyArgs(ctx, selectedVersion, getArgs) // Query modules referenced by command line arguments at requested versions. // We need to do this before loading packages since patterns that refer to @@ -448,13 +314,13 @@ func runGet(ctx context.Context, cmd *base.Command, args []string) { // Add missing modules to the build list. // We call SetBuildList here and elsewhere, since newUpgrader, - // ImportPathsQuiet, and other functions read the global build list. + // LoadPackages, and other functions read the global build list. for _, q := range queries { - if _, ok := versionByPath[q.m.Path]; !ok && q.m.Version != "none" { + if _, ok := selectedVersion[q.m.Path]; !ok && q.m.Version != "none" { buildList = append(buildList, q.m) } } - versionByPath = nil // out of date now; rebuilt later when needed + selectedVersion = nil // out of date now; rebuilt later when needed modload.SetBuildList(buildList) // Upgrade modules specifically named on the command line. This is our only @@ -508,7 +374,7 @@ func runGet(ctx context.Context, cmd *base.Command, args []string) { // Build a list of arguments that may refer to packages. var pkgPatterns []string var pkgGets []getArg - for _, arg := range gets { + for _, arg := range getArgs { if modOnly[arg.path] == nil && arg.vers != "none" { pkgPatterns = append(pkgPatterns, arg.path) pkgGets = append(pkgGets, arg) @@ -534,9 +400,16 @@ func runGet(ctx context.Context, cmd *base.Command, args []string) { if len(pkgPatterns) > 0 { // Don't load packages if pkgPatterns is empty. Both - // modload.ImportPathsQuiet and ModulePackages convert an empty list + // modload.LoadPackages and ModulePackages convert an empty list // of patterns to []string{"."}, which is not what we want. - matches = modload.ImportPathsQuiet(ctx, pkgPatterns, imports.AnyTags()) + loadOpts := modload.PackageOpts{ + Tags: imports.AnyTags(), + ResolveMissingImports: true, // dubious; see https://golang.org/issue/32567 + LoadTests: *getT, + SilenceErrors: true, // Errors may be fixed by subsequent upgrades or downgrades. + SilenceUnmatchedWarnings: true, // We will warn after iterating below. + } + matches, _ = modload.LoadPackages(ctx, loadOpts, pkgPatterns...) seenPkgs = make(map[string]bool) for i, match := range matches { arg := pkgGets[i] @@ -609,11 +482,6 @@ func runGet(ctx context.Context, cmd *base.Command, args []string) { } prevBuildList = buildList } - if !*getD { - // Only print warnings after the last iteration, - // and only if we aren't going to build. - search.WarnUnmatched(matches) - } // Handle downgrades. var down []module.Version @@ -640,12 +508,12 @@ func runGet(ctx context.Context, cmd *base.Command, args []string) { // Scan for any upgrades lost by the downgrades. var lostUpgrades []*query if len(down) > 0 { - versionByPath = make(map[string]string) + selectedVersion = make(map[string]string) for _, m := range modload.LoadedModules() { - versionByPath[m.Path] = m.Version + selectedVersion[m.Path] = m.Version } for _, q := range byPath { - if v, ok := versionByPath[q.m.Path]; q.m.Version != "none" && (!ok || semver.Compare(v, q.m.Version) != 0) { + if v, ok := selectedVersion[q.m.Path]; q.m.Version != "none" && (!ok || semver.Compare(v, q.m.Version) != 0) { lostUpgrades = append(lostUpgrades, q) } } @@ -692,7 +560,7 @@ func runGet(ctx context.Context, cmd *base.Command, args []string) { if sep != "," { // We have no idea why this happened. // At least report the problem. - if v := versionByPath[q.m.Path]; v == "" { + if v := selectedVersion[q.m.Path]; v == "" { fmt.Fprintf(&buf, " removed unexpectedly") } else { fmt.Fprintf(&buf, " ended up at %s unexpectedly", v) @@ -703,6 +571,37 @@ func runGet(ctx context.Context, cmd *base.Command, args []string) { base.Fatalf("%v", buf.String()) } + if len(pkgPatterns) > 0 || len(args) == 0 { + // Before we write the updated go.mod file, reload the requested packages to + // check for errors. + loadOpts := modload.PackageOpts{ + Tags: imports.AnyTags(), + LoadTests: *getT, + + // Only print warnings after the last iteration, and only if we aren't going + // to build (to avoid doubled warnings). + // + // Only local patterns in the main module, such as './...', can be unmatched. + // (See the mod_get_nopkgs test for more detail.) + SilenceUnmatchedWarnings: !*getD, + } + modload.LoadPackages(ctx, loadOpts, pkgPatterns...) + } + + // If -d was specified, we're done after the module work. + // We've already downloaded modules by loading packages above. + // Otherwise, we need to build and install the packages matched by + // command line arguments. This may be a different set of packages, + // since we only build packages for the target platform. + // 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.PackagesForBuild(ctx, pkgPatterns) + work.InstallPackages(ctx, pkgPatterns, pkgs) + } + // Everything succeeded. Update go.mod. modload.AllowWriteGoMod() modload.WriteGoMod() @@ -715,21 +614,174 @@ func runGet(ctx context.Context, cmd *base.Command, args []string) { // contains information about direct dependencies that WriteGoMod uses. // Refactor to avoid these kinds of global side effects. reportRetractions(ctx) +} - // If -d was specified, we're done after the module work. - // We've already downloaded modules by loading packages above. - // Otherwise, we need to build and install the packages matched by - // command line arguments. This may be a different set of packages, - // since we only build packages for the target platform. - // 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 { - return +// 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(rawArgs []string) []getArg { + defer base.ExitIfErrors() + + var gets []getArg + for _, raw := range search.CleanPatterns(rawArgs) { + // Argument is path or path@vers. + path := raw + vers := "" + if i := strings.Index(raw, "@"); i >= 0 { + path, vers = raw[:i], raw[i+1:] + } + if strings.Contains(vers, "@") || raw != path && vers == "" { + base.Errorf("go get %s: invalid module version syntax", raw) + continue + } + + // 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(raw, ".go") && vers == "" { + if !strings.Contains(raw, "/") { + base.Errorf("go get %s: arguments must be package or module paths", raw) + continue + } + if fi, err := os.Stat(raw); err == nil && !fi.IsDir() { + base.Errorf("go get: %s exists as a file, but 'go get' requires package arguments", raw) + continue + } + } + + // If no version suffix is specified, assume @upgrade. + // If -u=patch was specified, assume @patch instead. + if vers == "" { + if getU != "" { + vers = string(getU) + } else { + vers = "upgrade" + } + } + + gets = append(gets, getArg{raw: raw, path: path, vers: vers}) } - work.BuildInit() - pkgs := load.PackagesForBuild(ctx, pkgPatterns) - work.InstallPackages(ctx, pkgPatterns, pkgs) + + return gets +} + +// classifyArgs determines which arguments refer to packages and which refer to +// modules, and creates queries to look up modules at target versions before +// loading packages. +// +// This is an imprecise process, but it helps reduce unnecessary +// queries and package loading. It's also necessary for handling +// patterns like golang.org/x/tools/..., which can't be expanded +// during package loading until they're in the build list. +func classifyArgs(ctx context.Context, selectedVersion map[string]string, args []getArg) []*query { + defer base.ExitIfErrors() + + queries := make([]*query, 0, len(args)) + + for _, arg := range args { + path := arg.path + switch { + case filepath.IsAbs(path) || search.IsRelativePath(path): + // Absolute paths like C:\foo and relative paths like ../foo... + // are restricted to matching packages in the main module. If the path + // is explicit and contains no wildcards (...), check that it is a + // package in the main module. If the path contains wildcards but + // matches no packages, we'll warn after package loading. + if !strings.Contains(path, "...") { + m := search.NewMatch(path) + if pkgPath := modload.DirImportPath(path); pkgPath != "." { + m = modload.TargetPackages(ctx, pkgPath) + } + if len(m.Pkgs) == 0 { + for _, err := range m.Errs { + base.Errorf("go get %s: %v", arg, err) + } + + abs, err := filepath.Abs(path) + if err != nil { + abs = path + } + base.Errorf("go get %s: path %s is not a package in module rooted at %s", arg, abs, modload.ModRoot()) + continue + } + } + + if arg.path != arg.raw { + base.Errorf("go get %s: can't request explicit version of path in main module", arg) + continue + } + + case strings.Contains(path, "..."): + // Wait until we load packages to look up modules. + // We don't know yet whether any modules in the build list provide + // packages matching the pattern. For example, suppose + // golang.org/x/tools and golang.org/x/tools/playground are separate + // modules, and only golang.org/x/tools is in the build list. If the + // user runs 'go get golang.org/x/tools/playground/...', we should + // add a requirement for golang.org/x/tools/playground. We should not + // upgrade golang.org/x/tools. + + case path == "all": + // If there is no main module, "all" is not meaningful. + if !modload.HasModRoot() { + base.Errorf(`go get %s: cannot match "all": working directory is not part of a module`, arg) + } + // Don't query modules until we load packages. We'll automatically + // look up any missing modules. + + case search.IsMetaPackage(path): + base.Errorf("go get %s: explicit requirement on standard-library module %s not allowed", path, path) + continue + + default: + // The argument is a package or module path. + if modload.HasModRoot() { + if m := modload.TargetPackages(ctx, path); len(m.Pkgs) != 0 { + // The path is in the main module. Nothing to query. + if arg.vers != "upgrade" && arg.vers != "patch" { + base.Errorf("go get %s: can't request explicit version of path in main module", arg) + } + continue + } + } + + first := path + if i := strings.IndexByte(first, '/'); i >= 0 { + first = path + } + if !strings.Contains(first, ".") { + // The path doesn't have a dot in the first component and cannot be + // queried as a module. It may be a package in the standard library, + // which is fine, so don't report an error unless we encounter + // a problem loading packages. + continue + } + + // If we're querying "upgrade" or "patch", we need to know the current + // version of the module. For "upgrade", we want to avoid accidentally + // downgrading from a newer prerelease. For "patch", we need to query + // the correct minor version. + // Here, we check if "path" is the name of a module in the build list + // (other than the main module) and set prevM if so. If "path" isn't + // a module in the build list, the current version doesn't matter + // since it's either an unknown module or a package within a module + // that we'll discover later. + q := &query{querySpec: querySpec{path: arg.path, vers: arg.vers}, arg: arg.raw} + if v, ok := selectedVersion[path]; ok { + if path == modload.Target.Path { + // TODO(bcmills): This is held over from a previous version of the get + // implementation. Why was it a special case? + } else { + q.prevM = module.Version{Path: path, Version: v} + q.forceModulePath = true + } + } + queries = append(queries, q) + } + } + + return queries } // runQueries looks up modules at target versions in parallel. Results will be |