package main
import (
+ "bytes"
"log"
"os"
- "path"
+ "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 string // directories generated by walk.
- paths []string // Cache of known paths.
- offset int // Counter for Next.
+ 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 ...string) {
- dirs.paths = make([]string, 0, 1000)
- dirs.paths = append(dirs.paths, extra...)
- dirs.scan = make(chan string)
- go dirs.walk()
+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.
// Next returns the next directory in the scan. The boolean
// is false when the scan is done.
-func (d *Dirs) Next() (string, bool) {
- if d.offset < len(d.paths) {
- path := d.paths[d.offset]
+func (d *Dirs) Next() (Dir, bool) {
+ if d.offset < len(d.hist) {
+ dir := d.hist[d.offset]
d.offset++
- return path, true
+ return dir, true
}
- path, ok := <-d.scan
+ dir, ok := <-d.scan
if !ok {
- return "", false
+ return Dir{}, false
}
- d.paths = append(d.paths, path)
+ d.hist = append(d.hist, dir)
d.offset++
- return path, ok
+ return dir, ok
}
// walk walks the trees in GOROOT and GOPATH.
-func (d *Dirs) walk() {
- d.bfsWalkRoot(buildCtx.GOROOT)
- for _, root := range splitGopath() {
+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 string) {
- root = path.Join(root, "src")
+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}
+ next := []string{root.dir}
for len(next) > 0 {
this, next = next, this[0:0]
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.
- d.scan <- dir
+ 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
+}
return
}
}
+
importPath := pkg.build.ImportComment
if importPath == "" {
importPath = pkg.build.ImportPath
}
+
+ // If we're using modules, the import path derived from module code locations wins.
+ // If we did a file system scan, we knew the import path when we found the directory.
+ // But if we started with a directory name, we never knew the import path.
+ // Either way, we don't know it now, and it's cheap to (re)compute it.
+ if usingModules {
+ for _, root := range codeRoots() {
+ if pkg.build.Dir == root.dir {
+ importPath = root.importPath
+ break
+ }
+ if strings.HasPrefix(pkg.build.Dir, root.dir+string(filepath.Separator)) {
+ suffix := filepath.ToSlash(pkg.build.Dir[len(root.dir)+1:])
+ if root.importPath == "" {
+ importPath = suffix
+ } else {
+ importPath = root.importPath + "/" + suffix
+ }
+ break
+ }
+ }
+ }
+
pkg.Printf("package %s // import %q\n\n", pkg.name, importPath)
- if importPath != pkg.build.ImportPath {
+ if !usingModules && importPath != pkg.build.ImportPath {
pkg.Printf("WARNING: package source is installed in %q\n", pkg.build.ImportPath)
}
}