...

Source file src/pkg/cmd/doc/dirs.go

     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		"log"
    10		"os"
    11		"os/exec"
    12		"path/filepath"
    13		"strings"
    14		"sync"
    15	)
    16	
    17	// A Dir describes a directory holding code by specifying
    18	// the expected import path and the file system directory.
    19	type Dir struct {
    20		importPath string // import path for that dir
    21		dir        string // file system directory
    22	}
    23	
    24	// Dirs is a structure for scanning the directory tree.
    25	// Its Next method returns the next Go source directory it finds.
    26	// Although it can be used to scan the tree multiple times, it
    27	// only walks the tree once, caching the data it finds.
    28	type Dirs struct {
    29		scan   chan Dir // Directories generated by walk.
    30		hist   []Dir    // History of reported Dirs.
    31		offset int      // Counter for Next.
    32	}
    33	
    34	var dirs Dirs
    35	
    36	// dirsInit starts the scanning of package directories in GOROOT and GOPATH. Any
    37	// extra paths passed to it are included in the channel.
    38	func dirsInit(extra ...Dir) {
    39		dirs.hist = make([]Dir, 0, 1000)
    40		dirs.hist = append(dirs.hist, extra...)
    41		dirs.scan = make(chan Dir)
    42		go dirs.walk(codeRoots())
    43	}
    44	
    45	// Reset puts the scan back at the beginning.
    46	func (d *Dirs) Reset() {
    47		d.offset = 0
    48	}
    49	
    50	// Next returns the next directory in the scan. The boolean
    51	// is false when the scan is done.
    52	func (d *Dirs) Next() (Dir, bool) {
    53		if d.offset < len(d.hist) {
    54			dir := d.hist[d.offset]
    55			d.offset++
    56			return dir, true
    57		}
    58		dir, ok := <-d.scan
    59		if !ok {
    60			return Dir{}, false
    61		}
    62		d.hist = append(d.hist, dir)
    63		d.offset++
    64		return dir, ok
    65	}
    66	
    67	// walk walks the trees in GOROOT and GOPATH.
    68	func (d *Dirs) walk(roots []Dir) {
    69		for _, root := range roots {
    70			d.bfsWalkRoot(root)
    71		}
    72		close(d.scan)
    73	}
    74	
    75	// bfsWalkRoot walks a single directory hierarchy in breadth-first lexical order.
    76	// Each Go source directory it finds is delivered on d.scan.
    77	func (d *Dirs) bfsWalkRoot(root Dir) {
    78		root.dir = filepath.Clean(root.dir) // because filepath.Join will do it anyway
    79	
    80		// this is the queue of directories to examine in this pass.
    81		this := []string{}
    82		// next is the queue of directories to examine in the next pass.
    83		next := []string{root.dir}
    84	
    85		for len(next) > 0 {
    86			this, next = next, this[0:0]
    87			for _, dir := range this {
    88				fd, err := os.Open(dir)
    89				if err != nil {
    90					log.Print(err)
    91					continue
    92				}
    93				entries, err := fd.Readdir(0)
    94				fd.Close()
    95				if err != nil {
    96					log.Print(err)
    97					continue
    98				}
    99				hasGoFiles := false
   100				for _, entry := range entries {
   101					name := entry.Name()
   102					// For plain files, remember if this directory contains any .go
   103					// source files, but ignore them otherwise.
   104					if !entry.IsDir() {
   105						if !hasGoFiles && strings.HasSuffix(name, ".go") {
   106							hasGoFiles = true
   107						}
   108						continue
   109					}
   110					// Entry is a directory.
   111	
   112					// The go tool ignores directories starting with ., _, or named "testdata".
   113					if name[0] == '.' || name[0] == '_' || name == "testdata" {
   114						continue
   115					}
   116					// Ignore vendor when using modules.
   117					if usingModules && name == "vendor" {
   118						continue
   119					}
   120					// Remember this (fully qualified) directory for the next pass.
   121					next = append(next, filepath.Join(dir, name))
   122				}
   123				if hasGoFiles {
   124					// It's a candidate.
   125					importPath := root.importPath
   126					if len(dir) > len(root.dir) {
   127						if importPath != "" {
   128							importPath += "/"
   129						}
   130						importPath += filepath.ToSlash(dir[len(root.dir)+1:])
   131					}
   132					d.scan <- Dir{importPath, dir}
   133				}
   134			}
   135	
   136		}
   137	}
   138	
   139	var testGOPATH = false // force GOPATH use for testing
   140	
   141	// codeRoots returns the code roots to search for packages.
   142	// In GOPATH mode this is GOROOT/src and GOPATH/src, with empty import paths.
   143	// In module mode, this is each module root, with an import path set to its module path.
   144	func codeRoots() []Dir {
   145		codeRootsCache.once.Do(func() {
   146			codeRootsCache.roots = findCodeRoots()
   147		})
   148		return codeRootsCache.roots
   149	}
   150	
   151	var codeRootsCache struct {
   152		once  sync.Once
   153		roots []Dir
   154	}
   155	
   156	var usingModules bool
   157	
   158	func findCodeRoots() []Dir {
   159		list := []Dir{{"", filepath.Join(buildCtx.GOROOT, "src")}}
   160	
   161		if !testGOPATH {
   162			// Check for use of modules by 'go env GOMOD',
   163			// which reports a go.mod file path if modules are enabled.
   164			stdout, _ := exec.Command("go", "env", "GOMOD").Output()
   165			usingModules = len(bytes.TrimSpace(stdout)) > 0
   166		}
   167	
   168		if !usingModules {
   169			for _, root := range splitGopath() {
   170				list = append(list, Dir{"", filepath.Join(root, "src")})
   171			}
   172			return list
   173		}
   174	
   175		// Find module root directories from go list.
   176		// Eventually we want golang.org/x/tools/go/packages
   177		// to handle the entire file system search and become go/packages,
   178		// but for now enumerating the module roots lets us fit modules
   179		// into the current code with as few changes as possible.
   180		cmd := exec.Command("go", "list", "-m", "-f={{.Path}}\t{{.Dir}}", "all")
   181		cmd.Stderr = os.Stderr
   182		out, _ := cmd.Output()
   183		for _, line := range strings.Split(string(out), "\n") {
   184			i := strings.Index(line, "\t")
   185			if i < 0 {
   186				continue
   187			}
   188			path, dir := line[:i], line[i+1:]
   189			if dir != "" {
   190				list = append(list, Dir{path, dir})
   191			}
   192		}
   193	
   194		return list
   195	}
   196	

View as plain text