github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/tools/refactor/importgraph/graph.go (about) 1 // Copyright 2014 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 importgraph computes the forward and reverse import 6 // dependency graphs for all packages in a Go workspace. 7 package importgraph // import "golang.org/x/tools/refactor/importgraph" 8 9 import ( 10 "go/build" 11 "sync" 12 13 "golang.org/x/tools/go/buildutil" 14 ) 15 16 // A Graph is an import dependency graph, either forward or reverse. 17 // 18 // The graph maps each node (a package import path) to the set of its 19 // successors in the graph. For a forward graph, this is the set of 20 // imported packages (prerequisites); for a reverse graph, it is the set 21 // of importing packages (clients). 22 // 23 type Graph map[string]map[string]bool 24 25 func (g Graph) addEdge(from, to string) { 26 edges := g[from] 27 if edges == nil { 28 edges = make(map[string]bool) 29 g[from] = edges 30 } 31 edges[to] = true 32 } 33 34 // Search returns all the nodes of the graph reachable from 35 // any of the specified roots, by following edges forwards. 36 // Relationally, this is the reflexive transitive closure. 37 func (g Graph) Search(roots ...string) map[string]bool { 38 seen := make(map[string]bool) 39 var visit func(x string) 40 visit = func(x string) { 41 if !seen[x] { 42 seen[x] = true 43 for y := range g[x] { 44 visit(y) 45 } 46 } 47 } 48 for _, root := range roots { 49 visit(root) 50 } 51 return seen 52 } 53 54 // Build scans the specified Go workspace and builds the forward and 55 // reverse import dependency graphs for all its packages. 56 // It also returns a mapping from canonical import paths to errors for packages 57 // whose loading was not entirely successful. 58 // A package may appear in the graph and in the errors mapping. 59 // All package paths are canonical and may contain "/vendor/". 60 func Build(ctxt *build.Context) (forward, reverse Graph, errors map[string]error) { 61 type importEdge struct { 62 from, to string 63 } 64 type pathError struct { 65 path string 66 err error 67 } 68 69 ch := make(chan interface{}) 70 71 go func() { 72 sema := make(chan int, 20) // I/O concurrency limiting semaphore 73 var wg sync.WaitGroup 74 buildutil.ForEachPackage(ctxt, func(path string, err error) { 75 if err != nil { 76 ch <- pathError{path, err} 77 return 78 } 79 80 wg.Add(1) 81 go func() { 82 defer wg.Done() 83 84 sema <- 1 85 bp, err := ctxt.Import(path, "", buildutil.AllowVendor) 86 <-sema 87 88 if err != nil { 89 if _, ok := err.(*build.NoGoError); ok { 90 // empty directory is not an error 91 } else { 92 ch <- pathError{path, err} 93 } 94 // Even in error cases, Import usually returns a package. 95 } 96 97 // absolutize resolves an import path relative 98 // to the current package bp. 99 // The absolute form may contain "vendor". 100 // 101 // The vendoring feature slows down Build by 3×. 102 // Here are timings from a 1400 package workspace: 103 // 1100ms: current code (with vendor check) 104 // 880ms: with a nonblocking cache around ctxt.IsDir 105 // 840ms: nonblocking cache with duplicate suppression 106 // 340ms: original code (no vendor check) 107 // TODO(adonovan): optimize, somehow. 108 absolutize := func(path string) string { return path } 109 if buildutil.AllowVendor != 0 { 110 memo := make(map[string]string) 111 absolutize = func(path string) string { 112 canon, ok := memo[path] 113 if !ok { 114 sema <- 1 115 bp2, _ := ctxt.Import(path, bp.Dir, build.FindOnly|buildutil.AllowVendor) 116 <-sema 117 118 if bp2 != nil { 119 canon = bp2.ImportPath 120 } else { 121 canon = path 122 } 123 memo[path] = canon 124 } 125 return canon 126 } 127 } 128 129 if bp != nil { 130 for _, imp := range bp.Imports { 131 ch <- importEdge{path, absolutize(imp)} 132 } 133 for _, imp := range bp.TestImports { 134 ch <- importEdge{path, absolutize(imp)} 135 } 136 for _, imp := range bp.XTestImports { 137 ch <- importEdge{path, absolutize(imp)} 138 } 139 } 140 141 }() 142 }) 143 wg.Wait() 144 close(ch) 145 }() 146 147 forward = make(Graph) 148 reverse = make(Graph) 149 150 for e := range ch { 151 switch e := e.(type) { 152 case pathError: 153 if errors == nil { 154 errors = make(map[string]error) 155 } 156 errors[e.path] = e.err 157 158 case importEdge: 159 if e.to == "C" { 160 continue // "C" is fake 161 } 162 forward.addEdge(e.from, e.to) 163 reverse.addEdge(e.to, e.from) 164 } 165 } 166 167 return forward, reverse, errors 168 }