|  | // Copyright 2015 The Go Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style | 
|  | // license that can be found in the LICENSE file. | 
|  |  | 
|  | package main | 
|  |  | 
|  | import ( | 
|  | "bytes" | 
|  | "log" | 
|  | "os" | 
|  | "os/exec" | 
|  | "path/filepath" | 
|  | "strings" | 
|  | "sync" | 
|  | ) | 
|  |  | 
|  | // A Dir describes a directory holding code by specifying | 
|  | // the expected import path and the file system directory. | 
|  | type Dir struct { | 
|  | importPath string // import path for that dir | 
|  | dir        string // file system directory | 
|  | } | 
|  |  | 
|  | // Dirs is a structure for scanning the directory tree. | 
|  | // Its Next method returns the next Go source directory it finds. | 
|  | // Although it can be used to scan the tree multiple times, it | 
|  | // only walks the tree once, caching the data it finds. | 
|  | type Dirs struct { | 
|  | scan   chan Dir // Directories generated by walk. | 
|  | hist   []Dir    // History of reported Dirs. | 
|  | offset int      // Counter for Next. | 
|  | } | 
|  |  | 
|  | var dirs Dirs | 
|  |  | 
|  | // dirsInit starts the scanning of package directories in GOROOT and GOPATH. Any | 
|  | // extra paths passed to it are included in the channel. | 
|  | func dirsInit(extra ...Dir) { | 
|  | dirs.hist = make([]Dir, 0, 1000) | 
|  | dirs.hist = append(dirs.hist, extra...) | 
|  | dirs.scan = make(chan Dir) | 
|  | go dirs.walk(codeRoots()) | 
|  | } | 
|  |  | 
|  | // Reset puts the scan back at the beginning. | 
|  | func (d *Dirs) Reset() { | 
|  | d.offset = 0 | 
|  | } | 
|  |  | 
|  | // Next returns the next directory in the scan. The boolean | 
|  | // is false when the scan is done. | 
|  | func (d *Dirs) Next() (Dir, bool) { | 
|  | if d.offset < len(d.hist) { | 
|  | dir := d.hist[d.offset] | 
|  | d.offset++ | 
|  | return dir, true | 
|  | } | 
|  | dir, ok := <-d.scan | 
|  | if !ok { | 
|  | return Dir{}, false | 
|  | } | 
|  | d.hist = append(d.hist, dir) | 
|  | d.offset++ | 
|  | return dir, ok | 
|  | } | 
|  |  | 
|  | // walk walks the trees in GOROOT and GOPATH. | 
|  | func (d *Dirs) walk(roots []Dir) { | 
|  | for _, root := range roots { | 
|  | d.bfsWalkRoot(root) | 
|  | } | 
|  | close(d.scan) | 
|  | } | 
|  |  | 
|  | // bfsWalkRoot walks a single directory hierarchy in breadth-first lexical order. | 
|  | // Each Go source directory it finds is delivered on d.scan. | 
|  | func (d *Dirs) bfsWalkRoot(root Dir) { | 
|  | root.dir = filepath.Clean(root.dir) // because filepath.Join will do it anyway | 
|  |  | 
|  | // this is the queue of directories to examine in this pass. | 
|  | this := []string{} | 
|  | // next is the queue of directories to examine in the next pass. | 
|  | next := []string{root.dir} | 
|  |  | 
|  | for len(next) > 0 { | 
|  | this, next = next, this[0:0] | 
|  | for _, dir := range this { | 
|  | fd, err := os.Open(dir) | 
|  | if err != nil { | 
|  | log.Print(err) | 
|  | continue | 
|  | } | 
|  | entries, err := fd.Readdir(0) | 
|  | fd.Close() | 
|  | if err != nil { | 
|  | log.Print(err) | 
|  | continue | 
|  | } | 
|  | hasGoFiles := false | 
|  | for _, entry := range entries { | 
|  | name := entry.Name() | 
|  | // For plain files, remember if this directory contains any .go | 
|  | // source files, but ignore them otherwise. | 
|  | if !entry.IsDir() { | 
|  | if !hasGoFiles && strings.HasSuffix(name, ".go") { | 
|  | hasGoFiles = true | 
|  | } | 
|  | continue | 
|  | } | 
|  | // Entry is a directory. | 
|  |  | 
|  | // The go tool ignores directories starting with ., _, or named "testdata". | 
|  | if name[0] == '.' || name[0] == '_' || name == "testdata" { | 
|  | continue | 
|  | } | 
|  | // Ignore vendor when using modules. | 
|  | if usingModules && name == "vendor" { | 
|  | continue | 
|  | } | 
|  | // Remember this (fully qualified) directory for the next pass. | 
|  | next = append(next, filepath.Join(dir, name)) | 
|  | } | 
|  | if hasGoFiles { | 
|  | // It's a candidate. | 
|  | importPath := root.importPath | 
|  | if len(dir) > len(root.dir) { | 
|  | if importPath != "" { | 
|  | importPath += "/" | 
|  | } | 
|  | importPath += filepath.ToSlash(dir[len(root.dir)+1:]) | 
|  | } | 
|  | d.scan <- Dir{importPath, dir} | 
|  | } | 
|  | } | 
|  |  | 
|  | } | 
|  | } | 
|  |  | 
|  | var testGOPATH = false // force GOPATH use for testing | 
|  |  | 
|  | // codeRoots returns the code roots to search for packages. | 
|  | // In GOPATH mode this is GOROOT/src and GOPATH/src, with empty import paths. | 
|  | // In module mode, this is each module root, with an import path set to its module path. | 
|  | func codeRoots() []Dir { | 
|  | codeRootsCache.once.Do(func() { | 
|  | codeRootsCache.roots = findCodeRoots() | 
|  | }) | 
|  | return codeRootsCache.roots | 
|  | } | 
|  |  | 
|  | var codeRootsCache struct { | 
|  | once  sync.Once | 
|  | roots []Dir | 
|  | } | 
|  |  | 
|  | var usingModules bool | 
|  |  | 
|  | func findCodeRoots() []Dir { | 
|  | list := []Dir{{"", filepath.Join(buildCtx.GOROOT, "src")}} | 
|  |  | 
|  | if !testGOPATH { | 
|  | // Check for use of modules by 'go env GOMOD', | 
|  | // which reports a go.mod file path if modules are enabled. | 
|  | stdout, _ := exec.Command("go", "env", "GOMOD").Output() | 
|  | usingModules = bytes.Contains(stdout, []byte("go.mod")) | 
|  | } | 
|  |  | 
|  | if !usingModules { | 
|  | for _, root := range splitGopath() { | 
|  | list = append(list, Dir{"", filepath.Join(root, "src")}) | 
|  | } | 
|  | return list | 
|  | } | 
|  |  | 
|  | // Find module root directories from go list. | 
|  | // Eventually we want golang.org/x/tools/go/packages | 
|  | // to handle the entire file system search and become go/packages, | 
|  | // but for now enumerating the module roots lets us fit modules | 
|  | // into the current code with as few changes as possible. | 
|  | cmd := exec.Command("go", "list", "-m", "-f={{.Path}}\t{{.Dir}}", "all") | 
|  | cmd.Stderr = os.Stderr | 
|  | out, _ := cmd.Output() | 
|  | for _, line := range strings.Split(string(out), "\n") { | 
|  | i := strings.Index(line, "\t") | 
|  | if i < 0 { | 
|  | continue | 
|  | } | 
|  | path, dir := line[:i], line[i+1:] | 
|  | if dir != "" { | 
|  | list = append(list, Dir{path, dir}) | 
|  | } | 
|  | } | 
|  |  | 
|  | return list | 
|  | } |