github.com/gagliardetto/golang-go@v0.0.0-20201020153340-53909ea70814/cmd/doc/dirs.go (about) 1 // Copyright 2015 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 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 // A Dir describes a directory holding code by specifying 22 // the expected import path and the file system directory. 23 type Dir struct { 24 importPath string // import path for that dir 25 dir string // file system directory 26 inModule bool 27 } 28 29 // Dirs is a structure for scanning the directory tree. 30 // Its Next method returns the next Go source directory it finds. 31 // Although it can be used to scan the tree multiple times, it 32 // only walks the tree once, caching the data it finds. 33 type Dirs struct { 34 scan chan Dir // Directories generated by walk. 35 hist []Dir // History of reported Dirs. 36 offset int // Counter for Next. 37 } 38 39 var dirs Dirs 40 41 // dirsInit starts the scanning of package directories in GOROOT and GOPATH. Any 42 // extra paths passed to it are included in the channel. 43 func dirsInit(extra ...Dir) { 44 dirs.hist = make([]Dir, 0, 1000) 45 dirs.hist = append(dirs.hist, extra...) 46 dirs.scan = make(chan Dir) 47 go dirs.walk(codeRoots()) 48 } 49 50 // Reset puts the scan back at the beginning. 51 func (d *Dirs) Reset() { 52 d.offset = 0 53 } 54 55 // Next returns the next directory in the scan. The boolean 56 // is false when the scan is done. 57 func (d *Dirs) Next() (Dir, bool) { 58 if d.offset < len(d.hist) { 59 dir := d.hist[d.offset] 60 d.offset++ 61 return dir, true 62 } 63 dir, ok := <-d.scan 64 if !ok { 65 return Dir{}, false 66 } 67 d.hist = append(d.hist, dir) 68 d.offset++ 69 return dir, ok 70 } 71 72 // walk walks the trees in GOROOT and GOPATH. 73 func (d *Dirs) walk(roots []Dir) { 74 for _, root := range roots { 75 d.bfsWalkRoot(root) 76 } 77 close(d.scan) 78 } 79 80 // bfsWalkRoot walks a single directory hierarchy in breadth-first lexical order. 81 // Each Go source directory it finds is delivered on d.scan. 82 func (d *Dirs) bfsWalkRoot(root Dir) { 83 root.dir = filepath.Clean(root.dir) // because filepath.Join will do it anyway 84 85 // this is the queue of directories to examine in this pass. 86 this := []string{} 87 // next is the queue of directories to examine in the next pass. 88 next := []string{root.dir} 89 90 for len(next) > 0 { 91 this, next = next, this[0:0] 92 for _, dir := range this { 93 fd, err := os.Open(dir) 94 if err != nil { 95 log.Print(err) 96 continue 97 } 98 entries, err := fd.Readdir(0) 99 fd.Close() 100 if err != nil { 101 log.Print(err) 102 continue 103 } 104 hasGoFiles := false 105 for _, entry := range entries { 106 name := entry.Name() 107 // For plain files, remember if this directory contains any .go 108 // source files, but ignore them otherwise. 109 if !entry.IsDir() { 110 if !hasGoFiles && strings.HasSuffix(name, ".go") { 111 hasGoFiles = true 112 } 113 continue 114 } 115 // Entry is a directory. 116 117 // The go tool ignores directories starting with ., _, or named "testdata". 118 if name[0] == '.' || name[0] == '_' || name == "testdata" { 119 continue 120 } 121 // When in a module, ignore vendor directories and stop at module boundaries. 122 if root.inModule { 123 if name == "vendor" { 124 continue 125 } 126 if fi, err := os.Stat(filepath.Join(dir, name, "go.mod")); err == nil && !fi.IsDir() { 127 continue 128 } 129 } 130 // Remember this (fully qualified) directory for the next pass. 131 next = append(next, filepath.Join(dir, name)) 132 } 133 if hasGoFiles { 134 // It's a candidate. 135 importPath := root.importPath 136 if len(dir) > len(root.dir) { 137 if importPath != "" { 138 importPath += "/" 139 } 140 importPath += filepath.ToSlash(dir[len(root.dir)+1:]) 141 } 142 d.scan <- Dir{importPath, dir, root.inModule} 143 } 144 } 145 146 } 147 } 148 149 var testGOPATH = false // force GOPATH use for testing 150 151 // codeRoots returns the code roots to search for packages. 152 // In GOPATH mode this is GOROOT/src and GOPATH/src, with empty import paths. 153 // In module mode, this is each module root, with an import path set to its module path. 154 func codeRoots() []Dir { 155 codeRootsCache.once.Do(func() { 156 codeRootsCache.roots = findCodeRoots() 157 }) 158 return codeRootsCache.roots 159 } 160 161 var codeRootsCache struct { 162 once sync.Once 163 roots []Dir 164 } 165 166 var usingModules bool 167 168 func findCodeRoots() []Dir { 169 var list []Dir 170 if !testGOPATH { 171 // Check for use of modules by 'go env GOMOD', 172 // which reports a go.mod file path if modules are enabled. 173 stdout, _ := exec.Command("go", "env", "GOMOD").Output() 174 gomod := string(bytes.TrimSpace(stdout)) 175 176 usingModules = len(gomod) > 0 177 if usingModules { 178 list = append(list, 179 Dir{dir: filepath.Join(buildCtx.GOROOT, "src"), inModule: true}, 180 Dir{importPath: "cmd", dir: filepath.Join(buildCtx.GOROOT, "src", "cmd"), inModule: true}) 181 } 182 183 if gomod == os.DevNull { 184 // Modules are enabled, but the working directory is outside any module. 185 // We can still access std, cmd, and packages specified as source files 186 // on the command line, but there are no module roots. 187 // Avoid 'go list -m all' below, since it will not work. 188 return list 189 } 190 } 191 192 if !usingModules { 193 list = append(list, Dir{dir: filepath.Join(buildCtx.GOROOT, "src")}) 194 for _, root := range splitGopath() { 195 list = append(list, Dir{dir: filepath.Join(root, "src")}) 196 } 197 return list 198 } 199 200 // Find module root directories from go list. 201 // Eventually we want golang.org/x/tools/go/packages 202 // to handle the entire file system search and become go/packages, 203 // but for now enumerating the module roots lets us fit modules 204 // into the current code with as few changes as possible. 205 mainMod, vendorEnabled, err := vendorEnabled() 206 if err != nil { 207 return list 208 } 209 if vendorEnabled { 210 // Add the vendor directory to the search path ahead of "std". 211 // That way, if the main module *is* "std", we will identify the path 212 // without the "vendor/" prefix before the one with that prefix. 213 list = append([]Dir{{dir: filepath.Join(mainMod.Dir, "vendor"), inModule: false}}, list...) 214 if mainMod.Path != "std" { 215 list = append(list, Dir{importPath: mainMod.Path, dir: mainMod.Dir, inModule: true}) 216 } 217 return list 218 } 219 220 cmd := exec.Command("go", "list", "-m", "-f={{.Path}}\t{{.Dir}}", "all") 221 cmd.Stderr = os.Stderr 222 out, _ := cmd.Output() 223 for _, line := range strings.Split(string(out), "\n") { 224 i := strings.Index(line, "\t") 225 if i < 0 { 226 continue 227 } 228 path, dir := line[:i], line[i+1:] 229 if dir != "" { 230 list = append(list, Dir{importPath: path, dir: dir, inModule: true}) 231 } 232 } 233 234 return list 235 } 236 237 // The functions below are derived from x/tools/internal/imports at CL 203017. 238 239 type moduleJSON struct { 240 Path, Dir, GoVersion string 241 } 242 243 var modFlagRegexp = regexp.MustCompile(`-mod[ =](\w+)`) 244 245 // vendorEnabled indicates if vendoring is enabled. 246 // Inspired by setDefaultBuildMod in modload/init.go 247 func vendorEnabled() (*moduleJSON, bool, error) { 248 mainMod, go114, err := getMainModuleAnd114() 249 if err != nil { 250 return nil, false, err 251 } 252 253 stdout, _ := exec.Command("go", "env", "GOFLAGS").Output() 254 goflags := string(bytes.TrimSpace(stdout)) 255 matches := modFlagRegexp.FindStringSubmatch(goflags) 256 var modFlag string 257 if len(matches) != 0 { 258 modFlag = matches[1] 259 } 260 if modFlag != "" { 261 // Don't override an explicit '-mod=' argument. 262 return mainMod, modFlag == "vendor", nil 263 } 264 if mainMod == nil || !go114 { 265 return mainMod, false, nil 266 } 267 // Check 1.14's automatic vendor mode. 268 if fi, err := os.Stat(filepath.Join(mainMod.Dir, "vendor")); err == nil && fi.IsDir() { 269 if mainMod.GoVersion != "" && semver.Compare("v"+mainMod.GoVersion, "v1.14") >= 0 { 270 // The Go version is at least 1.14, and a vendor directory exists. 271 // Set -mod=vendor by default. 272 return mainMod, true, nil 273 } 274 } 275 return mainMod, false, nil 276 } 277 278 // getMainModuleAnd114 gets the main module's information and whether the 279 // go command in use is 1.14+. This is the information needed to figure out 280 // if vendoring should be enabled. 281 func getMainModuleAnd114() (*moduleJSON, bool, error) { 282 const format = `{{.Path}} 283 {{.Dir}} 284 {{.GoVersion}} 285 {{range context.ReleaseTags}}{{if eq . "go1.14"}}{{.}}{{end}}{{end}} 286 ` 287 cmd := exec.Command("go", "list", "-m", "-f", format) 288 cmd.Stderr = os.Stderr 289 stdout, err := cmd.Output() 290 if err != nil { 291 return nil, false, nil 292 } 293 lines := strings.Split(string(stdout), "\n") 294 if len(lines) < 5 { 295 return nil, false, fmt.Errorf("unexpected stdout: %q", stdout) 296 } 297 mod := &moduleJSON{ 298 Path: lines[0], 299 Dir: lines[1], 300 GoVersion: lines[2], 301 } 302 return mod, lines[3] == "go1.14", nil 303 }