blob: d2d8f098b3f50230c897809d1d0c7ad6975f2003 [file] [log] [blame]
Alan Donovan3cded4a2014-09-09 18:39:26 -04001// 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.
David Symonds24257c82014-12-09 15:00:58 +11007package importgraph // import "golang.org/x/tools/refactor/importgraph"
Alan Donovan3cded4a2014-09-09 18:39:26 -04008
9import (
10 "go/build"
11 "sync"
12
Andrew Gerrand5ebbcd12014-11-10 08:50:40 +110013 "golang.org/x/tools/go/buildutil"
Alan Donovan3cded4a2014-09-09 18:39:26 -040014)
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//
Alan Donovanac021062016-02-22 15:26:12 -050023// Graph construction inspects all imports in each package's directory,
24// including those in _test.go files, so the resulting graph may be cyclic.
Alan Donovan3cded4a2014-09-09 18:39:26 -040025type Graph map[string]map[string]bool
26
27func (g Graph) addEdge(from, to string) {
28 edges := g[from]
29 if edges == nil {
30 edges = make(map[string]bool)
31 g[from] = edges
32 }
33 edges[to] = true
34}
35
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.
39func (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) {
43 if !seen[x] {
44 seen[x] = true
45 for y := range g[x] {
46 visit(y)
47 }
48 }
49 }
50 for _, root := range roots {
51 visit(root)
52 }
53 return seen
54}
55
Alan Donovane079f6c2015-01-21 14:13:33 -050056// Build scans the specified Go workspace and builds the forward and
Alan Donovan3cded4a2014-09-09 18:39:26 -040057// reverse import dependency graphs for all its packages.
Alan Donovanfa833fd2015-12-18 14:11:09 -050058// It also returns a mapping from canonical import paths to errors for packages
Alan Donovana9866432015-06-04 18:38:33 -040059// whose loading was not entirely successful.
60// A package may appear in the graph and in the errors mapping.
Alan Donovanfa833fd2015-12-18 14:11:09 -050061// All package paths are canonical and may contain "/vendor/".
Alan Donovan3cded4a2014-09-09 18:39:26 -040062func Build(ctxt *build.Context) (forward, reverse Graph, errors map[string]error) {
Alan Donovan41f0d012014-12-03 09:56:23 -050063 type importEdge struct {
64 from, to string
65 }
Alan Donovan3cded4a2014-09-09 18:39:26 -040066 type pathError struct {
67 path string
68 err error
69 }
Alan Donovan41f0d012014-12-03 09:56:23 -050070
71 ch := make(chan interface{})
Alan Donovan3cded4a2014-09-09 18:39:26 -040072
Alan Donovan3a85b8d2015-12-30 14:17:09 -050073 go func() {
74 sema := make(chan int, 20) // I/O concurrency limiting semaphore
75 var wg sync.WaitGroup
76 buildutil.ForEachPackage(ctxt, func(path string, err error) {
Alan Donovan41f0d012014-12-03 09:56:23 -050077 if err != nil {
78 ch <- pathError{path, err}
79 return
80 }
Alan Donovana9866432015-06-04 18:38:33 -040081
Alan Donovan3a85b8d2015-12-30 14:17:09 -050082 wg.Add(1)
83 go func() {
84 defer wg.Done()
Alan Donovanfa833fd2015-12-18 14:11:09 -050085
Alan Donovan3a85b8d2015-12-30 14:17:09 -050086 sema <- 1
Alan Donovan10712092016-01-08 13:18:57 -050087 bp, err := ctxt.Import(path, "", 0)
Alan Donovan3a85b8d2015-12-30 14:17:09 -050088 <-sema
Alan Donovanfa833fd2015-12-18 14:11:09 -050089
Alan Donovan3a85b8d2015-12-30 14:17:09 -050090 if err != nil {
91 if _, ok := err.(*build.NoGoError); ok {
92 // empty directory is not an error
93 } else {
94 ch <- pathError{path, err}
Alan Donovanfa833fd2015-12-18 14:11:09 -050095 }
Alan Donovan3a85b8d2015-12-30 14:17:09 -050096 // Even in error cases, Import usually returns a package.
Alan Donovanfa833fd2015-12-18 14:11:09 -050097 }
Alan Donovanfa833fd2015-12-18 14:11:09 -050098
Alan Donovan3a85b8d2015-12-30 14:17:09 -050099 // absolutize resolves an import path relative
100 // to the current package bp.
101 // The absolute form may contain "vendor".
102 //
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.
Alan Donovan10712092016-01-08 13:18:57 -0500110 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)
116 <-sema
Alan Donovan3a85b8d2015-12-30 14:17:09 -0500117
Alan Donovan10712092016-01-08 13:18:57 -0500118 if bp2 != nil {
119 canon = bp2.ImportPath
120 } else {
121 canon = path
Alan Donovan3a85b8d2015-12-30 14:17:09 -0500122 }
Alan Donovan10712092016-01-08 13:18:57 -0500123 memo[path] = canon
Alan Donovan3a85b8d2015-12-30 14:17:09 -0500124 }
Alan Donovan10712092016-01-08 13:18:57 -0500125 return canon
Alan Donovana9866432015-06-04 18:38:33 -0400126 }
Alan Donovan3a85b8d2015-12-30 14:17:09 -0500127
128 if bp != nil {
129 for _, imp := range bp.Imports {
130 ch <- importEdge{path, absolutize(imp)}
131 }
132 for _, imp := range bp.TestImports {
133 ch <- importEdge{path, absolutize(imp)}
134 }
135 for _, imp := range bp.XTestImports {
136 ch <- importEdge{path, absolutize(imp)}
137 }
Alan Donovana9866432015-06-04 18:38:33 -0400138 }
Alan Donovan3a85b8d2015-12-30 14:17:09 -0500139
140 }()
141 })
Alan Donovan41f0d012014-12-03 09:56:23 -0500142 wg.Wait()
143 close(ch)
144 }()
Alan Donovan3cded4a2014-09-09 18:39:26 -0400145
Alan Donovan41f0d012014-12-03 09:56:23 -0500146 forward = make(Graph)
147 reverse = make(Graph)
148
149 for e := range ch {
150 switch e := e.(type) {
151 case pathError:
152 if errors == nil {
153 errors = make(map[string]error)
154 }
155 errors[e.path] = e.err
156
157 case importEdge:
158 if e.to == "C" {
159 continue // "C" is fake
160 }
161 forward.addEdge(e.from, e.to)
162 reverse.addEdge(e.to, e.from)
163 }
164 }
Alan Donovan3cded4a2014-09-09 18:39:26 -0400165
166 return forward, reverse, errors
167}