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