import.go 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. package main
  2. /*
  3. This file holds a direct copy of the import path matching code of
  4. https://github.com/golang/go/blob/master/src/cmd/go/main.go. It can be
  5. replaced when https://golang.org/issue/8768 is resolved.
  6. It has been updated to follow upstream changes in a few ways.
  7. */
  8. import (
  9. "fmt"
  10. "go/build"
  11. "log"
  12. "os"
  13. "path"
  14. "path/filepath"
  15. "regexp"
  16. "runtime"
  17. "strings"
  18. )
  19. var (
  20. buildContext = build.Default
  21. goroot = filepath.Clean(runtime.GOROOT())
  22. gorootSrc = filepath.Join(goroot, "src")
  23. )
  24. // importPathsNoDotExpansion returns the import paths to use for the given
  25. // command line, but it does no ... expansion.
  26. func importPathsNoDotExpansion(args []string) []string {
  27. if len(args) == 0 {
  28. return []string{"."}
  29. }
  30. var out []string
  31. for _, a := range args {
  32. // Arguments are supposed to be import paths, but
  33. // as a courtesy to Windows developers, rewrite \ to /
  34. // in command-line arguments. Handles .\... and so on.
  35. if filepath.Separator == '\\' {
  36. a = strings.Replace(a, `\`, `/`, -1)
  37. }
  38. // Put argument in canonical form, but preserve leading ./.
  39. if strings.HasPrefix(a, "./") {
  40. a = "./" + path.Clean(a)
  41. if a == "./." {
  42. a = "."
  43. }
  44. } else {
  45. a = path.Clean(a)
  46. }
  47. if a == "all" || a == "std" {
  48. out = append(out, allPackages(a)...)
  49. continue
  50. }
  51. out = append(out, a)
  52. }
  53. return out
  54. }
  55. // importPaths returns the import paths to use for the given command line.
  56. func importPaths(args []string) []string {
  57. args = importPathsNoDotExpansion(args)
  58. var out []string
  59. for _, a := range args {
  60. if strings.Contains(a, "...") {
  61. if build.IsLocalImport(a) {
  62. out = append(out, allPackagesInFS(a)...)
  63. } else {
  64. out = append(out, allPackages(a)...)
  65. }
  66. continue
  67. }
  68. out = append(out, a)
  69. }
  70. return out
  71. }
  72. // matchPattern(pattern)(name) reports whether
  73. // name matches pattern. Pattern is a limited glob
  74. // pattern in which '...' means 'any string' and there
  75. // is no other special syntax.
  76. func matchPattern(pattern string) func(name string) bool {
  77. re := regexp.QuoteMeta(pattern)
  78. re = strings.Replace(re, `\.\.\.`, `.*`, -1)
  79. // Special case: foo/... matches foo too.
  80. if strings.HasSuffix(re, `/.*`) {
  81. re = re[:len(re)-len(`/.*`)] + `(/.*)?`
  82. }
  83. reg := regexp.MustCompile(`^` + re + `$`)
  84. return func(name string) bool {
  85. return reg.MatchString(name)
  86. }
  87. }
  88. // hasPathPrefix reports whether the path s begins with the
  89. // elements in prefix.
  90. func hasPathPrefix(s, prefix string) bool {
  91. switch {
  92. default:
  93. return false
  94. case len(s) == len(prefix):
  95. return s == prefix
  96. case len(s) > len(prefix):
  97. if prefix != "" && prefix[len(prefix)-1] == '/' {
  98. return strings.HasPrefix(s, prefix)
  99. }
  100. return s[len(prefix)] == '/' && s[:len(prefix)] == prefix
  101. }
  102. }
  103. // treeCanMatchPattern(pattern)(name) reports whether
  104. // name or children of name can possibly match pattern.
  105. // Pattern is the same limited glob accepted by matchPattern.
  106. func treeCanMatchPattern(pattern string) func(name string) bool {
  107. wildCard := false
  108. if i := strings.Index(pattern, "..."); i >= 0 {
  109. wildCard = true
  110. pattern = pattern[:i]
  111. }
  112. return func(name string) bool {
  113. return len(name) <= len(pattern) && hasPathPrefix(pattern, name) ||
  114. wildCard && strings.HasPrefix(name, pattern)
  115. }
  116. }
  117. // allPackages returns all the packages that can be found
  118. // under the $GOPATH directories and $GOROOT matching pattern.
  119. // The pattern is either "all" (all packages), "std" (standard packages)
  120. // or a path including "...".
  121. func allPackages(pattern string) []string {
  122. pkgs := matchPackages(pattern)
  123. if len(pkgs) == 0 {
  124. fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
  125. }
  126. return pkgs
  127. }
  128. func matchPackages(pattern string) []string {
  129. match := func(string) bool { return true }
  130. treeCanMatch := func(string) bool { return true }
  131. if pattern != "all" && pattern != "std" {
  132. match = matchPattern(pattern)
  133. treeCanMatch = treeCanMatchPattern(pattern)
  134. }
  135. have := map[string]bool{
  136. "builtin": true, // ignore pseudo-package that exists only for documentation
  137. }
  138. if !buildContext.CgoEnabled {
  139. have["runtime/cgo"] = true // ignore during walk
  140. }
  141. var pkgs []string
  142. // Commands
  143. cmd := filepath.Join(goroot, "src/cmd") + string(filepath.Separator)
  144. filepath.Walk(cmd, func(path string, fi os.FileInfo, err error) error {
  145. if err != nil || !fi.IsDir() || path == cmd {
  146. return nil
  147. }
  148. name := path[len(cmd):]
  149. if !treeCanMatch(name) {
  150. return filepath.SkipDir
  151. }
  152. // Commands are all in cmd/, not in subdirectories.
  153. if strings.Contains(name, string(filepath.Separator)) {
  154. return filepath.SkipDir
  155. }
  156. // We use, e.g., cmd/gofmt as the pseudo import path for gofmt.
  157. name = "cmd/" + name
  158. if have[name] {
  159. return nil
  160. }
  161. have[name] = true
  162. if !match(name) {
  163. return nil
  164. }
  165. _, err = buildContext.ImportDir(path, 0)
  166. if err != nil {
  167. if _, noGo := err.(*build.NoGoError); !noGo {
  168. log.Print(err)
  169. }
  170. return nil
  171. }
  172. pkgs = append(pkgs, name)
  173. return nil
  174. })
  175. for _, src := range buildContext.SrcDirs() {
  176. if (pattern == "std" || pattern == "cmd") && src != gorootSrc {
  177. continue
  178. }
  179. src = filepath.Clean(src) + string(filepath.Separator)
  180. root := src
  181. if pattern == "cmd" {
  182. root += "cmd" + string(filepath.Separator)
  183. }
  184. filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
  185. if err != nil || !fi.IsDir() || path == src {
  186. return nil
  187. }
  188. // Avoid .foo, _foo, and testdata directory trees.
  189. _, elem := filepath.Split(path)
  190. if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
  191. return filepath.SkipDir
  192. }
  193. name := filepath.ToSlash(path[len(src):])
  194. if pattern == "std" && (strings.Contains(name, ".") || name == "cmd") {
  195. // The name "std" is only the standard library.
  196. // If the name is cmd, it's the root of the command tree.
  197. return filepath.SkipDir
  198. }
  199. if !treeCanMatch(name) {
  200. return filepath.SkipDir
  201. }
  202. if have[name] {
  203. return nil
  204. }
  205. have[name] = true
  206. if !match(name) {
  207. return nil
  208. }
  209. _, err = buildContext.ImportDir(path, 0)
  210. if err != nil {
  211. if _, noGo := err.(*build.NoGoError); noGo {
  212. return nil
  213. }
  214. }
  215. pkgs = append(pkgs, name)
  216. return nil
  217. })
  218. }
  219. return pkgs
  220. }
  221. // allPackagesInFS is like allPackages but is passed a pattern
  222. // beginning ./ or ../, meaning it should scan the tree rooted
  223. // at the given directory. There are ... in the pattern too.
  224. func allPackagesInFS(pattern string) []string {
  225. pkgs := matchPackagesInFS(pattern)
  226. if len(pkgs) == 0 {
  227. fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
  228. }
  229. return pkgs
  230. }
  231. func matchPackagesInFS(pattern string) []string {
  232. // Find directory to begin the scan.
  233. // Could be smarter but this one optimization
  234. // is enough for now, since ... is usually at the
  235. // end of a path.
  236. i := strings.Index(pattern, "...")
  237. dir, _ := path.Split(pattern[:i])
  238. // pattern begins with ./ or ../.
  239. // path.Clean will discard the ./ but not the ../.
  240. // We need to preserve the ./ for pattern matching
  241. // and in the returned import paths.
  242. prefix := ""
  243. if strings.HasPrefix(pattern, "./") {
  244. prefix = "./"
  245. }
  246. match := matchPattern(pattern)
  247. var pkgs []string
  248. filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
  249. if err != nil || !fi.IsDir() {
  250. return nil
  251. }
  252. if path == dir {
  253. // filepath.Walk starts at dir and recurses. For the recursive case,
  254. // the path is the result of filepath.Join, which calls filepath.Clean.
  255. // The initial case is not Cleaned, though, so we do this explicitly.
  256. //
  257. // This converts a path like "./io/" to "io". Without this step, running
  258. // "cd $GOROOT/src/pkg; go list ./io/..." would incorrectly skip the io
  259. // package, because prepending the prefix "./" to the unclean path would
  260. // result in "././io", and match("././io") returns false.
  261. path = filepath.Clean(path)
  262. }
  263. // Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..".
  264. _, elem := filepath.Split(path)
  265. dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".."
  266. if dot || strings.HasPrefix(elem, "_") || elem == "testdata" {
  267. return filepath.SkipDir
  268. }
  269. name := prefix + filepath.ToSlash(path)
  270. if !match(name) {
  271. return nil
  272. }
  273. if _, err = build.ImportDir(path, 0); err != nil {
  274. if _, noGo := err.(*build.NoGoError); !noGo {
  275. log.Print(err)
  276. }
  277. return nil
  278. }
  279. pkgs = append(pkgs, name)
  280. return nil
  281. })
  282. return pkgs
  283. }