Source file
src/cmd/doc/dirs.go
1
2
3
4
5 package main
6
7 import (
8 "bytes"
9 "fmt"
10 "log"
11 "os"
12 "os/exec"
13 "path/filepath"
14 "regexp"
15 "strings"
16 "sync"
17
18 "golang.org/x/mod/semver"
19 )
20
21
22
23 type Dir struct {
24 importPath string
25 dir string
26 inModule bool
27 }
28
29
30
31
32
33 type Dirs struct {
34 scan chan Dir
35 hist []Dir
36 offset int
37 }
38
39 var dirs Dirs
40
41
42
43 func dirsInit(extra ...Dir) {
44 if buildCtx.GOROOT == "" {
45 stdout, err := exec.Command("go", "env", "GOROOT").Output()
46 if err != nil {
47 if ee, ok := err.(*exec.ExitError); ok && len(ee.Stderr) > 0 {
48 log.Fatalf("failed to determine GOROOT: $GOROOT is not set and 'go env GOROOT' failed:\n%s", ee.Stderr)
49 }
50 log.Fatalf("failed to determine GOROOT: $GOROOT is not set and could not run 'go env GOROOT':\n\t%s", err)
51 }
52 buildCtx.GOROOT = string(bytes.TrimSpace(stdout))
53 }
54
55 dirs.hist = make([]Dir, 0, 1000)
56 dirs.hist = append(dirs.hist, extra...)
57 dirs.scan = make(chan Dir)
58 go dirs.walk(codeRoots())
59 }
60
61
62 func goCmd() string {
63 if buildCtx.GOROOT == "" {
64 return "go"
65 }
66 return filepath.Join(buildCtx.GOROOT, "bin", "go")
67 }
68
69
70 func (d *Dirs) Reset() {
71 d.offset = 0
72 }
73
74
75
76 func (d *Dirs) Next() (Dir, bool) {
77 if d.offset < len(d.hist) {
78 dir := d.hist[d.offset]
79 d.offset++
80 return dir, true
81 }
82 dir, ok := <-d.scan
83 if !ok {
84 return Dir{}, false
85 }
86 d.hist = append(d.hist, dir)
87 d.offset++
88 return dir, ok
89 }
90
91
92 func (d *Dirs) walk(roots []Dir) {
93 for _, root := range roots {
94 d.bfsWalkRoot(root)
95 }
96 close(d.scan)
97 }
98
99
100
101 func (d *Dirs) bfsWalkRoot(root Dir) {
102 root.dir = filepath.Clean(root.dir)
103
104
105 this := []string{}
106
107 next := []string{root.dir}
108
109 for len(next) > 0 {
110 this, next = next, this[0:0]
111 for _, dir := range this {
112 fd, err := os.Open(dir)
113 if err != nil {
114 log.Print(err)
115 continue
116 }
117 entries, err := fd.Readdir(0)
118 fd.Close()
119 if err != nil {
120 log.Print(err)
121 continue
122 }
123 hasGoFiles := false
124 for _, entry := range entries {
125 name := entry.Name()
126
127
128 if !entry.IsDir() {
129 if !hasGoFiles && strings.HasSuffix(name, ".go") {
130 hasGoFiles = true
131 }
132 continue
133 }
134
135
136
137 if name[0] == '.' || name[0] == '_' || name == "testdata" {
138 continue
139 }
140
141 if root.inModule {
142 if name == "vendor" {
143 continue
144 }
145 if fi, err := os.Stat(filepath.Join(dir, name, "go.mod")); err == nil && !fi.IsDir() {
146 continue
147 }
148 }
149
150 next = append(next, filepath.Join(dir, name))
151 }
152 if hasGoFiles {
153
154 importPath := root.importPath
155 if len(dir) > len(root.dir) {
156 if importPath != "" {
157 importPath += "/"
158 }
159 importPath += filepath.ToSlash(dir[len(root.dir)+1:])
160 }
161 d.scan <- Dir{importPath, dir, root.inModule}
162 }
163 }
164
165 }
166 }
167
168 var testGOPATH = false
169
170
171
172
173 func codeRoots() []Dir {
174 codeRootsCache.once.Do(func() {
175 codeRootsCache.roots = findCodeRoots()
176 })
177 return codeRootsCache.roots
178 }
179
180 var codeRootsCache struct {
181 once sync.Once
182 roots []Dir
183 }
184
185 var usingModules bool
186
187 func findCodeRoots() []Dir {
188 var list []Dir
189 if !testGOPATH {
190
191
192 stdout, _ := exec.Command(goCmd(), "env", "GOMOD").Output()
193 gomod := string(bytes.TrimSpace(stdout))
194
195 usingModules = len(gomod) > 0
196 if usingModules && buildCtx.GOROOT != "" {
197 list = append(list,
198 Dir{dir: filepath.Join(buildCtx.GOROOT, "src"), inModule: true},
199 Dir{importPath: "cmd", dir: filepath.Join(buildCtx.GOROOT, "src", "cmd"), inModule: true})
200 }
201
202 if gomod == os.DevNull {
203
204
205
206
207 return list
208 }
209 }
210
211 if !usingModules {
212 if buildCtx.GOROOT != "" {
213 list = append(list, Dir{dir: filepath.Join(buildCtx.GOROOT, "src")})
214 }
215 for _, root := range splitGopath() {
216 list = append(list, Dir{dir: filepath.Join(root, "src")})
217 }
218 return list
219 }
220
221
222
223
224
225
226 mainMod, vendorEnabled, err := vendorEnabled()
227 if err != nil {
228 return list
229 }
230 if vendorEnabled {
231
232
233
234 list = append([]Dir{{dir: filepath.Join(mainMod.Dir, "vendor"), inModule: false}}, list...)
235 if mainMod.Path != "std" {
236 list = append(list, Dir{importPath: mainMod.Path, dir: mainMod.Dir, inModule: true})
237 }
238 return list
239 }
240
241 cmd := exec.Command(goCmd(), "list", "-m", "-f={{.Path}}\t{{.Dir}}", "all")
242 cmd.Stderr = os.Stderr
243 out, _ := cmd.Output()
244 for _, line := range strings.Split(string(out), "\n") {
245 path, dir, _ := strings.Cut(line, "\t")
246 if dir != "" {
247 list = append(list, Dir{importPath: path, dir: dir, inModule: true})
248 }
249 }
250
251 return list
252 }
253
254
255
256 type moduleJSON struct {
257 Path, Dir, GoVersion string
258 }
259
260 var modFlagRegexp = regexp.MustCompile(`-mod[ =](\w+)`)
261
262
263
264 func vendorEnabled() (*moduleJSON, bool, error) {
265 mainMod, go114, err := getMainModuleAnd114()
266 if err != nil {
267 return nil, false, err
268 }
269
270 stdout, _ := exec.Command(goCmd(), "env", "GOFLAGS").Output()
271 goflags := string(bytes.TrimSpace(stdout))
272 matches := modFlagRegexp.FindStringSubmatch(goflags)
273 var modFlag string
274 if len(matches) != 0 {
275 modFlag = matches[1]
276 }
277 if modFlag != "" {
278
279 return mainMod, modFlag == "vendor", nil
280 }
281 if mainMod == nil || !go114 {
282 return mainMod, false, nil
283 }
284
285 if fi, err := os.Stat(filepath.Join(mainMod.Dir, "vendor")); err == nil && fi.IsDir() {
286 if mainMod.GoVersion != "" && semver.Compare("v"+mainMod.GoVersion, "v1.14") >= 0 {
287
288
289 return mainMod, true, nil
290 }
291 }
292 return mainMod, false, nil
293 }
294
295
296
297
298 func getMainModuleAnd114() (*moduleJSON, bool, error) {
299 const format = `{{.Path}}
300 {{.Dir}}
301 {{.GoVersion}}
302 {{range context.ReleaseTags}}{{if eq . "go1.14"}}{{.}}{{end}}{{end}}
303 `
304 cmd := exec.Command(goCmd(), "list", "-m", "-f", format)
305 cmd.Stderr = os.Stderr
306 stdout, err := cmd.Output()
307 if err != nil {
308 return nil, false, nil
309 }
310 lines := strings.Split(string(stdout), "\n")
311 if len(lines) < 5 {
312 return nil, false, fmt.Errorf("unexpected stdout: %q", stdout)
313 }
314 mod := &moduleJSON{
315 Path: lines[0],
316 Dir: lines[1],
317 GoVersion: lines[2],
318 }
319 return mod, lines[3] == "go1.14", nil
320 }
321
View as plain text