| // 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 |
| } |