go/packages: alter the internal api to go list

This separates the go list specific behavior from the generalised go/packages
loading behaviour, to enable alternate build system back ends.

Change-Id: I07e7649f8f2afc7470a3ee3d0d743cbbcc6f713e
Reviewed-on: https://go-review.googlesource.com/125715
Run-TryBot: Ian Cottrell <iancottrell@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Michael Matloob <matloob@golang.org>
This commit is contained in:
Ian Cottrell 2018-07-24 14:54:35 -04:00
parent 65cc56d756
commit b1e4acd68a
4 changed files with 105 additions and 63 deletions

View File

@ -30,7 +30,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(cfg *rawConfig, words ...string) ([]*loaderPackage, error) { func golistPackages(cfg *rawConfig, words ...string) ([]*rawPackage, 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 {
@ -71,7 +71,7 @@ func golistPackages(cfg *rawConfig, words ...string) ([]*loaderPackage, error) {
return nil, err return nil, err
} }
// Decode the JSON and convert it to Package form. // Decode the JSON and convert it to Package form.
var result []*loaderPackage var result []*rawPackage
for dec := json.NewDecoder(buf); dec.More(); { for dec := json.NewDecoder(buf); dec.More(); {
p := new(jsonPackage) p := new(jsonPackage)
if err := dec.Decode(p); err != nil { if err := dec.Decode(p); err != nil {
@ -150,17 +150,15 @@ func golistPackages(cfg *rawConfig, words ...string) ([]*loaderPackage, error) {
imports[id] = id // identity import imports[id] = id // identity import
} }
pkg := &loaderPackage{ pkg := &rawPackage{
Package: &Package{ ID: id,
ID: id, Name: p.Name,
Name: p.Name, GoFiles: absJoin(p.Dir, p.GoFiles, p.CgoFiles),
GoFiles: absJoin(p.Dir, p.GoFiles, p.CgoFiles), OtherFiles: absJoin(p.Dir, p.SFiles, p.CFiles),
OtherFiles: absJoin(p.Dir, p.SFiles, p.CFiles), PkgPath: pkgpath,
}, Imports: imports,
pkgpath: pkgpath, Export: export,
imports: imports, DepOnly: p.DepOnly,
export: export,
indirect: p.DepOnly,
} }
result = append(result, pkg) result = append(result, pkg)
} }

View File

@ -23,9 +23,10 @@ package packages
import ( import (
"fmt" "fmt"
"go/build" "go/build"
"strings"
legacy "golang.org/x/tools/go/loader" legacy "golang.org/x/tools/go/loader"
"golang.org/x/tools/imports" "golang.org/x/tools/imports"
"strings"
) )
func loaderFallback(dir string, env []string, patterns []string) ([]*Package, error) { func loaderFallback(dir string, env []string, patterns []string) ([]*Package, error) {
@ -84,6 +85,13 @@ func loaderFallback(dir string, env []string, patterns []string) ([]*Package, er
} }
allpkgs[id] = &loaderPackage{ allpkgs[id] = &loaderPackage{
raw: &rawPackage{
ID: id,
Name: lpkg.Pkg.Name(),
Imports: pkgimports,
GoFiles: goFiles,
DepOnly: !initial[lpkg],
},
Package: &Package{ Package: &Package{
ID: id, ID: id,
Name: lpkg.Pkg.Name(), Name: lpkg.Pkg.Name(),
@ -96,14 +104,13 @@ func loaderFallback(dir string, env []string, patterns []string) ([]*Package, er
IllTyped: !lpkg.TransitivelyErrorFree, IllTyped: !lpkg.TransitivelyErrorFree,
OtherFiles: nil, // Never set for the fallback, because we can't extract from loader. OtherFiles: nil, // Never set for the fallback, because we can't extract from loader.
}, },
imports: pkgimports,
} }
} }
// Do a second pass to populate imports. // Do a second pass to populate imports.
for _, pkg := range allpkgs { for _, pkg := range allpkgs {
pkg.Imports = make(map[string]*Package) pkg.Imports = make(map[string]*Package)
for imppath, impid := range pkg.imports { for imppath, impid := range pkg.raw.Imports {
target, ok := allpkgs[impid] target, ok := allpkgs[impid]
if !ok { if !ok {
// return nil, fmt.Errorf("could not load package: %v", impid) // return nil, fmt.Errorf("could not load package: %v", impid)

View File

@ -135,10 +135,7 @@ type Config struct {
// Load and returns the Go packages named by the given patterns. // Load and returns the Go packages named by the given patterns.
func Load(cfg *Config, patterns ...string) ([]*Package, error) { func Load(cfg *Config, patterns ...string) ([]*Package, error) {
l := &loader{} l := newLoader(cfg)
if cfg != nil {
l.Config = *cfg
}
return l.load(patterns...) return l.load(patterns...)
} }
@ -209,23 +206,12 @@ type Package struct {
// loaderPackage augments Package with state used during the loading phase // loaderPackage augments Package with state used during the loading phase
type loaderPackage struct { type loaderPackage struct {
raw *rawPackage
*Package *Package
importErrors map[string]error // maps each bad import to its error
// export holds the path to the export data file
// for this package, if mode == TypeCheck.
// The export data file contains the package's type information
// in a compiler-specific format; see
// golang.org/x/tools/go/{gc,gccgo}exportdata.
// May be the empty string if the build failed.
export string
indirect bool // package is a dependency, not explicitly requested
imports map[string]string // nominal form of Imports graph
importErrors map[string]error // maps each bad import to its error
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 }
@ -237,7 +223,11 @@ type loader struct {
exportMu sync.Mutex // enforces mutual exclusion of exportdata operations exportMu sync.Mutex // enforces mutual exclusion of exportdata operations
} }
func (ld *loader) load(patterns ...string) ([]*Package, error) { func newLoader(cfg *Config) *loader {
ld := &loader{}
if cfg != nil {
ld.Config = *cfg
}
if ld.Context == nil { if ld.Context == nil {
ld.Context = context.Background() ld.Context = context.Background()
} }
@ -259,7 +249,10 @@ func (ld *loader) load(patterns ...string) ([]*Package, error) {
} }
} }
} }
return ld
}
func (ld *loader) load(patterns ...string) ([]*Package, error) {
if len(patterns) == 0 { if len(patterns) == 0 {
return nil, fmt.Errorf("no packages to load") return nil, fmt.Errorf("no packages to load")
} }
@ -274,27 +267,39 @@ func (ld *loader) load(patterns ...string) ([]*Package, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
ld.pkgs = make(map[string]*loaderPackage) return ld.loadFrom(list...)
}
func (ld *loader) loadFrom(list ...*rawPackage) ([]*Package, error) {
ld.pkgs = make(map[string]*loaderPackage, len(list))
var initial []*loaderPackage var initial []*loaderPackage
// first pass, fixup and build the map and roots
for _, pkg := range list { for _, pkg := range list {
ld.pkgs[pkg.ID] = pkg lpkg := &loaderPackage{
raw: pkg,
// Record the set of initial packages Package: &Package{
// corresponding to the patterns. ID: pkg.ID,
if !pkg.indirect { Name: pkg.Name,
initial = append(initial, pkg) GoFiles: pkg.GoFiles,
OtherFiles: pkg.OtherFiles,
if ld.Mode == LoadSyntax { },
pkg.needsrc = true // TODO: should needsrc also be true if pkg.Export == ""
} needsrc: ld.Mode >= LoadAllSyntax,
} }
if ld.Mode >= LoadAllSyntax { ld.pkgs[lpkg.ID] = lpkg
pkg.needsrc = true if !pkg.DepOnly {
initial = append(initial, lpkg)
if ld.Mode == LoadSyntax {
lpkg.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")
} }
if len(initial) == 0 {
return nil, fmt.Errorf("packages had no initial set")
}
// Materialize the import graph. // Materialize the import graph.
@ -325,9 +330,8 @@ func (ld *loader) load(patterns ...string) ([]*Package, error) {
} }
lpkg.color = grey lpkg.color = grey
stack = append(stack, lpkg) // push stack = append(stack, lpkg) // push
lpkg.Imports = make(map[string]*Package, len(lpkg.raw.Imports))
imports := make(map[string]*Package) for importPath, id := range lpkg.raw.Imports {
for importPath, id := range lpkg.imports {
var importErr error var importErr error
imp := ld.pkgs[id] imp := ld.pkgs[id]
if imp == nil { if imp == nil {
@ -347,10 +351,8 @@ func (ld *loader) load(patterns ...string) ([]*Package, error) {
if visit(imp) { if visit(imp) {
lpkg.needsrc = true lpkg.needsrc = true
} }
imports[importPath] = imp.Package lpkg.Imports[importPath] = imp.Package
} }
lpkg.imports = nil // no longer needed
lpkg.Imports = imports
stack = stack[:len(stack)-1] // pop stack = stack[:len(stack)-1] // pop
lpkg.color = black lpkg.color = black
@ -414,7 +416,7 @@ 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.raw.PkgPath == "unsafe" {
// Fill in the blanks to avoid surprises. // Fill in the blanks to avoid surprises.
lpkg.Types = types.Unsafe lpkg.Types = types.Unsafe
lpkg.Fset = ld.Fset lpkg.Fset = ld.Fset
@ -449,7 +451,7 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) {
// 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.Types = types.NewPackage(lpkg.pkgpath, lpkg.Name) lpkg.Types = types.NewPackage(lpkg.raw.PkgPath, lpkg.Name)
lpkg.TypesInfo = &types.Info{ lpkg.TypesInfo = &types.Info{
Types: make(map[ast.Expr]types.TypeAndValue), Types: make(map[ast.Expr]types.TypeAndValue),
@ -587,7 +589,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.raw.PkgPath == "" {
log.Fatalf("internal error: Package %s has no PkgPath", lpkg) log.Fatalf("internal error: Package %s has no PkgPath", lpkg)
} }
@ -612,11 +614,11 @@ func (ld *loader) loadFromExportData(lpkg *loaderPackage) (*types.Package, error
lpkg.IllTyped = true // fail safe lpkg.IllTyped = true // fail safe
if lpkg.export == "" { if lpkg.raw.Export == "" {
// Errors while building export data will have been printed to stderr. // Errors while building export data will have been printed to stderr.
return nil, fmt.Errorf("no export data file") return nil, fmt.Errorf("no export data file")
} }
f, err := os.Open(lpkg.export) f, err := os.Open(lpkg.raw.Export)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -630,7 +632,7 @@ func (ld *loader) loadFromExportData(lpkg *loaderPackage) (*types.Package, error
// queries.) // queries.)
r, err := gcexportdata.NewReader(f) r, err := gcexportdata.NewReader(f)
if err != nil { if err != nil {
return nil, fmt.Errorf("reading %s: %v", lpkg.export, err) return nil, fmt.Errorf("reading %s: %v", lpkg.raw.Export, err)
} }
// Build the view. // Build the view.
@ -671,7 +673,7 @@ func (ld *loader) loadFromExportData(lpkg *loaderPackage) (*types.Package, error
if !seen[p] { if !seen[p] {
seen[p] = true seen[p] = true
if p.Types != nil { if p.Types != nil {
view[p.pkgpath] = p.Types view[p.raw.PkgPath] = p.Types
} else { } else {
copyback = append(copyback, p) copyback = append(copyback, p)
} }
@ -684,15 +686,15 @@ 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.raw.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.raw.Export, err)
} }
// 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.Types = view[p.pkgpath] // may still be nil p.Types = view[p.raw.PkgPath] // may still be nil
} }
lpkg.Types = tpkg lpkg.Types = tpkg

View File

@ -13,6 +13,41 @@ import (
// This file contains the structs needed at the seam between the packages // This file contains the structs needed at the seam between the packages
// loader and the underlying build tool // loader and the underlying build tool
// rawPackage is the serialized form of a package
type rawPackage struct {
// ID is a unique identifier for a package.
// This is the same as Package.ID
ID string
// Name is the package name as it appears in the package source code.
// This is the same as Package.name
Name string `json:",omitempty"`
// This is the package path as used in the export data.
// This is used to map entries in the export data back to the package they
// come from.
// This is not currently exposed in Package.
PkgPath string `json:",omitempty"`
// Imports maps import paths appearing in the package's Go source files
// to corresponding package identifiers.
// This is similar to Package.Imports, but maps to the ID rather than the
// package itself.
Imports map[string]string `json:",omitempty"`
// Export is the absolute path to a file containing the export data for the
// package.
// This is not currently exposed in Package.
Export string `json:",omitempty"`
// GoFiles lists the absolute file paths of the package's Go source files.
// This is the same as Package.GoFiles
GoFiles []string `json:",omitempty"`
// OtherFiles lists the absolute file paths of the package's non-Go source
// files.
// This is the same as Package.OtherFiles
OtherFiles []string `json:",omitempty"`
// DepOnly marks a package that is in a list because it was a dependency.
// It is used to find the roots when constructing a graph from a package list.
// This is not exposed in Package.
DepOnly bool `json:",omitempty"`
}
// rawConfig specifies details about what raw package information is needed // rawConfig specifies details about what raw package information is needed
// and how the underlying build tool should load package data. // and how the underlying build tool should load package data.
type rawConfig struct { type rawConfig struct {