168 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			168 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Go
		
	
	
	
// 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
 | 
						||
}
 |