| // 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 any) | |
| 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.Go(func() { | |
| 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 | |
| } |