"runtime"
"sort"
"strings"
+ "sync"
)
func goCmd() string {
c.Compiler = build.Default.Compiler
}
- var pkgNames []string
- if flag.NArg() > 0 {
- pkgNames = flag.Args()
- } else {
- stds, err := exec.Command(goCmd(), "list", "std").Output()
- if err != nil {
- log.Fatalf("go list std: %v\n%s", err, stds)
- }
- for _, pkg := range strings.Fields(string(stds)) {
- if !internalPkg.MatchString(pkg) {
- pkgNames = append(pkgNames, pkg)
- }
- }
+ walkers := make([]*Walker, len(contexts))
+ var wg sync.WaitGroup
+ for i, context := range contexts {
+ i, context := i, context
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ walkers[i] = NewWalker(context, filepath.Join(build.Default.GOROOT, "src"))
+ }()
}
+ wg.Wait()
- importDir, importMap := loadImports()
-
- // The code below assumes that the import map can vary
- // by package, so that an import in one package (directory) might mean
- // something different from the same import in another.
- // While this can happen in GOPATH mode with vendoring,
- // it is not possible in the standard library: the one importMap
- // returned by loadImports applies to all packages.
- // Construct a per-directory importMap that resolves to
- // that single map for all packages.
- importMapForDir := make(map[string]map[string]string)
- for _, dir := range importDir {
- importMapForDir[dir] = importMap
- }
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.importDir = importDir
- w.importMap = importMapForDir
+ for _, w := range walkers {
+ pkgNames := w.stdPackages
+ if flag.NArg() > 0 {
+ pkgNames = flag.Args()
+ }
for _, name := range pkgNames {
- // Vendored packages do not contribute to our
- // public API surface.
- if strings.HasPrefix(name, "vendor/") {
+ pkg, err := w.Import(name)
+ if _, nogo := err.(*build.NoGoError); nogo {
continue
}
- // - Package "unsafe" contains special signatures requiring
- // extra care when printing them - ignore since it is not
- // going to change w/o a language change.
- // - We don't care about the API of commands.
- if name != "unsafe" && !strings.HasPrefix(name, "cmd/") {
- if name == "runtime/cgo" && !context.CgoEnabled {
- // w.Import(name) will return nil
- continue
- }
- pkg, err := w.Import(name)
- if _, nogo := err.(*build.NoGoError); nogo {
- continue
- }
- if err != nil {
- log.Fatalf("Import(%q): %v", name, err)
- }
- w.export(pkg)
+ if err != nil {
+ log.Fatalf("Import(%q): %v", name, err)
}
+ w.export(pkg)
}
- ctxName := contextName(context)
+ ctxName := contextName(w.context)
for _, f := range w.Features() {
if featureCtx[f] == nil {
featureCtx[f] = make(map[string]bool)
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
- importMap map[string]map[string]string // importer dir -> import path -> canonical path
- importDir map[string]string // canonical import path -> dir
+ context *build.Context
+ root string
+ scope []string
+ current *types.Package
+ features map[string]bool // set
+ imported map[string]*types.Package // packages already imported
+ stdPackages []string // names, omitting "unsafe", internal, and vendored packages
+ 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 &Walker{
+ w := &Walker{
context: context,
root: root,
features: map[string]bool{},
imported: map[string]*types.Package{"unsafe": types.Unsafe},
}
+ w.loadImports()
+ return w
}
func (w *Walker) Features() (fs []string) {
return key
}
-// loadImports returns information about the packages in the standard library
-// and the packages they themselves import.
-// importDir maps expanded import path to the directory containing that package.
-// importMap maps source import path to expanded import path.
+type listImports struct {
+ stdPackages []string // names, omitting "unsafe", internal, and vendored packages
+ importDir map[string]string // canonical import path → directory
+ importMap map[string]map[string]string // import path → canonical import path
+}
+
+var listCache sync.Map // map[string]listImports, keyed by contextName
+
+// loadImports populates w with information about the packages in the standard
+// library and the packages they themselves import in w's build context.
+//
// The source import path and expanded import path are identical except for vendored packages.
// For example, on return:
//
-// importMap["math"] = "math"
-// importDir["math"] = "<goroot>/src/math"
+// w.importMap["math"] = "math"
+// w.importDir["math"] = "<goroot>/src/math"
//
-// importMap["golang.org/x/net/route"] = "vendor/golang.org/x/net/route"
-// importDir["vendor/golang.org/x/net/route"] = "<goroot>/src/vendor/golang.org/x/net/route"
+// w.importMap["golang.org/x/net/route"] = "vendor/golang.org/x/net/route"
+// w.importDir["vendor/golang.org/x/net/route"] = "<goroot>/src/vendor/golang.org/x/net/route"
//
-// There are a few imports that only appear on certain platforms,
-// including it turns out x/net/route, and we add those explicitly.
-func loadImports() (importDir map[string]string, importMap map[string]string) {
- out, err := exec.Command(goCmd(), "list", "-e", "-deps", "-json", "std").CombinedOutput()
- if err != nil {
- log.Fatalf("loading imports: %v\n%s", err, out)
+// Since the set of packages that exist depends on context, the result of
+// loadImports also depends on context. However, to improve test running time
+// the configuration for each environment is cached across runs.
+func (w *Walker) loadImports() {
+ if w.context == nil {
+ return // test-only Walker; does not use the import map
}
- importDir = make(map[string]string)
- importMap = make(map[string]string)
- dec := json.NewDecoder(bytes.NewReader(out))
- for {
- var pkg struct {
- ImportPath, Dir string
- ImportMap map[string]string
- }
- err := dec.Decode(&pkg)
- if err == io.EOF {
- break
- }
+ name := contextName(w.context)
+
+ imports, ok := listCache.Load(name)
+ if !ok {
+ cmd := exec.Command(goCmd(), "list", "-e", "-deps", "-json", "std")
+ cmd.Env = listEnv(w.context)
+ out, err := cmd.CombinedOutput()
if err != nil {
- log.Fatalf("go list: invalid output: %v", err)
+ log.Fatalf("loading imports: %v\n%s", err, out)
+ }
+
+ var stdPackages []string
+ importMap := make(map[string]map[string]string)
+ importDir := make(map[string]string)
+ dec := json.NewDecoder(bytes.NewReader(out))
+ for {
+ var pkg struct {
+ ImportPath, Dir string
+ ImportMap map[string]string
+ }
+ err := dec.Decode(&pkg)
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ log.Fatalf("go list: invalid output: %v", err)
+ }
+
+ // - Package "unsafe" contains special signatures requiring
+ // extra care when printing them - ignore since it is not
+ // going to change w/o a language change.
+ // - internal and vendored packages do not contribute to our
+ // API surface.
+ // - 'go list std' does not include commands, which cannot be
+ // imported anyway.
+ if ip := pkg.ImportPath; ip != "unsafe" && !strings.HasPrefix(ip, "vendor/") && !internalPkg.MatchString(ip) {
+ stdPackages = append(stdPackages, ip)
+ }
+ importDir[pkg.ImportPath] = pkg.Dir
+ if len(pkg.ImportMap) > 0 {
+ importMap[pkg.Dir] = make(map[string]string, len(pkg.ImportMap))
+ }
+ for k, v := range pkg.ImportMap {
+ importMap[pkg.Dir][k] = v
+ }
}
- importDir[pkg.ImportPath] = pkg.Dir
- for k, v := range pkg.ImportMap {
- importMap[k] = v
+ sort.Strings(stdPackages)
+ imports = listImports{
+ stdPackages: stdPackages,
+ importMap: importMap,
+ importDir: importDir,
}
+ imports, _ = listCache.LoadOrStore(name, imports)
}
- // Fixup for vendor packages listed in args above.
- fixup := []string{
- "vendor/golang.org/x/net/route",
+ li := imports.(listImports)
+ w.stdPackages = li.stdPackages
+ w.importDir = li.importDir
+ w.importMap = li.importMap
+}
+
+// listEnv returns the process environment to use when invoking 'go list' for
+// the given context.
+func listEnv(c *build.Context) []string {
+ if c == nil {
+ return os.Environ()
}
- for _, pkg := range fixup {
- importDir[pkg] = filepath.Join(build.Default.GOROOT, "src", pkg)
- importMap[strings.TrimPrefix(pkg, "vendor/")] = pkg
+
+ environ := append(os.Environ(),
+ "GOOS="+c.GOOS,
+ "GOARCH="+c.GOARCH)
+ if c.CgoEnabled {
+ environ = append(environ, "CGO_ENABLED=1")
+ } else {
+ environ = append(environ, "CGO_ENABLED=0")
}
- return
+ return environ
}
// Importing is a sentinel taking the place in Walker.imported
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 (from import %s in %s): %v", name, fromPath, fromDir, err)
+ log.Panicf("no source in tree for import %q (from import %s in %s): %v", name, fromPath, fromDir, err)
}
context := w.context
"flag"
"fmt"
"go/build"
- "internal/testenv"
"io/ioutil"
"os"
- "os/exec"
"path/filepath"
"sort"
"strings"
+ "sync"
"testing"
)
+func TestMain(m *testing.M) {
+ flag.Parse()
+ for _, c := range contexts {
+ c.Compiler = build.Default.Compiler
+ }
+
+ // Warm up the import cache in parallel.
+ var wg sync.WaitGroup
+ for _, context := range contexts {
+ context := context
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ _ = NewWalker(context, filepath.Join(build.Default.GOROOT, "src"))
+ }()
+ }
+ wg.Wait()
+
+ os.Exit(m.Run())
+}
+
var (
updateGolden = flag.Bool("updategolden", false, "update golden files")
)
}
func BenchmarkAll(b *testing.B) {
- stds, err := exec.Command(testenv.GoToolPath(b), "list", "std").Output()
- if err != nil {
- b.Fatal(err)
- }
- b.ResetTimer()
- pkgNames := strings.Fields(string(stds))
-
- for _, c := range contexts {
- c.Compiler = build.Default.Compiler
- }
-
for i := 0; i < b.N; i++ {
for _, context := range contexts {
w := NewWalker(context, filepath.Join(build.Default.GOROOT, "src"))
- for _, name := range pkgNames {
- if name != "unsafe" && !strings.HasPrefix(name, "cmd/") && !internalPkg.MatchString(name) {
- pkg, _ := w.Import(name)
- w.export(pkg)
- }
+ for _, name := range w.stdPackages {
+ pkg, _ := w.Import(name)
+ w.export(pkg)
}
w.Features()
}
}
func TestIssue21181(t *testing.T) {
- for _, c := range contexts {
- c.Compiler = build.Default.Compiler
- }
for _, context := range contexts {
w := NewWalker(context, "testdata/src/issue21181")
pkg, err := w.Import("p")
}
func TestIssue29837(t *testing.T) {
- for _, c := range contexts {
- c.Compiler = build.Default.Compiler
- }
for _, context := range contexts {
w := NewWalker(context, "testdata/src/issue29837")
_, err := w.Import("p")