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 }