import (
"bufio"
"bytes"
+ "encoding/json"
"flag"
"fmt"
"go/ast"
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
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 {
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 {
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)
}