-
Notifications
You must be signed in to change notification settings - Fork 4
/
main.go
165 lines (146 loc) · 3.41 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
// prdeps prints the dependency graph of a Go package.
//
// Usage:
//
// prdeps <importpath>
//
// prdeps takes one or more import paths as arguments.
// An easy way to satisfy this requirement is to use go list:
//
// % prdeps $(go list) # runs prdeps for the cwd
// % prdeps $(go list ./...) # runs prdeps for a package tree
package main
import (
"bytes"
"errors"
"flag"
"fmt"
"go/build"
"log"
"os"
"path/filepath"
"sort"
"strings"
"text/template"
)
// cache of resolved packages
var pkgcache = make(map[string]*build.Package)
// cache of output at indent level
var printcache = make(map[struct {
int
string
}]string)
var (
stdlib bool // exclude the stdlib
testimports bool // print test imports
xtestimports bool // print external test imports
maxdepth int // maximum depth to follow dependency chains
)
func spaces(n int) string {
return strings.Repeat(" ", n*2)
}
func loadpkg(importpath string) *build.Package {
pkg, ok := pkgcache[importpath]
if ok {
return pkg
}
pkg, err := build.Import(importpath, "", 0)
if err != nil {
log.Fatalf("could not locate %q: %v", importpath, err)
}
pkgcache[pkg.ImportPath] = pkg
return pkg
}
func printpkg(importpath string, t *template.Template, depth int) {
switch importpath {
case "C", "unsafe":
// fake packages, ignore
return
}
pkg := loadpkg(importpath)
if pkg.Goroot && !stdlib {
// do not traverse into the stdlib unless requested
return
}
key := struct {
int
string
}{depth, pkg.ImportPath}
if out, ok := printcache[key]; ok {
os.Stdout.Write([]byte(out))
} else {
var b bytes.Buffer
t.Execute(&b, struct {
*build.Package
Indent string
}{Package: pkg, Indent: spaces(depth)})
out := b.Bytes()
os.Stdout.Write(out)
printcache[key] = string(out)
}
var deps []string
switch {
case testimports && depth == 0:
deps = pkg.TestImports
case xtestimports && depth == 0:
deps = pkg.XTestImports
default:
deps = pkg.Imports
}
sort.Strings(deps)
depth++
if depth > maxdepth {
return
}
for _, dep := range deps {
printpkg(dep, t, depth)
}
}
// rel returns the relative path between srcDir and wd, or an error if wd is not
// a subdirectory of srcDir
func getGoSubpath(srcDir, wd string) (string, error) {
if wd == "" {
return "", errors.New("working directory is not in GOPATH")
}
relpath, err := filepath.Rel(srcDir, wd)
if err != nil {
return "", err
}
if relpath == "" || relpath[0] == '.' {
return "", fmt.Errorf("working directory %s is not in GOPATH", wd)
}
return relpath, nil
}
func main() {
flag.BoolVar(&stdlib, "s", false, "include stdlib")
flag.BoolVar(&testimports, "t", false, "print test imports")
flag.BoolVar(&xtestimports, "T", false, "print external test imports")
flag.IntVar(&maxdepth, "d", 2<<31-1, "maximum recurse depth")
tmpl := flag.String("f", "{{.Indent}}{{.ImportPath}}:", "output format")
flag.Parse()
*tmpl += "\n"
var args []string
if flag.NArg() < 1 {
wd, err := os.Getwd()
if err != nil {
log.Printf("Usage: %s <importpath>\n", os.Args[0])
flag.Usage()
}
srcPath := filepath.Join(build.Default.GOPATH, "src")
rel, err := getGoSubpath(srcPath, wd)
if err != nil {
log.Printf("%v\nUsage: %s <importpath>\n", err, os.Args[0])
flag.Usage()
}
args = []string{rel}
} else {
args = flag.Args()
}
t, err := template.New("").Parse(*tmpl)
if err != nil {
log.Fatal(err)
}
for _, pkg := range args {
printpkg(pkg, t, 0)
}
}