depth.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. // Package depth provides the ability to traverse and retrieve Go source code dependencies in the form of
  2. // internal and external packages.
  3. //
  4. // For example, the dependencies of the stdlib `strings` package can be resolved like so:
  5. //
  6. // import "github.com/KyleBanks/depth"
  7. //
  8. // var t depth.Tree
  9. // err := t.Resolve("strings")
  10. // if err != nil {
  11. // log.Fatal(err)
  12. // }
  13. //
  14. // // Output: "strings has 4 dependencies."
  15. // log.Printf("%v has %v dependencies.", t.Root.Name, len(t.Root.Deps))
  16. //
  17. // For additional customization, simply set the appropriate flags on the `Tree` before resolving:
  18. //
  19. // import "github.com/KyleBanks/depth"
  20. //
  21. // t := depth.Tree {
  22. // ResolveInternal: true,
  23. // ResolveTest: true,
  24. // MaxDepth: 10,
  25. // }
  26. // err := t.Resolve("strings")
  27. package depth
  28. import (
  29. "errors"
  30. "go/build"
  31. "os"
  32. )
  33. // ErrRootPkgNotResolved is returned when the root Pkg of the Tree cannot be resolved,
  34. // typically because it does not exist.
  35. var ErrRootPkgNotResolved = errors.New("unable to resolve root package")
  36. // Importer defines a type that can import a package and return its details.
  37. type Importer interface {
  38. Import(name, srcDir string, im build.ImportMode) (*build.Package, error)
  39. }
  40. // Tree represents the top level of a Pkg and the configuration used to
  41. // initialize and represent its contents.
  42. type Tree struct {
  43. Root *Pkg
  44. ResolveInternal bool
  45. ResolveTest bool
  46. MaxDepth int
  47. Importer Importer
  48. importCache map[string]struct{}
  49. }
  50. // Resolve recursively finds all dependencies for the root Pkg name provided,
  51. // and the packages it depends on.
  52. func (t *Tree) Resolve(name string) error {
  53. pwd, err := os.Getwd()
  54. if err != nil {
  55. return err
  56. }
  57. t.Root = &Pkg{
  58. Name: name,
  59. Tree: t,
  60. SrcDir: pwd,
  61. Test: false,
  62. }
  63. // Reset the import cache each time to ensure a reused Tree doesn't
  64. // reuse the same cache.
  65. t.importCache = nil
  66. // Allow custom importers, but use build.Default if none is provided.
  67. if t.Importer == nil {
  68. t.Importer = &build.Default
  69. }
  70. t.Root.Resolve(t.Importer)
  71. if !t.Root.Resolved {
  72. return ErrRootPkgNotResolved
  73. }
  74. return nil
  75. }
  76. // shouldResolveInternal determines if internal packages should be further resolved beyond the
  77. // current parent.
  78. //
  79. // For example, if the parent Pkg is `github.com/foo/bar` and true is returned, all the
  80. // internal dependencies it relies on will be resolved. If for example `strings` is one of those
  81. // dependencies, and it is passed as the parent here, false may be returned and its internal
  82. // dependencies will not be resolved.
  83. func (t *Tree) shouldResolveInternal(parent *Pkg) bool {
  84. if t.ResolveInternal {
  85. return true
  86. }
  87. return parent == t.Root
  88. }
  89. // isAtMaxDepth returns true when the depth of the Pkg provided is at or beyond the maximum
  90. // depth allowed by the tree.
  91. //
  92. // If the Tree has a MaxDepth of zero, true is never returned.
  93. func (t *Tree) isAtMaxDepth(p *Pkg) bool {
  94. if t.MaxDepth == 0 {
  95. return false
  96. }
  97. return p.depth() >= t.MaxDepth
  98. }
  99. // hasSeenImport returns true if the import name provided has already been seen within the tree.
  100. // This function only returns false for a name once.
  101. func (t *Tree) hasSeenImport(name string) bool {
  102. if t.importCache == nil {
  103. t.importCache = make(map[string]struct{})
  104. }
  105. if _, ok := t.importCache[name]; ok {
  106. return true
  107. }
  108. t.importCache[name] = struct{}{}
  109. return false
  110. }