blob: c6f5cd9af8516289f336e25dec18128351398869 [file] [log] [blame]
// 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 = len(bytes.TrimSpace(stdout)) > 0
}
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
}