src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

allpackages.go (4995B)


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