allpackages.go (4995B)
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 wg.Go(func() { 56 allPackages(ctxt, root, ch) 57 }) 58 } 59 go func() { 60 wg.Wait() 61 close(ch) 62 }() 63 64 // All calls to found occur in the caller's goroutine. 65 for i := range ch { 66 found(i.importPath, i.err) 67 } 68 } 69 70 type item struct { 71 importPath string 72 err error // (optional) 73 } 74 75 // We use a process-wide counting semaphore to limit 76 // the number of parallel calls to ReadDir. 77 var ioLimit = make(chan bool, 20) 78 79 func allPackages(ctxt *build.Context, root string, ch chan<- item) { 80 root = filepath.Clean(root) + string(os.PathSeparator) 81 82 var wg sync.WaitGroup 83 84 var walkDir func(dir string) 85 walkDir = func(dir string) { 86 // Avoid .foo, _foo, and testdata directory trees. 87 base := filepath.Base(dir) 88 if base == "" || base[0] == '.' || base[0] == '_' || base == "testdata" { 89 return 90 } 91 92 pkg := filepath.ToSlash(strings.TrimPrefix(dir, root)) 93 94 // Prune search if we encounter any of these import paths. 95 switch pkg { 96 case "builtin": 97 return 98 } 99 100 ioLimit <- true 101 files, err := ReadDir(ctxt, dir) 102 <-ioLimit 103 if pkg != "" || err != nil { 104 ch <- item{pkg, err} 105 } 106 for _, fi := range files { 107 if fi.IsDir() { 108 wg.Go(func() { 109 walkDir(filepath.Join(dir, fi.Name())) 110 }) 111 } 112 } 113 } 114 115 walkDir(root) 116 wg.Wait() 117 } 118 119 // ExpandPatterns returns the set of packages matched by patterns, 120 // which may have the following forms: 121 // 122 // golang.org/x/tools/cmd/guru # a single package 123 // golang.org/x/tools/... # all packages beneath dir 124 // ... # the entire workspace. 125 // 126 // Order is significant: a pattern preceded by '-' removes matching 127 // packages from the set. For example, these patterns match all encoding 128 // packages except encoding/xml: 129 // 130 // encoding/... -encoding/xml 131 // 132 // A trailing slash in a pattern is ignored. (Path components of Go 133 // package names are separated by slash, not the platform's path separator.) 134 func ExpandPatterns(ctxt *build.Context, patterns []string) map[string]bool { 135 // TODO(adonovan): support other features of 'go list': 136 // - "std"/"cmd"/"all" meta-packages 137 // - "..." not at the end of a pattern 138 // - relative patterns using "./" or "../" prefix 139 140 pkgs := make(map[string]bool) 141 doPkg := func(pkg string, neg bool) { 142 if neg { 143 delete(pkgs, pkg) 144 } else { 145 pkgs[pkg] = true 146 } 147 } 148 149 // Scan entire workspace if wildcards are present. 150 // TODO(adonovan): opt: scan only the necessary subtrees of the workspace. 151 var all []string 152 for _, arg := range patterns { 153 if strings.HasSuffix(arg, "...") { 154 all = AllPackages(ctxt) 155 break 156 } 157 } 158 159 for _, arg := range patterns { 160 if arg == "" { 161 continue 162 } 163 164 neg := arg[0] == '-' 165 if neg { 166 arg = arg[1:] 167 } 168 169 if arg == "..." { 170 // ... matches all packages 171 for _, pkg := range all { 172 doPkg(pkg, neg) 173 } 174 } else if dir, ok := strings.CutSuffix(arg, "/..."); ok { 175 // dir/... matches all packages beneath dir 176 for _, pkg := range all { 177 if strings.HasPrefix(pkg, dir) && 178 (len(pkg) == len(dir) || pkg[len(dir)] == '/') { 179 doPkg(pkg, neg) 180 } 181 } 182 } else { 183 // single package 184 doPkg(strings.TrimSuffix(arg, "/"), neg) 185 } 186 } 187 188 return pkgs 189 }