diff options
author | Jay Conrod <jayconrod@google.com> | 2021-04-15 11:36:23 -0400 |
---|---|---|
committer | Jay Conrod <jayconrod@google.com> | 2021-04-16 14:15:49 +0000 |
commit | 0613c748e8919536c360cfc9be4e63a0b55d4286 (patch) | |
tree | d06fd9e8098aedf7d1d031140177d44657f0d945 /src/cmd/go/internal/load/pkg.go | |
parent | dc76c4756599ac4bd9644e407aa2af9f9c8a3bdc (diff) | |
download | go-0613c748e8919536c360cfc9be4e63a0b55d4286.tar.gz go-0613c748e8919536c360cfc9be4e63a0b55d4286.zip |
cmd/go: move 'go install cmd@version' code into internal/load
'go run cmd@version' will use the same code.
This changes error messages a bit.
For #42088
Change-Id: Iaed3997a3d27f9fc0e868013ab765f1fb638a0b5
Reviewed-on: https://go-review.googlesource.com/c/go/+/310410
Trust: Jay Conrod <jayconrod@google.com>
Run-TryBot: Jay Conrod <jayconrod@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Bryan C. Mills <bcmills@google.com>
Diffstat (limited to 'src/cmd/go/internal/load/pkg.go')
-rw-r--r-- | src/cmd/go/internal/load/pkg.go | 187 |
1 files changed, 187 insertions, 0 deletions
diff --git a/src/cmd/go/internal/load/pkg.go b/src/cmd/go/internal/load/pkg.go index 8a74930209..98a695ca24 100644 --- a/src/cmd/go/internal/load/pkg.go +++ b/src/cmd/go/internal/load/pkg.go @@ -14,6 +14,7 @@ import ( "go/build" "go/scanner" "go/token" + "internal/goroot" "io/fs" "os" "path" @@ -30,6 +31,7 @@ import ( "cmd/go/internal/cfg" "cmd/go/internal/fsys" "cmd/go/internal/imports" + "cmd/go/internal/modfetch" "cmd/go/internal/modinfo" "cmd/go/internal/modload" "cmd/go/internal/par" @@ -38,6 +40,7 @@ import ( "cmd/go/internal/trace" "cmd/internal/sys" + "golang.org/x/mod/modfile" "golang.org/x/mod/module" ) @@ -2390,6 +2393,13 @@ type PackageOpts struct { // of those packages could be missing, and resolving those missing dependencies // could change the selected versions of modules that provide other packages. ModResolveTests bool + + // MainOnly is true if the caller only wants to load main packages. + // For a literal argument matching a non-main package, a stub may be returned + // with an error. For a non-literal argument (with "..."), non-main packages + // are not be matched, and their dependencies may not be loaded. A warning + // may be printed for non-literal arguments that match no main packages. + MainOnly bool } // PackagesAndErrors returns the packages named by the command line arguments @@ -2480,6 +2490,10 @@ func PackagesAndErrors(ctx context.Context, opts PackageOpts, patterns []string) } } + if opts.MainOnly { + pkgs = mainPackagesOnly(pkgs, patterns) + } + // Now that CmdlinePkg is set correctly, // compute the effective flags for all loaded packages // (not just the ones matching the patterns but also @@ -2528,6 +2542,51 @@ func CheckPackageErrors(pkgs []*Package) { base.ExitIfErrors() } +// mainPackagesOnly filters out non-main packages matched only by arguments +// containing "..." and returns the remaining main packages. +// +// mainPackagesOnly sets a package's error if it is named by a literal argument. +// +// mainPackagesOnly prints warnings for non-literal arguments that only match +// non-main packages. +func mainPackagesOnly(pkgs []*Package, patterns []string) []*Package { + matchers := make([]func(string) bool, len(patterns)) + for i, p := range patterns { + if strings.Contains(p, "...") { + matchers[i] = search.MatchPattern(p) + } + } + + mainPkgs := make([]*Package, 0, len(pkgs)) + mainCount := make([]int, len(patterns)) + nonMainCount := make([]int, len(patterns)) + for _, pkg := range pkgs { + if pkg.Name == "main" { + mainPkgs = append(mainPkgs, pkg) + for i := range patterns { + if matchers[i] != nil && matchers[i](pkg.ImportPath) { + mainCount[i]++ + } + } + } else { + for i := range patterns { + if matchers[i] == nil && patterns[i] == pkg.ImportPath && pkg.Error == nil { + pkg.Error = &PackageError{Err: ImportErrorf(pkg.ImportPath, "package %s is not a main package", pkg.ImportPath)} + } else if matchers[i] != nil && matchers[i](pkg.ImportPath) { + nonMainCount[i]++ + } + } + } + } + for i, p := range patterns { + if matchers[i] != nil && mainCount[i] == 0 && nonMainCount[i] > 0 { + fmt.Fprintf(os.Stderr, "go: warning: %q matched no main packages\n", p) + } + } + + return mainPkgs +} + func setToolFlags(pkgs ...*Package) { for _, p := range PackageList(pkgs) { p.Internal.Asmflags = BuildAsmflags.For(p) @@ -2624,3 +2683,131 @@ func GoFilesPackage(ctx context.Context, opts PackageOpts, gofiles []string) *Pa return pkg } + +// PackagesAndErrorsOutsideModule is like PackagesAndErrors but runs in +// module-aware mode and ignores the go.mod file in the current directory or any +// parent directory, if there is one. This is used in the implementation of 'go +// install pkg@version' and other commands that support similar forms. +// +// modload.ForceUseModules must be true, and modload.RootMode must be NoRoot +// before calling this function. +// +// PackagesAndErrorsOutsideModule imposes several constraints to avoid +// ambiguity. All arguments must have the same version suffix (not just a suffix +// that resolves to the same version). They must refer to packages in the same +// module, which must not be std or cmd. That module is not considered the main +// module, but its go.mod file (if it has one) must not contain directives that +// would cause it to be interpreted differently if it were the main module +// (replace, exclude). +func PackagesAndErrorsOutsideModule(ctx context.Context, opts PackageOpts, args []string) ([]*Package, error) { + if !modload.ForceUseModules { + panic("modload.ForceUseModules must be true") + } + if modload.RootMode != modload.NoRoot { + panic("modload.RootMode must be NoRoot") + } + + // Check that the arguments satisfy syntactic constraints. + var version string + for _, arg := range args { + if i := strings.Index(arg, "@"); i >= 0 { + version = arg[i+1:] + if version == "" { + return nil, fmt.Errorf("%s: version must not be empty", arg) + } + break + } + } + patterns := make([]string, len(args)) + for i, arg := range args { + if !strings.HasSuffix(arg, "@"+version) { + return nil, fmt.Errorf("%s: all arguments must have the same version (@%s)", arg, version) + } + p := arg[:len(arg)-len(version)-1] + switch { + case build.IsLocalImport(p): + return nil, fmt.Errorf("%s: argument must be a package path, not a relative path", arg) + case filepath.IsAbs(p): + return nil, fmt.Errorf("%s: argument must be a package path, not an absolute path", arg) + case search.IsMetaPackage(p): + return nil, fmt.Errorf("%s: argument must be a package path, not a meta-package", arg) + case path.Clean(p) != p: + return nil, fmt.Errorf("%s: argument must be a clean package path", arg) + case !strings.Contains(p, "...") && search.IsStandardImportPath(p) && goroot.IsStandardPackage(cfg.GOROOT, cfg.BuildContext.Compiler, p): + return nil, fmt.Errorf("%s: argument must not be a package in the standard library", arg) + default: + patterns[i] = p + } + } + + // Query the module providing the first argument, load its go.mod file, and + // check that it doesn't contain directives that would cause it to be + // interpreted differently if it were the main module. + // + // If multiple modules match the first argument, accept the longest match + // (first result). It's possible this module won't provide packages named by + // later arguments, and other modules would. Let's not try to be too + // magical though. + allowed := modload.CheckAllowed + if modload.IsRevisionQuery(version) { + // Don't check for retractions if a specific revision is requested. + allowed = nil + } + noneSelected := func(path string) (version string) { return "none" } + qrs, err := modload.QueryPackages(ctx, patterns[0], version, noneSelected, allowed) + if err != nil { + return nil, fmt.Errorf("%s: %w", args[0], err) + } + rootMod := qrs[0].Mod + data, err := modfetch.GoMod(rootMod.Path, rootMod.Version) + if err != nil { + return nil, fmt.Errorf("%s: %w", args[0], err) + } + f, err := modfile.Parse("go.mod", data, nil) + if err != nil { + return nil, fmt.Errorf("%s (in %s): %w", args[0], rootMod, err) + } + directiveFmt := "%s (in %s):\n" + + "\tThe go.mod file for the module providing named packages contains one or\n" + + "\tmore %s directives. It must not contain directives that would cause\n" + + "\tit to be interpreted differently than if it were the main module." + if len(f.Replace) > 0 { + return nil, fmt.Errorf(directiveFmt, args[0], rootMod, "replace") + } + if len(f.Exclude) > 0 { + return nil, fmt.Errorf(directiveFmt, args[0], rootMod, "exclude") + } + + // Since we are in NoRoot mode, the build list initially contains only + // the dummy command-line-arguments module. Add a requirement on the + // module that provides the packages named on the command line. + if _, err := modload.EditBuildList(ctx, nil, []module.Version{rootMod}); err != nil { + return nil, fmt.Errorf("%s: %w", args[0], err) + } + + // Load packages for all arguments. + pkgs := PackagesAndErrors(ctx, opts, patterns) + + // Check that named packages are all provided by the same module. + for _, pkg := range pkgs { + var pkgErr error + if pkg.Module == nil { + // Packages in std, cmd, and their vendored dependencies + // don't have this field set. + pkgErr = fmt.Errorf("package %s not provided by module %s", pkg.ImportPath, rootMod) + } else if pkg.Module.Path != rootMod.Path || pkg.Module.Version != rootMod.Version { + pkgErr = fmt.Errorf("package %s provided by module %s@%s\n\tAll packages must be provided by the same module (%s).", pkg.ImportPath, pkg.Module.Path, pkg.Module.Version, rootMod) + } + if pkgErr != nil && pkg.Error == nil { + pkg.Error = &PackageError{Err: pkgErr} + } + } + + matchers := make([]func(string) bool, len(patterns)) + for i, p := range patterns { + if strings.Contains(p, "...") { + matchers[i] = search.MatchPattern(p) + } + } + return pkgs, nil +} |