198 lines
5 KiB
Go
198 lines
5 KiB
Go
// 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
|
|
}
|