go/packages: changes to the API per CL 125305

Collapse the main entry points to the single Load function.
Make public the Mode enumeration in the Config.
Add a lowest level mode that does not do the import graph.
Remove PkgPath.
Remove the DisableCgo option.
Rename some fields of Package:
  Srcs -> GoFiles
  OtherSrcs -> OtherFiles
  Type -> Types
  Files -> Syntax
  Info -> TypesInfo
delete the All function

Change-Id: I54e5dc3ca5cb0b7e33fe6aa6f70cc66e18ea8cac
Reviewed-on: https://go-review.googlesource.com/125535
Run-TryBot: Ian Cottrell <iancottrell@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Russ Cox <rsc@golang.org>
Reviewed-by: Michael Matloob <matloob@golang.org>
This commit is contained in:
Ian Cottrell 2018-07-23 16:29:28 -04:00
parent ded554d068
commit 214274eeeb
6 changed files with 223 additions and 266 deletions

View File

@ -31,7 +31,7 @@ type GoTooOldError struct {
// golistPackages uses the "go list" command to expand the // golistPackages uses the "go list" command to expand the
// pattern words and return metadata for the specified packages. // pattern words and return metadata for the specified packages.
// dir may be "" and env may be nil, as per os/exec.Command. // 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; // Fields must match go list;
// see $GOROOT/src/cmd/go/internal/load/pkg.go. // see $GOROOT/src/cmd/go/internal/load/pkg.go.
type jsonPackage struct { type jsonPackage struct {
@ -68,7 +68,7 @@ func golistPackages(ctx context.Context, dir string, env []string, cgo, export,
// Run "go list" for complete // Run "go list" for complete
// information on the specified packages. // 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 { if err != nil {
return nil, err return nil, err
} }
@ -140,7 +140,7 @@ func golistPackages(ctx context.Context, dir string, env []string, cgo, export,
} }
for id := range ids { for id := range ids {
// Go issue 26136: go list omits imports in cgo-generated files. // Go issue 26136: go list omits imports in cgo-generated files.
if id == "C" && cgo { if id == "C" {
imports["unsafe"] = "unsafe" imports["unsafe"] = "unsafe"
imports["syscall"] = "syscall" imports["syscall"] = "syscall"
if pkgpath != "runtime/cgo" { if pkgpath != "runtime/cgo" {
@ -156,10 +156,10 @@ func golistPackages(ctx context.Context, dir string, env []string, cgo, export,
Package: &Package{ Package: &Package{
ID: id, ID: id,
Name: p.Name, Name: p.Name,
PkgPath: pkgpath, GoFiles: absJoin(p.Dir, p.GoFiles, p.CgoFiles),
Srcs: absJoin(p.Dir, p.GoFiles, p.CgoFiles), OtherFiles: absJoin(p.Dir, p.SFiles, p.CFiles),
OtherSrcs: absJoin(p.Dir, p.SFiles, p.CFiles),
}, },
pkgpath: pkgpath,
imports: imports, imports: imports,
export: export, export: export,
indirect: p.DepOnly, 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. // 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) out := new(bytes.Buffer)
cmd := exec.CommandContext(ctx, "go", append([]string{ cmd := exec.CommandContext(ctx, "go", append([]string{
"list", "list",
"-e", "-e",
fmt.Sprintf("-cgo=%t", cgo), fmt.Sprintf("-cgo=%t", true),
fmt.Sprintf("-test=%t", tests), fmt.Sprintf("-test=%t", tests),
fmt.Sprintf("-export=%t", export), fmt.Sprintf("-export=%t", export),
"-deps", fmt.Sprintf("-deps=%t", deps),
"-json", "-json",
"--", "--",
}, args...)...) }, args...)...)
@ -200,9 +200,6 @@ func golist(ctx context.Context, dir string, env []string, cgo, export, tests bo
if env == nil { if env == nil {
env = os.Environ() env = os.Environ()
} }
if !cgo {
env = append(env, "CGO_ENABLED=0")
}
cmd.Env = env cmd.Env = env
cmd.Dir = dir cmd.Dir = dir
cmd.Stdout = out cmd.Stdout = out

View File

@ -28,8 +28,7 @@ import (
var ( var (
depsFlag = flag.Bool("deps", false, "show dependencies too") depsFlag = flag.Bool("deps", false, "show dependencies too")
testFlag = flag.Bool("test", false, "include any tests implied by the patterns") testFlag = flag.Bool("test", false, "include any tests implied by the patterns")
cgoFlag = flag.Bool("cgo", true, "process cgo files") mode = flag.String("mode", "graph", "mode (one of metadata, graph, typed, alltyped)")
mode = flag.String("mode", "metadata", "mode (one of metadata, typecheck, wholeprogram)")
private = flag.Bool("private", false, "show non-exported declarations too") private = flag.Bool("private", false, "show non-exported declarations too")
cpuprofile = flag.String("cpuprofile", "", "write CPU profile to this file") 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 // -mode flag
load := packages.TypeCheck
switch strings.ToLower(*mode) { switch strings.ToLower(*mode) {
case "metadata": case "files":
load = packages.Metadata cfg.Mode = packages.LoadFiles
case "typecheck": case "imports":
load = packages.TypeCheck cfg.Mode = packages.LoadImports
case "wholeprogram": case "types":
load = packages.WholeProgram cfg.Mode = packages.LoadTypes
case "syntax":
cfg.Mode = packages.LoadSyntax
case "allsyntax":
cfg.Mode = packages.LoadAllSyntax
default: default:
log.Fatalf("invalid mode: %s", *mode) log.Fatalf("invalid mode: %s", *mode)
} }
// Load, parse, and type-check the packages named on the command line. lpkgs, err := packages.Load(cfg, flag.Args()...)
opts := &packages.Options{
Error: func(error) {}, // we'll take responsibility for printing errors
DisableCgo: !*cgoFlag,
Tests: *testFlag,
}
lpkgs, err := load(opts, flag.Args()...)
if err != nil { if err != nil {
log.Fatal(err) 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("Go %s %q:\n", kind, lpkg.ID) // unique ID
fmt.Printf("\tpackage %s\n", lpkg.Name) fmt.Printf("\tpackage %s\n", lpkg.Name)
fmt.Printf("\treflect.Type.PkgPath %q\n", lpkg.PkgPath)
// characterize type info // characterize type info
if lpkg.Type == nil { if lpkg.Types == nil {
fmt.Printf("\thas no exported type info\n") 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") 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") fmt.Printf("\thas complete exported type info\n")
} else { } else {
fmt.Printf("\thas complete exported type info and typed ASTs\n") 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") fmt.Printf("\thas an error among its dependencies\n")
} }
// source files // source files
for _, src := range lpkg.Srcs { for _, src := range lpkg.GoFiles {
fmt.Printf("\tfile %s\n", src) fmt.Printf("\tfile %s\n", src)
} }
@ -217,9 +219,9 @@ func print(lpkg *packages.Package) {
} }
// package members (TypeCheck or WholeProgram mode) // package members (TypeCheck or WholeProgram mode)
if lpkg.Type != nil { if lpkg.Types != nil {
qual := types.RelativeTo(lpkg.Type) qual := types.RelativeTo(lpkg.Types)
scope := lpkg.Type.Scope() scope := lpkg.Types.Scope()
for _, name := range scope.Names() { for _, name := range scope.Names() {
obj := scope.Lookup(name) obj := scope.Lookup(name)
if !obj.Exported() && !*private { if !obj.Exported() && !*private {

View File

@ -20,10 +20,41 @@ import (
"golang.org/x/tools/go/gcexportdata" "golang.org/x/tools/go/gcexportdata"
) )
// An Options specifies details about how packages should be loaded. // A LoadMode specifies the amount of detail to return when loading packages.
// The loaders do not modify this struct. type LoadMode int
// TODO(rsc): Better name would be Config.
type Options struct { 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. // Context specifies the context for the load operation.
// If the context is cancelled, the loader may stop early // If the context is cancelled, the loader may stop early
// and return an ErrCancelled error. // and return an ErrCancelled error.
@ -35,14 +66,6 @@ type Options struct {
// If Dir is empty, the tool is run in the current directory. // If Dir is empty, the tool is run in the current directory.
Dir string 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. // Env is the environment to use when invoking the build system tool.
// If Env is nil, the current environment is used. // If Env is nil, the current environment is used.
// Like in os/exec's Cmd, only the last value in the slice for // Like in os/exec's Cmd, only the last value in the slice for
@ -110,37 +133,11 @@ type Options struct {
TypeChecker types.Config TypeChecker types.Config
} }
// Metadata loads and returns the Go packages named by the given patterns, // Load and returns the Go packages named by the given patterns.
// omitting type information and type-checked syntax trees from all packages. func Load(cfg *Config, patterns ...string) ([]*Package, error) {
// TODO(rsc): Better name would be Load. l := &loader{}
func Metadata(o *Options, patterns ...string) ([]*Package, error) { if cfg != nil {
l := &loader{mode: metadata} l.Config = *cfg
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
} }
return l.load(patterns...) return l.load(patterns...)
} }
@ -155,18 +152,6 @@ type Package struct {
// interpret them. // interpret them.
ID string 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 is the package name as it appears in the package source code.
Name string Name string
@ -179,7 +164,7 @@ type Package struct {
// to corresponding loaded Packages. // to corresponding loaded Packages.
Imports map[string]*Package 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, // If a package has typed syntax trees and the DisableCgo option is false,
// the cgo-processed output files are listed instead of the original // 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 // TODO(rsc): Actually, in TypeCheck mode even the packages without
// syntax trees (pure dependencies) lose their original sources. // syntax trees (pure dependencies) lose their original sources.
// We should fix that. // We should fix that.
// GoFiles []string
// TODO(rsc): This should be GoFiles.
Srcs []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. // including assembly, C, C++, Fortran, Objective-C, SWIG, and so on.
// OtherFiles []string
// TODO(rsc): This should be OtherFiles.
OtherSrcs []string
// Type is the type information for the package. // Type is the type information for the package.
// The TypeCheck and WholeProgram loaders set this field for all packages. // The TypeCheck and WholeProgram loaders set this field for all packages.
// TODO(rsc): This should be Types. Types *types.Package
Type *types.Package
// IllTyped indicates whether the package has any type errors. // IllTyped indicates whether the package has any type errors.
// The TypeCheck and WholeProgram loaders set this field for all packages. // 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 TypeCheck loader sets Files for packages matching the patterns.
// The WholeProgram loader sets Files for all packages, including dependencies. // The WholeProgram loader sets Files for all packages, including dependencies.
// Syntax []*ast.File
// TODO(rsc): This should be ASTs or Syntax.
Files []*ast.File
// Info is the type-checking results for the package's syntax trees. // Info is the type-checking results for the package's syntax trees.
// It is set only when Files is set. // It is set only when Files is set.
// TypesInfo *types.Info
// TODO(rsc): This should be TypesInfo.
Info *types.Info
// Fset is the token.FileSet for the package's syntax trees listed in Files. // Fset is the token.FileSet for the package's syntax trees listed in Files.
// It is set only when Files is set. // It is set only when Files is set.
@ -249,42 +225,27 @@ type loaderPackage struct {
loadOnce sync.Once loadOnce sync.Once
color uint8 // for cycle detection color uint8 // for cycle detection
mark, needsrc bool // used in TypeCheck mode only mark, needsrc bool // used in TypeCheck mode only
pkgpath string
} }
func (lpkg *Package) String() string { return lpkg.ID } func (lpkg *Package) String() string { return lpkg.ID }
// loader holds the working state of a single call to load. // loader holds the working state of a single call to load.
type loader struct { type loader struct {
mode mode
cgo bool
pkgs map[string]*loaderPackage pkgs map[string]*loaderPackage
Options Config
exportMu sync.Mutex // enforces mutual exclusion of exportdata operations 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) { func (ld *loader) load(patterns ...string) ([]*Package, error) {
if ld.Context == nil { if ld.Context == nil {
ld.Context = context.Background() ld.Context = context.Background()
} }
if ld.Mode >= LoadSyntax {
if ld.mode > metadata {
if ld.Fset == nil { if ld.Fset == nil {
ld.Fset = token.NewFileSet() ld.Fset = token.NewFileSet()
} }
ld.cgo = !ld.DisableCgo
if ld.Error == nil { if ld.Error == nil {
ld.Error = func(e error) { ld.Error = func(e error) {
fmt.Fprintln(os.Stderr, e) fmt.Fprintln(os.Stderr, e)
@ -305,7 +266,9 @@ func (ld *loader) load(patterns ...string) ([]*Package, error) {
// Do the metadata query and partial build. // Do the metadata query and partial build.
// TODO(adonovan): support alternative build systems at this seam. // 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 { if err != nil {
return nil, err return nil, err
} }
@ -319,10 +282,13 @@ func (ld *loader) load(patterns ...string) ([]*Package, error) {
if !pkg.indirect { if !pkg.indirect {
initial = append(initial, pkg) initial = append(initial, pkg)
if ld.mode == typeCheck { if ld.Mode == LoadSyntax {
pkg.needsrc = true pkg.needsrc = true
} }
} }
if ld.Mode >= LoadAllSyntax {
pkg.needsrc = true
}
} }
if len(ld.pkgs) == 0 { if len(ld.pkgs) == 0 {
return nil, fmt.Errorf("packages not found") 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. // 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. // 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 // visit returns whether the package needs src or has a transitive
// dependency on an initial package. These are the only packages // dependency on a package that does. These are the only packages
// for which we load source code in typeCheck mode. // for which we load source code.
var stack []*loaderPackage var stack []*loaderPackage
var visit func(lpkg *loaderPackage) bool var visit func(lpkg *loaderPackage) bool
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 return lpkg.needsrc
} }
if ld.Mode >= LoadImports {
// For each initial package, create its import DAG. // For each initial package, create its import DAG.
for _, lpkg := range initial { for _, lpkg := range initial {
visit(lpkg) 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). // the initial packages (roots of the import DAG).
if ld.mode != metadata { if ld.Mode >= LoadTypes {
var wg sync.WaitGroup var wg sync.WaitGroup
for _, lpkg := range initial { for _, lpkg := range initial {
wg.Add(1) wg.Add(1)
@ -445,16 +412,16 @@ func (ld *loader) loadRecursive(lpkg *loaderPackage) {
// after immediate dependencies are loaded. // after immediate dependencies are loaded.
// Precondition: ld.mode != Metadata. // Precondition: ld.mode != Metadata.
func (ld *loader) loadPackage(lpkg *loaderPackage) { func (ld *loader) loadPackage(lpkg *loaderPackage) {
if lpkg.PkgPath == "unsafe" { if lpkg.pkgpath == "unsafe" {
// Fill in the blanks to avoid surprises. // Fill in the blanks to avoid surprises.
lpkg.Type = types.Unsafe lpkg.Types = types.Unsafe
lpkg.Fset = ld.Fset lpkg.Fset = ld.Fset
lpkg.Files = []*ast.File{} lpkg.Syntax = []*ast.File{}
lpkg.Info = new(types.Info) lpkg.TypesInfo = new(types.Info)
return return
} }
if ld.mode == typeCheck && !lpkg.needsrc { if !lpkg.needsrc {
return // not a source package return // not a source package
} }
@ -469,20 +436,20 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) {
lpkg.Errors = append(lpkg.Errors, err) lpkg.Errors = append(lpkg.Errors, err)
} }
files, errs := ld.parseFiles(lpkg.Srcs) files, errs := ld.parseFiles(lpkg.GoFiles)
for _, err := range errs { for _, err := range errs {
appendError(err) appendError(err)
} }
lpkg.Fset = ld.Fset lpkg.Fset = ld.Fset
lpkg.Files = files lpkg.Syntax = files
// Call NewPackage directly with explicit name. // Call NewPackage directly with explicit name.
// This avoids skew between golist and go/types when the files' // This avoids skew between golist and go/types when the files'
// package declarations are inconsistent. // 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), Types: make(map[ast.Expr]types.TypeAndValue),
Defs: make(map[*ast.Ident]types.Object), Defs: make(map[*ast.Ident]types.Object),
Uses: 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. // Copy the prototype types.Config as it must vary across Packages.
tc := ld.TypeChecker // copy tc := ld.TypeChecker // copy
if !ld.cgo {
tc.FakeImportC = true
}
tc.Importer = importerFunc(func(path string) (*types.Package, error) { tc.Importer = importerFunc(func(path string) (*types.Package, error) {
if path == "unsafe" { if path == "unsafe" {
return types.Unsafe, nil return types.Unsafe, nil
@ -513,11 +477,11 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) {
// used to supply alternative file contents. // used to supply alternative file contents.
return nil, fmt.Errorf("no metadata for %s", path) return nil, fmt.Errorf("no metadata for %s", path)
} }
if ipkg.Type != nil && ipkg.Type.Complete() { if ipkg.Types != nil && ipkg.Types.Complete() {
return ipkg.Type, nil return ipkg.Types, nil
} }
imp := ld.pkgs[ipkg.ID] imp := ld.pkgs[ipkg.ID]
if ld.mode == typeCheck && !imp.needsrc { if !imp.needsrc {
return ld.loadFromExportData(imp) return ld.loadFromExportData(imp)
} }
log.Fatalf("internal error: nil Pkg importing %q from %q", path, lpkg) 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 tc.Error = appendError
// type-check // 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 lpkg.importErrors = nil // no longer needed
@ -540,7 +504,7 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) {
// we could just set IllTyped quietly. // we could just set IllTyped quietly.
if tc.FakeImportC { if tc.FakeImportC {
outer: outer:
for _, f := range lpkg.Files { for _, f := range lpkg.Syntax {
for _, imp := range f.Imports { for _, imp := range f.Imports {
if imp.Path.Value == `"C"` { if imp.Path.Value == `"C"` {
appendError(fmt.Errorf(`%s: import "C" ignored`, 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 // loadFromExportData returns type information for the specified
// package, loading it from an export data file on the first request. // package, loading it from an export data file on the first request.
func (ld *loader) loadFromExportData(lpkg *loaderPackage) (*types.Package, error) { 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) 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() ld.exportMu.Lock()
defer ld.exportMu.Unlock() 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 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 // export data packages, processing of the A->B and A->C import
// edges may occur in either order, depending on the sequence // edges may occur in either order, depending on the sequence
// of imports within A. If B is processed first, and its export // 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.) // before processing of C.)
// We could do export data processing in topological order using // We could do export data processing in topological order using
// loadRecursive, but there's no parallelism to be gained. // 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) { visit = func(p *loaderPackage) {
if !seen[p] { if !seen[p] {
seen[p] = true seen[p] = true
if p.Type != nil { if p.Types != nil {
view[p.PkgPath] = p.Type view[p.pkgpath] = p.Types
} else { } else {
copyback = append(copyback, p) copyback = append(copyback, p)
} }
@ -718,7 +682,7 @@ func (ld *loader) loadFromExportData(lpkg *loaderPackage) (*types.Package, error
// Parse the export data. // Parse the export data.
// (May create/modify packages in view.) // (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 { if err != nil {
return nil, fmt.Errorf("reading %s: %v", lpkg.export, err) 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, // For each newly created types.Package in the view,
// save it in the main graph. // save it in the main graph.
for _, p := range copyback { 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 lpkg.IllTyped = false
return tpkg, nil 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
}

View File

@ -13,7 +13,7 @@ import (
) )
func TestGoIsTooOld(t *testing.T) { func TestGoIsTooOld(t *testing.T) {
_, err := packages.Metadata(nil, "errors") _, err := packages.Load(nil, "errors")
if _, ok := err.(packages.GoTooOldError); !ok { if _, ok := err.(packages.GoTooOldError); !ok {
t.Fatalf("using go/packages with pre-Go 1.11 go: err=%v, want ErrGoTooOld", err) t.Fatalf("using go/packages with pre-Go 1.11 go: err=%v, want ErrGoTooOld", err)

View File

@ -65,8 +65,11 @@ func TestMetadataImportGraph(t *testing.T) {
}) })
defer cleanup() defer cleanup()
opts := &packages.Options{Env: append(os.Environ(), "GOPATH="+tmp)} cfg := &packages.Config{
initial, err := packages.Metadata(opts, "c", "subdir/d", "e") Mode: packages.LoadImports,
Env: append(os.Environ(), "GOPATH="+tmp),
}
initial, err := packages.Load(cfg, "c", "subdir/d", "e")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -93,8 +96,8 @@ func TestMetadataImportGraph(t *testing.T) {
t.Errorf("wrong import graph: got <<%s>>, want <<%s>>", graph, wantGraph) t.Errorf("wrong import graph: got <<%s>>, want <<%s>>", graph, wantGraph)
} }
opts.Tests = true cfg.Tests = true
initial, err = packages.Metadata(opts, "c", "subdir/d", "e") initial, err = packages.Load(cfg, "c", "subdir/d", "e")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -174,7 +177,7 @@ func TestMetadataImportGraph(t *testing.T) {
} }
// Test an ad-hoc package, analogous to "go run hello.go". // 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) t.Errorf("failed to obtain metadata for ad-hoc package: %s", err)
} else { } else {
got := fmt.Sprintf("%s %s", initial[0].ID, srcs(initial[0])) 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. // TODO(adonovan): test "all" returns everything in the current module.
{ {
// "..." (subdirectory) // "..." (subdirectory)
initial, err = packages.Metadata(opts, "subdir/...") initial, err = packages.Load(cfg, "subdir/...")
if err != nil { if err != nil {
t.Fatal(err) 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"), "./a", "packages not found"},
{filepath.Join(tmp, "/src/a"), "./b", `"a/b"`}, {filepath.Join(tmp, "/src/a"), "./b", `"a/b"`},
} { } {
opts := &packages.Options{ cfg := &packages.Config{
Mode: packages.LoadSyntax, // Use LoadSyntax to ensure that files can be opened.
Dir: test.dir, Dir: test.dir,
Env: append(os.Environ(), "GOPATH="+tmp), 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 var got string
if err != nil { if err != nil {
got = err.Error() got = err.Error()
@ -266,8 +270,12 @@ func TestTypeCheckOK(t *testing.T) {
}) })
defer cleanup() defer cleanup()
opts := &packages.Options{Env: append(os.Environ(), "GOPATH="+tmp), Error: func(error) {}} cfg := &packages.Config{
initial, err := packages.TypeCheck(opts, "a", "c") Mode: packages.LoadSyntax,
Env: append(os.Environ(), "GOPATH="+tmp),
Error: func(error) {},
}
initial, err := packages.Load(cfg, "a", "c")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -291,7 +299,7 @@ func TestTypeCheckOK(t *testing.T) {
for _, test := range []struct { for _, test := range []struct {
id string id string
wantType bool wantType bool
wantFiles bool wantSyntax bool
}{ }{
{"a", true, true}, // source package {"a", true, true}, // source package
{"b", 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) t.Errorf("missing package: %s", test.id)
continue continue
} }
if (p.Type != nil) != test.wantType { if (p.Types != nil) != test.wantType {
if test.wantType { if test.wantType {
t.Errorf("missing types.Package for %s", p) t.Errorf("missing types.Package for %s", p)
} else { } else {
t.Errorf("unexpected types.Package for %s", p) t.Errorf("unexpected types.Package for %s", p)
} }
} }
if (p.Files != nil) != test.wantFiles { if (p.Syntax != nil) != test.wantSyntax {
if test.wantFiles { if test.wantSyntax {
t.Errorf("missing ast.Files for %s", p) t.Errorf("missing ast.Files for %s", p)
} else { } else {
t.Errorf("unexpected ast.Files for for %s", p) t.Errorf("unexpected ast.Files for for %s", p)
@ -347,18 +355,34 @@ func TestTypeCheckError(t *testing.T) {
}) })
defer cleanup() defer cleanup()
opts := &packages.Options{Env: append(os.Environ(), "GOPATH="+tmp), Error: func(error) {}} cfg := &packages.Config{
initial, err := packages.TypeCheck(opts, "a", "c") Mode: packages.LoadSyntax,
Env: append(os.Environ(), "GOPATH="+tmp),
Error: func(error) {},
}
initial, err := packages.Load(cfg, "a", "c")
if err != nil { if err != nil {
t.Fatal(err) 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 { for _, test := range []struct {
id string id string
wantType bool wantTypes bool
wantFiles bool wantSyntax bool
wantIllTyped bool wantIllTyped bool
wantErrs []string wantErrs []string
}{ }{
@ -373,15 +397,15 @@ func TestTypeCheckError(t *testing.T) {
t.Errorf("missing package: %s", test.id) t.Errorf("missing package: %s", test.id)
continue continue
} }
if (p.Type != nil) != test.wantType { if (p.Types != nil) != test.wantTypes {
if test.wantType { if test.wantTypes {
t.Errorf("missing types.Package for %s", test.id) t.Errorf("missing types.Package for %s", test.id)
} else { } else {
t.Errorf("unexpected types.Package for %s", test.id) t.Errorf("unexpected types.Package for %s", test.id)
} }
} }
if (p.Files != nil) != test.wantFiles { if (p.Syntax != nil) != test.wantSyntax {
if test.wantFiles { if test.wantSyntax {
t.Errorf("missing ast.Files for %s", test.id) t.Errorf("missing ast.Files for %s", test.id)
} else { } else {
t.Errorf("unexpected ast.Files for for %s", test.id) t.Errorf("unexpected ast.Files for for %s", test.id)
@ -439,12 +463,13 @@ func TestWholeProgramOverlay(t *testing.T) {
} }
} }
var errs errCollector var errs errCollector
opts := &packages.Options{ cfg := &packages.Config{
Mode: packages.LoadAllSyntax,
Env: append(os.Environ(), "GOPATH="+tmp), Env: append(os.Environ(), "GOPATH="+tmp),
Error: errs.add, Error: errs.add,
ParseFile: parseFile, ParseFile: parseFile,
} }
initial, err := packages.WholeProgram(opts, "a") initial, err := packages.Load(cfg, "a")
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue continue
@ -483,11 +508,12 @@ import (
os.Mkdir(filepath.Join(tmp, "src/empty"), 0777) // create an existing but empty package os.Mkdir(filepath.Join(tmp, "src/empty"), 0777) // create an existing but empty package
var errs2 errCollector var errs2 errCollector
opts := &packages.Options{ cfg := &packages.Config{
Mode: packages.LoadAllSyntax,
Env: append(os.Environ(), "GOPATH="+tmp), Env: append(os.Environ(), "GOPATH="+tmp),
Error: errs2.add, Error: errs2.add,
} }
initial, err := packages.WholeProgram(opts, "root") initial, err := packages.Load(cfg, "root")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -530,10 +556,10 @@ import (
t.Errorf("missing package: %s", test.id) t.Errorf("missing package: %s", test.id)
continue continue
} }
if p.Type == nil { if p.Types == nil {
t.Errorf("missing types.Package for %s", test.id) 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) t.Errorf("missing ast.Files for %s", test.id)
} }
if !p.IllTyped { if !p.IllTyped {
@ -560,7 +586,7 @@ func errorMessages(errors []error) []string {
} }
func srcs(p *packages.Package) (basenames []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") { if strings.Contains(src, ".cache/go-build") {
src = fmt.Sprintf("%d.go", i) // make cache names predictable src = fmt.Sprintf("%d.go", i) // make cache names predictable
} else { } 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 { 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)
} }

View File

@ -32,7 +32,7 @@ func TestStdlibMetadata(t *testing.T) {
alloc := memstats.Alloc alloc := memstats.Alloc
// Load, parse and type-check the program. // Load, parse and type-check the program.
pkgs, err := packages.Metadata(nil, "std") pkgs, err := packages.Load(nil, "std")
if err != nil { if err != nil {
t.Fatalf("failed to load metadata: %v", err) t.Fatalf("failed to load metadata: %v", err)
} }
@ -94,36 +94,28 @@ func TestCgoOption(t *testing.T) {
{"net", "cgoLookupHost", "cgo_stub.go"}, {"net", "cgoLookupHost", "cgo_stub.go"},
{"os/user", "current", "lookup_stubs.go"}, {"os/user", "current", "lookup_stubs.go"},
} { } {
for i := 0; i < 2; i++ { // !cgo, cgo cfg := &packages.Config{
opts := &packages.Options{ Mode: packages.LoadSyntax,
DisableCgo: i == 0,
Error: func(error) {}, Error: func(error) {},
} }
pkgs, err := packages.TypeCheck(opts, test.pkg) pkgs, err := packages.Load(cfg, test.pkg)
if err != nil { if err != nil {
t.Errorf("Load failed: %v", err) t.Errorf("Load failed: %v", err)
continue continue
} }
pkg := pkgs[0] pkg := pkgs[0]
obj := pkg.Type.Scope().Lookup(test.name) obj := pkg.Types.Scope().Lookup(test.name)
if obj == nil { if obj == nil {
t.Errorf("no object %s.%s", test.pkg, test.name) t.Errorf("no object %s.%s", test.pkg, test.name)
continue continue
} }
posn := pkg.Fset.Position(obj.Pos()) posn := pkg.Fset.Position(obj.Pos())
if false {
t.Logf("DisableCgo=%t, obj=%s, posn=%s", opts.DisableCgo, obj, posn)
}
gotFile := filepath.Base(posn.Filename) gotFile := filepath.Base(posn.Filename)
filesMatch := gotFile == test.genericFile filesMatch := gotFile == test.genericFile
if !opts.DisableCgo && filesMatch { if filesMatch {
t.Errorf("!DisableCgo: %s found in %s, want native file", t.Errorf("!DisableCgo: %s found in %s, want native file",
obj, gotFile) 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. // Load the file and check the object is declared at the right place.
@ -138,5 +130,4 @@ func TestCgoOption(t *testing.T) {
t.Errorf("%s: %s not declared here (looking at %q)", posn, obj, line) t.Errorf("%s: %s not declared here (looking at %q)", posn, obj, line)
} }
} }
}
} }