+++ /dev/null
-// 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
-}