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 "go/build" 9 "log" 10 "os" 11 "path" 12 "path/filepath" 13 "strings" 14 ) 15 16 // Dirs is a structure for scanning the directory tree. 17 // Its Next method returns the next Go source directory it finds. 18 // Although it can be used to scan the tree multiple times, it 19 // only walks the tree once, caching the data it finds. 20 type Dirs struct { 21 scan chan string // directories generated by walk. 22 paths []string // Cache of known paths. 23 offset int // Counter for Next. 24 } 25 26 var dirs Dirs 27 28 func init() { 29 dirs.paths = make([]string, 0, 1000) 30 dirs.scan = make(chan string) 31 go dirs.walk() 32 } 33 34 // Reset puts the scan back at the beginning. 35 func (d *Dirs) Reset() { 36 d.offset = 0 37 } 38 39 // Next returns the next directory in the scan. The boolean 40 // is false when the scan is done. 41 func (d *Dirs) Next() (string, bool) { 42 if d.offset < len(d.paths) { 43 path := d.paths[d.offset] 44 d.offset++ 45 return path, true 46 } 47 path, ok := <-d.scan 48 if !ok { 49 return "", false 50 } 51 d.paths = append(d.paths, path) 52 d.offset++ 53 return path, ok 54 } 55 56 // walk walks the trees in GOROOT and GOPATH. 57 func (d *Dirs) walk() { 58 d.bfsWalkRoot(build.Default.GOROOT) 59 for _, root := range splitGopath() { 60 d.bfsWalkRoot(root) 61 } 62 close(d.scan) 63 } 64 65 // bfsWalkRoot walks a single directory hierarchy in breadth-first lexical order. 66 // Each Go source directory it finds is delivered on d.scan. 67 func (d *Dirs) bfsWalkRoot(root string) { 68 root = path.Join(root, "src") 69 70 // this is the queue of directories to examine in this pass. 71 this := []string{} 72 // next is the queue of directories to examine in the next pass. 73 next := []string{root} 74 75 for len(next) > 0 { 76 this, next = next, this[0:0] 77 for _, dir := range this { 78 fd, err := os.Open(dir) 79 if err != nil { 80 log.Print(err) 81 continue 82 } 83 entries, err := fd.Readdir(0) 84 fd.Close() 85 if err != nil { 86 log.Print(err) 87 continue 88 } 89 hasGoFiles := false 90 for _, entry := range entries { 91 name := entry.Name() 92 // For plain files, remember if this directory contains any .go 93 // source files, but ignore them otherwise. 94 if !entry.IsDir() { 95 if !hasGoFiles && strings.HasSuffix(name, ".go") { 96 hasGoFiles = true 97 } 98 continue 99 } 100 // Entry is a directory. 101 // No .git or other dot nonsense please. 102 if strings.HasPrefix(name, ".") { 103 continue 104 } 105 // Remember this (fully qualified) directory for the next pass. 106 next = append(next, filepath.Join(dir, name)) 107 } 108 if hasGoFiles { 109 // It's a candidate. 110 d.scan <- dir 111 } 112 } 113 114 } 115 } 116