blob: c124f7b6811fe4f37a8120da5e47a23fab742c17 [file] [log] [blame]
// 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 packages
// See doc.go for package documentation and implementation notes.
import (
"context"
"fmt"
"go/ast"
"go/parser"
"go/token"
"go/types"
"log"
"os"
"sync"
"golang.org/x/tools/go/gcexportdata"
"path/filepath"
"strings"
)
// 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.
// If Context is nil, the load cannot be cancelled.
Context context.Context
// 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
// Error is called for each error encountered during package loading.
// It must be safe to call Error simultaneously from multiple goroutines.
// In addition to calling Error, the loader will record each error
// in the corresponding Package's Errors list.
// If Error is nil, the loader will print errors to os.Stderr.
// To disable printing of errors, set opt.Error = func(error){}.
// TODO(rsc): What happens in the Metadata loader? Currently nothing.
Error func(error)
// Fset is the token.FileSet to use when parsing loaded source files.
// If Fset is nil, the loader will create one.
Fset *token.FileSet
// ParseFile is called to read and parse each file
// when preparing a package's type-checked syntax tree.
// It must be safe to call ParseFile simultaneously from multiple goroutines.
// If ParseFile is nil, the loader will uses parser.ParseFile.
//
// Setting ParseFile to a custom implementation can allow
// providing alternate file content in order to type-check
// unsaved text editor buffers, or to selectively eliminate
// unwanted function bodies to reduce the amount of work
// done by the type checker.
ParseFile func(fset *token.FileSet, filename string) (*ast.File, error)
// 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
// TypeChecker provides additional configuration for type-checking syntax trees.
//
// The TypeCheck loader does not use the TypeChecker configuration
// for packages that have their type information provided by the
// underlying build system.
//
// The TypeChecker.Error function is ignored:
// errors are reported using the Error function defined above.
//
// The TypeChecker.Importer function is ignored:
// the loader defines an appropriate importer.
//
// The TypeChecker.Sizes are only used by the WholeProgram loader.
// The TypeCheck loader uses the same sizes as the main build.
// TODO(rsc): At least, it should. Derive these from runtime?
TypeChecker types.Config
}
// Load and returns the Go packages named by the given patterns.
func Load(cfg *Config, patterns ...string) ([]*Package, error) {
l := newLoader(cfg)
return l.load(patterns...)
}
// A Package describes a single loaded Go 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
// 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.
//
// If a package has typed syntax trees and the DisableCgo option is false,
// the cgo-processed output files are listed instead of the original
// source files that contained import "C" statements.
// In this case, the file paths may not even end in ".go".
// Although the original sources are not listed in Srcs, the corresponding
// syntax tree positions will still refer back to the orignal source code,
// respecting the //line directives in the cgo-processed output.
//
// TODO(rsc): Actually, in TypeCheck mode even the packages without
// syntax trees (pure dependencies) lose their original sources.
// We should fix that.
GoFiles []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
// Type is the type information for the package.
// The TypeCheck and WholeProgram loaders set this field for all packages.
Types *types.Package
// IllTyped indicates whether the package has any type errors.
// The TypeCheck and WholeProgram loaders set this field for all packages.
IllTyped bool
// Files is the package's syntax trees, for the files listed in Srcs.
//
// The TypeCheck loader sets Files for packages matching the patterns.
// The WholeProgram loader sets Files for all packages, including dependencies.
Syntax []*ast.File
// Info is the type-checking results for the package's syntax trees.
// It is set only when Files is set.
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.
// All packages loaded together share a single Fset.
Fset *token.FileSet
}
// loaderPackage augments Package with state used during the loading phase
type loaderPackage struct {
raw *rawPackage
*Package
importErrors map[string]error // maps each bad import to its error
loadOnce sync.Once
color uint8 // for cycle detection
mark, needsrc bool // used in TypeCheck mode only
}
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
Config
exportMu sync.Mutex // enforces mutual exclusion of exportdata operations
}
func newLoader(cfg *Config) *loader {
ld := &loader{}
if cfg != nil {
ld.Config = *cfg
}
if ld.Context == nil {
ld.Context = context.Background()
}
// Determine directory to be used for relative contains: paths.
if ld.Dir == "" {
if cwd, err := os.Getwd(); err == nil {
ld.Dir = cwd
}
}
if ld.Mode >= LoadSyntax {
if ld.Fset == nil {
ld.Fset = token.NewFileSet()
}
if ld.Error == nil {
ld.Error = func(e error) {
fmt.Fprintln(os.Stderr, e)
}
}
if ld.ParseFile == nil {
ld.ParseFile = func(fset *token.FileSet, filename string) (*ast.File, error) {
const mode = parser.AllErrors | parser.ParseComments
return parser.ParseFile(fset, filename, nil, mode)
}
}
}
return ld
}
func (ld *loader) load(patterns ...string) ([]*Package, error) {
if len(patterns) == 0 {
return nil, fmt.Errorf("no packages to load")
}
if ld.Dir == "" {
return nil, fmt.Errorf("failed to get working directory")
}
// Determine files requested in contains patterns
var containFiles []string
{
restPatterns := patterns[:0]
for _, pattern := range patterns {
if containFile := strings.TrimPrefix(pattern, "contains:"); containFile != pattern {
containFiles = append(containFiles, containFile)
} else {
restPatterns = append(restPatterns, pattern)
}
}
containFiles = absJoin(ld.Dir, containFiles)
patterns = restPatterns
}
// Do the metadata query and partial build.
// TODO(adonovan): support alternative build systems at this seam.
rawCfg := newRawConfig(&ld.Config)
listfunc := golistPackages
// TODO(matloob): Patterns may now be empty, if it was solely comprised of contains: patterns.
// See if the extra process invocation can be avoided.
list, err := listfunc(rawCfg, patterns...)
if _, ok := err.(GoTooOldError); ok {
if ld.Config.Mode >= LoadTypes {
// Upgrade to LoadAllSyntax because we can't depend on the existance
// of export data. We can remove this once iancottrell's cl is in.
ld.Config.Mode = LoadAllSyntax
}
listfunc = golistPackagesFallback
list, err = listfunc(rawCfg, patterns...)
}
if err != nil {
return nil, err
}
// Run go list for contains: patterns.
seenPkgs := make(map[string]bool) // for deduplication. different containing queries could produce same packages
if len(containFiles) > 0 {
for _, pkg := range list {
seenPkgs[pkg.ID] = true
}
}
for _, f := range containFiles {
// TODO(matloob): Do only one query per directory.
fdir := filepath.Dir(f)
rawCfg.Dir = fdir
cList, err := listfunc(rawCfg, ".")
if err != nil {
return nil, err
}
// Deduplicate and set deplist to set of packages requested files.
dedupedList := cList[:0] // invariant: only packages that haven't been seen before
for _, pkg := range cList {
if seenPkgs[pkg.ID] {
continue
}
seenPkgs[pkg.ID] = true
dedupedList = append(dedupedList, pkg)
pkg.DepOnly = true
for _, pkgFile := range pkg.GoFiles {
if filepath.Base(f) == filepath.Base(pkgFile) {
pkg.DepOnly = false
break
}
}
}
list = append(list, dedupedList...)
}
return ld.loadFrom(list...)
}
func (ld *loader) loadFrom(list ...*rawPackage) ([]*Package, error) {
ld.pkgs = make(map[string]*loaderPackage, len(list))
var initial []*loaderPackage
// first pass, fixup and build the map and roots
for _, pkg := range list {
lpkg := &loaderPackage{
raw: pkg,
Package: &Package{
ID: pkg.ID,
Name: pkg.Name,
GoFiles: pkg.GoFiles,
OtherFiles: pkg.OtherFiles,
},
// TODO: should needsrc also be true if pkg.Export == ""
needsrc: ld.Mode >= LoadAllSyntax,
}
ld.pkgs[lpkg.ID] = lpkg
if !pkg.DepOnly {
initial = append(initial, lpkg)
if ld.Mode == LoadSyntax {
lpkg.needsrc = true
}
}
}
if len(ld.pkgs) == 0 {
return nil, fmt.Errorf("packages not found")
}
if len(initial) == 0 {
return nil, fmt.Errorf("packages had no initial set")
}
// Materialize the import graph.
const (
white = 0 // new
grey = 1 // in progress
black = 2 // complete
)
// visit traverses the import graph, depth-first,
// and materializes the graph as Packages.Imports.
//
// Valid imports are saved in the Packages.Import 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.
//
// 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 {
switch lpkg.color {
case black:
return lpkg.needsrc
case grey:
panic("internal error: grey node")
}
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 {
var importErr error
imp := ld.pkgs[id]
if imp == nil {
// (includes package "C" when DisableCgo)
importErr = fmt.Errorf("missing package: %q", id)
} else if imp.color == grey {
importErr = fmt.Errorf("import cycle: %s", stack)
}
if importErr != nil {
if lpkg.importErrors == nil {
lpkg.importErrors = make(map[string]error)
}
lpkg.importErrors[importPath] = importErr
continue
}
if visit(imp) {
lpkg.needsrc = true
}
lpkg.Imports[importPath] = imp.Package
}
stack = stack[:len(stack)-1] // pop
lpkg.color = black
return lpkg.needsrc
}
if ld.Mode >= LoadImports {
// For each initial package, create its import DAG.
for _, lpkg := range initial {
visit(lpkg)
}
}
// Load type data if needed, starting at
// the initial packages (roots of the import DAG).
if ld.Mode >= LoadTypes {
var wg sync.WaitGroup
for _, lpkg := range initial {
wg.Add(1)
go func(lpkg *loaderPackage) {
ld.loadRecursive(lpkg)
wg.Done()
}(lpkg)
}
wg.Wait()
}
result := make([]*Package, len(initial))
for i, lpkg := range initial {
result[i] = lpkg.Package
}
return result, nil
}
// loadRecursive loads, parses, and type-checks the specified package and its
// dependencies, recursively, in parallel, in topological order.
// It is atomic and idempotent.
// Precondition: ld.mode != Metadata.
// In typeCheck mode, only needsrc packages are loaded.
func (ld *loader) loadRecursive(lpkg *loaderPackage) {
lpkg.loadOnce.Do(func() {
// Load the direct dependencies, in parallel.
var wg sync.WaitGroup
for _, ipkg := range lpkg.Imports {
imp := ld.pkgs[ipkg.ID]
wg.Add(1)
go func(imp *loaderPackage) {
ld.loadRecursive(imp)
wg.Done()
}(imp)
}
wg.Wait()
ld.loadPackage(lpkg)
})
}
// loadPackage loads, parses, and type-checks the
// files of the specified package, if needed.
// It must be called only once per Package,
// after immediate dependencies are loaded.
// Precondition: ld.mode != Metadata.
func (ld *loader) loadPackage(lpkg *loaderPackage) {
if lpkg.raw.PkgPath == "unsafe" {
// Fill in the blanks to avoid surprises.
lpkg.Types = types.Unsafe
lpkg.Fset = ld.Fset
lpkg.Syntax = []*ast.File{}
lpkg.TypesInfo = new(types.Info)
return
}
if !lpkg.needsrc {
return // not a source package
}
hardErrors := false
appendError := func(err error) {
if terr, ok := err.(types.Error); ok && terr.Soft {
// Don't mark the package as bad.
} else {
hardErrors = true
}
ld.Error(err)
lpkg.Errors = append(lpkg.Errors, err)
}
files, errs := ld.parseFiles(lpkg.GoFiles)
for _, err := range errs {
appendError(err)
}
lpkg.Fset = ld.Fset
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.Types = types.NewPackage(lpkg.raw.PkgPath, lpkg.Name)
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),
Implicits: make(map[ast.Node]types.Object),
Scopes: make(map[ast.Node]*types.Scope),
Selections: make(map[*ast.SelectorExpr]*types.Selection),
}
// Copy the prototype types.Config as it must vary across Packages.
tc := ld.TypeChecker // copy
tc.Importer = importerFunc(func(path string) (*types.Package, error) {
if path == "unsafe" {
return types.Unsafe, nil
}
// The imports map is keyed by import path.
ipkg := lpkg.Imports[path]
if ipkg == nil {
if err := lpkg.importErrors[path]; err != nil {
return nil, err
}
// There was skew between the metadata and the
// import declarations, likely due to an edit
// race, or because the ParseFile feature was
// used to supply alternative file contents.
return nil, fmt.Errorf("no metadata for %s", path)
}
if ipkg.Types != nil && ipkg.Types.Complete() {
return ipkg.Types, nil
}
imp := ld.pkgs[ipkg.ID]
if !imp.needsrc {
return ld.loadFromExportData(imp)
}
log.Fatalf("internal error: nil Pkg importing %q from %q", path, lpkg)
panic("unreachable")
})
tc.Error = appendError
// type-check
types.NewChecker(&tc, ld.Fset, lpkg.Types, lpkg.TypesInfo).Files(lpkg.Syntax)
lpkg.importErrors = nil // no longer needed
// If !Cgo, the type-checker uses FakeImportC mode, so
// it doesn't invoke the importer for import "C",
// nor report an error for the import,
// or for any undefined C.f reference.
// We must detect this explicitly and correctly
// mark the package as IllTyped (by reporting an error).
// TODO(adonovan): if these errors are annoying,
// we could just set IllTyped quietly.
if tc.FakeImportC {
outer:
for _, f := range lpkg.Syntax {
for _, imp := range f.Imports {
if imp.Path.Value == `"C"` {
appendError(fmt.Errorf(`%s: import "C" ignored`,
lpkg.Fset.Position(imp.Pos())))
break outer
}
}
}
}
// Record accumulated errors.
for _, imp := range lpkg.Imports {
if imp.IllTyped {
hardErrors = true
break
}
}
lpkg.IllTyped = hardErrors
}
// An importFunc is an implementation of the single-method
// types.Importer interface based on a function value.
type importerFunc func(path string) (*types.Package, error)
func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) }
// We use a counting semaphore to limit
// the number of parallel I/O calls per process.
var ioLimit = make(chan bool, 20)
// parseFiles reads and parses the Go source files and returns the ASTs
// of the ones that could be at least partially parsed, along with a
// list of I/O and parse errors encountered.
//
// Because files are scanned in parallel, the token.Pos
// positions of the resulting ast.Files are not ordered.
//
func (ld *loader) parseFiles(filenames []string) ([]*ast.File, []error) {
var wg sync.WaitGroup
n := len(filenames)
parsed := make([]*ast.File, n)
errors := make([]error, n)
for i, file := range filenames {
wg.Add(1)
go func(i int, filename string) {
ioLimit <- true // wait
// ParseFile may return both an AST and an error.
parsed[i], errors[i] = ld.ParseFile(ld.Fset, filename)
<-ioLimit // signal
wg.Done()
}(i, file)
}
wg.Wait()
// Eliminate nils, preserving order.
var o int
for _, f := range parsed {
if f != nil {
parsed[o] = f
o++
}
}
parsed = parsed[:o]
o = 0
for _, err := range errors {
if err != nil {
errors[o] = err
o++
}
}
errors = errors[:o]
return parsed, errors
}
// 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 == "" {
log.Fatalf("internal error: Package %s has no PkgPath", lpkg)
}
// Because gcexportdata.Read has the potential to create or
// modify the types.Package for each node in the transitive
// closure of dependencies of lpkg, all exportdata operations
// must be sequential. (Finer-grained locking would require
// changes to the gcexportdata API.)
//
// The exportMu lock guards the Package.Pkg field and the
// types.Package it points to, for each Package in the graph.
//
// Not all accesses to Package.Pkg need to be protected by exportMu:
// graph ordering ensures that direct dependencies of source
// packages are fully loaded before the importer reads their Pkg field.
ld.exportMu.Lock()
defer ld.exportMu.Unlock()
if tpkg := lpkg.Types; tpkg != nil && tpkg.Complete() {
return tpkg, nil // cache hit
}
lpkg.IllTyped = true // fail safe
if lpkg.raw.Export == "" {
// 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)
if err != nil {
return nil, err
}
defer f.Close()
// Read gc export data.
//
// We don't currently support gccgo export data because all
// underlying workspaces use the gc toolchain. (Even build
// systems that support gccgo don't use it for workspace
// queries.)
r, err := gcexportdata.NewReader(f)
if err != nil {
return nil, fmt.Errorf("reading %s: %v", lpkg.raw.Export, err)
}
// Build the view.
//
// The gcexportdata machinery has no concept of package ID.
// It identifies packages by their PkgPath, which although not
// globally unique is unique within the scope of one invocation
// of the linker, type-checker, or gcexportdata.
//
// So, we must build a PkgPath-keyed view of the global
// (conceptually ID-keyed) cache of packages and pass it to
// gcexportdata, then copy back to the global cache any newly
// created entries in the view map. The view must contain every
// existing package that might possibly be mentioned by the
// current package---its reflexive transitive closure.
//
// (Yes, reflexive: although loadRecursive processes source
// packages in topological order, export data packages are
// processed only lazily within Importer calls. In the graph
// A->B->C, A->C where A is a source package and B and C are
// 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 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.
//
// TODO(adonovan): it would be more simpler and more efficient
// if the export data machinery invoked a callback to
// get-or-create a package instead of a map.
//
view := make(map[string]*types.Package) // view seen by gcexportdata
seen := make(map[*loaderPackage]bool) // all visited packages
var copyback []*loaderPackage // candidates for copying back to global cache
var visit func(p *loaderPackage)
visit = func(p *loaderPackage) {
if !seen[p] {
seen[p] = true
if p.Types != nil {
view[p.raw.PkgPath] = p.Types
} else {
copyback = append(copyback, p)
}
for _, p := range p.Imports {
visit(ld.pkgs[p.ID])
}
}
}
visit(lpkg)
// Parse the export data.
// (May create/modify packages in view.)
tpkg, err := gcexportdata.Read(r, ld.Fset, view, lpkg.raw.PkgPath)
if err != nil {
return nil, fmt.Errorf("reading %s: %v", lpkg.raw.Export, err)
}
// For each newly created types.Package in the view,
// save it in the main graph.
for _, p := range copyback {
p.Types = view[p.raw.PkgPath] // may still be nil
}
lpkg.Types = tpkg
lpkg.IllTyped = false
return tpkg, nil
}