You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

198 lines
5.0 KiB

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