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.
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"
13 "golang.org/x/tools/go/buildutil"
16 // A Graph is an import dependency graph, either forward or reverse.
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).
23 // Graph construction inspects all imports in each package's directory,
24 // including those in _test.go files, so the resulting graph may be cyclic.
25 type Graph map[string]map[string]bool
27 func (g Graph) addEdge(from, to string) {
30 edges = make(map[string]bool)
36 // Search returns all the nodes of the graph reachable from
37 // any of the specified roots, by following edges forwards.
38 // Relationally, this is the reflexive transitive closure.
39 func (g Graph) Search(roots ...string) map[string]bool {
40 seen := make(map[string]bool)
41 var visit func(x string)
42 visit = func(x string) {
50 for _, root := range roots {
56 // Build scans the specified Go workspace and builds the forward and
57 // reverse import dependency graphs for all its packages.
58 // It also returns a mapping from canonical import paths to errors for packages
59 // whose loading was not entirely successful.
60 // A package may appear in the graph and in the errors mapping.
61 // All package paths are canonical and may contain "/vendor/".
62 func Build(ctxt *build.Context) (forward, reverse Graph, errors map[string]error) {
63 type importEdge struct {
66 type pathError struct {
71 ch := make(chan interface{})
74 sema := make(chan int, 20) // I/O concurrency limiting semaphore
76 buildutil.ForEachPackage(ctxt, func(path string, err error) {
78 ch <- pathError{path, err}
87 bp, err := ctxt.Import(path, "", 0)
91 if _, ok := err.(*build.NoGoError); ok {
92 // empty directory is not an error
94 ch <- pathError{path, err}
96 // Even in error cases, Import usually returns a package.
99 // absolutize resolves an import path relative
100 // to the current package bp.
101 // The absolute form may contain "vendor".
103 // The vendoring feature slows down Build by 3×.
104 // Here are timings from a 1400 package workspace:
105 // 1100ms: current code (with vendor check)
106 // 880ms: with a nonblocking cache around ctxt.IsDir
107 // 840ms: nonblocking cache with duplicate suppression
108 // 340ms: original code (no vendor check)
109 // TODO(adonovan): optimize, somehow.
110 memo := make(map[string]string)
111 absolutize := func(path string) string {
112 canon, ok := memo[path]
115 bp2, _ := ctxt.Import(path, bp.Dir, build.FindOnly)
119 canon = bp2.ImportPath
129 for _, imp := range bp.Imports {
130 ch <- importEdge{path, absolutize(imp)}
132 for _, imp := range bp.TestImports {
133 ch <- importEdge{path, absolutize(imp)}
135 for _, imp := range bp.XTestImports {
136 ch <- importEdge{path, absolutize(imp)}
146 forward = make(Graph)
147 reverse = make(Graph)
150 switch e := e.(type) {
153 errors = make(map[string]error)
155 errors[e.path] = e.err
159 continue // "C" is fake
161 forward.addEdge(e.from, e.to)
162 reverse.addEdge(e.to, e.from)
166 return forward, reverse, errors