| 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 | } |
| 196 |
Members