github.com/bir3/gocompiler@v0.3.205/src/cmd/gocmd/internal/modload/search.go (about) 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 5 package modload 6 7 import ( 8 "context" 9 "errors" 10 "fmt" 11 "io/fs" 12 "os" 13 "path" 14 "path/filepath" 15 "runtime" 16 "sort" 17 "strings" 18 "sync" 19 20 "github.com/bir3/gocompiler/src/cmd/gocmd/internal/cfg" 21 "github.com/bir3/gocompiler/src/cmd/gocmd/internal/fsys" 22 "github.com/bir3/gocompiler/src/cmd/gocmd/internal/imports" 23 "github.com/bir3/gocompiler/src/cmd/gocmd/internal/modindex" 24 "github.com/bir3/gocompiler/src/cmd/gocmd/internal/par" 25 "github.com/bir3/gocompiler/src/cmd/gocmd/internal/search" 26 "github.com/bir3/gocompiler/src/cmd/gocmd/internal/trace" 27 "github.com/bir3/gocompiler/src/cmd/internal/pkgpattern" 28 29 "github.com/bir3/gocompiler/src/xvendor/golang.org/x/mod/module" 30 ) 31 32 type stdFilter int8 33 34 const ( 35 omitStd = stdFilter(iota) 36 includeStd 37 ) 38 39 // matchPackages is like m.MatchPackages, but uses a local variable (rather than 40 // a global) for tags, can include or exclude packages in the standard library, 41 // and is restricted to the given list of modules. 42 func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, filter stdFilter, modules []module.Version) { 43 ctx, span := trace.StartSpan(ctx, "modload.matchPackages") 44 defer span.Done() 45 46 m.Pkgs = []string{} 47 48 isMatch := func(string) bool { return true } 49 treeCanMatch := func(string) bool { return true } 50 if !m.IsMeta() { 51 isMatch = pkgpattern.MatchPattern(m.Pattern()) 52 treeCanMatch = pkgpattern.TreeCanMatchPattern(m.Pattern()) 53 } 54 55 var mu sync.Mutex 56 have := map[string]bool{ 57 "builtin": true, // ignore pseudo-package that exists only for documentation 58 } 59 addPkg := func(p string) { 60 mu.Lock() 61 m.Pkgs = append(m.Pkgs, p) 62 mu.Unlock() 63 } 64 if !cfg.BuildContext.CgoEnabled { 65 have["runtime/cgo"] = true // ignore during walk 66 } 67 68 type pruning int8 69 const ( 70 pruneVendor = pruning(1 << iota) 71 pruneGoMod 72 ) 73 74 q := par.NewQueue(runtime.GOMAXPROCS(0)) 75 76 walkPkgs := func(root, importPathRoot string, prune pruning) { 77 _, span := trace.StartSpan(ctx, "walkPkgs "+root) 78 defer span.Done() 79 80 root = filepath.Clean(root) 81 err := fsys.Walk(root, func(path string, fi fs.FileInfo, err error) error { 82 if err != nil { 83 m.AddError(err) 84 return nil 85 } 86 87 want := true 88 elem := "" 89 90 // Don't use GOROOT/src but do walk down into it. 91 if path == root { 92 if importPathRoot == "" { 93 return nil 94 } 95 } else { 96 // Avoid .foo, _foo, and testdata subdirectory trees. 97 _, elem = filepath.Split(path) 98 if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" { 99 want = false 100 } 101 } 102 103 name := importPathRoot + filepath.ToSlash(path[len(root):]) 104 if importPathRoot == "" { 105 name = name[1:] // cut leading slash 106 } 107 if !treeCanMatch(name) { 108 want = false 109 } 110 111 if !fi.IsDir() { 112 if fi.Mode()&fs.ModeSymlink != 0 && want && strings.Contains(m.Pattern(), "...") { 113 if target, err := fsys.Stat(path); err == nil && target.IsDir() { 114 fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", path) 115 } 116 } 117 return nil 118 } 119 120 if !want { 121 return filepath.SkipDir 122 } 123 // Stop at module boundaries. 124 if (prune&pruneGoMod != 0) && path != root { 125 if fi, err := os.Stat(filepath.Join(path, "go.mod")); err == nil && !fi.IsDir() { 126 return filepath.SkipDir 127 } 128 } 129 130 if !have[name] { 131 have[name] = true 132 if isMatch(name) { 133 q.Add(func() { 134 if _, _, err := scanDir(root, path, tags); err != imports.ErrNoGo { 135 addPkg(name) 136 } 137 }) 138 } 139 } 140 141 if elem == "vendor" && (prune&pruneVendor != 0) { 142 return filepath.SkipDir 143 } 144 return nil 145 }) 146 if err != nil { 147 m.AddError(err) 148 } 149 } 150 151 // Wait for all in-flight operations to complete before returning. 152 defer func() { 153 <-q.Idle() 154 sort.Strings(m.Pkgs) // sort everything we added for determinism 155 }() 156 157 if filter == includeStd { 158 walkPkgs(cfg.GOROOTsrc, "", pruneGoMod) 159 if treeCanMatch("cmd") { 160 walkPkgs(filepath.Join(cfg.GOROOTsrc, "cmd"), "cmd", pruneGoMod) 161 } 162 } 163 164 if cfg.BuildMod == "vendor" { 165 mod := MainModules.mustGetSingleMainModule() 166 if modRoot := MainModules.ModRoot(mod); modRoot != "" { 167 walkPkgs(modRoot, MainModules.PathPrefix(mod), pruneGoMod|pruneVendor) 168 walkPkgs(filepath.Join(modRoot, "vendor"), "", pruneVendor) 169 } 170 return 171 } 172 173 for _, mod := range modules { 174 if !treeCanMatch(mod.Path) { 175 continue 176 } 177 178 var ( 179 root, modPrefix string 180 isLocal bool 181 ) 182 if MainModules.Contains(mod.Path) { 183 if MainModules.ModRoot(mod) == "" { 184 continue // If there is no main module, we can't search in it. 185 } 186 root = MainModules.ModRoot(mod) 187 modPrefix = MainModules.PathPrefix(mod) 188 isLocal = true 189 } else { 190 var err error 191 root, isLocal, err = fetch(ctx, mod) 192 if err != nil { 193 m.AddError(err) 194 continue 195 } 196 modPrefix = mod.Path 197 } 198 if mi, err := modindex.GetModule(root); err == nil { 199 walkFromIndex(mi, modPrefix, isMatch, treeCanMatch, tags, have, addPkg) 200 continue 201 } else if !errors.Is(err, modindex.ErrNotIndexed) { 202 m.AddError(err) 203 } 204 205 prune := pruneVendor 206 if isLocal { 207 prune |= pruneGoMod 208 } 209 walkPkgs(root, modPrefix, prune) 210 } 211 212 return 213 } 214 215 // walkFromIndex matches packages in a module using the module index. modroot 216 // is the module's root directory on disk, index is the modindex.Module for the 217 // module, and importPathRoot is the module's path prefix. 218 func walkFromIndex(index *modindex.Module, importPathRoot string, isMatch, treeCanMatch func(string) bool, tags, have map[string]bool, addPkg func(string)) { 219 index.Walk(func(reldir string) { 220 // Avoid .foo, _foo, and testdata subdirectory trees. 221 p := reldir 222 for { 223 elem, rest, found := strings.Cut(p, string(filepath.Separator)) 224 if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" { 225 return 226 } 227 if found && elem == "vendor" { 228 // Ignore this path if it contains the element "vendor" anywhere 229 // except for the last element (packages named vendor are allowed 230 // for historical reasons). Note that found is true when this 231 // isn't the last path element. 232 return 233 } 234 if !found { 235 // Didn't find the separator, so we're considering the last element. 236 break 237 } 238 p = rest 239 } 240 241 // Don't use GOROOT/src. 242 if reldir == "" && importPathRoot == "" { 243 return 244 } 245 246 name := path.Join(importPathRoot, filepath.ToSlash(reldir)) 247 if !treeCanMatch(name) { 248 return 249 } 250 251 if !have[name] { 252 have[name] = true 253 if isMatch(name) { 254 if _, _, err := index.Package(reldir).ScanDir(tags); err != imports.ErrNoGo { 255 addPkg(name) 256 } 257 } 258 } 259 }) 260 } 261 262 // MatchInModule identifies the packages matching the given pattern within the 263 // given module version, which does not need to be in the build list or module 264 // requirement graph. 265 // 266 // If m is the zero module.Version, MatchInModule matches the pattern 267 // against the standard library (std and cmd) in GOROOT/src. 268 func MatchInModule(ctx context.Context, pattern string, m module.Version, tags map[string]bool) *search.Match { 269 match := search.NewMatch(pattern) 270 if m == (module.Version{}) { 271 matchPackages(ctx, match, tags, includeStd, nil) 272 } 273 274 LoadModFile(ctx) // Sets Target, needed by fetch and matchPackages. 275 276 if !match.IsLiteral() { 277 matchPackages(ctx, match, tags, omitStd, []module.Version{m}) 278 return match 279 } 280 281 root, isLocal, err := fetch(ctx, m) 282 if err != nil { 283 match.Errs = []error{err} 284 return match 285 } 286 287 dir, haveGoFiles, err := dirInModule(pattern, m.Path, root, isLocal) 288 if err != nil { 289 match.Errs = []error{err} 290 return match 291 } 292 if haveGoFiles { 293 if _, _, err := scanDir(root, dir, tags); err != imports.ErrNoGo { 294 // ErrNoGo indicates that the directory is not actually a Go package, 295 // perhaps due to the tags in use. Any other non-nil error indicates a 296 // problem with one or more of the Go source files, but such an error does 297 // not stop the package from existing, so it has no impact on matching. 298 match.Pkgs = []string{pattern} 299 } 300 } 301 return match 302 }