gopkg.in/alecthomas/gometalinter.v3@v3.0.0/_linters/src/golang.org/x/tools/go/buildutil/allpackages.go (about)

     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.
    10  package buildutil // import "golang.org/x/tools/go/buildutil"
    11  
    12  import (
    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  //
    32  func 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  //
    52  func 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  
    75  type 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.
    82  var ioLimit = make(chan bool, 20)
    83  
    84  func 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  //
   143  func 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  }