pkgwalk.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. /*
  2. Copyright 2017 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package main
  14. import (
  15. "fmt"
  16. "go/build"
  17. "os"
  18. "path"
  19. "sort"
  20. )
  21. // VisitFunc is a function called by WalkPkg to examine a single package.
  22. type VisitFunc func(importPath string, absPath string) error
  23. // ErrSkipPkg can be returned by a VisitFunc to indicate that the package in
  24. // question should not be walked any further.
  25. var ErrSkipPkg = fmt.Errorf("package skipped")
  26. // WalkPkg recursively visits all packages under pkgName. This is similar
  27. // to filepath.Walk, except that it follows symlinks. A package is always
  28. // visited before the children of that package. If visit returns ErrSkipPkg,
  29. // pkgName will not be walked.
  30. func WalkPkg(pkgName string, visit VisitFunc) error {
  31. // Visit the package itself.
  32. pkg, err := findPackage(pkgName)
  33. if err != nil {
  34. return err
  35. }
  36. if err := visit(pkg.ImportPath, pkg.Dir); err == ErrSkipPkg {
  37. return nil
  38. } else if err != nil {
  39. return err
  40. }
  41. // Read all of the child dirents and find sub-packages.
  42. infos, err := readDirInfos(pkg.Dir)
  43. if err != nil {
  44. return err
  45. }
  46. for _, info := range infos {
  47. if !info.IsDir() {
  48. continue
  49. }
  50. name := info.Name()
  51. if name[0] == '_' || (len(name) > 1 && name[0] == '.') || name == "testdata" {
  52. continue
  53. }
  54. // Don't use path.Join() because it drops leading `./` via path.Clean().
  55. err := WalkPkg(pkgName+"/"+name, visit)
  56. if err != nil {
  57. return err
  58. }
  59. }
  60. return nil
  61. }
  62. // findPackage finds a Go package.
  63. func findPackage(pkgName string) (*build.Package, error) {
  64. debug("find", pkgName)
  65. pkg, err := build.Import(pkgName, getwd(), build.FindOnly)
  66. if err != nil {
  67. return nil, err
  68. }
  69. return pkg, nil
  70. }
  71. // readDirInfos returns a list of os.FileInfo structures for the dirents under
  72. // dirPath. The result list is sorted by name. This is very similar to
  73. // ioutil.ReadDir, except that it follows symlinks.
  74. func readDirInfos(dirPath string) ([]os.FileInfo, error) {
  75. names, err := readDirNames(dirPath)
  76. if err != nil {
  77. return nil, err
  78. }
  79. sort.Strings(names)
  80. infos := make([]os.FileInfo, 0, len(names))
  81. for _, n := range names {
  82. info, err := os.Stat(path.Join(dirPath, n))
  83. if err != nil {
  84. return nil, err
  85. }
  86. infos = append(infos, info)
  87. }
  88. return infos, nil
  89. }
  90. // readDirNames returns a list of all dirents in dirPath. The result list is
  91. // not sorted or filtered.
  92. func readDirNames(dirPath string) ([]string, error) {
  93. d, err := os.Open(dirPath)
  94. if err != nil {
  95. return nil, err
  96. }
  97. defer d.Close()
  98. names, err := d.Readdirnames(-1)
  99. if err != nil {
  100. return nil, err
  101. }
  102. return names, nil
  103. }