diff options
author | Bryan C. Mills <bcmills@google.com> | 2019-07-08 18:13:23 -0400 |
---|---|---|
committer | Bryan C. Mills <bcmills@google.com> | 2020-02-28 19:09:53 +0000 |
commit | 5a61de3fe160cc8b327ee893cd74c4d0ce9dc13d (patch) | |
tree | 3dbf0328ac9de682fd44c47cd28b600a2cacbc84 /src/cmd/go/internal/modload/load.go | |
parent | d11e1f92fc578c5d2e604acfe9ea60d7afb84a0c (diff) | |
download | go-5a61de3fe160cc8b327ee893cd74c4d0ce9dc13d.tar.gz go-5a61de3fe160cc8b327ee893cd74c4d0ce9dc13d.zip |
cmd/go: rationalize errors in internal/load and internal/modload
This change is a non-minimal fix for #32917, but incidentally fixes
several other bugs and makes the error messages much more ergonomic.
Updates #32917
Updates #27122
Updates #28459
Updates #29280
Updates #30590
Updates #37214
Updates #36173
Updates #36587
Fixes #36008
Fixes #30992
Change-Id: Iedb26d2e0963697c130df5d0f72e7f83ec2dcf06
Reviewed-on: https://go-review.googlesource.com/c/go/+/185345
Reviewed-by: Michael Matloob <matloob@golang.org>
Reviewed-by: Jay Conrod <jayconrod@google.com>
Diffstat (limited to 'src/cmd/go/internal/modload/load.go')
-rw-r--r-- | src/cmd/go/internal/modload/load.go | 197 |
1 files changed, 106 insertions, 91 deletions
diff --git a/src/cmd/go/internal/modload/load.go b/src/cmd/go/internal/modload/load.go index 5506fc9b3c..32841d96cb 100644 --- a/src/cmd/go/internal/modload/load.go +++ b/src/cmd/go/internal/modload/load.go @@ -94,79 +94,21 @@ func ImportPathsQuiet(patterns []string, tags map[string]bool) []*search.Match { pkgs := str.StringList(fsDirs[i]) m.Pkgs = pkgs[:0] for _, pkg := range pkgs { - var dir string - if !filepath.IsAbs(pkg) { - dir = filepath.Join(base.Cwd, pkg) - } else { - dir = filepath.Clean(pkg) - } + pkg, err := resolveLocalPackage(pkg) + if err != nil { + if !m.IsLiteral() && (err == errPkgIsBuiltin || err == errPkgIsGorootSrc) { + continue // Don't include "builtin" or GOROOT/src in wildcard patterns. + } - // golang.org/issue/32917: We should resolve a relative path to a - // package path only if the relative path actually contains the code - // for that package. - if !dirContainsPackage(dir) { // If we're outside of a module, ensure that the failure mode // indicates that. ModRoot() - // If the directory is local but does not exist, don't return it - // while loader is iterating, since this might trigger a fetch. - // After loader is done iterating, we still need to return the - // path, so that "go list -e" produces valid output. if !iterating { - // We don't have a valid path to resolve to, so report the - // unresolved path. - m.Pkgs = append(m.Pkgs, pkg) + m.AddError(err) } continue } - - // Note: The checks for @ here are just to avoid misinterpreting - // the module cache directories (formerly GOPATH/src/mod/foo@v1.5.2/bar). - // It's not strictly necessary but helpful to keep the checks. - if modRoot != "" && dir == modRoot { - pkg = targetPrefix - if modRoot == cfg.GOROOTsrc { - // A package in GOROOT/src would have an empty path. - // Keep the path as cfg.GOROOTsrc. We'll report an error in Import. - // See golang.org/issue/36587. - pkg = modRoot - } - } else if modRoot != "" && strings.HasPrefix(dir, modRoot+string(filepath.Separator)) && !strings.Contains(dir[len(modRoot):], "@") { - suffix := filepath.ToSlash(dir[len(modRoot):]) - if strings.HasPrefix(suffix, "/vendor/") { - // TODO getmode vendor check - pkg = strings.TrimPrefix(suffix, "/vendor/") - } else if targetInGorootSrc && Target.Path == "std" { - // Don't add the prefix "std/" to packages in the "std" module. - // It's the one module path that isn't a prefix of its packages. - pkg = strings.TrimPrefix(suffix, "/") - if pkg == "builtin" { - // "builtin" is a pseudo-package with a real source file. - // It's not included in "std", so it shouldn't be included in - // "./..." within module "std" either. - continue - } - } else { - modPkg := targetPrefix + suffix - if _, ok := dirInModule(modPkg, targetPrefix, modRoot, true); ok { - pkg = modPkg - } else if !iterating { - ModRoot() - base.Errorf("go: directory %s is outside main module", base.ShortPath(dir)) - } - } - } else if sub := search.InDir(dir, cfg.GOROOTsrc); sub != "" && sub != "." && !strings.Contains(sub, "@") { - pkg = filepath.ToSlash(sub) - } else if path := pathInModuleCache(dir); path != "" { - pkg = path - } else { - pkg = "" - if !iterating { - ModRoot() - base.Errorf("go: directory %s outside available modules", base.ShortPath(dir)) - } - } m.Pkgs = append(m.Pkgs, pkg) } @@ -244,6 +186,105 @@ func checkMultiplePaths() { base.ExitIfErrors() } +// resolveLocalPackage resolves a filesystem path to a package path. +func resolveLocalPackage(dir string) (string, error) { + var absDir string + if filepath.IsAbs(dir) { + absDir = filepath.Clean(dir) + } else { + absDir = filepath.Join(base.Cwd, dir) + } + + bp, err := cfg.BuildContext.ImportDir(absDir, 0) + if err != nil && (bp == nil || len(bp.IgnoredGoFiles) == 0) { + // golang.org/issue/32917: We should resolve a relative path to a + // package path only if the relative path actually contains the code + // for that package. + // + // If the named directory does not exist or contains no Go files, + // the package does not exist. + // Other errors may affect package loading, but not resolution. + if _, err := os.Stat(absDir); err != nil { + if os.IsNotExist(err) { + // Canonicalize OS-specific errors to errDirectoryNotFound so that error + // messages will be easier for users to search for. + return "", &os.PathError{Op: "stat", Path: absDir, Err: errDirectoryNotFound} + } + return "", err + } + if _, noGo := err.(*build.NoGoError); noGo { + // A directory that does not contain any Go source files — even ignored + // ones! — is not a Go package, and we can't resolve it to a package + // path because that path could plausibly be provided by some other + // module. + // + // Any other error indicates that the package “exists” (at least in the + // sense that it cannot exist in any other module), but has some other + // problem (such as a syntax error). + return "", err + } + } + + if modRoot != "" && absDir == modRoot { + if absDir == cfg.GOROOTsrc { + return "", errPkgIsGorootSrc + } + return targetPrefix, nil + } + + // Note: The checks for @ here are just to avoid misinterpreting + // the module cache directories (formerly GOPATH/src/mod/foo@v1.5.2/bar). + // It's not strictly necessary but helpful to keep the checks. + if modRoot != "" && strings.HasPrefix(absDir, modRoot+string(filepath.Separator)) && !strings.Contains(absDir[len(modRoot):], "@") { + suffix := filepath.ToSlash(absDir[len(modRoot):]) + if strings.HasPrefix(suffix, "/vendor/") { + if cfg.BuildMod != "vendor" { + return "", fmt.Errorf("without -mod=vendor, directory %s has no package path", absDir) + } + + readVendorList() + pkg := strings.TrimPrefix(suffix, "/vendor/") + if _, ok := vendorPkgModule[pkg]; !ok { + return "", fmt.Errorf("directory %s is not a package listed in vendor/modules.txt", absDir) + } + return pkg, nil + } + + if targetPrefix == "" { + pkg := strings.TrimPrefix(suffix, "/") + if pkg == "builtin" { + // "builtin" is a pseudo-package with a real source file. + // It's not included in "std", so it shouldn't resolve from "." + // within module "std" either. + return "", errPkgIsBuiltin + } + return pkg, nil + } + + pkg := targetPrefix + suffix + if _, ok := dirInModule(pkg, targetPrefix, modRoot, true); !ok { + return "", &PackageNotInModuleError{Mod: Target, Pattern: pkg} + } + return pkg, nil + } + + if sub := search.InDir(absDir, cfg.GOROOTsrc); sub != "" && sub != "." && !strings.Contains(sub, "@") { + return filepath.ToSlash(sub), nil + } + + pkg := pathInModuleCache(absDir) + if pkg == "" { + return "", fmt.Errorf("directory %s outside available modules", base.ShortPath(absDir)) + } + return pkg, nil +} + +var ( + errDirectoryNotFound = errors.New("directory not found") + errPkgIsGorootSrc = errors.New("GOROOT/src is not an importable package") + errPkgIsBuiltin = errors.New(`"builtin" is a pseudo-package, not an importable package`) +) + // pathInModuleCache returns the import path of the directory dir, // if dir is in the module cache copy of a module in our build list. func pathInModuleCache(dir string) string { @@ -273,32 +314,6 @@ func pathInModuleCache(dir string) string { return "" } -var dirContainsPackageCache sync.Map // absolute dir → bool - -func dirContainsPackage(dir string) bool { - isPkg, ok := dirContainsPackageCache.Load(dir) - if !ok { - _, err := cfg.BuildContext.ImportDir(dir, 0) - if err == nil { - isPkg = true - } else { - if fi, statErr := os.Stat(dir); statErr != nil || !fi.IsDir() { - // A non-directory or inaccessible directory is not a Go package. - isPkg = false - } else if _, noGo := err.(*build.NoGoError); noGo { - // A directory containing no Go source files is not a Go package. - isPkg = false - } else { - // An error other than *build.NoGoError indicates that the package exists - // but has some other problem (such as a syntax error). - isPkg = true - } - } - isPkg, _ = dirContainsPackageCache.LoadOrStore(dir, isPkg) - } - return isPkg.(bool) -} - // ImportFromFiles adds modules to the build list as needed // to satisfy the imports in the named Go source files. func ImportFromFiles(gofiles []string) { @@ -767,7 +782,7 @@ func (ld *loader) doPkg(item interface{}) { // Leave for error during load. return } - if build.IsLocalImport(pkg.path) { + if build.IsLocalImport(pkg.path) || filepath.IsAbs(pkg.path) { // Leave for error during load. // (Module mode does not allow local imports.) return |