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