...

Source file src/golang.org/x/tools/go/buildutil/allpackages.go

Documentation: golang.org/x/tools/go/buildutil

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

View as plain text