199 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			199 lines
		
	
	
		
			5.0 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
 | 
						|
}
 |