...
Run Format

Source file src/cmd/doc/dirs.go

Documentation: cmd/doc

     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 = bytes.Contains(stdout, []byte("go.mod"))
   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