refactor/importgraph: changes for vendor support

Each string in build.Package.{,Test,XTest}Imports must now be
interpreted relative to the Package.Dir.  This adds a ton of
redundant I/O.

Also:
- use a counting semaphore to limit concurrent I/O calls
  (Fixes golang/go#10306)
- remove obsolete call to runtime.GOMAXPROCS from the test.

Change-Id: Ic556c4cf41cce7a88c0158800c992e66f354c484
Reviewed-on: https://go-review.googlesource.com/18050
Reviewed-by: Russ Cox <rsc@golang.org>
diff --git a/refactor/importgraph/graph.go b/refactor/importgraph/graph.go
index 8ad8014..fc8691d 100644
--- a/refactor/importgraph/graph.go
+++ b/refactor/importgraph/graph.go
@@ -53,9 +53,10 @@
 
 // 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 import paths to errors for 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
@@ -67,6 +68,7 @@
 
 	ch := make(chan interface{})
 
+	sema := make(chan int, 20) // I/O concurrency limiting semaphore
 	var wg sync.WaitGroup
 	buildutil.ForEachPackage(ctxt, func(path string, err error) {
 		wg.Add(1)
@@ -77,7 +79,10 @@
 				return
 			}
 
-			bp, err := ctxt.Import(path, "", 0)
+			sema <- 1
+			bp, err := ctxt.Import(path, "", buildutil.AllowVendor)
+			<-sema
+
 			if err != nil {
 				if _, ok := err.(*build.NoGoError); ok {
 					// empty directory is not an error
@@ -86,15 +91,43 @@
 				}
 				// 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".
+			// TODO(adonovan): opt: experiment with
+			// overriding the IsDir method with a caching version
+			// to avoid a huge number of redundant I/O calls.
+			absolutize := func(path string) string { return path }
+			if buildutil.AllowVendor != 0 {
+				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|buildutil.AllowVendor)
+						<-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, imp}
+					ch <- importEdge{path, absolutize(imp)}
 				}
 				for _, imp := range bp.TestImports {
-					ch <- importEdge{path, imp}
+					ch <- importEdge{path, absolutize(imp)}
 				}
 				for _, imp := range bp.XTestImports {
-					ch <- importEdge{path, imp}
+					ch <- importEdge{path, absolutize(imp)}
 				}
 			}
 		}()