|
- // 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
- }
|