gopkg.in/alecthomas/gometalinter.v3@v3.0.0/_linters/src/golang.org/x/tools/internal/gopathwalk/walk.go (about)

     1  // Copyright 2018 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 gopathwalk is like filepath.Walk but specialized for finding Go
     6  // packages, particularly in $GOPATH and $GOROOT.
     7  package gopathwalk
     8  
     9  import (
    10  	"bufio"
    11  	"bytes"
    12  	"fmt"
    13  	"go/build"
    14  	"io/ioutil"
    15  	"log"
    16  	"os"
    17  	"path/filepath"
    18  	"strings"
    19  
    20  	"golang.org/x/tools/internal/fastwalk"
    21  )
    22  
    23  // Options controls the behavior of a Walk call.
    24  type Options struct {
    25  	Debug          bool // Enable debug logging
    26  	ModulesEnabled bool // Search module caches. Also disables legacy goimports ignore rules.
    27  }
    28  
    29  // RootType indicates the type of a Root.
    30  type RootType int
    31  
    32  const (
    33  	RootUnknown RootType = iota
    34  	RootGOROOT
    35  	RootGOPATH
    36  	RootCurrentModule
    37  	RootModuleCache
    38  )
    39  
    40  // A Root is a starting point for a Walk.
    41  type Root struct {
    42  	Path string
    43  	Type RootType
    44  }
    45  
    46  // SrcDirsRoots returns the roots from build.Default.SrcDirs(). Not modules-compatible.
    47  func SrcDirsRoots() []Root {
    48  	var roots []Root
    49  	roots = append(roots, Root{filepath.Join(build.Default.GOROOT, "src"), RootGOROOT})
    50  	for _, p := range filepath.SplitList(build.Default.GOPATH) {
    51  		roots = append(roots, Root{filepath.Join(p, "src"), RootGOPATH})
    52  	}
    53  	return roots
    54  }
    55  
    56  // Walk walks Go source directories ($GOROOT, $GOPATH, etc) to find packages.
    57  // For each package found, add will be called (concurrently) with the absolute
    58  // paths of the containing source directory and the package directory.
    59  // add will be called concurrently.
    60  func Walk(roots []Root, add func(root Root, dir string), opts Options) {
    61  	for _, root := range roots {
    62  		walkDir(root, add, opts)
    63  	}
    64  }
    65  
    66  func walkDir(root Root, add func(Root, string), opts Options) {
    67  	if _, err := os.Stat(root.Path); os.IsNotExist(err) {
    68  		if opts.Debug {
    69  			log.Printf("skipping nonexistant directory: %v", root.Path)
    70  		}
    71  		return
    72  	}
    73  	if opts.Debug {
    74  		log.Printf("scanning %s", root.Path)
    75  	}
    76  	w := &walker{
    77  		root: root,
    78  		add:  add,
    79  		opts: opts,
    80  	}
    81  	w.init()
    82  	if err := fastwalk.Walk(root.Path, w.walk); err != nil {
    83  		log.Printf("gopathwalk: scanning directory %v: %v", root.Path, err)
    84  	}
    85  
    86  	if opts.Debug {
    87  		log.Printf("scanned %s", root.Path)
    88  	}
    89  }
    90  
    91  // walker is the callback for fastwalk.Walk.
    92  type walker struct {
    93  	root Root               // The source directory to scan.
    94  	add  func(Root, string) // The callback that will be invoked for every possible Go package dir.
    95  	opts Options            // Options passed to Walk by the user.
    96  
    97  	ignoredDirs []os.FileInfo // The ignored directories, loaded from .goimportsignore files.
    98  }
    99  
   100  // init initializes the walker based on its Options.
   101  func (w *walker) init() {
   102  	var ignoredPaths []string
   103  	if w.root.Type == RootModuleCache {
   104  		ignoredPaths = []string{"cache"}
   105  	}
   106  	if !w.opts.ModulesEnabled && w.root.Type == RootGOPATH {
   107  		ignoredPaths = w.getIgnoredDirs(w.root.Path)
   108  		ignoredPaths = append(ignoredPaths, "v", "mod")
   109  	}
   110  
   111  	for _, p := range ignoredPaths {
   112  		full := filepath.Join(w.root.Path, p)
   113  		if fi, err := os.Stat(full); err == nil {
   114  			w.ignoredDirs = append(w.ignoredDirs, fi)
   115  			if w.opts.Debug {
   116  				log.Printf("Directory added to ignore list: %s", full)
   117  			}
   118  		} else if w.opts.Debug {
   119  			log.Printf("Error statting ignored directory: %v", err)
   120  		}
   121  	}
   122  }
   123  
   124  // getIgnoredDirs reads an optional config file at <path>/.goimportsignore
   125  // of relative directories to ignore when scanning for go files.
   126  // The provided path is one of the $GOPATH entries with "src" appended.
   127  func (w *walker) getIgnoredDirs(path string) []string {
   128  	file := filepath.Join(path, ".goimportsignore")
   129  	slurp, err := ioutil.ReadFile(file)
   130  	if w.opts.Debug {
   131  		if err != nil {
   132  			log.Print(err)
   133  		} else {
   134  			log.Printf("Read %s", file)
   135  		}
   136  	}
   137  	if err != nil {
   138  		return nil
   139  	}
   140  
   141  	var ignoredDirs []string
   142  	bs := bufio.NewScanner(bytes.NewReader(slurp))
   143  	for bs.Scan() {
   144  		line := strings.TrimSpace(bs.Text())
   145  		if line == "" || strings.HasPrefix(line, "#") {
   146  			continue
   147  		}
   148  		ignoredDirs = append(ignoredDirs, line)
   149  	}
   150  	return ignoredDirs
   151  }
   152  
   153  func (w *walker) shouldSkipDir(fi os.FileInfo) bool {
   154  	for _, ignoredDir := range w.ignoredDirs {
   155  		if os.SameFile(fi, ignoredDir) {
   156  			return true
   157  		}
   158  	}
   159  	return false
   160  }
   161  
   162  func (w *walker) walk(path string, typ os.FileMode) error {
   163  	dir := filepath.Dir(path)
   164  	if typ.IsRegular() {
   165  		if dir == w.root.Path {
   166  			// Doesn't make sense to have regular files
   167  			// directly in your $GOPATH/src or $GOROOT/src.
   168  			return fastwalk.SkipFiles
   169  		}
   170  		if !strings.HasSuffix(path, ".go") {
   171  			return nil
   172  		}
   173  
   174  		w.add(w.root, dir)
   175  		return fastwalk.SkipFiles
   176  	}
   177  	if typ == os.ModeDir {
   178  		base := filepath.Base(path)
   179  		if base == "" || base[0] == '.' || base[0] == '_' ||
   180  			base == "testdata" ||
   181  			(w.root.Type == RootGOROOT && w.opts.ModulesEnabled && base == "vendor") ||
   182  			(!w.opts.ModulesEnabled && base == "node_modules") {
   183  			return filepath.SkipDir
   184  		}
   185  		fi, err := os.Lstat(path)
   186  		if err == nil && w.shouldSkipDir(fi) {
   187  			return filepath.SkipDir
   188  		}
   189  		return nil
   190  	}
   191  	if typ == os.ModeSymlink {
   192  		base := filepath.Base(path)
   193  		if strings.HasPrefix(base, ".#") {
   194  			// Emacs noise.
   195  			return nil
   196  		}
   197  		fi, err := os.Lstat(path)
   198  		if err != nil {
   199  			// Just ignore it.
   200  			return nil
   201  		}
   202  		if w.shouldTraverse(dir, fi) {
   203  			return fastwalk.TraverseLink
   204  		}
   205  	}
   206  	return nil
   207  }
   208  
   209  // shouldTraverse reports whether the symlink fi, found in dir,
   210  // should be followed.  It makes sure symlinks were never visited
   211  // before to avoid symlink loops.
   212  func (w *walker) shouldTraverse(dir string, fi os.FileInfo) bool {
   213  	path := filepath.Join(dir, fi.Name())
   214  	target, err := filepath.EvalSymlinks(path)
   215  	if err != nil {
   216  		return false
   217  	}
   218  	ts, err := os.Stat(target)
   219  	if err != nil {
   220  		fmt.Fprintln(os.Stderr, err)
   221  		return false
   222  	}
   223  	if !ts.IsDir() {
   224  		return false
   225  	}
   226  	if w.shouldSkipDir(ts) {
   227  		return false
   228  	}
   229  	// Check for symlink loops by statting each directory component
   230  	// and seeing if any are the same file as ts.
   231  	for {
   232  		parent := filepath.Dir(path)
   233  		if parent == path {
   234  			// Made it to the root without seeing a cycle.
   235  			// Use this symlink.
   236  			return true
   237  		}
   238  		parentInfo, err := os.Stat(parent)
   239  		if err != nil {
   240  			return false
   241  		}
   242  		if os.SameFile(ts, parentInfo) {
   243  			// Cycle. Don't traverse.
   244  			return false
   245  		}
   246  		path = parent
   247  	}
   248  
   249  }