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.
5 // Package buildutil provides utilities related to the go/build
6 // package in the standard library.
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"
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/".
26 // The result may include import paths for directories that contain no
27 // *.go files, such as "archive" (in $GOROOT/src).
29 // All I/O is done via the build.Context file system interface,
30 // which must be concurrency-safe.
32 func AllPackages(ctxt *build.Context) []string {
34 ForEachPackage(ctxt, func(pkg string, _ error) {
35 list = append(list, pkg)
41 // ForEachPackage calls the found function with the package path of
42 // each Go package it finds in any source directory of the specified
43 // build context (e.g. $GOROOT or an element of $GOPATH).
44 // All package paths are canonical, and thus may contain "/vendor/".
46 // If the package directory exists but could not be read, the second
47 // argument to the found function provides the error.
49 // All I/O is done via the build.Context file system interface,
50 // which must be concurrency-safe.
52 func ForEachPackage(ctxt *build.Context, found func(importPath string, err error)) {
56 for _, root := range ctxt.SrcDirs() {
60 allPackages(ctxt, root, ch)
69 // All calls to found occur in the caller's goroutine.
71 found(i.importPath, i.err)
77 err error // (optional)
80 // We use a process-wide counting semaphore to limit
81 // the number of parallel calls to ReadDir.
82 var ioLimit = make(chan bool, 20)
84 func allPackages(ctxt *build.Context, root string, ch chan<- item) {
85 root = filepath.Clean(root) + string(os.PathSeparator)
89 var walkDir func(dir string)
90 walkDir = func(dir string) {
91 // Avoid .foo, _foo, and testdata directory trees.
92 base := filepath.Base(dir)
93 if base == "" || base[0] == '.' || base[0] == '_' || base == "testdata" {
97 pkg := filepath.ToSlash(strings.TrimPrefix(dir, root))
99 // Prune search if we encounter any of these import paths.
106 files, err := ReadDir(ctxt, dir)
108 if pkg != "" || err != nil {
111 for _, fi := range files {
116 walkDir(filepath.Join(dir, fi.Name()))
127 // ExpandPatterns returns the set of packages matched by patterns,
128 // which may have the following forms:
130 // golang.org/x/tools/cmd/guru # a single package
131 // golang.org/x/tools/... # all packages beneath dir
132 // ... # the entire workspace.
134 // Order is significant: a pattern preceded by '-' removes matching
135 // packages from the set. For example, these patterns match all encoding
136 // packages except encoding/xml:
138 // encoding/... -encoding/xml
140 // A trailing slash in a pattern is ignored. (Path components of Go
141 // package names are separated by slash, not the platform's path separator.)
143 func ExpandPatterns(ctxt *build.Context, patterns []string) map[string]bool {
144 // TODO(adonovan): support other features of 'go list':
145 // - "std"/"cmd"/"all" meta-packages
146 // - "..." not at the end of a pattern
147 // - relative patterns using "./" or "../" prefix
149 pkgs := make(map[string]bool)
150 doPkg := func(pkg string, neg bool) {
158 // Scan entire workspace if wildcards are present.
159 // TODO(adonovan): opt: scan only the necessary subtrees of the workspace.
161 for _, arg := range patterns {
162 if strings.HasSuffix(arg, "...") {
163 all = AllPackages(ctxt)
168 for _, arg := range patterns {
179 // ... matches all packages
180 for _, pkg := range all {
183 } else if dir := strings.TrimSuffix(arg, "/..."); dir != arg {
184 // dir/... matches all packages beneath dir
185 for _, pkg := range all {
186 if strings.HasPrefix(pkg, dir) &&
187 (len(pkg) == len(dir) || pkg[len(dir)] == '/') {
193 doPkg(strings.TrimSuffix(arg, "/"), neg)