github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/go/buildutil/allpackages.go (about) 1 // Copyright 2014 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 buildutil provides utilities related to the go/build 6 // package in the standard library. 7 // 8 // All I/O is done via the build.Context file system interface, which must 9 // be concurrency-safe. 10 package buildutil // import "golang.org/x/tools/go/buildutil" 11 12 import ( 13 "go/build" 14 "os" 15 "path/filepath" 16 "sort" 17 "strings" 18 "sync" 19 ) 20 21 // AllPackages returns the package path of each Go package in any source 22 // directory of the specified build context (e.g. $GOROOT or an element 23 // of $GOPATH). Errors are ignored. The results are sorted. 24 // All package paths are canonical, and thus may contain "/vendor/". 25 // 26 // The result may include import paths for directories that contain no 27 // *.go files, such as "archive" (in $GOROOT/src). 28 // 29 // All I/O is done via the build.Context file system interface, 30 // which must be concurrency-safe. 31 func AllPackages(ctxt *build.Context) []string { 32 var list []string 33 ForEachPackage(ctxt, func(pkg string, _ error) { 34 list = append(list, pkg) 35 }) 36 sort.Strings(list) 37 return list 38 } 39 40 // ForEachPackage calls the found function with the package path of 41 // each Go package it finds in any source directory of the specified 42 // build context (e.g. $GOROOT or an element of $GOPATH). 43 // All package paths are canonical, and thus may contain "/vendor/". 44 // 45 // If the package directory exists but could not be read, the second 46 // argument to the found function provides the error. 47 // 48 // All I/O is done via the build.Context file system interface, 49 // which must be concurrency-safe. 50 func ForEachPackage(ctxt *build.Context, found func(importPath string, err error)) { 51 ch := make(chan item) 52 53 var wg sync.WaitGroup 54 for _, root := range ctxt.SrcDirs() { 55 root := root 56 wg.Add(1) 57 go func() { 58 allPackages(ctxt, root, ch) 59 wg.Done() 60 }() 61 } 62 go func() { 63 wg.Wait() 64 close(ch) 65 }() 66 67 // All calls to found occur in the caller's goroutine. 68 for i := range ch { 69 found(i.importPath, i.err) 70 } 71 } 72 73 type item struct { 74 importPath string 75 err error // (optional) 76 } 77 78 // We use a process-wide counting semaphore to limit 79 // the number of parallel calls to ReadDir. 80 var ioLimit = make(chan bool, 20) 81 82 func allPackages(ctxt *build.Context, root string, ch chan<- item) { 83 root = filepath.Clean(root) + string(os.PathSeparator) 84 85 var wg sync.WaitGroup 86 87 var walkDir func(dir string) 88 walkDir = func(dir string) { 89 // Avoid .foo, _foo, and testdata directory trees. 90 base := filepath.Base(dir) 91 if base == "" || base[0] == '.' || base[0] == '_' || base == "testdata" { 92 return 93 } 94 95 pkg := filepath.ToSlash(strings.TrimPrefix(dir, root)) 96 97 // Prune search if we encounter any of these import paths. 98 switch pkg { 99 case "builtin": 100 return 101 } 102 103 ioLimit <- true 104 files, err := ReadDir(ctxt, dir) 105 <-ioLimit 106 if pkg != "" || err != nil { 107 ch <- item{pkg, err} 108 } 109 for _, fi := range files { 110 fi := fi 111 if fi.IsDir() { 112 wg.Add(1) 113 go func() { 114 walkDir(filepath.Join(dir, fi.Name())) 115 wg.Done() 116 }() 117 } 118 } 119 } 120 121 walkDir(root) 122 wg.Wait() 123 } 124 125 // ExpandPatterns returns the set of packages matched by patterns, 126 // which may have the following forms: 127 // 128 // golang.org/x/tools/cmd/guru # a single package 129 // golang.org/x/tools/... # all packages beneath dir 130 // ... # the entire workspace. 131 // 132 // Order is significant: a pattern preceded by '-' removes matching 133 // packages from the set. For example, these patterns match all encoding 134 // packages except encoding/xml: 135 // 136 // encoding/... -encoding/xml 137 // 138 // A trailing slash in a pattern is ignored. (Path components of Go 139 // package names are separated by slash, not the platform's path separator.) 140 func ExpandPatterns(ctxt *build.Context, patterns []string) map[string]bool { 141 // TODO(adonovan): support other features of 'go list': 142 // - "std"/"cmd"/"all" meta-packages 143 // - "..." not at the end of a pattern 144 // - relative patterns using "./" or "../" prefix 145 146 pkgs := make(map[string]bool) 147 doPkg := func(pkg string, neg bool) { 148 if neg { 149 delete(pkgs, pkg) 150 } else { 151 pkgs[pkg] = true 152 } 153 } 154 155 // Scan entire workspace if wildcards are present. 156 // TODO(adonovan): opt: scan only the necessary subtrees of the workspace. 157 var all []string 158 for _, arg := range patterns { 159 if strings.HasSuffix(arg, "...") { 160 all = AllPackages(ctxt) 161 break 162 } 163 } 164 165 for _, arg := range patterns { 166 if arg == "" { 167 continue 168 } 169 170 neg := arg[0] == '-' 171 if neg { 172 arg = arg[1:] 173 } 174 175 if arg == "..." { 176 // ... matches all packages 177 for _, pkg := range all { 178 doPkg(pkg, neg) 179 } 180 } else if dir := strings.TrimSuffix(arg, "/..."); dir != arg { 181 // dir/... matches all packages beneath dir 182 for _, pkg := range all { 183 if strings.HasPrefix(pkg, dir) && 184 (len(pkg) == len(dir) || pkg[len(dir)] == '/') { 185 doPkg(pkg, neg) 186 } 187 } 188 } else { 189 // single package 190 doPkg(strings.TrimSuffix(arg, "/"), neg) 191 } 192 } 193 194 return pkgs 195 }