From af5e78811ae33d6683fe32926e3c744d88d75bba Mon Sep 17 00:00:00 2001 From: Ian Cottrell Date: Mon, 6 Aug 2018 23:01:37 -0400 Subject: [PATCH] go/packages: use Package as the raw form This deletes the raw and golist packatges, instead using the main Package structure as the serialzied form between the build tool and the loader. This requires a few new fields on Package for information that we used to hide, and some extra json tags on Package. It also required Package to have custom JSON marshalling methods. Change-Id: Ib72171bc93e2b494b97f4b0266504acaa9b3f647 Reviewed-on: https://go-review.googlesource.com/128120 Reviewed-by: Michael Matloob Run-TryBot: Michael Matloob TryBot-Result: Gobot Gobot --- go/packages/external.go | 95 ++++--- go/packages/{golist => }/golist.go | 148 +++++------ go/packages/{golist => }/golist_fallback.go | 66 +++-- go/packages/gopackages/main.go | 15 +- go/packages/packages.go | 266 ++++++++++++++------ go/packages/packages_test.go | 204 ++++++++++++++- go/packages/raw/raw.go | 142 ----------- go/packages/stdlib_test.go | 6 +- 8 files changed, 538 insertions(+), 404 deletions(-) rename go/packages/{golist => }/golist.go (69%) rename go/packages/{golist => }/golist_fallback.go (83%) delete mode 100644 go/packages/raw/raw.go diff --git a/go/packages/external.go b/go/packages/external.go index e60e2a80..39e5ed99 100644 --- a/go/packages/external.go +++ b/go/packages/external.go @@ -10,70 +10,59 @@ package packages import ( "bytes" - "context" "encoding/json" - "errors" "fmt" "os/exec" "strings" - - "golang.org/x/tools/go/packages/raw" ) -// externalPackages uses an external command to interpret the words and produce -// raw packages. -// dir may be "" and env may be nil, as per os/exec.Command. -func findRawTool(ctx context.Context, cfg *raw.Config) string { - const toolPrefix = "GOPACKAGESRAW=" +// findExternalTool returns the file path of a tool that supplies supplies +// the build system package structure, or "" if not found." +// If GOPACKAGESDRIVER is set in the environment findExternalTool returns its +// value, otherwise it searches for a binary named gopackagesdriver on the PATH. +func findExternalDriver(cfg *Config) driver { + const toolPrefix = "GOPACKAGESDRIVER=" + tool := "" for _, env := range cfg.Env { if val := strings.TrimPrefix(env, toolPrefix); val != env { - return val + tool = val } } - if found, err := exec.LookPath("gopackagesraw"); err == nil { - return found + if tool != "" && tool == "off" { + return nil } - return "" -} - -// externalPackages uses an external command to interpret the words and produce -// raw packages. -// cfg.Dir may be "" and cfg.Env may be nil, as per os/exec.Command. -func externalPackages(ctx context.Context, cfg *raw.Config, tool string, words ...string) ([]string, []*raw.Package, error) { - buf := new(bytes.Buffer) - fullargs := []string{ - fmt.Sprintf("-test=%t", cfg.Tests), - fmt.Sprintf("-export=%t", cfg.Export), - fmt.Sprintf("-deps=%t", cfg.Deps), - } - for _, f := range cfg.Flags { - fullargs = append(fullargs, fmt.Sprintf("-flags=%v", f)) - } - fullargs = append(fullargs, "--") - fullargs = append(fullargs, words...) - cmd := exec.CommandContext(ctx, tool, fullargs...) - cmd.Env = cfg.Env - cmd.Dir = cfg.Dir - cmd.Stdout = buf - cmd.Stderr = new(bytes.Buffer) - if err := cmd.Run(); err != nil { - return nil, nil, fmt.Errorf("%v: %v: %s", tool, err, cmd.Stderr) - } - var results raw.Results - var pkgs []*raw.Package - dec := json.NewDecoder(buf) - if err := dec.Decode(&results); err != nil { - return nil, nil, fmt.Errorf("JSON decoding raw.Results failed: %v", err) - } - if results.Error != "" { - return nil, nil, errors.New(results.Error) - } - for dec.More() { - p := new(raw.Package) - if err := dec.Decode(p); err != nil { - return nil, nil, fmt.Errorf("JSON decoding raw.Package failed: %v", err) + if tool == "" { + var err error + tool, err = exec.LookPath("gopackagesdriver") + if err != nil { + return nil } - pkgs = append(pkgs, p) } - return results.Roots, pkgs, nil + return func(cfg *Config, words ...string) (*driverResponse, error) { + buf := new(bytes.Buffer) + fullargs := []string{ + "list", + fmt.Sprintf("-test=%t", cfg.Tests), + fmt.Sprintf("-export=%t", usesExportData(cfg)), + fmt.Sprintf("-deps=%t", cfg.Mode >= LoadImports), + } + for _, f := range cfg.Flags { + fullargs = append(fullargs, fmt.Sprintf("-flags=%v", f)) + } + fullargs = append(fullargs, "--") + fullargs = append(fullargs, words...) + cmd := exec.CommandContext(cfg.Context, tool, fullargs...) + cmd.Env = cfg.Env + cmd.Dir = cfg.Dir + cmd.Stdout = buf + cmd.Stderr = new(bytes.Buffer) + if err := cmd.Run(); err != nil { + return nil, fmt.Errorf("%v: %v: %s", tool, err, cmd.Stderr) + } + var response driverResponse + if err := json.Unmarshal(buf.Bytes(), &response); err != nil { + return nil, err + } + return &response, nil + } } diff --git a/go/packages/golist/golist.go b/go/packages/golist.go similarity index 69% rename from go/packages/golist/golist.go rename to go/packages/golist.go index a04b1b9d..3e9d1e0c 100644 --- a/go/packages/golist/golist.go +++ b/go/packages/golist.go @@ -2,12 +2,10 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package golist defines the "go list" implementation of the Packages metadata query. -package golist +package packages import ( "bytes" - "context" "encoding/json" "fmt" "log" @@ -15,8 +13,6 @@ import ( "os/exec" "path/filepath" "strings" - - "golang.org/x/tools/go/packages/raw" ) // A goTooOldError reports that the go command @@ -25,23 +21,10 @@ type goTooOldError struct { error } -// LoadRaw and returns the raw Go packages named by the given patterns. -// This is a low level API, in general you should be using the packages.Load -// unless you have a very strong need for the raw data, and you know that you -// are using conventional go layout as supported by `go list` -// It returns the packages identifiers that directly matched the patterns, the -// full set of packages requested (which may include the dependencies) and -// an error if the operation failed. -func LoadRaw(ctx context.Context, cfg *raw.Config, patterns ...string) ([]string, []*raw.Package, error) { - if len(patterns) == 0 { - return nil, nil, fmt.Errorf("no packages to load") - } - if cfg == nil { - return nil, nil, fmt.Errorf("Load must be passed a valid Config") - } - if cfg.Dir == "" { - return nil, nil, fmt.Errorf("Config does not have a working directory") - } +// goListDriver uses the go list command to interpret the patterns and produce +// the build system package structure. +// See driver for more details. +func goListDriver(cfg *Config, patterns ...string) (*driverResponse, error) { // Determine files requested in contains patterns var containFiles []string restPatterns := make([]string, 0, len(patterns)) @@ -57,64 +40,72 @@ func LoadRaw(ctx context.Context, cfg *raw.Config, patterns ...string) ([]string patterns = restPatterns // TODO(matloob): Remove the definition of listfunc and just use golistPackages once go1.12 is released. - var listfunc func(ctx context.Context, cfg *raw.Config, words ...string) ([]string, []*raw.Package, error) - listfunc = func(ctx context.Context, cfg *raw.Config, words ...string) ([]string, []*raw.Package, error) { - roots, pkgs, err := golistPackages(ctx, cfg, patterns...) + var listfunc driver + listfunc = func(cfg *Config, words ...string) (*driverResponse, error) { + response, err := golistDriverCurrent(cfg, patterns...) if _, ok := err.(goTooOldError); ok { - listfunc = golistPackagesFallback - return listfunc(ctx, cfg, patterns...) + listfunc = golistDriverFallback + return listfunc(cfg, patterns...) } - listfunc = golistPackages - return roots, pkgs, err + listfunc = golistDriverCurrent + return response, err } - roots, pkgs, err := []string(nil), []*raw.Package(nil), error(nil) + var response *driverResponse + var err error - // TODO(matloob): Patterns may now be empty, if it was solely comprised of contains: patterns. - // See if the extra process invocation can be avoided. + // see if we have any patterns to pass through to go list. if len(patterns) > 0 { - roots, pkgs, err = listfunc(ctx, cfg, patterns...) + response, err = listfunc(cfg, patterns...) if err != nil { - return nil, nil, err + return nil, err } + } else { + response = &driverResponse{} } // Run go list for contains: patterns. - seenPkgs := make(map[string]bool) // for deduplication. different containing queries could produce same packages - seenRoots := make(map[string]bool) + seenPkgs := make(map[string]*Package) // for deduplication. different containing queries could produce same packages if len(containFiles) > 0 { - for _, pkg := range pkgs { - seenPkgs[pkg.ID] = true + for _, pkg := range response.Packages { + seenPkgs[pkg.ID] = pkg } } for _, f := range containFiles { // TODO(matloob): Do only one query per directory. fdir := filepath.Dir(f) cfg.Dir = fdir - _, cList, err := listfunc(ctx, cfg, ".") + dirResponse, err := listfunc(cfg, ".") if err != nil { - return nil, nil, err + return nil, err } - // Deduplicate and set deplist to set of packages requested files. - for _, pkg := range cList { - if seenRoots[pkg.ID] { + isRoot := make(map[string]bool, len(dirResponse.Roots)) + for _, root := range dirResponse.Roots { + isRoot[root] = true + } + for _, pkg := range dirResponse.Packages { + // Add any new packages to the main set + // We don't bother to filter packages that will be dropped by the changes of roots, + // that will happen anyway during graph construction outside this function. + // Over-reporting packages is not a problem. + if _, ok := seenPkgs[pkg.ID]; !ok { + // it is a new package, just add it + seenPkgs[pkg.ID] = pkg + response.Packages = append(response.Packages, pkg) + } + // if the package was not a root one, it cannot have the file + if !isRoot[pkg.ID] { continue } for _, pkgFile := range pkg.GoFiles { if filepath.Base(f) == filepath.Base(pkgFile) { - seenRoots[pkg.ID] = true - roots = append(roots, pkg.ID) + response.Roots = append(response.Roots, pkg.ID) break } } - if seenPkgs[pkg.ID] { - continue - } - seenPkgs[pkg.ID] = true - pkgs = append(pkgs, pkg) } } - return roots, pkgs, nil + return response, nil } // Fields must match go list; @@ -151,10 +142,10 @@ func otherFiles(p *jsonPackage) [][]string { return [][]string{p.CFiles, p.CXXFiles, p.MFiles, p.HFiles, p.FFiles, p.SFiles, p.SwigFiles, p.SwigCXXFiles, p.SysoFiles} } -// golistPackages uses the "go list" command to expand the +// golistDriverCurrent uses the "go list" command to expand the // pattern words and return metadata for the specified packages. // dir may be "" and env may be nil, as per os/exec.Command. -func golistPackages(ctx context.Context, cfg *raw.Config, words ...string) ([]string, []*raw.Package, error) { +func golistDriverCurrent(cfg *Config, words ...string) (*driverResponse, error) { // go list uses the following identifiers in ImportPath and Imports: // // "p" -- importable package or main (command) @@ -168,18 +159,16 @@ func golistPackages(ctx context.Context, cfg *raw.Config, words ...string) ([]st // Run "go list" for complete // information on the specified packages. - - buf, err := golist(ctx, cfg, golistargs(cfg, words)) + buf, err := golist(cfg, golistargs(cfg, words)) if err != nil { - return nil, nil, err + return nil, err } // Decode the JSON and convert it to Package form. - var roots []string - var result []*raw.Package + var response driverResponse for dec := json.NewDecoder(buf); dec.More(); { p := new(jsonPackage) if err := dec.Decode(p); err != nil { - return nil, nil, fmt.Errorf("JSON decoding failed: %v", err) + return nil, fmt.Errorf("JSON decoding failed: %v", err) } // Bad package? @@ -235,46 +224,45 @@ func golistPackages(ctx context.Context, cfg *raw.Config, words ...string) ([]st for _, id := range p.Imports { ids[id] = true } - imports := make(map[string]string) + imports := make(map[string]*Package) for path, id := range p.ImportMap { - imports[path] = id // non-identity import + imports[path] = &Package{ID: id} // non-identity import delete(ids, id) } for id := range ids { // Go issue 26136: go list omits imports in cgo-generated files. if id == "C" { - imports["unsafe"] = "unsafe" - imports["syscall"] = "syscall" + imports["unsafe"] = &Package{ID: "unsafe"} + imports["syscall"] = &Package{ID: "syscall"} if pkgpath != "runtime/cgo" { - imports["runtime/cgo"] = "runtime/cgo" + imports["runtime/cgo"] = &Package{ID: "runtime/cgo"} } continue } - imports[id] = id // identity import + imports[id] = &Package{ID: id} // identity import } - - pkg := &raw.Package{ + if !p.DepOnly { + response.Roots = append(response.Roots, id) + } + pkg := &Package{ ID: id, Name: p.Name, + PkgPath: pkgpath, GoFiles: absJoin(p.Dir, p.GoFiles, p.CgoFiles), CompiledGoFiles: absJoin(p.Dir, p.CompiledGoFiles), OtherFiles: absJoin(p.Dir, otherFiles(p)...), - PkgPath: pkgpath, Imports: imports, - Export: export, + ExportFile: export, } // TODO(matloob): Temporary hack since CompiledGoFiles isn't always set. if len(pkg.CompiledGoFiles) == 0 { pkg.CompiledGoFiles = pkg.GoFiles } - if !p.DepOnly { - roots = append(roots, pkg.ID) - } - result = append(result, pkg) + response.Packages = append(response.Packages, pkg) } - return roots, result, nil + return &response, nil } // absJoin absolutizes and flattens the lists of files. @@ -290,12 +278,12 @@ func absJoin(dir string, fileses ...[]string) (res []string) { return res } -func golistargs(cfg *raw.Config, words []string) []string { +func golistargs(cfg *Config, words []string) []string { fullargs := []string{ "list", "-e", "-json", "-compiled", fmt.Sprintf("-test=%t", cfg.Tests), - fmt.Sprintf("-export=%t", cfg.Export), - fmt.Sprintf("-deps=%t", cfg.Deps), + fmt.Sprintf("-export=%t", usesExportData(cfg)), + fmt.Sprintf("-deps=%t", cfg.Mode >= LoadImports), } fullargs = append(fullargs, cfg.Flags...) fullargs = append(fullargs, "--") @@ -304,9 +292,9 @@ func golistargs(cfg *raw.Config, words []string) []string { } // golist returns the JSON-encoded result of a "go list args..." query. -func golist(ctx context.Context, cfg *raw.Config, args []string) (*bytes.Buffer, error) { +func golist(cfg *Config, args []string) (*bytes.Buffer, error) { out := new(bytes.Buffer) - cmd := exec.CommandContext(ctx, "go", args...) + cmd := exec.CommandContext(cfg.Context, "go", args...) cmd.Env = cfg.Env cmd.Dir = cfg.Dir cmd.Stdout = out @@ -329,7 +317,7 @@ func golist(ctx context.Context, cfg *raw.Config, args []string) (*bytes.Buffer, // If that build fails, errors appear on stderr // (despite the -e flag) and the Export field is blank. // Do not fail in that case. - if !cfg.Export { + if !usesExportData(cfg) { return nil, fmt.Errorf("go list: %s: %s", exitErr, cmd.Stderr) } } diff --git a/go/packages/golist/golist_fallback.go b/go/packages/golist_fallback.go similarity index 83% rename from go/packages/golist/golist_fallback.go rename to go/packages/golist_fallback.go index 0b59f36d..60357758 100644 --- a/go/packages/golist/golist_fallback.go +++ b/go/packages/golist_fallback.go @@ -2,22 +2,21 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package golist +package packages import ( - "context" "encoding/json" "fmt" "go/build" - "golang.org/x/tools/go/internal/cgo" - "golang.org/x/tools/go/packages/raw" - "golang.org/x/tools/imports" "io/ioutil" "os" "path/filepath" "sort" "strings" + + "golang.org/x/tools/go/internal/cgo" + "golang.org/x/tools/imports" ) // TODO(matloob): Delete this file once Go 1.12 is released. @@ -29,16 +28,15 @@ import ( // This support will be removed once Go 1.12 is released // in Q1 2019. -func golistPackagesFallback(ctx context.Context, cfg *raw.Config, words ...string) ([]string, []*raw.Package, error) { - original, deps, err := getDeps(ctx, cfg, words...) +func golistDriverFallback(cfg *Config, words ...string) (*driverResponse, error) { + original, deps, err := getDeps(cfg, words...) if err != nil { - return nil, nil, err + return nil, err } var tmpdir string // used for generated cgo files - var result []*raw.Package - var roots []string + var response driverResponse addPackage := func(p *jsonPackage) { if p.Name == "" { return @@ -52,25 +50,22 @@ func golistPackagesFallback(ctx context.Context, cfg *raw.Config, words ...strin p.GoFiles = nil // ignore fake unsafe.go file } - importMap := func(importlist []string) map[string]string { - importMap := make(map[string]string) + importMap := func(importlist []string) map[string]*Package { + importMap := make(map[string]*Package) for _, id := range importlist { if id == "C" { - importMap["unsafe"] = "unsafe" - importMap["syscall"] = "syscall" + importMap["unsafe"] = &Package{ID: "unsafe"} + importMap["syscall"] = &Package{ID: "syscall"} if pkgpath != "runtime/cgo" { - importMap["runtime/cgo"] = "runtime/cgo" + importMap["runtime/cgo"] = &Package{ID: "runtime/cgo"} } continue } - importMap[imports.VendorlessPath(id)] = id + importMap[imports.VendorlessPath(id)] = &Package{ID: id} } return importMap } - if isRoot { - roots = append(roots, id) - } compiledGoFiles := absJoin(p.Dir, p.GoFiles) // Use a function to simplify control flow. It's just a bunch of gotos. var cgoErrors []error @@ -99,7 +94,10 @@ func golistPackagesFallback(ctx context.Context, cfg *raw.Config, words ...strin if len(p.CgoFiles) == 0 || !processCgo() { compiledGoFiles = append(compiledGoFiles, absJoin(p.Dir, p.CgoFiles)...) // Punt to typechecker. } - result = append(result, &raw.Package{ + if isRoot { + response.Roots = append(response.Roots, id) + } + response.Packages = append(response.Packages, &Package{ ID: id, Name: p.Name, GoFiles: absJoin(p.Dir, p.GoFiles, p.CgoFiles), @@ -113,9 +111,9 @@ func golistPackagesFallback(ctx context.Context, cfg *raw.Config, words ...strin testID := fmt.Sprintf("%s [%s.test]", id, id) if len(p.TestGoFiles) > 0 || len(p.XTestGoFiles) > 0 { if isRoot { - roots = append(roots, testID) + response.Roots = append(response.Roots, testID) } - result = append(result, &raw.Package{ + response.Packages = append(response.Packages, &Package{ ID: testID, Name: p.Name, GoFiles: absJoin(p.Dir, p.GoFiles, p.CgoFiles, p.TestGoFiles), @@ -128,7 +126,7 @@ func golistPackagesFallback(ctx context.Context, cfg *raw.Config, words ...strin if len(p.XTestGoFiles) > 0 { xtestID := fmt.Sprintf("%s_test [%s.test]", id, id) if isRoot { - roots = append(roots, xtestID) + response.Roots = append(response.Roots, xtestID) } for i, imp := range p.XTestImports { if imp == p.ImportPath { @@ -136,7 +134,7 @@ func golistPackagesFallback(ctx context.Context, cfg *raw.Config, words ...strin break } } - result = append(result, &raw.Package{ + response.Packages = append(response.Packages, &Package{ ID: xtestID, Name: p.Name + "_test", GoFiles: absJoin(p.Dir, p.XTestGoFiles), @@ -152,31 +150,31 @@ func golistPackagesFallback(ctx context.Context, cfg *raw.Config, words ...strin for _, pkg := range original { addPackage(pkg) } - if !cfg.Deps || len(deps) == 0 { - return roots, result, nil + if cfg.Mode < LoadImports || len(deps) == 0 { + return &response, nil } - buf, err := golist(ctx, cfg, golistArgsFallback(cfg, deps)) + buf, err := golist(cfg, golistArgsFallback(cfg, deps)) if err != nil { - return nil, nil, err + return nil, err } // Decode the JSON and convert it to Package form. for dec := json.NewDecoder(buf); dec.More(); { p := new(jsonPackage) if err := dec.Decode(p); err != nil { - return nil, nil, fmt.Errorf("JSON decoding failed: %v", err) + return nil, fmt.Errorf("JSON decoding failed: %v", err) } addPackage(p) } - return roots, result, nil + return &response, nil } // getDeps runs an initial go list to determine all the dependency packages. -func getDeps(ctx context.Context, cfg *raw.Config, words ...string) (originalSet map[string]*jsonPackage, deps []string, err error) { - buf, err := golist(ctx, cfg, golistArgsFallback(cfg, words)) +func getDeps(cfg *Config, words ...string) (originalSet map[string]*jsonPackage, deps []string, err error) { + buf, err := golist(cfg, golistArgsFallback(cfg, words)) if err != nil { return nil, nil, err } @@ -210,7 +208,7 @@ func getDeps(ctx context.Context, cfg *raw.Config, words ...string) (originalSet } // Get the deps of the packages imported by tests. if len(testImports) > 0 { - buf, err = golist(ctx, cfg, golistArgsFallback(cfg, testImports)) + buf, err = golist(cfg, golistArgsFallback(cfg, testImports)) if err != nil { return nil, nil, err } @@ -238,7 +236,7 @@ func getDeps(ctx context.Context, cfg *raw.Config, words ...string) (originalSet return originalSet, deps, nil } -func golistArgsFallback(cfg *raw.Config, words []string) []string { +func golistArgsFallback(cfg *Config, words []string) []string { fullargs := []string{"list", "-e", "-json"} fullargs = append(fullargs, cfg.Flags...) fullargs = append(fullargs, "--") diff --git a/go/packages/gopackages/main.go b/go/packages/gopackages/main.go index e99cb984..fa86c04a 100644 --- a/go/packages/gopackages/main.go +++ b/go/packages/gopackages/main.go @@ -9,6 +9,7 @@ package main import ( + "encoding/json" "flag" "fmt" "go/types" @@ -26,10 +27,11 @@ import ( // flags var ( - depsFlag = flag.Bool("deps", false, "show dependencies too") - testFlag = flag.Bool("test", false, "include any tests implied by the patterns") - mode = flag.String("mode", "imports", "mode (one of files, imports, types, syntax, allsyntax)") - private = flag.Bool("private", false, "show non-exported declarations too") + depsFlag = flag.Bool("deps", false, "show dependencies too") + testFlag = flag.Bool("test", false, "include any tests implied by the patterns") + mode = flag.String("mode", "imports", "mode (one of files, imports, types, syntax, allsyntax)") + private = flag.Bool("private", false, "show non-exported declarations too") + printJSON = flag.Bool("json", false, "print package in JSON form") cpuprofile = flag.String("cpuprofile", "", "write CPU profile to this file") memprofile = flag.String("memprofile", "", "write memory profile to this file") @@ -166,6 +168,11 @@ func main() { } func print(lpkg *packages.Package) { + if *printJSON { + data, _ := json.MarshalIndent(lpkg, "", "\t") + os.Stdout.Write(data) + return + } // title var kind string // TODO(matloob): If IsTest is added back print "test command" or diff --git a/go/packages/packages.go b/go/packages/packages.go index 89076ab6..302ac80a 100644 --- a/go/packages/packages.go +++ b/go/packages/packages.go @@ -8,6 +8,7 @@ package packages import ( "context" + "encoding/json" "fmt" "go/ast" "go/parser" @@ -18,8 +19,6 @@ import ( "sync" "golang.org/x/tools/go/gcexportdata" - "golang.org/x/tools/go/packages/golist" - "golang.org/x/tools/go/packages/raw" ) // A LoadMode specifies the amount of detail to return when loading packages. @@ -41,6 +40,8 @@ const ( // LoadTypes adds type information for the package's exported symbols. // Package fields added: Types, Fset, IllTyped. + // This will use the type information provided by the build system if + // possible, and the ExportFile field may be filled in. LoadTypes // LoadSyntax adds typed syntax trees for the packages matching the patterns. @@ -141,6 +142,26 @@ type Config struct { TypeChecker types.Config } +// driver is the type for functions that return the package structure as +// provided by a build system for the packages named by the given patterns. +type driver func(cfg *Config, patterns ...string) (*driverResponse, error) + +// driverResponse contains the results for a driver query. +type driverResponse struct { + // Roots is the set of package IDs that make up the root packages. + // We have to encode this separately because when we encode a single package + // we cannot know if it is one of the roots, that requires knowledge of the + // graph it is part of. + Roots []string `json:",omitempty"` + + // Packages is the full set of packages in the graph. + // The packages are not connected into a graph, the Imports if populated will be + // stubs that only have their ID set. + // It will be connected and then type and syntax information added in a later + // pass (see refine). + Packages []*Package +} + // Load and returns the Go packages named by the given patterns. // // Config specifies loading options; @@ -152,26 +173,21 @@ type Config struct { // for instance for an empty expansion of a valid wildcard. func Load(cfg *Config, patterns ...string) ([]*Package, error) { l := newLoader(cfg) - rawCfg := newRawConfig(&l.Config) - roots, pkgs, err := loadRaw(l.Context, rawCfg, patterns...) + response, err := defaultDriver(&l.Config, patterns...) if err != nil { return nil, err } - return l.loadFrom(roots, pkgs...) + return l.refine(response.Roots, response.Packages...) } -// loadRaw returns the raw Go packages named by the given patterns. -// This is a low level API, in general you should be using the Load function -// unless you have a very strong need for the raw data. -// It returns the packages identifiers that directly matched the patterns, the -// full set of packages requested (which may include the dependencies) and -// an error if the operation failed. -func loadRaw(ctx context.Context, cfg *raw.Config, patterns ...string) ([]string, []*raw.Package, error) { - //TODO: this is the seam at which we enable alternate build systems - if tool := findRawTool(ctx, cfg); tool != "" { - return externalPackages(ctx, cfg, tool, patterns...) +// defaultDriver is a driver that looks for an external driver binary, and if +// it does not find it falls back to the built in go list driver. +func defaultDriver(cfg *Config, patterns ...string) (*driverResponse, error) { + driver := findExternalDriver(cfg) + if driver == nil { + driver = goListDriver } - return golist.LoadRaw(ctx, cfg, patterns...) + return driver(cfg, patterns...) } // A Package describes a single loaded Go package. @@ -187,22 +203,36 @@ type Package struct { // Name is the package name as it appears in the package source code. Name string + // This is the package path as used by the types package. + // This is used to map entries in the type information back to the package + // they come from. + PkgPath string + // Errors lists any errors encountered while loading the package. // TODO(rsc): Say something about the errors or at least their Strings, // as far as file:line being at the beginning and so on. Errors []error - // Imports maps import paths appearing in the package's Go source files - // to corresponding loaded Packages. - Imports map[string]*Package - // GoFiles lists the absolute file paths of the package's Go source files. GoFiles []string + // CompiledGoFiles lists the absolute file paths of the package's source + // files that were presented to the compiler. + // This may differ from GoFiles if files are processed before compilation. + CompiledGoFiles []string + // OtherFiles lists the absolute file paths of the package's non-Go source files, // including assembly, C, C++, Fortran, Objective-C, SWIG, and so on. OtherFiles []string + // ExportFile is the absolute path to a file containing the type information + // provided by the build system. + ExportFile string + + // Imports maps import paths appearing in the package's Go source files + // to corresponding loaded Packages. + Imports map[string]*Package + // Types is the type information for the package. // Modes LoadTypes and above set this field for all packages. // @@ -212,7 +242,7 @@ type Package struct { Types *types.Package // Fset provides position information for Types, TypesInfo, and Syntax. - // Modes LoadTypes and above set this for field for all packages. + // Modes LoadTypes and above set this field for all packages. Fset *token.FileSet // IllTyped indicates whether the package has any type errors. @@ -230,9 +260,106 @@ type Package struct { TypesInfo *types.Info } +// packageError is used to serialize structured errors as much as possible. +// This has members compatible with the golist error type, and possibly some +// more if we need other error information to survive. +type packageError struct { + Pos string // position of error + Err string // the error itself +} + +func (e *packageError) Error() string { + return e.Pos + ": " + e.Err +} + +// flatPackage is the JSON form of Package +// It drops all the type and syntax fields, and transforms the Imports and Errors +type flatPackage struct { + ID string + Name string `json:",omitempty"` + PkgPath string `json:",omitempty"` + Errors []*packageError `json:",omitempty"` + GoFiles []string `json:",omitempty"` + CompiledGoFiles []string `json:",omitempty"` + OtherFiles []string `json:",omitempty"` + ExportFile string `json:",omitempty"` + Imports map[string]string `json:",omitempty"` +} + +// MarshalJSON returns the Package in its JSON form. +// For the most part, the structure fields are written out unmodified, and +// the type and syntax fields are skipped. +// The imports are written out as just a map of path to package id. +// The errors are written using a custom type that tries to preserve the +// structure of error types we know about. +// This method exists to enable support for additional build systems. It is +// not intended for use by clients of the API and we may change the format. +func (p *Package) MarshalJSON() ([]byte, error) { + flat := &flatPackage{ + ID: p.ID, + Name: p.Name, + PkgPath: p.PkgPath, + GoFiles: p.GoFiles, + CompiledGoFiles: p.CompiledGoFiles, + OtherFiles: p.OtherFiles, + ExportFile: p.ExportFile, + } + if len(p.Errors) > 0 { + flat.Errors = make([]*packageError, len(p.Errors)) + for i, err := range p.Errors { + //TODO: best effort mapping of errors to the serialized form + switch err := err.(type) { + case *packageError: + flat.Errors[i] = err + default: + flat.Errors[i] = &packageError{Err: err.Error()} + } + } + } + if len(p.Imports) > 0 { + flat.Imports = make(map[string]string, len(p.Imports)) + for path, ipkg := range p.Imports { + flat.Imports[path] = ipkg.ID + } + } + return json.Marshal(flat) +} + +// UnmarshalJSON reads in a Package from its JSON format. +// See MarshalJSON for details about the format accepted. +func (p *Package) UnmarshalJSON(b []byte) error { + flat := &flatPackage{} + if err := json.Unmarshal(b, &flat); err != nil { + return err + } + *p = Package{ + ID: flat.ID, + Name: flat.Name, + PkgPath: flat.PkgPath, + GoFiles: flat.GoFiles, + CompiledGoFiles: flat.CompiledGoFiles, + OtherFiles: flat.OtherFiles, + ExportFile: flat.ExportFile, + } + if len(flat.Errors) >= 0 { + p.Errors = make([]error, len(flat.Errors)) + for i, err := range flat.Errors { + p.Errors[i] = err + } + } + if len(flat.Imports) > 0 { + p.Imports = make(map[string]*Package, len(flat.Imports)) + for path, id := range flat.Imports { + p.Imports[path] = &Package{ID: id} + } + } + return nil +} + +func (p *Package) String() string { return p.ID } + // loaderPackage augments Package with state used during the loading phase type loaderPackage struct { - raw *raw.Package *Package importErrors map[string]error // maps each bad import to its error loadOnce sync.Once @@ -240,8 +367,6 @@ type loaderPackage struct { mark, needsrc bool // used when Mode >= LoadTypes } -func (lpkg *Package) String() string { return lpkg.ID } - // loader holds the working state of a single call to load. type loader struct { pkgs map[string]*loaderPackage @@ -287,55 +412,29 @@ func newLoader(cfg *Config) *loader { return ld } -func newRawConfig(cfg *Config) *raw.Config { - rawCfg := &raw.Config{ - Dir: cfg.Dir, - Env: cfg.Env, - Flags: cfg.Flags, - Export: cfg.Mode > LoadImports && cfg.Mode < LoadAllSyntax, - Tests: cfg.Tests, - Deps: cfg.Mode >= LoadImports, - } - if rawCfg.Env == nil { - rawCfg.Env = os.Environ() - } - return rawCfg -} - -func (ld *loader) loadFrom(roots []string, list ...*raw.Package) ([]*Package, error) { +// refine connects the supplied packages into a graph and then adds type and +// and syntax information as requested by the LoadMode. +func (ld *loader) refine(roots []string, list ...*Package) ([]*Package, error) { if len(list) == 0 { return nil, fmt.Errorf("packages not found") } - if len(roots) == 0 { - return nil, fmt.Errorf("packages had no initial set") + isRoot := make(map[string]bool, len(roots)) + for _, root := range roots { + isRoot[root] = true } - ld.pkgs = make(map[string]*loaderPackage, len(list)) + ld.pkgs = make(map[string]*loaderPackage) // first pass, fixup and build the map and roots + var initial []*loaderPackage for _, pkg := range list { lpkg := &loaderPackage{ - raw: pkg, - Package: &Package{ - ID: pkg.ID, - Name: pkg.Name, - GoFiles: pkg.GoFiles, - OtherFiles: pkg.OtherFiles, - }, + Package: pkg, needsrc: ld.Mode >= LoadAllSyntax || - (pkg.Export == "" && pkg.PkgPath != "unsafe"), + (ld.Mode >= LoadSyntax && isRoot[pkg.ID]) || + (pkg.ExportFile == "" && pkg.PkgPath != "unsafe"), } ld.pkgs[lpkg.ID] = lpkg - } - // check all the roots were found - initial := make([]*loaderPackage, len(roots)) - for i, root := range roots { - lpkg := ld.pkgs[root] - if lpkg == nil { - return nil, fmt.Errorf("root package %v not found", root) - } - initial[i] = lpkg - // mark the roots as needing source - if ld.Mode == LoadSyntax { - lpkg.needsrc = true + if isRoot[lpkg.ID] { + initial = append(initial, lpkg) } } @@ -368,13 +467,14 @@ func (ld *loader) loadFrom(roots []string, list ...*raw.Package) ([]*Package, er } lpkg.color = grey stack = append(stack, lpkg) // push - lpkg.Imports = make(map[string]*Package, len(lpkg.raw.Imports)) - for importPath, id := range lpkg.raw.Imports { + stubs := lpkg.Imports // the structure form has only stubs with the ID in the Imports + lpkg.Imports = make(map[string]*Package, len(stubs)) + for importPath, ipkg := range stubs { var importErr error - imp := ld.pkgs[id] + imp := ld.pkgs[ipkg.ID] if imp == nil { // (includes package "C" when DisableCgo) - importErr = fmt.Errorf("missing package: %q", id) + importErr = fmt.Errorf("missing package: %q", ipkg.ID) } else if imp.color == grey { importErr = fmt.Errorf("import cycle: %s", stack) } @@ -398,7 +498,13 @@ func (ld *loader) loadFrom(roots []string, list ...*raw.Package) ([]*Package, er return lpkg.needsrc } - if ld.Mode >= LoadImports { + if ld.Mode < LoadImports { + //TODO: we are throwing away correct information, is that the right thing to do? + //we do this to drop the stub import packages that we are not even going to try to resolve + for _, lpkg := range initial { + lpkg.Imports = nil + } + } else { // For each initial package, create its import DAG. for _, lpkg := range initial { visit(lpkg) @@ -452,7 +558,7 @@ func (ld *loader) loadRecursive(lpkg *loaderPackage) { // after immediate dependencies are loaded. // Precondition: ld.Mode >= LoadTypes. func (ld *loader) loadPackage(lpkg *loaderPackage) { - if lpkg.raw.PkgPath == "unsafe" { + if lpkg.PkgPath == "unsafe" { // Fill in the blanks to avoid surprises. lpkg.Types = types.Unsafe lpkg.Fset = ld.Fset @@ -464,7 +570,7 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) { // Call NewPackage directly with explicit name. // This avoids skew between golist and go/types when the files' // package declarations are inconsistent. - lpkg.Types = types.NewPackage(lpkg.raw.PkgPath, lpkg.Name) + lpkg.Types = types.NewPackage(lpkg.PkgPath, lpkg.Name) if !lpkg.needsrc { ld.loadFromExportData(lpkg) @@ -482,7 +588,7 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) { lpkg.Errors = append(lpkg.Errors, err) } - files, errs := ld.parseFiles(lpkg.raw.CompiledGoFiles) + files, errs := ld.parseFiles(lpkg.CompiledGoFiles) for _, err := range errs { appendError(err) } @@ -623,7 +729,7 @@ func (ld *loader) parseFiles(filenames []string) ([]*ast.File, []error) { // loadFromExportData returns type information for the specified // package, loading it from an export data file on the first request. func (ld *loader) loadFromExportData(lpkg *loaderPackage) (*types.Package, error) { - if lpkg.raw.PkgPath == "" { + if lpkg.PkgPath == "" { log.Fatalf("internal error: Package %s has no PkgPath", lpkg) } @@ -648,11 +754,11 @@ func (ld *loader) loadFromExportData(lpkg *loaderPackage) (*types.Package, error lpkg.IllTyped = true // fail safe - if lpkg.raw.Export == "" { + if lpkg.ExportFile == "" { // Errors while building export data will have been printed to stderr. return nil, fmt.Errorf("no export data file") } - f, err := os.Open(lpkg.raw.Export) + f, err := os.Open(lpkg.ExportFile) if err != nil { return nil, err } @@ -666,7 +772,7 @@ func (ld *loader) loadFromExportData(lpkg *loaderPackage) (*types.Package, error // queries.) r, err := gcexportdata.NewReader(f) if err != nil { - return nil, fmt.Errorf("reading %s: %v", lpkg.raw.Export, err) + return nil, fmt.Errorf("reading %s: %v", lpkg.ExportFile, err) } // Build the view. @@ -694,7 +800,7 @@ func (ld *loader) loadFromExportData(lpkg *loaderPackage) (*types.Package, error lpkg := ld.pkgs[p.ID] if !seen[lpkg] { seen[lpkg] = true - view[lpkg.raw.PkgPath] = lpkg.Types + view[lpkg.PkgPath] = lpkg.Types visit(lpkg.Imports) } } @@ -703,9 +809,9 @@ func (ld *loader) loadFromExportData(lpkg *loaderPackage) (*types.Package, error // Parse the export data. // (May create/modify packages in view.) - tpkg, err := gcexportdata.Read(r, ld.Fset, view, lpkg.raw.PkgPath) + tpkg, err := gcexportdata.Read(r, ld.Fset, view, lpkg.PkgPath) if err != nil { - return nil, fmt.Errorf("reading %s: %v", lpkg.raw.Export, err) + return nil, fmt.Errorf("reading %s: %v", lpkg.ExportFile, err) } lpkg.Types = tpkg @@ -713,3 +819,7 @@ func (ld *loader) loadFromExportData(lpkg *loaderPackage) (*types.Package, error return tpkg, nil } + +func usesExportData(cfg *Config) bool { + return LoadTypes <= cfg.Mode && cfg.Mode < LoadAllSyntax +} diff --git a/go/packages/packages_test.go b/go/packages/packages_test.go index afb45d33..1d502627 100644 --- a/go/packages/packages_test.go +++ b/go/packages/packages_test.go @@ -6,6 +6,7 @@ package packages_test import ( "bytes" + "encoding/json" "fmt" "go/ast" "go/parser" @@ -23,6 +24,11 @@ import ( "golang.org/x/tools/go/packages" ) +func init() { + // Insulate the tests from the users' environment. + os.Setenv("GOPACKAGESDRIVER", "off") +} + // TODO(matloob): remove this once Go 1.12 is released as we will end support // for versions of go list before Go 1.10.4. var usesOldGolist = false @@ -324,6 +330,9 @@ func TestVendorImports(t *testing.T) { } func imports(p *packages.Package) []string { + if p == nil { + return nil + } keys := make([]string, 0, len(p.Imports)) for k, v := range p.Imports { keys = append(keys, fmt.Sprintf("%s:%s", k, v.ID)) @@ -413,7 +422,7 @@ package b`, {`a`, []string{`-tags=tag tag2`}, "a.go b.go c.go d.go", "a.go b.go"}, } { cfg := &packages.Config{ - Mode: packages.LoadFiles, + Mode: packages.LoadImports, Flags: test.tags, Env: append(os.Environ(), "GOPATH="+tmp), } @@ -929,8 +938,12 @@ func TestContains(t *testing.T) { }) defer cleanup() - opts := &packages.Config{Env: append(os.Environ(), "GOPATH="+tmp), Dir: tmp, Mode: packages.LoadImports} - initial, err := packages.Load(opts, "contains:src/b/b.go") + cfg := &packages.Config{ + Mode: packages.LoadImports, + Dir: tmp, + Env: append(os.Environ(), "GOPATH="+tmp), + } + initial, err := packages.Load(cfg, "contains:src/b/b.go") if err != nil { t.Fatal(err) } @@ -957,8 +970,12 @@ func TestContains_FallbackSticks(t *testing.T) { }) defer cleanup() - opts := &packages.Config{Env: append(os.Environ(), "GOPATH="+tmp), Dir: tmp, Mode: packages.LoadImports} - initial, err := packages.Load(opts, "a", "contains:src/b/b.go") + cfg := &packages.Config{ + Mode: packages.LoadImports, + Dir: tmp, + Env: append(os.Environ(), "GOPATH="+tmp), + } + initial, err := packages.Load(cfg, "a", "contains:src/b/b.go") if err != nil { t.Fatal(err) } @@ -976,6 +993,165 @@ func TestContains_FallbackSticks(t *testing.T) { } } +func TestJSON(t *testing.T) { + //TODO: add in some errors + tmp, cleanup := makeTree(t, map[string]string{ + "src/a/a.go": `package a; const A = 1`, + "src/b/b.go": `package b; import "a"; var B = a.A`, + "src/c/c.go": `package c; import "b" ; var C = b.B`, + "src/d/d.go": `package d; import "b" ; var D = b.B`, + }) + defer cleanup() + + cfg := &packages.Config{ + Mode: packages.LoadImports, + Env: append(os.Environ(), "GOPATH="+tmp), + } + initial, err := packages.Load(cfg, "c", "d") + if err != nil { + t.Fatal(err) + } + buf := &bytes.Buffer{} + enc := json.NewEncoder(buf) + enc.SetIndent("", "\t") + seen := make(map[string]bool) + var visit func(*packages.Package) + visit = func(pkg *packages.Package) { + if seen[pkg.ID] { + return + } + seen[pkg.ID] = true + // trim the source lists for stable results + pkg.GoFiles = cleanPaths(pkg.GoFiles) + pkg.CompiledGoFiles = cleanPaths(pkg.CompiledGoFiles) + pkg.OtherFiles = cleanPaths(pkg.OtherFiles) + for _, ipkg := range pkg.Imports { + visit(ipkg) + } + if err := enc.Encode(pkg); err != nil { + t.Fatal(err) + } + } + for _, pkg := range initial { + visit(pkg) + } + wantJSON := ` +{ + "ID": "a", + "Name": "a", + "PkgPath": "a", + "GoFiles": [ + "a.go" + ], + "CompiledGoFiles": [ + "a.go" + ] +} +{ + "ID": "b", + "Name": "b", + "PkgPath": "b", + "GoFiles": [ + "b.go" + ], + "CompiledGoFiles": [ + "b.go" + ], + "Imports": { + "a": "a" + } +} +{ + "ID": "c", + "Name": "c", + "PkgPath": "c", + "GoFiles": [ + "c.go" + ], + "CompiledGoFiles": [ + "c.go" + ], + "Imports": { + "b": "b" + } +} +{ + "ID": "d", + "Name": "d", + "PkgPath": "d", + "GoFiles": [ + "d.go" + ], + "CompiledGoFiles": [ + "d.go" + ], + "Imports": { + "b": "b" + } +} +`[1:] + + if buf.String() != wantJSON { + t.Errorf("wrong JSON: got <<%s>>, want <<%s>>", buf.String(), wantJSON) + } + // now decode it again + var decoded []*packages.Package + dec := json.NewDecoder(buf) + for dec.More() { + p := new(packages.Package) + if err := dec.Decode(p); err != nil { + t.Fatal(err) + } + decoded = append(decoded, p) + } + if len(decoded) != 4 { + t.Fatalf("got %d packages, want 4", len(decoded)) + } + for i, want := range []*packages.Package{{ + ID: "a", + Name: "a", + }, { + ID: "b", + Name: "b", + Imports: map[string]*packages.Package{ + "a": &packages.Package{ID: "a"}, + }, + }, { + ID: "c", + Name: "c", + Imports: map[string]*packages.Package{ + "b": &packages.Package{ID: "b"}, + }, + }, { + ID: "d", + Name: "d", + Imports: map[string]*packages.Package{ + "b": &packages.Package{ID: "b"}, + }, + }} { + got := decoded[i] + if got.ID != want.ID { + t.Errorf("Package %d has ID %q want %q", i, got.ID, want.ID) + } + if got.Name != want.Name { + t.Errorf("Package %q has Name %q want %q", got.ID, got.Name, want.Name) + } + if len(got.Imports) != len(want.Imports) { + t.Errorf("Package %q has %d imports want %d", got.ID, len(got.Imports), len(want.Imports)) + continue + } + for path, ipkg := range got.Imports { + if want.Imports[path] == nil { + t.Errorf("Package %q has unexpected import %q", got.ID, path) + continue + } + if want.Imports[path].ID != ipkg.ID { + t.Errorf("Package %q import %q is %q want %q", got.ID, path, ipkg.ID, want.Imports[path].ID) + } + } + } +} + func errorMessages(errors []error) []string { var msgs []string for _, err := range errors { @@ -990,19 +1166,23 @@ func errorMessages(errors []error) []string { return msgs } -func srcs(p *packages.Package) (basenames []string) { - files := append(p.GoFiles, p.OtherFiles...) - for i, src := range files { +func srcs(p *packages.Package) []string { + return cleanPaths(append(p.GoFiles, p.OtherFiles...)) +} + +// cleanPaths attempts to reduce path names to stable forms +func cleanPaths(paths []string) []string { + result := make([]string, len(paths)) + for i, src := range paths { // The default location for cache data is a subdirectory named go-build // in the standard user cache directory for the current operating system. if strings.Contains(filepath.ToSlash(src), "/go-build/") { - src = fmt.Sprintf("%d.go", i) // make cache names predictable + result[i] = fmt.Sprintf("%d.go", i) // make cache names predictable } else { - src = filepath.Base(src) + result[i] = filepath.Base(src) } - basenames = append(basenames, src) } - return basenames + return result } // importGraph returns the import graph as a user-friendly string, diff --git a/go/packages/raw/raw.go b/go/packages/raw/raw.go deleted file mode 100644 index 0ee910f5..00000000 --- a/go/packages/raw/raw.go +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -/* -Package raw is the experimental API to raw package information. - -NOTE: THIS PACKAGE IS NOT YET READY FOR WIDESPREAD USE: - - The interface is not yet stable. - - We reserve the right to add and remove fields and change the command line args. - - This may remain unstable even after the higher level x/tools/go/packages API is stable. - -This package is used by x/tools/go/packages to provide the low level raw -information about file layout. -It should not be needed unless you are attempting to implement a new source of -data for the packages API, all tools should interact only with the packages API. -*/ -package raw - -import ( - "flag" -) - -// Results describes the results of a load operation. -type Results struct { - // Roots is the set of package identifiers that directly matched the patterns. - Roots []string - // Error is an error message if the query failed for some reason. - Error string `json:",omitempty"` -} - -// Package is the raw serialized form of a packages.Package -type Package struct { - // ID is a unique identifier for a package, - // in a syntax provided by the underlying build system. - // - // Because the syntax varies based on the build system, - // clients should treat IDs as opaque and not attempt to - // interpret them. - ID string - - // Name is the package name as it appears in the package source code. - Name string `json:",omitempty"` - - // This is the package path as used by the types package. - // This is used to map entries in the export data back to the package they - // come from. - PkgPath string `json:",omitempty"` - - // Imports maps import paths appearing in the package's Go source files - // to corresponding package identifiers. - Imports map[string]string `json:",omitempty"` - - // Export is the absolute path to a file containing the export data for the - // package. - Export string `json:",omitempty"` - - // GoFiles lists the absolute file paths of the package's Go source files. - GoFiles []string `json:",omitempty"` - - // CompiledGoFiles lists the absolute file paths of the package's source - // files that were handed to the compiler. - // This is allowed to be different to GoFiles in the presence of files that - // were automatically modified or processed before compilation. - CompiledGoFiles []string `json:",omitempty"` - - // OtherFiles lists the absolute file paths of the package's non-Go source - // files, including assembly, C, C++, Fortran, Objective-C, SWIG, and so on. - OtherFiles []string `json:",omitempty"` -} - -// Config specifies details about what raw package information is needed -// and how the underlying build tool should load package data. -type Config struct { - // Dir is the directory in which to run the build system tool - // that provides information about the packages. - // If Dir is empty, the tool is run in the current directory. - Dir string - - // Env is the environment to use when invoking the build system tool. - // If Env is nil, the current environment is used. - // Like in os/exec's Cmd, only the last value in the slice for - // each environment key is used. To specify the setting of only - // a few variables, append to the current environment, as in: - // - // opt.Env = append(os.Environ(), "GOOS=plan9", "GOARCH=386") - // - Env []string - - // Flags is a list of command-line flags to be passed through to - // the underlying query tool. - Flags []string - - // Export controls whether the raw packages must contain the export - // data file. - Export bool - - // If Tests is set, the loader includes not just the packages - // matching a particular pattern but also any related test packages, - // including test-only variants of the package and the test executable. - // - // For example, when using the go command, loading "fmt" with Tests=true - // returns four packages, with IDs "fmt" (the standard package), - // "fmt [fmt.test]" (the package as compiled for the test), - // "fmt_test" (the test functions from source files in package fmt_test), - // and "fmt.test" (the test binary). - // - // In build systems with explicit names for tests, - // setting Tests may have no effect. - Tests bool - - // If Deps is set, the loader will include the full dependency graph. - // Packages that are only in the results because of Deps will have DepOnly - // set on them. - Deps bool -} - -// AddFlags adds the standard flags used to set a Config to the supplied flag set. -// This is used by implementations of the external raw package binary to correctly -// interpret the flags passed from the config. -func (cfg *Config) AddFlags(flags *flag.FlagSet) { - flags.BoolVar(&cfg.Deps, "deps", false, "include all dependencies") - flags.BoolVar(&cfg.Tests, "test", false, "include all test packages") - flags.BoolVar(&cfg.Export, "export", false, "include export data files") - flags.Var(extraFlags{cfg}, "flags", "extra flags to pass to the underlying command") -} - -// extraFlags collects all occurrences of --flags into a single array -// We do this because it's much easier than escaping joining and splitting -// the extra flags that must be passed across the boundary unmodified -type extraFlags struct { - cfg *Config -} - -func (e extraFlags) String() string { - return "" -} - -func (e extraFlags) Set(value string) error { - e.cfg.Flags = append(e.cfg.Flags, value) - return nil -} diff --git a/go/packages/stdlib_test.go b/go/packages/stdlib_test.go index 69bfe464..ecaa89d6 100644 --- a/go/packages/stdlib_test.go +++ b/go/packages/stdlib_test.go @@ -30,7 +30,11 @@ func TestStdlibMetadata(t *testing.T) { alloc := memstats.Alloc // Load, parse and type-check the program. - pkgs, err := packages.Load(nil, "std") + cfg := &packages.Config{ + Mode: packages.LoadAllSyntax, + Error: func(error) {}, + } + pkgs, err := packages.Load(cfg, "std") if err != nil { t.Fatalf("failed to load metadata: %v", err) }