From 361a01983f01790694d5c7773d56a560c01b96cb Mon Sep 17 00:00:00 2001 From: "Bryan C. Mills" Date: Tue, 5 Mar 2019 16:52:48 -0500 Subject: [PATCH] cmd/api: use 'go list' to locate transitive dependencies of std With standard-library modules and vendoring, the mapping from import path to directory within the standard library is no longer entirely trivial. Fortunately, 'go list' makes that mapping straightforward to compute. Updates #30241 Updates #30228 Change-Id: Iddd77c21a527b7acdb30c17bec8b4bbd43e23756 Reviewed-on: https://go-review.googlesource.com/c/go/+/165497 Run-TryBot: Bryan C. Mills TryBot-Result: Gobot Gobot Reviewed-by: Jay Conrod --- src/cmd/api/goapi.go | 84 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 77 insertions(+), 7 deletions(-) diff --git a/src/cmd/api/goapi.go b/src/cmd/api/goapi.go index 60359229de..1a0242f60c 100644 --- a/src/cmd/api/goapi.go +++ b/src/cmd/api/goapi.go @@ -8,6 +8,7 @@ package main import ( "bufio" "bytes" + "encoding/json" "flag" "fmt" "go/ast" @@ -153,6 +154,7 @@ func main() { var featureCtx = make(map[string]map[string]bool) // feature -> context name -> true for _, context := range contexts { w := NewWalker(context, filepath.Join(build.Default.GOROOT, "src")) + w.loadImports(pkgNames, w.context) for _, name := range pkgNames { // Vendored packages do not contribute to our @@ -349,12 +351,14 @@ func fileFeatures(filename string) []string { var fset = token.NewFileSet() type Walker struct { - context *build.Context - root string - scope []string - current *types.Package - features map[string]bool // set - imported map[string]*types.Package // packages already imported + context *build.Context + root string + scope []string + current *types.Package + features map[string]bool // set + imported map[string]*types.Package // packages already imported + importMap map[string]map[string]string // importer dir -> import path -> canonical path + importDir map[string]string // canonical import path -> dir } func NewWalker(context *build.Context, root string) *Walker { @@ -434,11 +438,74 @@ func tagKey(dir string, context *build.Context, tags []string) string { return key } +func (w *Walker) loadImports(paths []string, context *build.Context) { + if context == nil { + context = &build.Default + } + + var ( + tags = context.BuildTags + cgoEnabled = "0" + ) + if context.CgoEnabled { + tags = append(tags[:len(tags):len(tags)], "cgo") + cgoEnabled = "1" + } + + // TODO(golang.org/issue/29666): Request only the fields that we need. + cmd := exec.Command(goCmd(), "list", "-e", "-deps", "-json") + if len(tags) > 0 { + cmd.Args = append(cmd.Args, "-tags", strings.Join(tags, " ")) + } + cmd.Args = append(cmd.Args, paths...) + + cmd.Env = append(os.Environ(), + "GOOS="+context.GOOS, + "GOARCH="+context.GOARCH, + "CGO_ENABLED="+cgoEnabled, + ) + + stdout := new(bytes.Buffer) + cmd.Stdout = stdout + cmd.Stderr = new(strings.Builder) + err := cmd.Run() + if err != nil { + log.Fatalf("%s failed: %v\n%s", strings.Join(cmd.Args, " "), err, cmd.Stderr) + } + + w.importDir = make(map[string]string) + w.importMap = make(map[string]map[string]string) + dec := json.NewDecoder(stdout) + for { + var pkg struct { + ImportPath, Dir string + ImportMap map[string]string + } + if err := dec.Decode(&pkg); err == io.EOF { + break + } else if err != nil { + log.Fatalf("%s: invalid output: %v", strings.Join(cmd.Args, " "), err) + } + + w.importDir[pkg.ImportPath] = pkg.Dir + w.importMap[pkg.Dir] = pkg.ImportMap + } +} + // Importing is a sentinel taking the place in Walker.imported // for a package that is in the process of being imported. var importing types.Package func (w *Walker) Import(name string) (*types.Package, error) { + return w.ImportFrom(name, "", 0) +} + +func (w *Walker) ImportFrom(fromPath, fromDir string, mode types.ImportMode) (*types.Package, error) { + name := fromPath + if canonical, ok := w.importMap[fromDir][fromPath]; ok { + name = canonical + } + pkg := w.imported[name] if pkg != nil { if pkg == &importing { @@ -449,7 +516,10 @@ func (w *Walker) Import(name string) (*types.Package, error) { w.imported[name] = &importing // Determine package files. - dir := filepath.Join(w.root, filepath.FromSlash(name)) + dir := w.importDir[name] + if dir == "" { + dir = filepath.Join(w.root, filepath.FromSlash(name)) + } if fi, err := os.Stat(dir); err != nil || !fi.IsDir() { log.Fatalf("no source in tree for import %q: %v", name, err) }