diff --git a/go/packages/golist.go b/go/packages/golist.go index ff568787..c1604694 100644 --- a/go/packages/golist.go +++ b/go/packages/golist.go @@ -31,7 +31,7 @@ type GoTooOldError struct { // golistPackages 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, dir string, env []string, cgo, export, tests bool, words []string) ([]*loaderPackage, error) { +func golistPackages(ctx context.Context, dir string, env []string, export, tests, deps bool, words []string) ([]*loaderPackage, error) { // Fields must match go list; // see $GOROOT/src/cmd/go/internal/load/pkg.go. type jsonPackage struct { @@ -68,7 +68,7 @@ func golistPackages(ctx context.Context, dir string, env []string, cgo, export, // Run "go list" for complete // information on the specified packages. - buf, err := golist(ctx, dir, env, cgo, export, tests, words) + buf, err := golist(ctx, dir, env, export, tests, deps, words) if err != nil { return nil, err } @@ -140,7 +140,7 @@ func golistPackages(ctx context.Context, dir string, env []string, cgo, export, } for id := range ids { // Go issue 26136: go list omits imports in cgo-generated files. - if id == "C" && cgo { + if id == "C" { imports["unsafe"] = "unsafe" imports["syscall"] = "syscall" if pkgpath != "runtime/cgo" { @@ -154,12 +154,12 @@ func golistPackages(ctx context.Context, dir string, env []string, cgo, export, pkg := &loaderPackage{ Package: &Package{ - ID: id, - Name: p.Name, - PkgPath: pkgpath, - Srcs: absJoin(p.Dir, p.GoFiles, p.CgoFiles), - OtherSrcs: absJoin(p.Dir, p.SFiles, p.CFiles), + ID: id, + Name: p.Name, + GoFiles: absJoin(p.Dir, p.GoFiles, p.CgoFiles), + OtherFiles: absJoin(p.Dir, p.SFiles, p.CFiles), }, + pkgpath: pkgpath, imports: imports, export: export, indirect: p.DepOnly, @@ -184,15 +184,15 @@ func absJoin(dir string, fileses ...[]string) (res []string) { } // golist returns the JSON-encoded result of a "go list args..." query. -func golist(ctx context.Context, dir string, env []string, cgo, export, tests bool, args []string) (*bytes.Buffer, error) { +func golist(ctx context.Context, dir string, env []string, export, tests, deps bool, args []string) (*bytes.Buffer, error) { out := new(bytes.Buffer) cmd := exec.CommandContext(ctx, "go", append([]string{ "list", "-e", - fmt.Sprintf("-cgo=%t", cgo), + fmt.Sprintf("-cgo=%t", true), fmt.Sprintf("-test=%t", tests), fmt.Sprintf("-export=%t", export), - "-deps", + fmt.Sprintf("-deps=%t", deps), "-json", "--", }, args...)...) @@ -200,9 +200,6 @@ func golist(ctx context.Context, dir string, env []string, cgo, export, tests bo if env == nil { env = os.Environ() } - if !cgo { - env = append(env, "CGO_ENABLED=0") - } cmd.Env = env cmd.Dir = dir cmd.Stdout = out diff --git a/go/packages/gopackages/main.go b/go/packages/gopackages/main.go index 10c2b210..36e5cbb5 100644 --- a/go/packages/gopackages/main.go +++ b/go/packages/gopackages/main.go @@ -28,8 +28,7 @@ import ( var ( depsFlag = flag.Bool("deps", false, "show dependencies too") testFlag = flag.Bool("test", false, "include any tests implied by the patterns") - cgoFlag = flag.Bool("cgo", true, "process cgo files") - mode = flag.String("mode", "metadata", "mode (one of metadata, typecheck, wholeprogram)") + mode = flag.String("mode", "graph", "mode (one of metadata, graph, typed, alltyped)") private = flag.Bool("private", false, "show non-exported declarations too") cpuprofile = flag.String("cpuprofile", "", "write CPU profile to this file") @@ -103,26 +102,30 @@ func main() { }() } + // Load, parse, and type-check the packages named on the command line. + cfg := &packages.Config{ + Mode: packages.LoadSyntax, + Error: func(error) {}, // we'll take responsibility for printing errors + Tests: *testFlag, + } + // -mode flag - load := packages.TypeCheck switch strings.ToLower(*mode) { - case "metadata": - load = packages.Metadata - case "typecheck": - load = packages.TypeCheck - case "wholeprogram": - load = packages.WholeProgram + case "files": + cfg.Mode = packages.LoadFiles + case "imports": + cfg.Mode = packages.LoadImports + case "types": + cfg.Mode = packages.LoadTypes + case "syntax": + cfg.Mode = packages.LoadSyntax + case "allsyntax": + cfg.Mode = packages.LoadAllSyntax default: log.Fatalf("invalid mode: %s", *mode) } - // Load, parse, and type-check the packages named on the command line. - opts := &packages.Options{ - Error: func(error) {}, // we'll take responsibility for printing errors - DisableCgo: !*cgoFlag, - Tests: *testFlag, - } - lpkgs, err := load(opts, flag.Args()...) + lpkgs, err := packages.Load(cfg, flag.Args()...) if err != nil { log.Fatal(err) } @@ -174,24 +177,23 @@ func print(lpkg *packages.Package) { } fmt.Printf("Go %s %q:\n", kind, lpkg.ID) // unique ID fmt.Printf("\tpackage %s\n", lpkg.Name) - fmt.Printf("\treflect.Type.PkgPath %q\n", lpkg.PkgPath) // characterize type info - if lpkg.Type == nil { + if lpkg.Types == nil { fmt.Printf("\thas no exported type info\n") - } else if !lpkg.Type.Complete() { + } else if !lpkg.Types.Complete() { fmt.Printf("\thas incomplete exported type info\n") - } else if len(lpkg.Files) == 0 { + } else if len(lpkg.Syntax) == 0 { fmt.Printf("\thas complete exported type info\n") } else { fmt.Printf("\thas complete exported type info and typed ASTs\n") } - if lpkg.Type != nil && lpkg.IllTyped && len(lpkg.Errors) == 0 { + if lpkg.Types != nil && lpkg.IllTyped && len(lpkg.Errors) == 0 { fmt.Printf("\thas an error among its dependencies\n") } // source files - for _, src := range lpkg.Srcs { + for _, src := range lpkg.GoFiles { fmt.Printf("\tfile %s\n", src) } @@ -217,9 +219,9 @@ func print(lpkg *packages.Package) { } // package members (TypeCheck or WholeProgram mode) - if lpkg.Type != nil { - qual := types.RelativeTo(lpkg.Type) - scope := lpkg.Type.Scope() + if lpkg.Types != nil { + qual := types.RelativeTo(lpkg.Types) + scope := lpkg.Types.Scope() for _, name := range scope.Names() { obj := scope.Lookup(name) if !obj.Exported() && !*private { diff --git a/go/packages/packages.go b/go/packages/packages.go index cc5bd28f..5a60e38b 100644 --- a/go/packages/packages.go +++ b/go/packages/packages.go @@ -20,10 +20,41 @@ import ( "golang.org/x/tools/go/gcexportdata" ) -// An Options specifies details about how packages should be loaded. -// The loaders do not modify this struct. -// TODO(rsc): Better name would be Config. -type Options struct { +// A LoadMode specifies the amount of detail to return when loading packages. +type LoadMode int + +const ( + _ LoadMode = iota + + // LoadFiles finds the packages and computes their source file lists. + // Package fields: ID, Name, Errors, GoFiles, OtherFiles. + LoadFiles + + // LoadImports adds import information for each package + // and its dependencies. + // Package fields added: Imports. + LoadImports + + // LoadTypes adds type information for the package's exported symbols. + // Package fields added: Types, IllTyped. + LoadTypes + + // LoadSyntax adds typed syntax trees for the packages matching the patterns. + // Package fields added: Syntax, TypesInfo, Fset, for direct pattern matches only. + LoadSyntax + + // LoadAllSyntax adds typed syntax trees for the packages matching the patterns + // and all dependencies. + // Package fields added: Syntax, TypesInfo, Fset, for all packages in import graph. + LoadAllSyntax +) + +// An Config specifies details about how packages should be loaded. +// Calls to Load do not modify this struct. +type Config struct { + // Mode controls the level of information returned for each package. + Mode LoadMode + // Context specifies the context for the load operation. // If the context is cancelled, the loader may stop early // and return an ErrCancelled error. @@ -35,14 +66,6 @@ type Options struct { // If Dir is empty, the tool is run in the current directory. Dir string - // DisableCgo disables cgo-processing of files that import "C", - // and removes the 'cgo' build tag, which may affect source file selection. - // By default, TypeCheck, and WholeProgram queries process such - // files, and the resulting Package.Srcs describes the generated - // files seen by the compiler. - // TODO(rsc): Drop entirely. I don't think these are the right semantics. - DisableCgo bool - // 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 @@ -110,37 +133,11 @@ type Options struct { TypeChecker types.Config } -// Metadata loads and returns the Go packages named by the given patterns, -// omitting type information and type-checked syntax trees from all packages. -// TODO(rsc): Better name would be Load. -func Metadata(o *Options, patterns ...string) ([]*Package, error) { - l := &loader{mode: metadata} - if o != nil { - l.Options = *o - } - return l.load(patterns...) -} - -// TypeCheck loads and returns the Go packages named by the given patterns. -// It includes type information in all packages, including dependencies. -// The packages named by the patterns also have type-checked syntax trees. -// TODO(rsc): Better name would be LoadTyped. -func TypeCheck(o *Options, patterns ...string) ([]*Package, error) { - l := &loader{mode: typeCheck} - if o != nil { - l.Options = *o - } - return l.load(patterns...) -} - -// WholeProgram loads and returns the Go packages named by the given patterns. -// It includes type information and type-checked syntax trees for all packages, -// including dependencies. -// TODO(rsc): Better name would be LoadAllTyped. -func WholeProgram(o *Options, patterns ...string) ([]*Package, error) { - l := &loader{mode: wholeProgram} - if o != nil { - l.Options = *o +// Load and returns the Go packages named by the given patterns. +func Load(cfg *Config, patterns ...string) ([]*Package, error) { + l := &loader{} + if cfg != nil { + l.Config = *cfg } return l.load(patterns...) } @@ -155,18 +152,6 @@ type Package struct { // interpret them. ID string - // PkgPath is the import path of the package during a particular build. - // - // Analyses that need a unique string to identify a returned Package - // should use ID, not PkgPath. Although PkgPath does uniquely identify - // a package in a particular build, the loader may return packages - // spanning multiple builds (for example, multiple commands, - // or a package and its tests), so PkgPath is not guaranteed unique - // across all packages returned by a single load. - // - // TODO(rsc): This name should be ImportPath. - PkgPath string - // Name is the package name as it appears in the package source code. Name string @@ -179,7 +164,7 @@ type Package struct { // to corresponding loaded Packages. Imports map[string]*Package - // Srcs lists the absolute file paths of the package's Go source files. + // GoFiles lists the absolute file paths of the package's Go source files. // // If a package has typed syntax trees and the DisableCgo option is false, // the cgo-processed output files are listed instead of the original @@ -192,20 +177,15 @@ type Package struct { // TODO(rsc): Actually, in TypeCheck mode even the packages without // syntax trees (pure dependencies) lose their original sources. // We should fix that. - // - // TODO(rsc): This should be GoFiles. - Srcs []string + GoFiles []string - // OtherSrcs lists the absolute file paths of the package's non-Go source files, + // 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. - // - // TODO(rsc): This should be OtherFiles. - OtherSrcs []string + OtherFiles []string // Type is the type information for the package. // The TypeCheck and WholeProgram loaders set this field for all packages. - // TODO(rsc): This should be Types. - Type *types.Package + Types *types.Package // IllTyped indicates whether the package has any type errors. // The TypeCheck and WholeProgram loaders set this field for all packages. @@ -215,15 +195,11 @@ type Package struct { // // The TypeCheck loader sets Files for packages matching the patterns. // The WholeProgram loader sets Files for all packages, including dependencies. - // - // TODO(rsc): This should be ASTs or Syntax. - Files []*ast.File + Syntax []*ast.File // Info is the type-checking results for the package's syntax trees. // It is set only when Files is set. - // - // TODO(rsc): This should be TypesInfo. - Info *types.Info + TypesInfo *types.Info // Fset is the token.FileSet for the package's syntax trees listed in Files. // It is set only when Files is set. @@ -249,42 +225,27 @@ type loaderPackage struct { loadOnce sync.Once color uint8 // for cycle detection mark, needsrc bool // used in TypeCheck mode only + pkgpath string } func (lpkg *Package) String() string { return lpkg.ID } // loader holds the working state of a single call to load. type loader struct { - mode mode - cgo bool pkgs map[string]*loaderPackage - Options + Config exportMu sync.Mutex // enforces mutual exclusion of exportdata operations } -// The mode determines which packages are visited -// and the level of information reported about each one. -// Modes are ordered by increasing detail. -type mode uint8 - -const ( - metadata = iota - typeCheck - wholeProgram -) - func (ld *loader) load(patterns ...string) ([]*Package, error) { if ld.Context == nil { ld.Context = context.Background() } - - if ld.mode > metadata { + if ld.Mode >= LoadSyntax { if ld.Fset == nil { ld.Fset = token.NewFileSet() } - ld.cgo = !ld.DisableCgo - if ld.Error == nil { ld.Error = func(e error) { fmt.Fprintln(os.Stderr, e) @@ -305,7 +266,9 @@ func (ld *loader) load(patterns ...string) ([]*Package, error) { // Do the metadata query and partial build. // TODO(adonovan): support alternative build systems at this seam. - list, err := golistPackages(ld.Context, ld.Dir, ld.Env, ld.cgo, ld.mode == typeCheck, ld.Tests, patterns) + export := ld.Mode > LoadImports && ld.Mode < LoadAllSyntax + deps := ld.Mode >= LoadImports + list, err := golistPackages(ld.Context, ld.Dir, ld.Env, export, ld.Tests, deps, patterns) if err != nil { return nil, err } @@ -319,10 +282,13 @@ func (ld *loader) load(patterns ...string) ([]*Package, error) { if !pkg.indirect { initial = append(initial, pkg) - if ld.mode == typeCheck { + if ld.Mode == LoadSyntax { pkg.needsrc = true } } + if ld.Mode >= LoadAllSyntax { + pkg.needsrc = true + } } if len(ld.pkgs) == 0 { return nil, fmt.Errorf("packages not found") @@ -343,9 +309,9 @@ func (ld *loader) load(patterns ...string) ([]*Package, error) { // Invalid imports (cycles and missing nodes) are saved in the importErrors map. // Thus, even in the presence of both kinds of errors, the Import graph remains a DAG. // - // visit returns whether the package is initial or has a transitive - // dependency on an initial package. These are the only packages - // for which we load source code in typeCheck mode. + // visit returns whether the package needs src or has a transitive + // dependency on a package that does. These are the only packages + // for which we load source code. var stack []*loaderPackage var visit func(lpkg *loaderPackage) bool visit = func(lpkg *loaderPackage) bool { @@ -390,14 +356,15 @@ func (ld *loader) load(patterns ...string) ([]*Package, error) { return lpkg.needsrc } - // For each initial package, create its import DAG. - for _, lpkg := range initial { - visit(lpkg) + if ld.Mode >= LoadImports { + // For each initial package, create its import DAG. + for _, lpkg := range initial { + visit(lpkg) + } } - - // Load some/all packages from source, starting at + // Load type data if needed, starting at // the initial packages (roots of the import DAG). - if ld.mode != metadata { + if ld.Mode >= LoadTypes { var wg sync.WaitGroup for _, lpkg := range initial { wg.Add(1) @@ -445,16 +412,16 @@ func (ld *loader) loadRecursive(lpkg *loaderPackage) { // after immediate dependencies are loaded. // Precondition: ld.mode != Metadata. func (ld *loader) loadPackage(lpkg *loaderPackage) { - if lpkg.PkgPath == "unsafe" { + if lpkg.pkgpath == "unsafe" { // Fill in the blanks to avoid surprises. - lpkg.Type = types.Unsafe + lpkg.Types = types.Unsafe lpkg.Fset = ld.Fset - lpkg.Files = []*ast.File{} - lpkg.Info = new(types.Info) + lpkg.Syntax = []*ast.File{} + lpkg.TypesInfo = new(types.Info) return } - if ld.mode == typeCheck && !lpkg.needsrc { + if !lpkg.needsrc { return // not a source package } @@ -469,20 +436,20 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) { lpkg.Errors = append(lpkg.Errors, err) } - files, errs := ld.parseFiles(lpkg.Srcs) + files, errs := ld.parseFiles(lpkg.GoFiles) for _, err := range errs { appendError(err) } lpkg.Fset = ld.Fset - lpkg.Files = files + lpkg.Syntax = files // Call NewPackage directly with explicit name. // This avoids skew between golist and go/types when the files' // package declarations are inconsistent. - lpkg.Type = types.NewPackage(lpkg.PkgPath, lpkg.Name) + lpkg.Types = types.NewPackage(lpkg.pkgpath, lpkg.Name) - lpkg.Info = &types.Info{ + lpkg.TypesInfo = &types.Info{ Types: make(map[ast.Expr]types.TypeAndValue), Defs: make(map[*ast.Ident]types.Object), Uses: make(map[*ast.Ident]types.Object), @@ -493,9 +460,6 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) { // Copy the prototype types.Config as it must vary across Packages. tc := ld.TypeChecker // copy - if !ld.cgo { - tc.FakeImportC = true - } tc.Importer = importerFunc(func(path string) (*types.Package, error) { if path == "unsafe" { return types.Unsafe, nil @@ -513,11 +477,11 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) { // used to supply alternative file contents. return nil, fmt.Errorf("no metadata for %s", path) } - if ipkg.Type != nil && ipkg.Type.Complete() { - return ipkg.Type, nil + if ipkg.Types != nil && ipkg.Types.Complete() { + return ipkg.Types, nil } imp := ld.pkgs[ipkg.ID] - if ld.mode == typeCheck && !imp.needsrc { + if !imp.needsrc { return ld.loadFromExportData(imp) } log.Fatalf("internal error: nil Pkg importing %q from %q", path, lpkg) @@ -526,7 +490,7 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) { tc.Error = appendError // type-check - types.NewChecker(&tc, ld.Fset, lpkg.Type, lpkg.Info).Files(lpkg.Files) + types.NewChecker(&tc, ld.Fset, lpkg.Types, lpkg.TypesInfo).Files(lpkg.Syntax) lpkg.importErrors = nil // no longer needed @@ -540,7 +504,7 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) { // we could just set IllTyped quietly. if tc.FakeImportC { outer: - for _, f := range lpkg.Files { + for _, f := range lpkg.Syntax { for _, imp := range f.Imports { if imp.Path.Value == `"C"` { appendError(fmt.Errorf(`%s: import "C" ignored`, @@ -621,7 +585,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.PkgPath == "" { + if lpkg.pkgpath == "" { log.Fatalf("internal error: Package %s has no PkgPath", lpkg) } @@ -640,7 +604,7 @@ func (ld *loader) loadFromExportData(lpkg *loaderPackage) (*types.Package, error ld.exportMu.Lock() defer ld.exportMu.Unlock() - if tpkg := lpkg.Type; tpkg != nil && tpkg.Complete() { + if tpkg := lpkg.Types; tpkg != nil && tpkg.Complete() { return tpkg, nil // cache hit } @@ -688,7 +652,7 @@ func (ld *loader) loadFromExportData(lpkg *loaderPackage) (*types.Package, error // export data packages, processing of the A->B and A->C import // edges may occur in either order, depending on the sequence // of imports within A. If B is processed first, and its export - // data mentions C, an imcomplete package for C will be created + // data mentions C, an incomplete package for C will be created // before processing of C.) // We could do export data processing in topological order using // loadRecursive, but there's no parallelism to be gained. @@ -704,8 +668,8 @@ func (ld *loader) loadFromExportData(lpkg *loaderPackage) (*types.Package, error visit = func(p *loaderPackage) { if !seen[p] { seen[p] = true - if p.Type != nil { - view[p.PkgPath] = p.Type + if p.Types != nil { + view[p.pkgpath] = p.Types } else { copyback = append(copyback, p) } @@ -718,7 +682,7 @@ 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.PkgPath) + tpkg, err := gcexportdata.Read(r, ld.Fset, view, lpkg.pkgpath) if err != nil { return nil, fmt.Errorf("reading %s: %v", lpkg.export, err) } @@ -726,34 +690,11 @@ func (ld *loader) loadFromExportData(lpkg *loaderPackage) (*types.Package, error // For each newly created types.Package in the view, // save it in the main graph. for _, p := range copyback { - p.Type = view[p.PkgPath] // may still be nil + p.Types = view[p.pkgpath] // may still be nil } - lpkg.Type = tpkg + lpkg.Types = tpkg lpkg.IllTyped = false return tpkg, nil } - -// All returns a map, from package ID to package, -// containing the packages in the given list and all their dependencies. -// Each call to All returns a new map. -// -// TODO(rsc): I don't understand why this function exists. -// It might be more useful to return a slice in dependency order. -func All(list []*Package) map[string]*Package { - all := make(map[string]*Package) - var visit func(p *Package) - visit = func(p *Package) { - if all[p.ID] == nil { - all[p.ID] = p - for _, imp := range p.Imports { - visit(imp) - } - } - } - for _, p := range list { - visit(p) - } - return all -} diff --git a/go/packages/packages110_test.go b/go/packages/packages110_test.go index 19bf3a2c..4d43ceb1 100644 --- a/go/packages/packages110_test.go +++ b/go/packages/packages110_test.go @@ -13,7 +13,7 @@ import ( ) func TestGoIsTooOld(t *testing.T) { - _, err := packages.Metadata(nil, "errors") + _, err := packages.Load(nil, "errors") if _, ok := err.(packages.GoTooOldError); !ok { t.Fatalf("using go/packages with pre-Go 1.11 go: err=%v, want ErrGoTooOld", err) diff --git a/go/packages/packages_test.go b/go/packages/packages_test.go index 342dc3e0..c8b46c00 100644 --- a/go/packages/packages_test.go +++ b/go/packages/packages_test.go @@ -65,8 +65,11 @@ func TestMetadataImportGraph(t *testing.T) { }) defer cleanup() - opts := &packages.Options{Env: append(os.Environ(), "GOPATH="+tmp)} - initial, err := packages.Metadata(opts, "c", "subdir/d", "e") + cfg := &packages.Config{ + Mode: packages.LoadImports, + Env: append(os.Environ(), "GOPATH="+tmp), + } + initial, err := packages.Load(cfg, "c", "subdir/d", "e") if err != nil { t.Fatal(err) } @@ -93,8 +96,8 @@ func TestMetadataImportGraph(t *testing.T) { t.Errorf("wrong import graph: got <<%s>>, want <<%s>>", graph, wantGraph) } - opts.Tests = true - initial, err = packages.Metadata(opts, "c", "subdir/d", "e") + cfg.Tests = true + initial, err = packages.Load(cfg, "c", "subdir/d", "e") if err != nil { t.Fatal(err) } @@ -174,7 +177,7 @@ func TestMetadataImportGraph(t *testing.T) { } // Test an ad-hoc package, analogous to "go run hello.go". - if initial, err := packages.Metadata(opts, filepath.Join(tmp, "src/c/c.go")); len(initial) == 0 { + if initial, err := packages.Load(cfg, filepath.Join(tmp, "src/c/c.go")); len(initial) == 0 { t.Errorf("failed to obtain metadata for ad-hoc package: %s", err) } else { got := fmt.Sprintf("%s %s", initial[0].ID, srcs(initial[0])) @@ -188,7 +191,7 @@ func TestMetadataImportGraph(t *testing.T) { // TODO(adonovan): test "all" returns everything in the current module. { // "..." (subdirectory) - initial, err = packages.Metadata(opts, "subdir/...") + initial, err = packages.Load(cfg, "subdir/...") if err != nil { t.Fatal(err) } @@ -225,12 +228,13 @@ func TestOptionsDir(t *testing.T) { {filepath.Join(tmp, "/src/a"), "./a", "packages not found"}, {filepath.Join(tmp, "/src/a"), "./b", `"a/b"`}, } { - opts := &packages.Options{ - Dir: test.dir, - Env: append(os.Environ(), "GOPATH="+tmp), + cfg := &packages.Config{ + Mode: packages.LoadSyntax, // Use LoadSyntax to ensure that files can be opened. + Dir: test.dir, + Env: append(os.Environ(), "GOPATH="+tmp), } - // Use TypeCheck to ensure that files can be opened. - initial, err := packages.TypeCheck(opts, test.pattern) + + initial, err := packages.Load(cfg, test.pattern) var got string if err != nil { got = err.Error() @@ -266,8 +270,12 @@ func TestTypeCheckOK(t *testing.T) { }) defer cleanup() - opts := &packages.Options{Env: append(os.Environ(), "GOPATH="+tmp), Error: func(error) {}} - initial, err := packages.TypeCheck(opts, "a", "c") + cfg := &packages.Config{ + Mode: packages.LoadSyntax, + Env: append(os.Environ(), "GOPATH="+tmp), + Error: func(error) {}, + } + initial, err := packages.Load(cfg, "a", "c") if err != nil { t.Fatal(err) } @@ -289,9 +297,9 @@ func TestTypeCheckOK(t *testing.T) { } for _, test := range []struct { - id string - wantType bool - wantFiles bool + id string + wantType bool + wantSyntax bool }{ {"a", true, true}, // source package {"b", true, true}, // source package @@ -304,15 +312,15 @@ func TestTypeCheckOK(t *testing.T) { t.Errorf("missing package: %s", test.id) continue } - if (p.Type != nil) != test.wantType { + if (p.Types != nil) != test.wantType { if test.wantType { t.Errorf("missing types.Package for %s", p) } else { t.Errorf("unexpected types.Package for %s", p) } } - if (p.Files != nil) != test.wantFiles { - if test.wantFiles { + if (p.Syntax != nil) != test.wantSyntax { + if test.wantSyntax { t.Errorf("missing ast.Files for %s", p) } else { t.Errorf("unexpected ast.Files for for %s", p) @@ -347,18 +355,34 @@ func TestTypeCheckError(t *testing.T) { }) defer cleanup() - opts := &packages.Options{Env: append(os.Environ(), "GOPATH="+tmp), Error: func(error) {}} - initial, err := packages.TypeCheck(opts, "a", "c") + cfg := &packages.Config{ + Mode: packages.LoadSyntax, + Env: append(os.Environ(), "GOPATH="+tmp), + Error: func(error) {}, + } + initial, err := packages.Load(cfg, "a", "c") if err != nil { t.Fatal(err) } - all := packages.All(initial) + all := make(map[string]*packages.Package) + var visit func(p *packages.Package) + visit = func(p *packages.Package) { + if all[p.ID] == nil { + all[p.ID] = p + for _, imp := range p.Imports { + visit(imp) + } + } + } + for _, p := range initial { + visit(p) + } for _, test := range []struct { id string - wantType bool - wantFiles bool + wantTypes bool + wantSyntax bool wantIllTyped bool wantErrs []string }{ @@ -373,15 +397,15 @@ func TestTypeCheckError(t *testing.T) { t.Errorf("missing package: %s", test.id) continue } - if (p.Type != nil) != test.wantType { - if test.wantType { + if (p.Types != nil) != test.wantTypes { + if test.wantTypes { t.Errorf("missing types.Package for %s", test.id) } else { t.Errorf("unexpected types.Package for %s", test.id) } } - if (p.Files != nil) != test.wantFiles { - if test.wantFiles { + if (p.Syntax != nil) != test.wantSyntax { + if test.wantSyntax { t.Errorf("missing ast.Files for %s", test.id) } else { t.Errorf("unexpected ast.Files for for %s", test.id) @@ -439,12 +463,13 @@ func TestWholeProgramOverlay(t *testing.T) { } } var errs errCollector - opts := &packages.Options{ + cfg := &packages.Config{ + Mode: packages.LoadAllSyntax, Env: append(os.Environ(), "GOPATH="+tmp), Error: errs.add, ParseFile: parseFile, } - initial, err := packages.WholeProgram(opts, "a") + initial, err := packages.Load(cfg, "a") if err != nil { t.Error(err) continue @@ -483,11 +508,12 @@ import ( os.Mkdir(filepath.Join(tmp, "src/empty"), 0777) // create an existing but empty package var errs2 errCollector - opts := &packages.Options{ + cfg := &packages.Config{ + Mode: packages.LoadAllSyntax, Env: append(os.Environ(), "GOPATH="+tmp), Error: errs2.add, } - initial, err := packages.WholeProgram(opts, "root") + initial, err := packages.Load(cfg, "root") if err != nil { t.Fatal(err) } @@ -530,10 +556,10 @@ import ( t.Errorf("missing package: %s", test.id) continue } - if p.Type == nil { + if p.Types == nil { t.Errorf("missing types.Package for %s", test.id) } - if p.Files == nil { + if p.Syntax == nil { t.Errorf("missing ast.Files for %s", test.id) } if !p.IllTyped { @@ -560,7 +586,7 @@ func errorMessages(errors []error) []string { } func srcs(p *packages.Package) (basenames []string) { - for i, src := range p.Srcs { + for i, src := range p.GoFiles { if strings.Contains(src, ".cache/go-build") { src = fmt.Sprintf("%d.go", i) // make cache names predictable } else { @@ -671,5 +697,5 @@ func makeTree(t *testing.T, tree map[string]string) (dir string, cleanup func()) } func constant(p *packages.Package, name string) *types.Const { - return p.Type.Scope().Lookup(name).(*types.Const) + return p.Types.Scope().Lookup(name).(*types.Const) } diff --git a/go/packages/stdlib_test.go b/go/packages/stdlib_test.go index 4a9b4168..3941f506 100644 --- a/go/packages/stdlib_test.go +++ b/go/packages/stdlib_test.go @@ -32,7 +32,7 @@ func TestStdlibMetadata(t *testing.T) { alloc := memstats.Alloc // Load, parse and type-check the program. - pkgs, err := packages.Metadata(nil, "std") + pkgs, err := packages.Load(nil, "std") if err != nil { t.Fatalf("failed to load metadata: %v", err) } @@ -94,49 +94,40 @@ func TestCgoOption(t *testing.T) { {"net", "cgoLookupHost", "cgo_stub.go"}, {"os/user", "current", "lookup_stubs.go"}, } { - for i := 0; i < 2; i++ { // !cgo, cgo - opts := &packages.Options{ - DisableCgo: i == 0, - Error: func(error) {}, - } - pkgs, err := packages.TypeCheck(opts, test.pkg) - if err != nil { - t.Errorf("Load failed: %v", err) - continue - } - pkg := pkgs[0] - obj := pkg.Type.Scope().Lookup(test.name) - if obj == nil { - t.Errorf("no object %s.%s", test.pkg, test.name) - continue - } - posn := pkg.Fset.Position(obj.Pos()) - if false { - t.Logf("DisableCgo=%t, obj=%s, posn=%s", opts.DisableCgo, obj, posn) - } + cfg := &packages.Config{ + Mode: packages.LoadSyntax, + Error: func(error) {}, + } + pkgs, err := packages.Load(cfg, test.pkg) + if err != nil { + t.Errorf("Load failed: %v", err) + continue + } + pkg := pkgs[0] + obj := pkg.Types.Scope().Lookup(test.name) + if obj == nil { + t.Errorf("no object %s.%s", test.pkg, test.name) + continue + } + posn := pkg.Fset.Position(obj.Pos()) + gotFile := filepath.Base(posn.Filename) + filesMatch := gotFile == test.genericFile - gotFile := filepath.Base(posn.Filename) - filesMatch := gotFile == test.genericFile + if filesMatch { + t.Errorf("!DisableCgo: %s found in %s, want native file", + obj, gotFile) + } - if !opts.DisableCgo && filesMatch { - t.Errorf("!DisableCgo: %s found in %s, want native file", - obj, gotFile) - } else if opts.DisableCgo && !filesMatch { - t.Errorf("DisableCgo: %s found in %s, want %s", - obj, gotFile, test.genericFile) - } - - // Load the file and check the object is declared at the right place. - b, err := ioutil.ReadFile(posn.Filename) - if err != nil { - t.Errorf("can't read %s: %s", posn.Filename, err) - continue - } - line := string(bytes.Split(b, []byte("\n"))[posn.Line-1]) - // Don't assume posn.Column is accurate. - if !strings.Contains(line, "func "+test.name) { - t.Errorf("%s: %s not declared here (looking at %q)", posn, obj, line) - } + // Load the file and check the object is declared at the right place. + b, err := ioutil.ReadFile(posn.Filename) + if err != nil { + t.Errorf("can't read %s: %s", posn.Filename, err) + continue + } + line := string(bytes.Split(b, []byte("\n"))[posn.Line-1]) + // Don't assume posn.Column is accurate. + if !strings.Contains(line, "func "+test.name) { + t.Errorf("%s: %s not declared here (looking at %q)", posn, obj, line) } } }