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) }