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