1// Copyright 2018 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package modload 6 7import ( 8 "context" 9 "fmt" 10 "io/fs" 11 "os" 12 "path/filepath" 13 "strings" 14 15 "cmd/go/internal/cfg" 16 "cmd/go/internal/fsys" 17 "cmd/go/internal/imports" 18 "cmd/go/internal/search" 19 20 "golang.org/x/mod/module" 21) 22 23type stdFilter int8 24 25const ( 26 omitStd = stdFilter(iota) 27 includeStd 28) 29 30// matchPackages is like m.MatchPackages, but uses a local variable (rather than 31// a global) for tags, can include or exclude packages in the standard library, 32// and is restricted to the given list of modules. 33func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, filter stdFilter, modules []module.Version) { 34 m.Pkgs = []string{} 35 36 isMatch := func(string) bool { return true } 37 treeCanMatch := func(string) bool { return true } 38 if !m.IsMeta() { 39 isMatch = search.MatchPattern(m.Pattern()) 40 treeCanMatch = search.TreeCanMatchPattern(m.Pattern()) 41 } 42 43 have := map[string]bool{ 44 "builtin": true, // ignore pseudo-package that exists only for documentation 45 } 46 if !cfg.BuildContext.CgoEnabled { 47 have["runtime/cgo"] = true // ignore during walk 48 } 49 50 type pruning int8 51 const ( 52 pruneVendor = pruning(1 << iota) 53 pruneGoMod 54 ) 55 56 walkPkgs := func(root, importPathRoot string, prune pruning) { 57 root = filepath.Clean(root) 58 err := fsys.Walk(root, func(path string, fi fs.FileInfo, err error) error { 59 if err != nil { 60 m.AddError(err) 61 return nil 62 } 63 64 want := true 65 elem := "" 66 67 // Don't use GOROOT/src but do walk down into it. 68 if path == root { 69 if importPathRoot == "" { 70 return nil 71 } 72 } else { 73 // Avoid .foo, _foo, and testdata subdirectory trees. 74 _, elem = filepath.Split(path) 75 if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" { 76 want = false 77 } 78 } 79 80 name := importPathRoot + filepath.ToSlash(path[len(root):]) 81 if importPathRoot == "" { 82 name = name[1:] // cut leading slash 83 } 84 if !treeCanMatch(name) { 85 want = false 86 } 87 88 if !fi.IsDir() { 89 if fi.Mode()&fs.ModeSymlink != 0 && want && strings.Contains(m.Pattern(), "...") { 90 if target, err := fsys.Stat(path); err == nil && target.IsDir() { 91 fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", path) 92 } 93 } 94 return nil 95 } 96 97 if !want { 98 return filepath.SkipDir 99 } 100 // Stop at module boundaries. 101 if (prune&pruneGoMod != 0) && path != root { 102 if fi, err := os.Stat(filepath.Join(path, "go.mod")); err == nil && !fi.IsDir() { 103 return filepath.SkipDir 104 } 105 } 106 107 if !have[name] { 108 have[name] = true 109 if isMatch(name) { 110 if _, _, err := scanDir(path, tags); err != imports.ErrNoGo { 111 m.Pkgs = append(m.Pkgs, name) 112 } 113 } 114 } 115 116 if elem == "vendor" && (prune&pruneVendor != 0) { 117 return filepath.SkipDir 118 } 119 return nil 120 }) 121 if err != nil { 122 m.AddError(err) 123 } 124 } 125 126 if filter == includeStd { 127 walkPkgs(cfg.GOROOTsrc, "", pruneGoMod) 128 if treeCanMatch("cmd") { 129 walkPkgs(filepath.Join(cfg.GOROOTsrc, "cmd"), "cmd", pruneGoMod) 130 } 131 } 132 133 if cfg.BuildMod == "vendor" { 134 mod := MainModules.mustGetSingleMainModule() 135 if modRoot := MainModules.ModRoot(mod); modRoot != "" { 136 walkPkgs(modRoot, MainModules.PathPrefix(mod), pruneGoMod|pruneVendor) 137 walkPkgs(filepath.Join(modRoot, "vendor"), "", pruneVendor) 138 } 139 return 140 } 141 142 for _, mod := range modules { 143 if !treeCanMatch(mod.Path) { 144 continue 145 } 146 147 var ( 148 root, modPrefix string 149 isLocal bool 150 ) 151 if MainModules.Contains(mod.Path) { 152 if MainModules.ModRoot(mod) == "" { 153 continue // If there is no main module, we can't search in it. 154 } 155 root = MainModules.ModRoot(mod) 156 modPrefix = MainModules.PathPrefix(mod) 157 isLocal = true 158 } else { 159 var err error 160 const needSum = true 161 root, isLocal, err = fetch(ctx, mod, needSum) 162 if err != nil { 163 m.AddError(err) 164 continue 165 } 166 modPrefix = mod.Path 167 } 168 169 prune := pruneVendor 170 if isLocal { 171 prune |= pruneGoMod 172 } 173 walkPkgs(root, modPrefix, prune) 174 } 175 176 return 177} 178 179// MatchInModule identifies the packages matching the given pattern within the 180// given module version, which does not need to be in the build list or module 181// requirement graph. 182// 183// If m is the zero module.Version, MatchInModule matches the pattern 184// against the standard library (std and cmd) in GOROOT/src. 185func MatchInModule(ctx context.Context, pattern string, m module.Version, tags map[string]bool) *search.Match { 186 match := search.NewMatch(pattern) 187 if m == (module.Version{}) { 188 matchPackages(ctx, match, tags, includeStd, nil) 189 } 190 191 LoadModFile(ctx) // Sets Target, needed by fetch and matchPackages. 192 193 if !match.IsLiteral() { 194 matchPackages(ctx, match, tags, omitStd, []module.Version{m}) 195 return match 196 } 197 198 const needSum = true 199 root, isLocal, err := fetch(ctx, m, needSum) 200 if err != nil { 201 match.Errs = []error{err} 202 return match 203 } 204 205 dir, haveGoFiles, err := dirInModule(pattern, m.Path, root, isLocal) 206 if err != nil { 207 match.Errs = []error{err} 208 return match 209 } 210 if haveGoFiles { 211 if _, _, err := scanDir(dir, tags); err != imports.ErrNoGo { 212 // ErrNoGo indicates that the directory is not actually a Go package, 213 // perhaps due to the tags in use. Any other non-nil error indicates a 214 // problem with one or more of the Go source files, but such an error does 215 // not stop the package from existing, so it has no impact on matching. 216 match.Pkgs = []string{pattern} 217 } 218 } 219 return match 220} 221