// module cache has 0444 directories;
// make them writable in order to remove content.
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
- if err != nil {
- return nil // ignore errors walking in file system
- }
- if info.IsDir() {
+ // chmod not only directories, but also things that we couldn't even stat
+ // due to permission errors: they may also be unreadable directories.
+ if err != nil || info.IsDir() {
os.Chmod(path, 0777)
}
return nil
// package in the main module. If the path contains wildcards but
// matches no packages, we'll warn after package loading.
if !strings.Contains(path, "...") {
- var pkgs []string
+ m := search.NewMatch(path)
if pkgPath := modload.DirImportPath(path); pkgPath != "." {
- pkgs = modload.TargetPackages(pkgPath)
+ m = modload.TargetPackages(pkgPath)
}
- if len(pkgs) == 0 {
+ 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
default:
// The argument is a package or module path.
if modload.HasModRoot() {
- if pkgs := modload.TargetPackages(path); len(pkgs) != 0 {
+ if m := modload.TargetPackages(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)
pathIsStd := search.IsStandardImportPath(path)
if pathIsStd && goroot.IsStandardPackage(cfg.GOROOT, cfg.BuildContext.Compiler, path) {
if targetInGorootSrc {
- if dir, ok := dirInModule(path, targetPrefix, ModRoot(), true); ok {
+ if dir, ok, err := dirInModule(path, targetPrefix, ModRoot(), true); err != nil {
+ return module.Version{}, dir, err
+ } else if ok {
return Target, dir, nil
}
}
// -mod=vendor is special.
// Everything must be in the main module or the main module's vendor directory.
if cfg.BuildMod == "vendor" {
- mainDir, mainOK := dirInModule(path, targetPrefix, ModRoot(), true)
- vendorDir, vendorOK := dirInModule(path, "", filepath.Join(ModRoot(), "vendor"), false)
+ mainDir, mainOK, mainErr := dirInModule(path, targetPrefix, ModRoot(), true)
+ vendorDir, vendorOK, _ := dirInModule(path, "", filepath.Join(ModRoot(), "vendor"), false)
if mainOK && vendorOK {
return module.Version{}, "", &AmbiguousImportError{importPath: path, Dirs: []string{mainDir, vendorDir}}
}
if !vendorOK && mainDir != "" {
return Target, mainDir, nil
}
+ if mainErr != nil {
+ return module.Version{}, "", mainErr
+ }
readVendorList()
return vendorPkgModule[path], vendorDir, nil
}
// not ambiguous.
return module.Version{}, "", err
}
- dir, ok := dirInModule(path, m.Path, root, isLocal)
- if ok {
+ if dir, ok, err := dirInModule(path, m.Path, root, isLocal); err != nil {
+ return module.Version{}, "", err
+ } else if ok {
mods = append(mods, m)
dirs = append(dirs, dir)
}
// Report fetch error as above.
return module.Version{}, "", err
}
- _, ok := dirInModule(path, m.Path, root, isLocal)
- if ok {
+ if _, ok, err := dirInModule(path, m.Path, root, isLocal); err != nil {
+ return m, "", err
+ } else if ok {
return m, "", &ImportMissingError{Path: path, Module: m}
}
}
len(path) > len(mpath) && path[len(mpath)] == '/' && path[:len(mpath)] == mpath
}
-var haveGoModCache, haveGoFilesCache par.Cache
+var (
+ haveGoModCache par.Cache // dir → bool
+ haveGoFilesCache par.Cache // dir → goFilesEntry
+)
+
+type goFilesEntry struct {
+ haveGoFiles bool
+ err error
+}
// dirInModule locates the directory that would hold the package named by the given path,
// if it were in the module with module path mpath and root mdir.
// If path is syntactically not within mpath,
// or if mdir is a local file tree (isLocal == true) and the directory
// that would hold path is in a sub-module (covered by a go.mod below mdir),
-// dirInModule returns "", false.
+// dirInModule returns "", false, nil.
//
// Otherwise, dirInModule returns the name of the directory where
// Go source files would be expected, along with a boolean indicating
// whether there are in fact Go source files in that directory.
-func dirInModule(path, mpath, mdir string, isLocal bool) (dir string, haveGoFiles bool) {
+// A non-nil error indicates that the existence of the directory and/or
+// source files could not be determined, for example due to a permission error.
+func dirInModule(path, mpath, mdir string, isLocal bool) (dir string, haveGoFiles bool, err error) {
// Determine where to expect the package.
if path == mpath {
dir = mdir
} else if len(path) > len(mpath) && path[len(mpath)] == '/' && path[:len(mpath)] == mpath {
dir = filepath.Join(mdir, path[len(mpath)+1:])
} else {
- return "", false
+ return "", false, nil
}
// Check that there aren't other modules in the way.
}).(bool)
if haveGoMod {
- return "", false
+ return "", false, nil
}
parent := filepath.Dir(d)
if parent == d {
// Are there Go source files in the directory?
// We don't care about build tags, not even "+build ignore".
// We're just looking for a plausible directory.
- haveGoFiles = haveGoFilesCache.Do(dir, func() interface{} {
- f, err := os.Open(dir)
- if err != nil {
- return false
+ res := haveGoFilesCache.Do(dir, func() interface{} {
+ ok, err := isDirWithGoFiles(dir)
+ return goFilesEntry{haveGoFiles: ok, err: err}
+ }).(goFilesEntry)
+
+ return dir, res.haveGoFiles, res.err
+}
+
+func isDirWithGoFiles(dir string) (bool, error) {
+ f, err := os.Open(dir)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return false, nil
}
- defer f.Close()
- names, _ := f.Readdirnames(-1)
- for _, name := range names {
- if strings.HasSuffix(name, ".go") {
- info, err := os.Stat(filepath.Join(dir, name))
- if err == nil && info.Mode().IsRegular() {
- return true
+ return false, err
+ }
+ defer f.Close()
+
+ names, firstErr := f.Readdirnames(-1)
+ if firstErr != nil {
+ if fi, err := f.Stat(); err == nil && !fi.IsDir() {
+ return false, nil
+ }
+
+ // Rewrite the error from ReadDirNames to include the path if not present.
+ // See https://golang.org/issue/38923.
+ var pe *os.PathError
+ if !errors.As(firstErr, &pe) {
+ firstErr = &os.PathError{Op: "readdir", Path: dir, Err: firstErr}
+ }
+ }
+
+ for _, name := range names {
+ if strings.HasSuffix(name, ".go") {
+ info, err := os.Stat(filepath.Join(dir, name))
+ if err == nil && info.Mode().IsRegular() {
+ // If any .go source file exists, the package exists regardless of
+ // errors for other source files. Leave further error reporting for
+ // later.
+ return true, nil
+ }
+ if firstErr == nil {
+ if os.IsNotExist(err) {
+ // If the file was concurrently deleted, or was a broken symlink,
+ // convert the error to an opaque error instead of one matching
+ // os.IsNotExist.
+ err = errors.New(err.Error())
}
+ firstErr = err
}
}
- return false
- }).(bool)
+ }
- return dir, haveGoFiles
+ return false, firstErr
}
m.Pkgs = []string{m.Pattern()}
case strings.Contains(m.Pattern(), "..."):
- m.Pkgs = matchPackages(m.Pattern(), loaded.tags, true, buildList)
+ m.Errs = m.Errs[:0]
+ matchPackages(m, loaded.tags, includeStd, buildList)
case m.Pattern() == "all":
loaded.testAll = true
if iterating {
// Enumerate the packages in the main module.
// We'll load the dependencies as we find them.
- m.Pkgs = matchPackages("...", loaded.tags, false, []module.Version{Target})
+ m.Errs = m.Errs[:0]
+ matchPackages(m, loaded.tags, omitStd, []module.Version{Target})
} else {
// Starting with the packages in the main module,
// enumerate the full list of "all".
}
pkg := targetPrefix + suffix
- if _, ok := dirInModule(pkg, targetPrefix, modRoot, true); !ok {
+ if _, ok, err := dirInModule(pkg, targetPrefix, modRoot, true); err != nil {
+ return "", err
+ } else if !ok {
return "", &PackageNotInModuleError{Mod: Target, Pattern: pkg}
}
return pkg, nil
loaded.testRoots = true
}
all := TargetPackages("...")
- loaded.load(func() []string { return all })
+ loaded.load(func() []string { return all.Pkgs })
checkMultiplePaths()
WriteGoMod()
}
paths = append(paths, pkg.path)
}
+ for _, err := range all.Errs {
+ base.Errorf("%v", err)
+ }
base.ExitIfErrors()
return paths
}
// TargetPackages returns the list of packages in the target (top-level) module
// matching pattern, which may be relative to the working directory, under all
// build tag settings.
-func TargetPackages(pattern string) []string {
+func TargetPackages(pattern string) *search.Match {
// TargetPackages is relative to the main module, so ensure that the main
// module is a thing that can contain packages.
ModRoot()
- return matchPackages(pattern, imports.AnyTags(), false, []module.Version{Target})
+ m := search.NewMatch(pattern)
+ matchPackages(m, imports.AnyTags(), omitStd, []module.Version{Target})
+ return m
}
// BuildList returns the module build list,
// possible modules.
func QueryPattern(pattern, query string, allowed func(module.Version) bool) ([]QueryResult, error) {
base := pattern
- var match func(m module.Version, root string, isLocal bool) (pkgs []string)
+
+ firstError := func(m *search.Match) error {
+ if len(m.Errs) == 0 {
+ return nil
+ }
+ return m.Errs[0]
+ }
+
+ var match func(mod module.Version, root string, isLocal bool) *search.Match
if i := strings.Index(pattern, "..."); i >= 0 {
base = pathpkg.Dir(pattern[:i+3])
- match = func(m module.Version, root string, isLocal bool) []string {
- return matchPackages(pattern, imports.AnyTags(), false, []module.Version{m})
+ match = func(mod module.Version, root string, isLocal bool) *search.Match {
+ m := search.NewMatch(pattern)
+ matchPackages(m, imports.AnyTags(), omitStd, []module.Version{mod})
+ return m
}
} else {
- match = func(m module.Version, root string, isLocal bool) []string {
- prefix := m.Path
- if m == Target {
+ match = func(mod module.Version, root string, isLocal bool) *search.Match {
+ m := search.NewMatch(pattern)
+ prefix := mod.Path
+ if mod == Target {
prefix = targetPrefix
}
- if _, ok := dirInModule(pattern, prefix, root, isLocal); ok {
- return []string{pattern}
- } else {
- return nil
+ if _, ok, err := dirInModule(pattern, prefix, root, isLocal); err != nil {
+ m.AddError(err)
+ } else if ok {
+ m.Pkgs = []string{pattern}
}
+ return m
}
}
if HasModRoot() {
- pkgs := match(Target, modRoot, true)
- if len(pkgs) > 0 {
+ m := match(Target, modRoot, true)
+ if len(m.Pkgs) > 0 {
if query != "latest" {
return nil, fmt.Errorf("can't query specific version for package %s in the main module (%s)", pattern, Target.Path)
}
return []QueryResult{{
Mod: Target,
Rev: &modfetch.RevInfo{Version: Target.Version},
- Packages: pkgs,
+ Packages: m.Pkgs,
}}, nil
}
+ if err := firstError(m); err != nil {
+ return nil, err
+ }
}
var (
if err != nil {
return r, err
}
- r.Packages = match(r.Mod, root, isLocal)
+ m := match(r.Mod, root, isLocal)
+ r.Packages = m.Pkgs
if len(r.Packages) == 0 {
+ if err := firstError(m); err != nil {
+ return r, err
+ }
return r, &PackageNotInModuleError{
Mod: r.Mod,
Replacement: Replacement(r.Mod),
if err != nil {
return false, err
}
- _, ok := dirInModule(m.Path, m.Path, root, isLocal)
- return ok, nil
+ _, ok, err := dirInModule(m.Path, m.Path, root, isLocal)
+ return ok, err
}
func versionHasGoMod(m module.Version) (bool, error) {
"path/filepath"
"strings"
- "cmd/go/internal/base"
"cmd/go/internal/cfg"
"cmd/go/internal/imports"
"cmd/go/internal/search"
"golang.org/x/mod/module"
)
-// matchPackages returns a list of packages in the list of modules
-// matching the pattern. Package loading assumes the given set of tags.
-func matchPackages(pattern string, tags map[string]bool, useStd bool, modules []module.Version) []string {
- match := func(string) bool { return true }
+type stdFilter int8
+
+const (
+ omitStd = stdFilter(iota)
+ includeStd
+)
+
+// matchPackages is like m.MatchPackages, but uses a local variable (rather than
+// a global) for tags, can include or exclude packages in the standard library,
+// and is restricted to the given list of modules.
+func matchPackages(m *search.Match, tags map[string]bool, filter stdFilter, modules []module.Version) {
+ m.Pkgs = []string{}
+
+ isMatch := func(string) bool { return true }
treeCanMatch := func(string) bool { return true }
- if !search.IsMetaPackage(pattern) {
- match = search.MatchPattern(pattern)
- treeCanMatch = search.TreeCanMatchPattern(pattern)
+ if !m.IsMeta() {
+ isMatch = search.MatchPattern(m.Pattern())
+ treeCanMatch = search.TreeCanMatchPattern(m.Pattern())
}
have := map[string]bool{
if !cfg.BuildContext.CgoEnabled {
have["runtime/cgo"] = true // ignore during walk
}
- var pkgs []string
type pruning int8
const (
walkPkgs := func(root, importPathRoot string, prune pruning) {
root = filepath.Clean(root)
- filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
+ err := filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
if err != nil {
+ m.AddError(err)
return nil
}
if !have[name] {
have[name] = true
- if match(name) {
+ if isMatch(name) {
if _, _, err := scanDir(path, tags); err != imports.ErrNoGo {
- pkgs = append(pkgs, name)
+ m.Pkgs = append(m.Pkgs, name)
}
}
}
}
return nil
})
+ if err != nil {
+ m.AddError(err)
+ }
}
- if useStd {
+ if filter == includeStd {
walkPkgs(cfg.GOROOTsrc, "", pruneGoMod)
if treeCanMatch("cmd") {
walkPkgs(filepath.Join(cfg.GOROOTsrc, "cmd"), "cmd", pruneGoMod)
walkPkgs(ModRoot(), targetPrefix, pruneGoMod|pruneVendor)
walkPkgs(filepath.Join(ModRoot(), "vendor"), "", pruneVendor)
}
- return pkgs
+ return
}
for _, mod := range modules {
var err error
root, isLocal, err = fetch(mod)
if err != nil {
- base.Errorf("go: %v", err)
+ m.AddError(err)
continue
}
modPrefix = mod.Path
walkPkgs(root, modPrefix, prune)
}
- return pkgs
+ return
}
root += "cmd" + string(filepath.Separator)
}
err := filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
- if err != nil || path == src {
- return nil
+ if err != nil {
+ return err // Likely a permission error, which could interfere with matching.
+ }
+ if path == src {
+ return nil // GOROOT/src and GOPATH/src cannot contain packages.
}
want := true
}
err := filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
- if err != nil || !fi.IsDir() {
+ if err != nil {
+ return err // Likely a permission error, which could interfere with matching.
+ }
+ if !fi.IsDir() {
return nil
}
top := false
cd $WORK
# Check output of go list to ensure no duplicates
-go list xtestonly ./testdata/src/xtestonly/...
+go list xtestonly ./tmp/testdata/src/xtestonly/...
cmp stdout $WORK/gopath/src/wantstdout
-- wantstdout --
package foo
-- $WORK/goroot/src/fmt/fmt.go --
package fmt
+-- $WORK/goroot/src/cmd/README --
+This directory must exist in order for the 'cmd' pattern to have something to
+match against.
-- $GOPATH/src/foo.go --
package foo
--- /dev/null
+env GO111MODULE=on
+
+# Establish baseline behavior, before mucking with file permissions.
+
+go list ./noread/...
+stdout '^example.com/noread$'
+
+go list example.com/noread/...
+stdout '^example.com/noread$'
+
+go list ./empty/...
+stderr 'matched no packages'
+
+[root] stop # Root typically ignores file permissions.
+
+# Make the directory ./noread unreadable, and verify that 'go list' reports an
+# explicit error for a pattern that should match it (rather than treating it as
+# equivalent to an empty directory).
+
+[windows] skip # Does not have Unix-style directory permissions.
+[plan9] skip # Might not have Unix-style directory permissions.
+
+chmod 000 noread
+
+# Check explicit paths.
+
+! go list ./noread
+! stdout '^example.com/noread$'
+! stderr 'matched no packages'
+
+! go list example.com/noread
+! stdout '^example.com/noread$'
+! stderr 'matched no packages'
+
+# Check filesystem-relative patterns.
+
+! go list ./...
+! stdout '^example.com/noread$'
+! stderr 'matched no packages'
+stderr '^pattern ./...: '
+
+! go list ./noread/...
+! stdout '^example.com/noread$'
+! stderr 'matched no packages'
+stderr '^pattern ./noread/...: '
+
+
+# Check module-prefix patterns.
+
+! go list example.com/...
+! stdout '^example.com/noread$'
+! stderr 'matched no packages'
+stderr '^pattern example.com/...: '
+
+! go list example.com/noread/...
+! stdout '^example.com/noread$'
+! stderr 'matched no packages'
+stderr '^pattern example.com/noread/...: '
+
+
+[short] stop
+
+# Check global patterns, which should still
+# fail due to errors in the local module.
+
+! go list all
+! stdout '^example.com/noread$'
+! stderr 'matched no packages'
+stderr '^pattern all: '
+
+! go list ...
+! stdout '^example.com/noread$'
+! stderr 'matched no packages'
+stderr '^pattern ...: '
+
+
+-- go.mod --
+module example.com
+go 1.15
+-- noread/noread.go --
+// Package noread exists, but will be made unreadable.
+package noread
+-- empty/README.txt --
+This directory intentionally left empty.