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 "runtime" 14 "strings" 15 16 "cmd/go/internal/cfg" 17 "cmd/go/internal/fsys" 18 "cmd/go/internal/imports" 19 "cmd/go/internal/search" 20 21 "golang.org/x/mod/module" 22) 23 24type stdFilter int8 25 26const ( 27 omitStd = stdFilter(iota) 28 includeStd 29) 30 31// matchPackages is like m.MatchPackages, but uses a local variable (rather than 32// a global) for tags, can include or exclude packages in the standard library, 33// and is restricted to the given list of modules. 34func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, filter stdFilter, modules []module.Version) { 35 m.Pkgs = []string{} 36 37 isMatch := func(string) bool { return true } 38 treeCanMatch := func(string) bool { return true } 39 if !m.IsMeta() { 40 isMatch = search.MatchPattern(m.Pattern()) 41 treeCanMatch = search.TreeCanMatchPattern(m.Pattern()) 42 } 43 44 have := map[string]bool{ 45 "builtin": true, // ignore pseudo-package that exists only for documentation 46 } 47 if !cfg.BuildContext.CgoEnabled { 48 have["runtime/cgo"] = true // ignore during walk 49 } 50 51 type pruning int8 52 const ( 53 pruneVendor = pruning(1 << iota) 54 pruneGoMod 55 ) 56 57 walkPkgs := func(root, importPathRoot string, prune pruning) { 58 root = filepath.Clean(root) 59 err := fsys.Walk(root, func(path string, fi fs.FileInfo, err error) error { 60 if err != nil { 61 m.AddError(err) 62 return nil 63 } 64 65 want := true 66 elem := "" 67 68 // Don't use GOROOT/src but do walk down into it. 69 if path == root { 70 if importPathRoot == "" { 71 return nil 72 } 73 } else { 74 // Avoid .foo, _foo, and testdata subdirectory trees. 75 _, elem = filepath.Split(path) 76 if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" { 77 want = false 78 } 79 } 80 81 name := importPathRoot + filepath.ToSlash(path[len(root):]) 82 if importPathRoot == "" { 83 name = name[1:] // cut leading slash 84 } 85 if !treeCanMatch(name) { 86 want = false 87 } 88 89 if !fi.IsDir() { 90 if fi.Mode()&fs.ModeSymlink != 0 && want { 91 if target, err := fsys.Stat(path); err == nil && target.IsDir() { 92 fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", path) 93 } 94 } 95 return nil 96 } 97 98 if !want { 99 return filepath.SkipDir 100 } 101 // Stop at module boundaries. 102 if (prune&pruneGoMod != 0) && path != root { 103 if fi, err := os.Stat(filepath.Join(path, "go.mod")); err == nil && !fi.IsDir() { 104 return filepath.SkipDir 105 } 106 } 107 108 if !have[name] { 109 have[name] = true 110 if isMatch(name) { 111 if _, _, err := scanDir(path, tags); err != imports.ErrNoGo { 112 m.Pkgs = append(m.Pkgs, name) 113 } 114 } 115 } 116 117 if elem == "vendor" && (prune&pruneVendor != 0) { 118 return filepath.SkipDir 119 } 120 return nil 121 }) 122 if err != nil { 123 m.AddError(err) 124 } 125 } 126 127 if filter == includeStd && runtime.Compiler != "gccgo" { 128 walkPkgs(cfg.GOROOTsrc, "", pruneGoMod) 129 if treeCanMatch("cmd") { 130 walkPkgs(filepath.Join(cfg.GOROOTsrc, "cmd"), "cmd", pruneGoMod) 131 } 132 } 133 134 if cfg.BuildMod == "vendor" { 135 if HasModRoot() { 136 walkPkgs(ModRoot(), targetPrefix, 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 mod == Target { 152 if !HasModRoot() { 153 continue // If there is no main module, we can't search in it. 154 } 155 root = ModRoot() 156 modPrefix = targetPrefix 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) 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