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