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