allpackages.go

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