github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/tools/refactor/importgraph/graph.go (about)

     1  // Copyright 2014 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package importgraph computes the forward and reverse import
     6  // dependency graphs for all packages in a Go workspace.
     7  package importgraph // import "golang.org/x/tools/refactor/importgraph"
     8  
     9  import (
    10  	"go/build"
    11  	"sync"
    12  
    13  	"golang.org/x/tools/go/buildutil"
    14  )
    15  
    16  // A Graph is an import dependency graph, either forward or reverse.
    17  //
    18  // The graph maps each node (a package import path) to the set of its
    19  // successors in the graph.  For a forward graph, this is the set of
    20  // imported packages (prerequisites); for a reverse graph, it is the set
    21  // of importing packages (clients).
    22  //
    23  type Graph map[string]map[string]bool
    24  
    25  func (g Graph) addEdge(from, to string) {
    26  	edges := g[from]
    27  	if edges == nil {
    28  		edges = make(map[string]bool)
    29  		g[from] = edges
    30  	}
    31  	edges[to] = true
    32  }
    33  
    34  // Search returns all the nodes of the graph reachable from
    35  // any of the specified roots, by following edges forwards.
    36  // Relationally, this is the reflexive transitive closure.
    37  func (g Graph) Search(roots ...string) map[string]bool {
    38  	seen := make(map[string]bool)
    39  	var visit func(x string)
    40  	visit = func(x string) {
    41  		if !seen[x] {
    42  			seen[x] = true
    43  			for y := range g[x] {
    44  				visit(y)
    45  			}
    46  		}
    47  	}
    48  	for _, root := range roots {
    49  		visit(root)
    50  	}
    51  	return seen
    52  }
    53  
    54  // Build scans the specified Go workspace and builds the forward and
    55  // reverse import dependency graphs for all its packages.
    56  // It also returns a mapping from canonical import paths to errors for packages
    57  // whose loading was not entirely successful.
    58  // A package may appear in the graph and in the errors mapping.
    59  // All package paths are canonical and may contain "/vendor/".
    60  func Build(ctxt *build.Context) (forward, reverse Graph, errors map[string]error) {
    61  	type importEdge struct {
    62  		from, to string
    63  	}
    64  	type pathError struct {
    65  		path string
    66  		err  error
    67  	}
    68  
    69  	ch := make(chan interface{})
    70  
    71  	go func() {
    72  		sema := make(chan int, 20) // I/O concurrency limiting semaphore
    73  		var wg sync.WaitGroup
    74  		buildutil.ForEachPackage(ctxt, func(path string, err error) {
    75  			if err != nil {
    76  				ch <- pathError{path, err}
    77  				return
    78  			}
    79  
    80  			wg.Add(1)
    81  			go func() {
    82  				defer wg.Done()
    83  
    84  				sema <- 1
    85  				bp, err := ctxt.Import(path, "", buildutil.AllowVendor)
    86  				<-sema
    87  
    88  				if err != nil {
    89  					if _, ok := err.(*build.NoGoError); ok {
    90  						// empty directory is not an error
    91  					} else {
    92  						ch <- pathError{path, err}
    93  					}
    94  					// Even in error cases, Import usually returns a package.
    95  				}
    96  
    97  				// absolutize resolves an import path relative
    98  				// to the current package bp.
    99  				// The absolute form may contain "vendor".
   100  				//
   101  				// The vendoring feature slows down Build by 3×.
   102  				// Here are timings from a 1400 package workspace:
   103  				//    1100ms: current code (with vendor check)
   104  				//     880ms: with a nonblocking cache around ctxt.IsDir
   105  				//     840ms: nonblocking cache with duplicate suppression
   106  				//     340ms: original code (no vendor check)
   107  				// TODO(adonovan): optimize, somehow.
   108  				absolutize := func(path string) string { return path }
   109  				if buildutil.AllowVendor != 0 {
   110  					memo := make(map[string]string)
   111  					absolutize = func(path string) string {
   112  						canon, ok := memo[path]
   113  						if !ok {
   114  							sema <- 1
   115  							bp2, _ := ctxt.Import(path, bp.Dir, build.FindOnly|buildutil.AllowVendor)
   116  							<-sema
   117  
   118  							if bp2 != nil {
   119  								canon = bp2.ImportPath
   120  							} else {
   121  								canon = path
   122  							}
   123  							memo[path] = canon
   124  						}
   125  						return canon
   126  					}
   127  				}
   128  
   129  				if bp != nil {
   130  					for _, imp := range bp.Imports {
   131  						ch <- importEdge{path, absolutize(imp)}
   132  					}
   133  					for _, imp := range bp.TestImports {
   134  						ch <- importEdge{path, absolutize(imp)}
   135  					}
   136  					for _, imp := range bp.XTestImports {
   137  						ch <- importEdge{path, absolutize(imp)}
   138  					}
   139  				}
   140  
   141  			}()
   142  		})
   143  		wg.Wait()
   144  		close(ch)
   145  	}()
   146  
   147  	forward = make(Graph)
   148  	reverse = make(Graph)
   149  
   150  	for e := range ch {
   151  		switch e := e.(type) {
   152  		case pathError:
   153  			if errors == nil {
   154  				errors = make(map[string]error)
   155  			}
   156  			errors[e.path] = e.err
   157  
   158  		case importEdge:
   159  			if e.to == "C" {
   160  				continue // "C" is fake
   161  			}
   162  			forward.addEdge(e.from, e.to)
   163  			reverse.addEdge(e.to, e.from)
   164  		}
   165  	}
   166  
   167  	return forward, reverse, errors
   168  }