| // Copyright 2014 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // Package buildutil provides utilities related to the go/build |
| // package in the standard library. |
| // |
| // All I/O is done via the build.Context file system interface, which must |
| // be concurrency-safe. |
| package buildutil // import "golang.org/x/tools/go/buildutil" |
| |
| import ( |
| "go/build" |
| "os" |
| "path/filepath" |
| "sort" |
| "strings" |
| "sync" |
| ) |
| |
| // AllPackages returns the package path of each Go package in any source |
| // directory of the specified build context (e.g. $GOROOT or an element |
| // of $GOPATH). Errors are ignored. The results are sorted. |
| // All package paths are canonical, and thus may contain "/vendor/". |
| // |
| // The result may include import paths for directories that contain no |
| // *.go files, such as "archive" (in $GOROOT/src). |
| // |
| // All I/O is done via the build.Context file system interface, |
| // which must be concurrency-safe. |
| // |
| func AllPackages(ctxt *build.Context) []string { |
| var list []string |
| ForEachPackage(ctxt, func(pkg string, _ error) { |
| list = append(list, pkg) |
| }) |
| sort.Strings(list) |
| return list |
| } |
| |
| // ForEachPackage calls the found function with the package path of |
| // each Go package it finds in any source directory of the specified |
| // build context (e.g. $GOROOT or an element of $GOPATH). |
| // All package paths are canonical, and thus may contain "/vendor/". |
| // |
| // If the package directory exists but could not be read, the second |
| // argument to the found function provides the error. |
| // |
| // All I/O is done via the build.Context file system interface, |
| // which must be concurrency-safe. |
| // |
| func ForEachPackage(ctxt *build.Context, found func(importPath string, err error)) { |
| ch := make(chan item) |
| |
| var wg sync.WaitGroup |
| for _, root := range ctxt.SrcDirs() { |
| root := root |
| wg.Add(1) |
| go func() { |
| allPackages(ctxt, root, ch) |
| wg.Done() |
| }() |
| } |
| go func() { |
| wg.Wait() |
| close(ch) |
| }() |
| |
| // All calls to found occur in the caller's goroutine. |
| for i := range ch { |
| found(i.importPath, i.err) |
| } |
| } |
| |
| type item struct { |
| importPath string |
| err error // (optional) |
| } |
| |
| // We use a process-wide counting semaphore to limit |
| // the number of parallel calls to ReadDir. |
| var ioLimit = make(chan bool, 20) |
| |
| func allPackages(ctxt *build.Context, root string, ch chan<- item) { |
| root = filepath.Clean(root) + string(os.PathSeparator) |
| |
| var wg sync.WaitGroup |
| |
| var walkDir func(dir string) |
| walkDir = func(dir string) { |
| // Avoid .foo, _foo, and testdata directory trees. |
| base := filepath.Base(dir) |
| if base == "" || base[0] == '.' || base[0] == '_' || base == "testdata" { |
| return |
| } |
| |
| pkg := filepath.ToSlash(strings.TrimPrefix(dir, root)) |
| |
| // Prune search if we encounter any of these import paths. |
| switch pkg { |
| case "builtin": |
| return |
| } |
| |
| ioLimit <- true |
| files, err := ReadDir(ctxt, dir) |
| <-ioLimit |
| if pkg != "" || err != nil { |
| ch <- item{pkg, err} |
| } |
| for _, fi := range files { |
| fi := fi |
| if fi.IsDir() { |
| wg.Add(1) |
| go func() { |
| walkDir(filepath.Join(dir, fi.Name())) |
| wg.Done() |
| }() |
| } |
| } |
| } |
| |
| walkDir(root) |
| wg.Wait() |
| } |
| |
| // ExpandPatterns returns the set of packages matched by patterns, |
| // which may have the following forms: |
| // |
| // golang.org/x/tools/cmd/guru # a single package |
| // golang.org/x/tools/... # all packages beneath dir |
| // ... # the entire workspace. |
| // |
| // Order is significant: a pattern preceded by '-' removes matching |
| // packages from the set. For example, these patterns match all encoding |
| // packages except encoding/xml: |
| // |
| // encoding/... -encoding/xml |
| // |
| // A trailing slash in a pattern is ignored. (Path components of Go |
| // package names are separated by slash, not the platform's path separator.) |
| // |
| func ExpandPatterns(ctxt *build.Context, patterns []string) map[string]bool { |
| // TODO(adonovan): support other features of 'go list': |
| // - "std"/"cmd"/"all" meta-packages |
| // - "..." not at the end of a pattern |
| // - relative patterns using "./" or "../" prefix |
| |
| pkgs := make(map[string]bool) |
| doPkg := func(pkg string, neg bool) { |
| if neg { |
| delete(pkgs, pkg) |
| } else { |
| pkgs[pkg] = true |
| } |
| } |
| |
| // Scan entire workspace if wildcards are present. |
| // TODO(adonovan): opt: scan only the necessary subtrees of the workspace. |
| var all []string |
| for _, arg := range patterns { |
| if strings.HasSuffix(arg, "...") { |
| all = AllPackages(ctxt) |
| break |
| } |
| } |
| |
| for _, arg := range patterns { |
| if arg == "" { |
| continue |
| } |
| |
| neg := arg[0] == '-' |
| if neg { |
| arg = arg[1:] |
| } |
| |
| if arg == "..." { |
| // ... matches all packages |
| for _, pkg := range all { |
| doPkg(pkg, neg) |
| } |
| } else if dir := strings.TrimSuffix(arg, "/..."); dir != arg { |
| // dir/... matches all packages beneath dir |
| for _, pkg := range all { |
| if strings.HasPrefix(pkg, dir) && |
| (len(pkg) == len(dir) || pkg[len(dir)] == '/') { |
| doPkg(pkg, neg) |
| } |
| } |
| } else { |
| // single package |
| doPkg(strings.TrimSuffix(arg, "/"), neg) |
| } |
| } |
| |
| return pkgs |
| } |