]> Cypherpunks.ru repositories - gostls13.git/blobdiff - src/cmd/go/internal/modload/search.go
cmd/go/internal/modload: omit return at the end of matchPackages
[gostls13.git] / src / cmd / go / internal / modload / search.go
index 799c48e50a8caca851b197c1ee584411bf2ad55c..d392b5bf3dd9780ec02e2d0a8d96adff7e7254c5 100644 (file)
@@ -6,16 +6,27 @@ package modload
 
 import (
        "context"
+       "errors"
        "fmt"
        "io/fs"
        "os"
+       "path"
        "path/filepath"
+       "runtime"
+       "sort"
        "strings"
+       "sync"
 
        "cmd/go/internal/cfg"
        "cmd/go/internal/fsys"
+       "cmd/go/internal/gover"
        "cmd/go/internal/imports"
+       "cmd/go/internal/modindex"
+       "cmd/go/internal/par"
        "cmd/go/internal/search"
+       "cmd/go/internal/str"
+       "cmd/go/internal/trace"
+       "cmd/internal/pkgpattern"
 
        "golang.org/x/mod/module"
 )
@@ -31,18 +42,27 @@ const (
 // a global) for tags, can include or exclude packages in the standard library,
 // and is restricted to the given list of modules.
 func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, filter stdFilter, modules []module.Version) {
+       ctx, span := trace.StartSpan(ctx, "modload.matchPackages")
+       defer span.Done()
+
        m.Pkgs = []string{}
 
        isMatch := func(string) bool { return true }
        treeCanMatch := func(string) bool { return true }
        if !m.IsMeta() {
-               isMatch = search.MatchPattern(m.Pattern())
-               treeCanMatch = search.TreeCanMatchPattern(m.Pattern())
+               isMatch = pkgpattern.MatchPattern(m.Pattern())
+               treeCanMatch = pkgpattern.TreeCanMatchPattern(m.Pattern())
        }
 
+       var mu sync.Mutex
        have := map[string]bool{
                "builtin": true, // ignore pseudo-package that exists only for documentation
        }
+       addPkg := func(p string) {
+               mu.Lock()
+               m.Pkgs = append(m.Pkgs, p)
+               mu.Unlock()
+       }
        if !cfg.BuildContext.CgoEnabled {
                have["runtime/cgo"] = true // ignore during walk
        }
@@ -53,9 +73,17 @@ func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, f
                pruneGoMod
        )
 
+       q := par.NewQueue(runtime.GOMAXPROCS(0))
+
        walkPkgs := func(root, importPathRoot string, prune pruning) {
-               root = filepath.Clean(root)
-               err := fsys.Walk(root, func(path string, fi fs.FileInfo, err error) error {
+               _, span := trace.StartSpan(ctx, "walkPkgs "+root)
+               defer span.Done()
+
+               // If the root itself is a symlink to a directory,
+               // we want to follow it (see https://go.dev/issue/50807).
+               // Add a trailing separator to force that to happen.
+               root = str.WithFilePathSeparator(filepath.Clean(root))
+               err := fsys.Walk(root, func(pkgDir string, fi fs.FileInfo, err error) error {
                        if err != nil {
                                m.AddError(err)
                                return nil
@@ -65,30 +93,27 @@ func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, f
                        elem := ""
 
                        // Don't use GOROOT/src but do walk down into it.
-                       if path == root {
+                       if pkgDir == root {
                                if importPathRoot == "" {
                                        return nil
                                }
                        } else {
                                // Avoid .foo, _foo, and testdata subdirectory trees.
-                               _, elem = filepath.Split(path)
+                               _, elem = filepath.Split(pkgDir)
                                if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
                                        want = false
                                }
                        }
 
-                       name := importPathRoot + filepath.ToSlash(path[len(root):])
-                       if importPathRoot == "" {
-                               name = name[1:] // cut leading slash
-                       }
+                       name := path.Join(importPathRoot, filepath.ToSlash(pkgDir[len(root):]))
                        if !treeCanMatch(name) {
                                want = false
                        }
 
                        if !fi.IsDir() {
                                if fi.Mode()&fs.ModeSymlink != 0 && want && strings.Contains(m.Pattern(), "...") {
-                                       if target, err := fsys.Stat(path); err == nil && target.IsDir() {
-                                               fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", path)
+                                       if target, err := fsys.Stat(pkgDir); err == nil && target.IsDir() {
+                                               fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", pkgDir)
                                        }
                                }
                                return nil
@@ -98,8 +123,8 @@ func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, f
                                return filepath.SkipDir
                        }
                        // Stop at module boundaries.
-                       if (prune&pruneGoMod != 0) && path != root {
-                               if fi, err := os.Stat(filepath.Join(path, "go.mod")); err == nil && !fi.IsDir() {
+                       if (prune&pruneGoMod != 0) && pkgDir != root {
+                               if fi, err := os.Stat(filepath.Join(pkgDir, "go.mod")); err == nil && !fi.IsDir() {
                                        return filepath.SkipDir
                                }
                        }
@@ -107,9 +132,11 @@ func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, f
                        if !have[name] {
                                have[name] = true
                                if isMatch(name) {
-                                       if _, _, err := scanDir(path, tags); err != imports.ErrNoGo {
-                                               m.Pkgs = append(m.Pkgs, name)
-                                       }
+                                       q.Add(func() {
+                                               if _, _, err := scanDir(root, pkgDir, tags); err != imports.ErrNoGo {
+                                                       addPkg(name)
+                                               }
+                                       })
                                }
                        }
 
@@ -123,6 +150,12 @@ func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, f
                }
        }
 
+       // Wait for all in-flight operations to complete before returning.
+       defer func() {
+               <-q.Idle()
+               sort.Strings(m.Pkgs) // sort everything we added for determinism
+       }()
+
        if filter == includeStd {
                walkPkgs(cfg.GOROOTsrc, "", pruneGoMod)
                if treeCanMatch("cmd") {
@@ -131,16 +164,19 @@ func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, f
        }
 
        if cfg.BuildMod == "vendor" {
-               mod := MainModules.mustGetSingleMainModule()
-               if modRoot := MainModules.ModRoot(mod); modRoot != "" {
-                       walkPkgs(modRoot, MainModules.PathPrefix(mod), pruneGoMod|pruneVendor)
-                       walkPkgs(filepath.Join(modRoot, "vendor"), "", pruneVendor)
+               for _, mod := range MainModules.Versions() {
+                       if modRoot := MainModules.ModRoot(mod); modRoot != "" {
+                               walkPkgs(modRoot, MainModules.PathPrefix(mod), pruneGoMod|pruneVendor)
+                       }
+               }
+               if HasModRoot() {
+                       walkPkgs(VendorDir(), "", pruneVendor)
                }
                return
        }
 
        for _, mod := range modules {
-               if !treeCanMatch(mod.Path) {
+               if gover.IsToolchain(mod.Path) || !treeCanMatch(mod.Path) {
                        continue
                }
 
@@ -157,14 +193,19 @@ func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, f
                        isLocal = true
                } else {
                        var err error
-                       const needSum = true
-                       root, isLocal, err = fetch(ctx, mod, needSum)
+                       root, isLocal, err = fetch(ctx, mod)
                        if err != nil {
                                m.AddError(err)
                                continue
                        }
                        modPrefix = mod.Path
                }
+               if mi, err := modindex.GetModule(root); err == nil {
+                       walkFromIndex(mi, modPrefix, isMatch, treeCanMatch, tags, have, addPkg)
+                       continue
+               } else if !errors.Is(err, modindex.ErrNotIndexed) {
+                       m.AddError(err)
+               }
 
                prune := pruneVendor
                if isLocal {
@@ -172,8 +213,53 @@ func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, f
                }
                walkPkgs(root, modPrefix, prune)
        }
+}
 
-       return
+// walkFromIndex matches packages in a module using the module index. modroot
+// is the module's root directory on disk, index is the modindex.Module for the
+// module, and importPathRoot is the module's path prefix.
+func walkFromIndex(index *modindex.Module, importPathRoot string, isMatch, treeCanMatch func(string) bool, tags, have map[string]bool, addPkg func(string)) {
+       index.Walk(func(reldir string) {
+               // Avoid .foo, _foo, and testdata subdirectory trees.
+               p := reldir
+               for {
+                       elem, rest, found := strings.Cut(p, string(filepath.Separator))
+                       if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
+                               return
+                       }
+                       if found && elem == "vendor" {
+                               // Ignore this path if it contains the element "vendor" anywhere
+                               // except for the last element (packages named vendor are allowed
+                               // for historical reasons). Note that found is true when this
+                               // isn't the last path element.
+                               return
+                       }
+                       if !found {
+                               // Didn't find the separator, so we're considering the last element.
+                               break
+                       }
+                       p = rest
+               }
+
+               // Don't use GOROOT/src.
+               if reldir == "" && importPathRoot == "" {
+                       return
+               }
+
+               name := path.Join(importPathRoot, filepath.ToSlash(reldir))
+               if !treeCanMatch(name) {
+                       return
+               }
+
+               if !have[name] {
+                       have[name] = true
+                       if isMatch(name) {
+                               if _, _, err := index.Package(reldir).ScanDir(tags); err != imports.ErrNoGo {
+                                       addPkg(name)
+                               }
+                       }
+               }
+       })
 }
 
 // MatchInModule identifies the packages matching the given pattern within the
@@ -195,8 +281,7 @@ func MatchInModule(ctx context.Context, pattern string, m module.Version, tags m
                return match
        }
 
-       const needSum = true
-       root, isLocal, err := fetch(ctx, m, needSum)
+       root, isLocal, err := fetch(ctx, m)
        if err != nil {
                match.Errs = []error{err}
                return match
@@ -208,7 +293,7 @@ func MatchInModule(ctx context.Context, pattern string, m module.Version, tags m
                return match
        }
        if haveGoFiles {
-               if _, _, err := scanDir(dir, tags); err != imports.ErrNoGo {
+               if _, _, err := scanDir(root, dir, tags); err != imports.ErrNoGo {
                        // ErrNoGo indicates that the directory is not actually a Go package,
                        // perhaps due to the tags in use. Any other non-nil error indicates a
                        // problem with one or more of the Go source files, but such an error does