2014-09-09 16:39:26 -06:00
|
|
|
|
// 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.
|
2014-12-08 21:00:58 -07:00
|
|
|
|
package importgraph // import "golang.org/x/tools/refactor/importgraph"
|
2014-09-09 16:39:26 -06:00
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"go/build"
|
|
|
|
|
"sync"
|
|
|
|
|
|
2014-11-09 14:50:40 -07:00
|
|
|
|
"golang.org/x/tools/go/buildutil"
|
2014-09-09 16:39:26 -06:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// 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).
|
|
|
|
|
//
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-21 12:13:33 -07:00
|
|
|
|
// Build scans the specified Go workspace and builds the forward and
|
2014-09-09 16:39:26 -06:00
|
|
|
|
// reverse import dependency graphs for all its packages.
|
2015-12-18 12:11:09 -07:00
|
|
|
|
// It also returns a mapping from canonical import paths to errors for packages
|
2015-06-04 16:38:33 -06:00
|
|
|
|
// whose loading was not entirely successful.
|
|
|
|
|
// A package may appear in the graph and in the errors mapping.
|
2015-12-18 12:11:09 -07:00
|
|
|
|
// All package paths are canonical and may contain "/vendor/".
|
2014-09-09 16:39:26 -06:00
|
|
|
|
func Build(ctxt *build.Context) (forward, reverse Graph, errors map[string]error) {
|
2014-12-03 07:56:23 -07:00
|
|
|
|
type importEdge struct {
|
|
|
|
|
from, to string
|
|
|
|
|
}
|
2014-09-09 16:39:26 -06:00
|
|
|
|
type pathError struct {
|
|
|
|
|
path string
|
|
|
|
|
err error
|
|
|
|
|
}
|
2014-12-03 07:56:23 -07:00
|
|
|
|
|
|
|
|
|
ch := make(chan interface{})
|
2014-09-09 16:39:26 -06:00
|
|
|
|
|
2015-12-30 12:17:09 -07:00
|
|
|
|
go func() {
|
|
|
|
|
sema := make(chan int, 20) // I/O concurrency limiting semaphore
|
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
|
buildutil.ForEachPackage(ctxt, func(path string, err error) {
|
2014-12-03 07:56:23 -07:00
|
|
|
|
if err != nil {
|
|
|
|
|
ch <- pathError{path, err}
|
|
|
|
|
return
|
|
|
|
|
}
|
2015-06-04 16:38:33 -06:00
|
|
|
|
|
2015-12-30 12:17:09 -07:00
|
|
|
|
wg.Add(1)
|
|
|
|
|
go func() {
|
|
|
|
|
defer wg.Done()
|
2015-12-18 12:11:09 -07:00
|
|
|
|
|
2015-12-30 12:17:09 -07:00
|
|
|
|
sema <- 1
|
2016-01-08 11:18:57 -07:00
|
|
|
|
bp, err := ctxt.Import(path, "", 0)
|
2015-12-30 12:17:09 -07:00
|
|
|
|
<-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.
|
2015-06-04 16:38:33 -06:00
|
|
|
|
}
|
2015-12-18 12:11:09 -07:00
|
|
|
|
|
2015-12-30 12:17:09 -07:00
|
|
|
|
// 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.
|
2016-01-08 11:18:57 -07:00
|
|
|
|
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
|
2015-12-18 12:11:09 -07:00
|
|
|
|
}
|
2016-01-08 11:18:57 -07:00
|
|
|
|
memo[path] = canon
|
2015-12-18 12:11:09 -07:00
|
|
|
|
}
|
2016-01-08 11:18:57 -07:00
|
|
|
|
return canon
|
2015-12-18 12:11:09 -07:00
|
|
|
|
}
|
|
|
|
|
|
2015-12-30 12:17:09 -07:00
|
|
|
|
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)}
|
|
|
|
|
}
|
2015-06-04 16:38:33 -06:00
|
|
|
|
}
|
2015-12-30 12:17:09 -07:00
|
|
|
|
|
|
|
|
|
}()
|
|
|
|
|
})
|
2014-12-03 07:56:23 -07:00
|
|
|
|
wg.Wait()
|
|
|
|
|
close(ch)
|
|
|
|
|
}()
|
2014-09-09 16:39:26 -06:00
|
|
|
|
|
2014-12-03 07:56:23 -07:00
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
2014-09-09 16:39:26 -06:00
|
|
|
|
|
|
|
|
|
return forward, reverse, errors
|
|
|
|
|
}
|