mirror of
https://github.com/golang/go
synced 2024-11-18 10:54:40 -07:00
refactor/importgraph: a utility for computing the import graph.
Refactoring tools can use this to determine an upper bound on the set of packages potentially affected by a refactoring. LGTM=gri R=gri CC=golang-codereviews https://golang.org/cl/141310043
This commit is contained in:
parent
77b9ff6df3
commit
3cded4a933
125
refactor/importgraph/graph.go
Normal file
125
refactor/importgraph/graph.go
Normal file
@ -0,0 +1,125 @@
|
||||
// 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 (
|
||||
"go/build"
|
||||
"sync"
|
||||
|
||||
"code.google.com/p/go.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).
|
||||
//
|
||||
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
|
||||
}
|
||||
|
||||
// Builds scans the specified Go workspace and builds the forward and
|
||||
// reverse import dependency graphs for all its packages.
|
||||
// It also returns a mapping from import paths to errors for packages
|
||||
// that could not be loaded.
|
||||
func Build(ctxt *build.Context) (forward, reverse Graph, errors map[string]error) {
|
||||
// The (sole) graph builder goroutine receives a stream of import
|
||||
// edges from the package loading goroutine.
|
||||
forward = make(Graph)
|
||||
reverse = make(Graph)
|
||||
edgec := make(chan [2]string)
|
||||
go func() {
|
||||
for edge := range edgec {
|
||||
if edge[1] == "C" {
|
||||
continue // "C" is fake
|
||||
}
|
||||
forward.addEdge(edge[0], edge[1])
|
||||
reverse.addEdge(edge[1], edge[0])
|
||||
}
|
||||
}()
|
||||
|
||||
// The (sole) error goroutine receives a stream of ReadDir and
|
||||
// Import errors.
|
||||
type pathError struct {
|
||||
path string
|
||||
err error
|
||||
}
|
||||
errorc := make(chan pathError)
|
||||
go func() {
|
||||
for e := range errorc {
|
||||
if errors == nil {
|
||||
errors = make(map[string]error)
|
||||
}
|
||||
errors[e.path] = e.err
|
||||
}
|
||||
}()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
buildutil.AllPackages(ctxt, func(path string, err error) {
|
||||
if err != nil {
|
||||
errorc <- pathError{path, err}
|
||||
return
|
||||
}
|
||||
wg.Add(1)
|
||||
// The import goroutines load the metadata for each package.
|
||||
go func(path string) {
|
||||
defer wg.Done()
|
||||
bp, err := ctxt.Import(path, "", 0)
|
||||
if _, ok := err.(*build.NoGoError); ok {
|
||||
return // empty directory is not an error
|
||||
}
|
||||
if err != nil {
|
||||
errorc <- pathError{path, err}
|
||||
return
|
||||
}
|
||||
for _, imp := range bp.Imports {
|
||||
edgec <- [2]string{path, imp}
|
||||
}
|
||||
for _, imp := range bp.TestImports {
|
||||
edgec <- [2]string{path, imp}
|
||||
}
|
||||
for _, imp := range bp.XTestImports {
|
||||
edgec <- [2]string{path, imp}
|
||||
}
|
||||
}(path)
|
||||
})
|
||||
wg.Wait()
|
||||
|
||||
close(edgec)
|
||||
close(errorc)
|
||||
|
||||
return forward, reverse, errors
|
||||
}
|
81
refactor/importgraph/graph_test.go
Normal file
81
refactor/importgraph/graph_test.go
Normal file
@ -0,0 +1,81 @@
|
||||
package importgraph_test
|
||||
|
||||
import (
|
||||
"go/build"
|
||||
"runtime"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"code.google.com/p/go.tools/refactor/importgraph"
|
||||
|
||||
_ "crypto/hmac" // just for test, below
|
||||
)
|
||||
|
||||
const this = "code.google.com/p/go.tools/refactor/importgraph"
|
||||
|
||||
func TestBuild(t *testing.T) {
|
||||
saved := runtime.GOMAXPROCS(8) // Build is highly parallel
|
||||
defer runtime.GOMAXPROCS(saved)
|
||||
|
||||
forward, reverse, errors := importgraph.Build(&build.Default)
|
||||
|
||||
// Test direct edges.
|
||||
// We throw in crypto/hmac to prove that external test files
|
||||
// (such as this one) are inspected.
|
||||
for _, p := range []string{"go/build", "runtime", "testing", "crypto/hmac"} {
|
||||
if !forward[this][p] {
|
||||
t.Errorf("forward[importgraph][%s] not found", p)
|
||||
}
|
||||
if !reverse[p][this] {
|
||||
t.Errorf("reverse[%s][importgraph] not found", p)
|
||||
}
|
||||
}
|
||||
|
||||
// Test non-existent direct edges
|
||||
for _, p := range []string{"fmt", "errors", "reflect"} {
|
||||
if forward[this][p] {
|
||||
t.Errorf("unexpected: forward[importgraph][%s] found", p)
|
||||
}
|
||||
if reverse[p][this] {
|
||||
t.Errorf("unexpected: reverse[%s][importgraph] found", p)
|
||||
}
|
||||
}
|
||||
|
||||
// Test Search is reflexive.
|
||||
if !forward.Search(this)[this] {
|
||||
t.Errorf("irreflexive: forward.Search(importgraph)[importgraph] not found")
|
||||
}
|
||||
if !reverse.Search(this)[this] {
|
||||
t.Errorf("irrefexive: reverse.Search(importgraph)[importgraph] not found")
|
||||
}
|
||||
|
||||
// Test Search is transitive. (There is no direct edge to these packages.)
|
||||
for _, p := range []string{"errors", "reflect", "unsafe"} {
|
||||
if !forward.Search(this)[p] {
|
||||
t.Errorf("intransitive: forward.Search(importgraph)[%s] not found", p)
|
||||
}
|
||||
if !reverse.Search(p)[this] {
|
||||
t.Errorf("intransitive: reverse.Search(%s)[importgraph] not found", p)
|
||||
}
|
||||
}
|
||||
|
||||
// debugging
|
||||
if false {
|
||||
for path, err := range errors {
|
||||
t.Logf("%s: %s", path, err)
|
||||
}
|
||||
printSorted := func(direction string, g importgraph.Graph, start string) {
|
||||
t.Log(direction)
|
||||
var pkgs []string
|
||||
for pkg := range g.Search(start) {
|
||||
pkgs = append(pkgs, pkg)
|
||||
}
|
||||
sort.Strings(pkgs)
|
||||
for _, pkg := range pkgs {
|
||||
t.Logf("\t%s", pkg)
|
||||
}
|
||||
}
|
||||
printSorted("forward", forward, this)
|
||||
printSorted("forward", reverse, this)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user