aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/golang.org/x/tools/go/packages
diff options
context:
space:
mode:
authorMichael Muré <batolettre@gmail.com>2019-04-10 20:30:34 +0200
committerMichael Muré <batolettre@gmail.com>2019-04-10 20:30:34 +0200
commit0e53d2555e9a6ddc707f6c59497f30e182116c80 (patch)
tree7fa6cd92d87c8ac6a0fd486be9647b69975b7d21 /vendor/golang.org/x/tools/go/packages
parent9722d7c9eca28b1710e50ac9075fd11d0db0606a (diff)
downloadgit-bug-0e53d2555e9a6ddc707f6c59497f30e182116c80.tar.gz
force a version of golang.org/x/tools due to an incompatibility with gqlgen
Diffstat (limited to 'vendor/golang.org/x/tools/go/packages')
-rw-r--r--vendor/golang.org/x/tools/go/packages/doc.go123
-rw-r--r--vendor/golang.org/x/tools/go/packages/external.go39
-rw-r--r--vendor/golang.org/x/tools/go/packages/golist.go727
-rw-r--r--vendor/golang.org/x/tools/go/packages/golist_fallback.go282
-rw-r--r--vendor/golang.org/x/tools/go/packages/golist_overlay.go104
-rw-r--r--vendor/golang.org/x/tools/go/packages/packages.go567
-rw-r--r--vendor/golang.org/x/tools/go/packages/visit.go55
7 files changed, 1232 insertions, 665 deletions
diff --git a/vendor/golang.org/x/tools/go/packages/doc.go b/vendor/golang.org/x/tools/go/packages/doc.go
index 4f5a1a14..3799f8ed 100644
--- a/vendor/golang.org/x/tools/go/packages/doc.go
+++ b/vendor/golang.org/x/tools/go/packages/doc.go
@@ -5,18 +5,28 @@
/*
Package packages loads Go packages for inspection and analysis.
-NOTE: THIS PACKAGE IS NOT YET READY FOR WIDESPREAD USE:
- - The interface is still being revised and is likely to change.
- - The implementation depends on the Go 1.11 go command.
- - We intend to finalize the API before Go 1.11 is released.
-
The Load function takes as input a list of patterns and return a list of Package
structs describing individual packages matched by those patterns.
-The LoadMode controls the amounts of detail about the loaded packages.
+The LoadMode controls the amount of detail in the loaded packages.
+
+Load passes most patterns directly to the underlying build tool,
+but all patterns with the prefix "query=", where query is a
+non-empty string of letters from [a-z], are reserved and may be
+interpreted as query operators.
+
+Two query operators are currently supported: "file" and "pattern".
-The patterns are used as arguments to the underlying build tool,
-such as the go command or Bazel, and are interpreted according to
-that tool's conventions.
+The query "file=path/to/file.go" matches the package or packages enclosing
+the Go source file path/to/file.go. For example "file=~/go/src/fmt/print.go"
+might return the packages "fmt" and "fmt [fmt.test]".
+
+The query "pattern=string" causes "string" to be passed directly to
+the underlying build tool. In most cases this is unnecessary,
+but an application can use Load("pattern=" + x) as an escaping mechanism
+to ensure that x is not interpreted as a query operator if it contains '='.
+
+All other query operators are reserved for future use and currently
+cause Load to report an error.
The Package struct provides basic information about the package, including
@@ -40,47 +50,25 @@ can match multiple packages and that a package might be matched by
multiple patterns: in general it is not possible to determine which
packages correspond to which patterns.
-Note that the list returned by Load (LoadAllSyntax in this case)
-only contains the packages matched by the patterns. Their dependencies
-can be found by walking the import graph using the Imports fields.
+Note that the list returned by Load contains only the packages matched
+by the patterns. Their dependencies can be found by walking the import
+graph using the Imports fields.
-The Load function can be configured by passing a non-nil Config struct as
-the first argument. If you pass nil for the Config Load will
-run in LoadAllSyntax mode, collecting the maximal amount of information
-it can.
+The Load function can be configured by passing a pointer to a Config as
+the first argument. A nil Config is equivalent to the zero Config, which
+causes Load to run in LoadFiles mode, collecting minimal information.
See the documentation for type Config for details.
-As noted earlier, the Config.Mode controls increasing amounts of detail
-about the loaded packages, with each mode returning all the data of the
+As noted earlier, the Config.Mode controls the amount of detail
+reported about the loaded packages, with each mode returning all the data of the
previous mode with some extra added. See the documentation for type LoadMode
for details.
Most tools should pass their command-line arguments (after any flags)
uninterpreted to the loader, so that the loader can interpret them
according to the conventions of the underlying build system.
-For example, this program prints the names of the source files
-for each package listed on the command line:
-
- package main
-
- import (
- "flag"
- "fmt"
- "log"
-
- "golang.org/x/tools/go/packages"
- )
-
- func main() {
- flag.Parse()
- pkgs, err := packages.Load(nil, flag.Args()...)
- if err != nil {
- log.Fatal(err)
- }
- for _, pkg := range pkgs {
- fmt.Print(pkg.ID, pkg.GoFiles)
- }
- }
+See the Example function for typical usage.
+
*/
package packages // import "golang.org/x/tools/go/packages"
@@ -114,12 +102,6 @@ about one package without visiting all its dependencies too, so there is
no additional asymptotic cost to providing transitive information.
(This property might not be true of a hypothetical 5th build system.)
-This package provides no parse-but-don't-typecheck operation because most tools
-that need only untyped syntax (such as gofmt, goimports, and golint)
-seem not to care about any files other than the ones they are directly
-instructed to look at. Also, it is trivial for a client to supplement
-this functionality on top of a Metadata query.
-
In calls to TypeCheck, all initial packages, and any package that
transitively depends on one of them, must be loaded from source.
Consider A->B->C->D->E: if A,C are initial, A,B,C must be loaded from
@@ -169,15 +151,13 @@ type-check again. This two-phase approach had four major problems:
in several times in sequence as is now possible in WholeProgram mode.
(TypeCheck mode has a similar one-shot restriction for a different reason.)
-Early drafts of this package supported "multi-shot" operation
-in the Metadata and WholeProgram modes, although this feature is not exposed
-through the API and will likely be removed.
+Early drafts of this package supported "multi-shot" operation.
Although it allowed clients to make a sequence of calls (or concurrent
calls) to Load, building up the graph of Packages incrementally,
it was of marginal value: it complicated the API
(since it allowed some options to vary across calls but not others),
it complicated the implementation,
-it cannot be made to work in TypeCheck mode, as explained above,
+it cannot be made to work in Types mode, as explained above,
and it was less efficient than making one combined call (when this is possible).
Among the clients we have inspected, none made multiple calls to load
but could not be easily and satisfactorily modified to make only a single call.
@@ -190,22 +170,13 @@ Instead, ssadump no longer requests the runtime package,
but seeks it among the dependencies of the user-specified packages,
and emits an error if it is not found.
-Overlays: the ParseFile hook in the API permits clients to vary the way
-in which ASTs are obtained from filenames; the default implementation is
-based on parser.ParseFile. This features enables editor-integrated tools
-that analyze the contents of modified but unsaved buffers: rather than
-read from the file system, a tool can read from an archive of modified
-buffers provided by the editor.
-This approach has its limits. Because package metadata is obtained by
-fork/execing an external query command for each build system, we can
-fake only the file contents seen by the parser, type-checker, and
-application, but not by the metadata query, so, for example:
-- additional imports in the fake file will not be described by the
- metadata, so the type checker will fail to load imports that create
- new dependencies.
-- in TypeCheck mode, because export data is produced by the query
- command, it will not reflect the fake file contents.
-- this mechanism cannot add files to a package without first saving them.
+Overlays: The Overlay field in the Config allows providing alternate contents
+for Go source files, by providing a mapping from file path to contents.
+go/packages will pull in new imports added in overlay files when go/packages
+is run in LoadImports mode or greater.
+Overlay support for the go list driver isn't complete yet: if the file doesn't
+exist on disk, it will only be recognized in an overlay if it is a non-test file
+and the package would be reported even without the overlay.
Questions & Tasks
@@ -227,10 +198,6 @@ Questions & Tasks
failed builds, import failures, import cycles, and so on, in a call to
Load?
-- Do we need a GeneratedBy map that maps the name of each generated Go
- source file in GoFiles to that of the original file, if known, or "" otherwise?
- Or are //line directives and "Generated" comments in those files enough?
-
- Support bazel, blaze, and go1.10 list, not just go1.11 list.
- Handle (and test) various partial success cases, e.g.
@@ -252,18 +219,4 @@ Questions & Tasks
order. I suspect this is due to the breadth-first resolution now used
by go/types. Is that a bug? Discuss with gri.
-- https://github.com/golang/go/issues/25980 causes these commands to crash:
- $ GOPATH=/none ./gopackages -all all
- due to:
- $ GOPATH=/none go list -e -test -json all
- and:
- $ go list -e -test ./relative/path
-
-- Modify stringer to use go/packages, perhaps initially under flag control.
-
-- Bug: "gopackages fmt a.go" doesn't produce an error.
-
-- If necessary, add back an IsTest boolean or expose ForTests on the Package struct.
- IsTest was removed because we couldn't agree on a useful definition.
-
*/
diff --git a/vendor/golang.org/x/tools/go/packages/external.go b/vendor/golang.org/x/tools/go/packages/external.go
index 39e5ed99..22ff769e 100644
--- a/vendor/golang.org/x/tools/go/packages/external.go
+++ b/vendor/golang.org/x/tools/go/packages/external.go
@@ -16,7 +16,17 @@ import (
"strings"
)
-// findExternalTool returns the file path of a tool that supplies supplies
+// Driver
+type driverRequest struct {
+ Command string `json:"command"`
+ Mode LoadMode `json:"mode"`
+ Env []string `json:"env"`
+ BuildFlags []string `json:"build_flags"`
+ Tests bool `json:"tests"`
+ Overlay map[string][]byte `json:"overlay"`
+}
+
+// findExternalDriver returns the file path of a tool that supplies
// the build system package structure, or "" if not found."
// If GOPACKAGESDRIVER is set in the environment findExternalTool returns its
// value, otherwise it searches for a binary named gopackagesdriver on the PATH.
@@ -39,21 +49,22 @@ func findExternalDriver(cfg *Config) driver {
}
}
return func(cfg *Config, words ...string) (*driverResponse, error) {
- buf := new(bytes.Buffer)
- fullargs := []string{
- "list",
- fmt.Sprintf("-test=%t", cfg.Tests),
- fmt.Sprintf("-export=%t", usesExportData(cfg)),
- fmt.Sprintf("-deps=%t", cfg.Mode >= LoadImports),
- }
- for _, f := range cfg.Flags {
- fullargs = append(fullargs, fmt.Sprintf("-flags=%v", f))
+ req, err := json.Marshal(driverRequest{
+ Mode: cfg.Mode,
+ Env: cfg.Env,
+ BuildFlags: cfg.BuildFlags,
+ Tests: cfg.Tests,
+ Overlay: cfg.Overlay,
+ })
+ if err != nil {
+ return nil, fmt.Errorf("failed to encode message to driver tool: %v", err)
}
- fullargs = append(fullargs, "--")
- fullargs = append(fullargs, words...)
- cmd := exec.CommandContext(cfg.Context, tool, fullargs...)
- cmd.Env = cfg.Env
+
+ buf := new(bytes.Buffer)
+ cmd := exec.CommandContext(cfg.Context, tool, words...)
cmd.Dir = cfg.Dir
+ cmd.Env = cfg.Env
+ cmd.Stdin = bytes.NewReader(req)
cmd.Stdout = buf
cmd.Stderr = new(bytes.Buffer)
if err := cmd.Run(); err != nil {
diff --git a/vendor/golang.org/x/tools/go/packages/golist.go b/vendor/golang.org/x/tools/go/packages/golist.go
index 26d62771..132d2834 100644
--- a/vendor/golang.org/x/tools/go/packages/golist.go
+++ b/vendor/golang.org/x/tools/go/packages/golist.go
@@ -8,76 +8,212 @@ import (
"bytes"
"encoding/json"
"fmt"
+ "go/types"
+ "io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
+ "reflect"
+ "regexp"
+ "strconv"
"strings"
+ "sync"
+ "time"
+
+ "golang.org/x/tools/go/internal/packagesdriver"
+ "golang.org/x/tools/internal/gopathwalk"
+ "golang.org/x/tools/internal/semver"
)
+// debug controls verbose logging.
+var debug, _ = strconv.ParseBool(os.Getenv("GOPACKAGESDEBUG"))
+
// A goTooOldError reports that the go command
// found by exec.LookPath is too old to use the new go list behavior.
type goTooOldError struct {
error
}
+// responseDeduper wraps a driverResponse, deduplicating its contents.
+type responseDeduper struct {
+ seenRoots map[string]bool
+ seenPackages map[string]*Package
+ dr *driverResponse
+}
+
+// init fills in r with a driverResponse.
+func (r *responseDeduper) init(dr *driverResponse) {
+ r.dr = dr
+ r.seenRoots = map[string]bool{}
+ r.seenPackages = map[string]*Package{}
+ for _, pkg := range dr.Packages {
+ r.seenPackages[pkg.ID] = pkg
+ }
+ for _, root := range dr.Roots {
+ r.seenRoots[root] = true
+ }
+}
+
+func (r *responseDeduper) addPackage(p *Package) {
+ if r.seenPackages[p.ID] != nil {
+ return
+ }
+ r.seenPackages[p.ID] = p
+ r.dr.Packages = append(r.dr.Packages, p)
+}
+
+func (r *responseDeduper) addRoot(id string) {
+ if r.seenRoots[id] {
+ return
+ }
+ r.seenRoots[id] = true
+ r.dr.Roots = append(r.dr.Roots, id)
+}
+
// goListDriver uses the go list command to interpret the patterns and produce
// the build system package structure.
// See driver for more details.
func goListDriver(cfg *Config, patterns ...string) (*driverResponse, error) {
+ var sizes types.Sizes
+ var sizeserr error
+ var sizeswg sync.WaitGroup
+ if cfg.Mode&NeedTypesSizes != 0 || cfg.Mode&NeedTypes != 0 {
+ sizeswg.Add(1)
+ go func() {
+ sizes, sizeserr = getSizes(cfg)
+ sizeswg.Done()
+ }()
+ }
+
// Determine files requested in contains patterns
var containFiles []string
+ var packagesNamed []string
restPatterns := make([]string, 0, len(patterns))
+ // Extract file= and other [querytype]= patterns. Report an error if querytype
+ // doesn't exist.
+extractQueries:
for _, pattern := range patterns {
- if strings.HasPrefix(pattern, "contains:") {
- containFile := strings.TrimPrefix(pattern, "contains:")
- containFiles = append(containFiles, containFile)
- } else {
+ eqidx := strings.Index(pattern, "=")
+ if eqidx < 0 {
restPatterns = append(restPatterns, pattern)
+ } else {
+ query, value := pattern[:eqidx], pattern[eqidx+len("="):]
+ switch query {
+ case "file":
+ containFiles = append(containFiles, value)
+ case "pattern":
+ restPatterns = append(restPatterns, value)
+ case "iamashamedtousethedisabledqueryname":
+ packagesNamed = append(packagesNamed, value)
+ case "": // not a reserved query
+ restPatterns = append(restPatterns, pattern)
+ default:
+ for _, rune := range query {
+ if rune < 'a' || rune > 'z' { // not a reserved query
+ restPatterns = append(restPatterns, pattern)
+ continue extractQueries
+ }
+ }
+ // Reject all other patterns containing "="
+ return nil, fmt.Errorf("invalid query type %q in query pattern %q", query, pattern)
+ }
}
}
- containFiles = absJoin(cfg.Dir, containFiles)
- patterns = restPatterns
-
- // TODO(matloob): Remove the definition of listfunc and just use golistPackages once go1.12 is released.
- var listfunc driver
- listfunc = func(cfg *Config, words ...string) (*driverResponse, error) {
- response, err := golistDriverCurrent(cfg, patterns...)
- if _, ok := err.(goTooOldError); ok {
- listfunc = golistDriverFallback
- return listfunc(cfg, patterns...)
- }
- listfunc = golistDriverCurrent
- return response, err
- }
- var response *driverResponse
+ response := &responseDeduper{}
var err error
- // see if we have any patterns to pass through to go list.
- if len(patterns) > 0 {
- response, err = listfunc(cfg, patterns...)
+ // See if we have any patterns to pass through to go list. Zero initial
+ // patterns also requires a go list call, since it's the equivalent of
+ // ".".
+ if len(restPatterns) > 0 || len(patterns) == 0 {
+ dr, err := golistDriver(cfg, restPatterns...)
if err != nil {
return nil, err
}
+ response.init(dr)
} else {
- response = &driverResponse{}
+ response.init(&driverResponse{})
+ }
+
+ sizeswg.Wait()
+ if sizeserr != nil {
+ return nil, sizeserr
}
+ // types.SizesFor always returns nil or a *types.StdSizes
+ response.dr.Sizes, _ = sizes.(*types.StdSizes)
- // Run go list for contains: patterns.
- seenPkgs := make(map[string]*Package) // for deduplication. different containing queries could produce same packages
+ var containsCandidates []string
+
+ if len(containFiles) != 0 {
+ if err := runContainsQueries(cfg, golistDriver, response, containFiles); err != nil {
+ return nil, err
+ }
+ }
+
+ if len(packagesNamed) != 0 {
+ if err := runNamedQueries(cfg, golistDriver, response, packagesNamed); err != nil {
+ return nil, err
+ }
+ }
+
+ modifiedPkgs, needPkgs, err := processGolistOverlay(cfg, response.dr)
+ if err != nil {
+ return nil, err
+ }
if len(containFiles) > 0 {
- for _, pkg := range response.Packages {
- seenPkgs[pkg.ID] = pkg
+ containsCandidates = append(containsCandidates, modifiedPkgs...)
+ containsCandidates = append(containsCandidates, needPkgs...)
+ }
+
+ if len(needPkgs) > 0 {
+ addNeededOverlayPackages(cfg, golistDriver, response, needPkgs)
+ if err != nil {
+ return nil, err
+ }
+ }
+ // Check candidate packages for containFiles.
+ if len(containFiles) > 0 {
+ for _, id := range containsCandidates {
+ pkg := response.seenPackages[id]
+ for _, f := range containFiles {
+ for _, g := range pkg.GoFiles {
+ if sameFile(f, g) {
+ response.addRoot(id)
+ }
+ }
+ }
}
}
- for _, f := range containFiles {
+
+ return response.dr, nil
+}
+
+func addNeededOverlayPackages(cfg *Config, driver driver, response *responseDeduper, pkgs []string) error {
+ dr, err := driver(cfg, pkgs...)
+ if err != nil {
+ return err
+ }
+ for _, pkg := range dr.Packages {
+ response.addPackage(pkg)
+ }
+ return nil
+}
+
+func runContainsQueries(cfg *Config, driver driver, response *responseDeduper, queries []string) error {
+ for _, query := range queries {
// TODO(matloob): Do only one query per directory.
- fdir := filepath.Dir(f)
- cfg.Dir = fdir
- dirResponse, err := listfunc(cfg, ".")
+ fdir := filepath.Dir(query)
+ // Pass absolute path of directory to go list so that it knows to treat it as a directory,
+ // not a package path.
+ pattern, err := filepath.Abs(fdir)
if err != nil {
- return nil, err
+ return fmt.Errorf("could not determine absolute path of file= query path %q: %v", query, err)
+ }
+ dirResponse, err := driver(cfg, pattern)
+ if err != nil {
+ return err
}
isRoot := make(map[string]bool, len(dirResponse.Roots))
for _, root := range dirResponse.Roots {
@@ -88,24 +224,278 @@ func goListDriver(cfg *Config, patterns ...string) (*driverResponse, error) {
// We don't bother to filter packages that will be dropped by the changes of roots,
// that will happen anyway during graph construction outside this function.
// Over-reporting packages is not a problem.
- if _, ok := seenPkgs[pkg.ID]; !ok {
- // it is a new package, just add it
- seenPkgs[pkg.ID] = pkg
- response.Packages = append(response.Packages, pkg)
- }
+ response.addPackage(pkg)
// if the package was not a root one, it cannot have the file
if !isRoot[pkg.ID] {
continue
}
for _, pkgFile := range pkg.GoFiles {
- if filepath.Base(f) == filepath.Base(pkgFile) {
- response.Roots = append(response.Roots, pkg.ID)
+ if filepath.Base(query) == filepath.Base(pkgFile) {
+ response.addRoot(pkg.ID)
break
}
}
}
}
- return response, nil
+ return nil
+}
+
+// modCacheRegexp splits a path in a module cache into module, module version, and package.
+var modCacheRegexp = regexp.MustCompile(`(.*)@([^/\\]*)(.*)`)
+
+func runNamedQueries(cfg *Config, driver driver, response *responseDeduper, queries []string) error {
+ // calling `go env` isn't free; bail out if there's nothing to do.
+ if len(queries) == 0 {
+ return nil
+ }
+ // Determine which directories are relevant to scan.
+ roots, modRoot, err := roots(cfg)
+ if err != nil {
+ return err
+ }
+
+ // Scan the selected directories. Simple matches, from GOPATH/GOROOT
+ // or the local module, can simply be "go list"ed. Matches from the
+ // module cache need special treatment.
+ var matchesMu sync.Mutex
+ var simpleMatches, modCacheMatches []string
+ add := func(root gopathwalk.Root, dir string) {
+ // Walk calls this concurrently; protect the result slices.
+ matchesMu.Lock()
+ defer matchesMu.Unlock()
+
+ path := dir
+ if dir != root.Path {
+ path = dir[len(root.Path)+1:]
+ }
+ if pathMatchesQueries(path, queries) {
+ switch root.Type {
+ case gopathwalk.RootModuleCache:
+ modCacheMatches = append(modCacheMatches, path)
+ case gopathwalk.RootCurrentModule:
+ // We'd need to read go.mod to find the full
+ // import path. Relative's easier.
+ rel, err := filepath.Rel(cfg.Dir, dir)
+ if err != nil {
+ // This ought to be impossible, since
+ // we found dir in the current module.
+ panic(err)
+ }
+ simpleMatches = append(simpleMatches, "./"+rel)
+ case gopathwalk.RootGOPATH, gopathwalk.RootGOROOT:
+ simpleMatches = append(simpleMatches, path)
+ }
+ }
+ }
+
+ startWalk := time.Now()
+ gopathwalk.Walk(roots, add, gopathwalk.Options{ModulesEnabled: modRoot != "", Debug: debug})
+ if debug {
+ log.Printf("%v for walk", time.Since(startWalk))
+ }
+
+ // Weird special case: the top-level package in a module will be in
+ // whatever directory the user checked the repository out into. It's
+ // more reasonable for that to not match the package name. So, if there
+ // are any Go files in the mod root, query it just to be safe.
+ if modRoot != "" {
+ rel, err := filepath.Rel(cfg.Dir, modRoot)
+ if err != nil {
+ panic(err) // See above.
+ }
+
+ files, err := ioutil.ReadDir(modRoot)
+ for _, f := range files {
+ if strings.HasSuffix(f.Name(), ".go") {
+ simpleMatches = append(simpleMatches, rel)
+ break
+ }
+ }
+ }
+
+ addResponse := func(r *driverResponse) {
+ for _, pkg := range r.Packages {
+ response.addPackage(pkg)
+ for _, name := range queries {
+ if pkg.Name == name {
+ response.addRoot(pkg.ID)
+ break
+ }
+ }
+ }
+ }
+
+ if len(simpleMatches) != 0 {
+ resp, err := driver(cfg, simpleMatches...)
+ if err != nil {
+ return err
+ }
+ addResponse(resp)
+ }
+
+ // Module cache matches are tricky. We want to avoid downloading new
+ // versions of things, so we need to use the ones present in the cache.
+ // go list doesn't accept version specifiers, so we have to write out a
+ // temporary module, and do the list in that module.
+ if len(modCacheMatches) != 0 {
+ // Collect all the matches, deduplicating by major version
+ // and preferring the newest.
+ type modInfo struct {
+ mod string
+ major string
+ }
+ mods := make(map[modInfo]string)
+ var imports []string
+ for _, modPath := range modCacheMatches {
+ matches := modCacheRegexp.FindStringSubmatch(modPath)
+ mod, ver := filepath.ToSlash(matches[1]), matches[2]
+ importPath := filepath.ToSlash(filepath.Join(matches[1], matches[3]))
+
+ major := semver.Major(ver)
+ if prevVer, ok := mods[modInfo{mod, major}]; !ok || semver.Compare(ver, prevVer) > 0 {
+ mods[modInfo{mod, major}] = ver
+ }
+
+ imports = append(imports, importPath)
+ }
+
+ // Build the temporary module.
+ var gomod bytes.Buffer
+ gomod.WriteString("module modquery\nrequire (\n")
+ for mod, version := range mods {
+ gomod.WriteString("\t" + mod.mod + " " + version + "\n")
+ }
+ gomod.WriteString(")\n")
+
+ tmpCfg := *cfg
+
+ // We're only trying to look at stuff in the module cache, so
+ // disable the network. This should speed things up, and has
+ // prevented errors in at least one case, #28518.
+ tmpCfg.Env = append(append([]string{"GOPROXY=off"}, cfg.Env...))
+
+ var err error
+ tmpCfg.Dir, err = ioutil.TempDir("", "gopackages-modquery")
+ if err != nil {
+ return err
+ }
+ defer os.RemoveAll(tmpCfg.Dir)
+
+ if err := ioutil.WriteFile(filepath.Join(tmpCfg.Dir, "go.mod"), gomod.Bytes(), 0777); err != nil {
+ return fmt.Errorf("writing go.mod for module cache query: %v", err)
+ }
+
+ // Run the query, using the import paths calculated from the matches above.
+ resp, err := driver(&tmpCfg, imports...)
+ if err != nil {
+ return fmt.Errorf("querying module cache matches: %v", err)
+ }
+ addResponse(resp)
+ }
+
+ return nil
+}
+
+func getSizes(cfg *Config) (types.Sizes, error) {
+ return packagesdriver.GetSizesGolist(cfg.Context, cfg.BuildFlags, cfg.Env, cfg.Dir, usesExportData(cfg))
+}
+
+// roots selects the appropriate paths to walk based on the passed-in configuration,
+// particularly the environment and the presence of a go.mod in cfg.Dir's parents.
+func roots(cfg *Config) ([]gopathwalk.Root, string, error) {
+ stdout, err := invokeGo(cfg, "env", "GOROOT", "GOPATH", "GOMOD")
+ if err != nil {
+ return nil, "", err
+ }
+
+ fields := strings.Split(stdout.String(), "\n")
+ if len(fields) != 4 || len(fields[3]) != 0 {
+ return nil, "", fmt.Errorf("go env returned unexpected output: %q", stdout.String())
+ }
+ goroot, gopath, gomod := fields[0], filepath.SplitList(fields[1]), fields[2]
+ var modDir string
+ if gomod != "" {
+ modDir = filepath.Dir(gomod)
+ }
+
+ var roots []gopathwalk.Root
+ // Always add GOROOT.
+ roots = append(roots, gopathwalk.Root{filepath.Join(goroot, "/src"), gopathwalk.RootGOROOT})
+ // If modules are enabled, scan the module dir.
+ if modDir != "" {
+ roots = append(roots, gopathwalk.Root{modDir, gopathwalk.RootCurrentModule})
+ }
+ // Add either GOPATH/src or GOPATH/pkg/mod, depending on module mode.
+ for _, p := range gopath {
+ if modDir != "" {
+ roots = append(roots, gopathwalk.Root{filepath.Join(p, "/pkg/mod"), gopathwalk.RootModuleCache})
+ } else {
+ roots = append(roots, gopathwalk.Root{filepath.Join(p, "/src"), gopathwalk.RootGOPATH})
+ }
+ }
+
+ return roots, modDir, nil
+}
+
+// These functions were copied from goimports. See further documentation there.
+
+// pathMatchesQueries is adapted from pkgIsCandidate.
+// TODO: is it reasonable to do Contains here, rather than an exact match on a path component?
+func pathMatchesQueries(path string, queries []string) bool {
+ lastTwo := lastTwoComponents(path)
+ for _, query := range queries {
+ if strings.Contains(lastTwo, query) {
+ return true
+ }
+ if hasHyphenOrUpperASCII(lastTwo) && !hasHyphenOrUpperASCII(query) {
+ lastTwo = lowerASCIIAndRemoveHyphen(lastTwo)
+ if strings.Contains(lastTwo, query) {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+// lastTwoComponents returns at most the last two path components
+// of v, using either / or \ as the path separator.
+func lastTwoComponents(v string) string {
+ nslash := 0
+ for i := len(v) - 1; i >= 0; i-- {
+ if v[i] == '/' || v[i] == '\\' {
+ nslash++
+ if nslash == 2 {
+ return v[i:]
+ }
+ }
+ }
+ return v
+}
+
+func hasHyphenOrUpperASCII(s string) bool {
+ for i := 0; i < len(s); i++ {
+ b := s[i]
+ if b == '-' || ('A' <= b && b <= 'Z') {
+ return true
+ }
+ }
+ return false
+}
+
+func lowerASCIIAndRemoveHyphen(s string) (ret string) {
+ buf := make([]byte, 0, len(s))
+ for i := 0; i < len(s); i++ {
+ b := s[i]
+ switch {
+ case b == '-':
+ continue
+ case 'A' <= b && b <= 'Z':
+ buf = append(buf, b+('a'-'A'))
+ default:
+ buf = append(buf, b)
+ }
+ }
+ return string(buf)
}
// Fields must match go list;
@@ -136,16 +526,24 @@ type jsonPackage struct {
XTestImports []string
ForTest string // q in a "p [q.test]" package, else ""
DepOnly bool
+
+ Error *jsonPackageError
+}
+
+type jsonPackageError struct {
+ ImportStack []string
+ Pos string
+ Err string
}
func otherFiles(p *jsonPackage) [][]string {
return [][]string{p.CFiles, p.CXXFiles, p.MFiles, p.HFiles, p.FFiles, p.SFiles, p.SwigFiles, p.SwigCXXFiles, p.SysoFiles}
}
-// golistDriverCurrent uses the "go list" command to expand the
-// pattern words and return metadata for the specified packages.
-// dir may be "" and env may be nil, as per os/exec.Command.
-func golistDriverCurrent(cfg *Config, words ...string) (*driverResponse, error) {
+// golistDriver 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 golistDriver(cfg *Config, words ...string) (*driverResponse, error) {
// go list uses the following identifiers in ImportPath and Imports:
//
// "p" -- importable package or main (command)
@@ -159,10 +557,11 @@ func golistDriverCurrent(cfg *Config, words ...string) (*driverResponse, error)
// Run "go list" for complete
// information on the specified packages.
- buf, err := golist(cfg, golistargs(cfg, words))
+ buf, err := invokeGo(cfg, golistargs(cfg, words)...)
if err != nil {
return nil, err
}
+ seen := make(map[string]*jsonPackage)
// Decode the JSON and convert it to Package form.
var response driverResponse
for dec := json.NewDecoder(buf); dec.More(); {
@@ -171,49 +570,77 @@ func golistDriverCurrent(cfg *Config, words ...string) (*driverResponse, error)
return nil, fmt.Errorf("JSON decoding failed: %v", err)
}
- // Bad package?
- if p.Name == "" {
- // This could be due to:
- // - no such package
- // - package directory contains no Go source files
- // - all package declarations are mangled
- // - and possibly other things.
- //
- // For now, we throw it away and let later
- // stages rediscover the problem, but this
- // discards the error message computed by go list
- // and computes a new one---by different logic:
- // if only one of the package declarations is
- // bad, for example, should we report an error
- // in Metadata mode?
- // Unless we parse and typecheck, we might not
- // notice there's a problem.
- //
- // Perhaps we should save a map of PackageID to
- // errors for such cases.
+ if p.ImportPath == "" {
+ // The documentation for go list says that “[e]rroneous packages will have
+ // a non-empty ImportPath”. If for some reason it comes back empty, we
+ // prefer to error out rather than silently discarding data or handing
+ // back a package without any way to refer to it.
+ if p.Error != nil {
+ return nil, Error{
+ Pos: p.Error.Pos,
+ Msg: p.Error.Err,
+ }
+ }
+ return nil, fmt.Errorf("package missing import path: %+v", p)
+ }
+
+ if old, found := seen[p.ImportPath]; found {
+ if !reflect.DeepEqual(p, old) {
+ return nil, fmt.Errorf("internal error: go list gives conflicting information for package %v", p.ImportPath)
+ }
+ // skip the duplicate
continue
}
+ seen[p.ImportPath] = p
+
+ pkg := &Package{
+ Name: p.Name,
+ ID: p.ImportPath,
+ GoFiles: absJoin(p.Dir, p.GoFiles, p.CgoFiles),
+ CompiledGoFiles: absJoin(p.Dir, p.CompiledGoFiles),
+ OtherFiles: absJoin(p.Dir, otherFiles(p)...),
+ }
+
+ // Work around https://golang.org/issue/28749:
+ // cmd/go puts assembly, C, and C++ files in CompiledGoFiles.
+ // Filter out any elements of CompiledGoFiles that are also in OtherFiles.
+ // We have to keep this workaround in place until go1.12 is a distant memory.
+ if len(pkg.OtherFiles) > 0 {
+ other := make(map[string]bool, len(pkg.OtherFiles))
+ for _, f := range pkg.OtherFiles {
+ other[f] = true
+ }
- id := p.ImportPath
+ out := pkg.CompiledGoFiles[:0]
+ for _, f := range pkg.CompiledGoFiles {
+ if other[f] {
+ continue
+ }
+ out = append(out, f)
+ }
+ pkg.CompiledGoFiles = out
+ }
// Extract the PkgPath from the package's ID.
- pkgpath := id
- if i := strings.IndexByte(id, ' '); i >= 0 {
- pkgpath = id[:i]
+ if i := strings.IndexByte(pkg.ID, ' '); i >= 0 {
+ pkg.PkgPath = pkg.ID[:i]
+ } else {
+ pkg.PkgPath = pkg.ID
}
- if pkgpath == "unsafe" {
- p.GoFiles = nil // ignore fake unsafe.go file
+ if pkg.PkgPath == "unsafe" {
+ pkg.GoFiles = nil // ignore fake unsafe.go file
}
// Assume go list emits only absolute paths for Dir.
- if !filepath.IsAbs(p.Dir) {
+ if p.Dir != "" && !filepath.IsAbs(p.Dir) {
log.Fatalf("internal error: go list returned non-absolute Package.Dir: %s", p.Dir)
}
- export := p.Export
- if export != "" && !filepath.IsAbs(export) {
- export = filepath.Join(p.Dir, export)
+ if p.Export != "" && !filepath.IsAbs(p.Export) {
+ pkg.ExportFile = filepath.Join(p.Dir, p.Export)
+ } else {
+ pkg.ExportFile = p.Export
}
// imports
@@ -224,9 +651,9 @@ func golistDriverCurrent(cfg *Config, words ...string) (*driverResponse, error)
for _, id := range p.Imports {
ids[id] = true
}
- imports := make(map[string]*Package)
+ pkg.Imports = make(map[string]*Package)
for path, id := range p.ImportMap {
- imports[path] = &Package{ID: id} // non-identity import
+ pkg.Imports[path] = &Package{ID: id} // non-identity import
delete(ids, id)
}
for id := range ids {
@@ -234,25 +661,26 @@ func golistDriverCurrent(cfg *Config, words ...string) (*driverResponse, error)
continue
}
- imports[id] = &Package{ID: id} // identity import
+ pkg.Imports[id] = &Package{ID: id} // identity import
}
if !p.DepOnly {
- response.Roots = append(response.Roots, id)
- }
- pkg := &Package{
- ID: id,
- Name: p.Name,
- PkgPath: pkgpath,
- GoFiles: absJoin(p.Dir, p.GoFiles, p.CgoFiles),
- CompiledGoFiles: absJoin(p.Dir, p.CompiledGoFiles),
- OtherFiles: absJoin(p.Dir, otherFiles(p)...),
- Imports: imports,
- ExportFile: export,
+ response.Roots = append(response.Roots, pkg.ID)
}
- // TODO(matloob): Temporary hack since CompiledGoFiles isn't always set.
+
+ // Work around for pre-go.1.11 versions of go list.
+ // TODO(matloob): they should be handled by the fallback.
+ // Can we delete this?
if len(pkg.CompiledGoFiles) == 0 {
pkg.CompiledGoFiles = pkg.GoFiles
}
+
+ if p.Error != nil {
+ pkg.Errors = append(pkg.Errors, Error{
+ Pos: p.Error.Pos,
+ Msg: p.Error.Err,
+ })
+ }
+
response.Packages = append(response.Packages, pkg)
}
@@ -273,65 +701,128 @@ func absJoin(dir string, fileses ...[]string) (res []string) {
}
func golistargs(cfg *Config, words []string) []string {
+ const findFlags = NeedImports | NeedTypes | NeedSyntax | NeedTypesInfo
fullargs := []string{
- "list", "-e", "-json", "-compiled",
+ "list", "-e", "-json",
+ fmt.Sprintf("-compiled=%t", cfg.Mode&(NeedCompiledGoFiles|NeedSyntax|NeedTypesInfo|NeedTypesSizes) != 0),
fmt.Sprintf("-test=%t", cfg.Tests),
fmt.Sprintf("-export=%t", usesExportData(cfg)),
- fmt.Sprintf("-deps=%t", cfg.Mode >= LoadImports),
+ fmt.Sprintf("-deps=%t", cfg.Mode&NeedDeps != 0),
+ // go list doesn't let you pass -test and -find together,
+ // probably because you'd just get the TestMain.
+ fmt.Sprintf("-find=%t", !cfg.Tests && cfg.Mode&findFlags == 0),
}
- fullargs = append(fullargs, cfg.Flags...)
+ fullargs = append(fullargs, cfg.BuildFlags...)
fullargs = append(fullargs, "--")
fullargs = append(fullargs, words...)
return fullargs
}
-// golist returns the JSON-encoded result of a "go list args..." query.
-func golist(cfg *Config, args []string) (*bytes.Buffer, error) {
- out := new(bytes.Buffer)
+// invokeGo returns the stdout of a go command invocation.
+func invokeGo(cfg *Config, args ...string) (*bytes.Buffer, error) {
+ stdout := new(bytes.Buffer)
+ stderr := new(bytes.Buffer)
cmd := exec.CommandContext(cfg.Context, "go", args...)
- cmd.Env = cfg.Env
+ // On darwin the cwd gets resolved to the real path, which breaks anything that
+ // expects the working directory to keep the original path, including the
+ // go command when dealing with modules.
+ // The Go stdlib has a special feature where if the cwd and the PWD are the
+ // same node then it trusts the PWD, so by setting it in the env for the child
+ // process we fix up all the paths returned by the go command.
+ cmd.Env = append(append([]string{}, cfg.Env...), "PWD="+cfg.Dir)
cmd.Dir = cfg.Dir
- cmd.Stdout = out
- cmd.Stderr = new(bytes.Buffer)
+ cmd.Stdout = stdout
+ cmd.Stderr = stderr
+ if debug {
+ defer func(start time.Time) {
+ log.Printf("%s for %v, stderr: <<%s>>\n", time.Since(start), cmdDebugStr(cmd, args...), stderr)
+ }(time.Now())
+ }
+
if err := cmd.Run(); err != nil {
+ // Check for 'go' executable not being found.
+ if ee, ok := err.(*exec.Error); ok && ee.Err == exec.ErrNotFound {
+ return nil, fmt.Errorf("'go list' driver requires 'go', but %s", exec.ErrNotFound)
+ }
+
exitErr, ok := err.(*exec.ExitError)
if !ok {
// Catastrophic error:
- // - executable not found
// - context cancellation
- return nil, fmt.Errorf("couldn't exec 'go list': %s %T", err, err)
+ return nil, fmt.Errorf("couldn't exec 'go %v': %s %T", args, err, err)
+ }
+
+ // Old go version?
+ if strings.Contains(stderr.String(), "flag provided but not defined") {
+ return nil, goTooOldError{fmt.Errorf("unsupported version of go: %s: %s", exitErr, stderr)}
+ }
+
+ // This error only appears in stderr. See golang.org/cl/166398 for a fix in go list to show
+ // the error in the Err section of stdout in case -e option is provided.
+ // This fix is provided for backwards compatibility.
+ if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "named files must be .go files") {
+ output := fmt.Sprintf(`{"ImportPath": "command-line-arguments","Incomplete": true,"Error": {"Pos": "","Err": %q}}`,
+ strings.Trim(stderr.String(), "\n"))
+ return bytes.NewBufferString(output), nil
}
- // Old go list?
- if strings.Contains(fmt.Sprint(cmd.Stderr), "flag provided but not defined") {
- return nil, goTooOldError{fmt.Errorf("unsupported version of go list: %s: %s", exitErr, cmd.Stderr)}
+ // Workaround for #29280: go list -e has incorrect behavior when an ad-hoc package doesn't exist.
+ if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "no such file or directory") {
+ output := fmt.Sprintf(`{"ImportPath": "command-line-arguments","Incomplete": true,"Error": {"Pos": "","Err": %q}}`,
+ strings.Trim(stderr.String(), "\n"))
+ return bytes.NewBufferString(output), nil
}
// Export mode entails a build.
// If that build fails, errors appear on stderr
// (despite the -e flag) and the Export field is blank.
// Do not fail in that case.
- if !usesExportData(cfg) {
- return nil, fmt.Errorf("go list: %s: %s", exitErr, cmd.Stderr)
+ // The same is true if an ad-hoc package given to go list doesn't exist.
+ // TODO(matloob): Remove these once we can depend on go list to exit with a zero status with -e even when
+ // packages don't exist or a build fails.
+ if !usesExportData(cfg) && !containsGoFile(args) {
+ return nil, fmt.Errorf("go %v: %s: %s", args, exitErr, stderr)
}
}
- // Print standard error output from "go list".
- // Due to the -e flag, this should be empty.
- // However, in -export mode it contains build errors.
- // Should go list save build errors in the Package.Error JSON field?
- // See https://github.com/golang/go/issues/26319.
- // If so, then we should continue to print stderr as go list
- // will be silent unless something unexpected happened.
- // If not, perhaps we should suppress it to reduce noise.
- if stderr := fmt.Sprint(cmd.Stderr); stderr != "" {
- fmt.Fprintf(os.Stderr, "go list stderr <<%s>>\n", stderr)
+ // As of writing, go list -export prints some non-fatal compilation
+ // errors to stderr, even with -e set. We would prefer that it put
+ // them in the Package.Error JSON (see https://golang.org/issue/26319).
+ // In the meantime, there's nowhere good to put them, but they can
+ // be useful for debugging. Print them if $GOPACKAGESPRINTGOLISTERRORS
+ // is set.
+ if len(stderr.Bytes()) != 0 && os.Getenv("GOPACKAGESPRINTGOLISTERRORS") != "" {
+ fmt.Fprintf(os.Stderr, "%s stderr: <<%s>>\n", cmdDebugStr(cmd, args...), stderr)
}
// debugging
if false {
- fmt.Fprintln(os.Stderr, out)
+ fmt.Fprintf(os.Stderr, "%s stdout: <<%s>>\n", cmdDebugStr(cmd, args...), stdout)
+ }
+
+ return stdout, nil
+}
+
+func containsGoFile(s []string) bool {
+ for _, f := range s {
+ if strings.HasSuffix(f, ".go") {
+ return true
+ }
+ }
+ return false
+}
+
+func cmdDebugStr(cmd *exec.Cmd, args ...string) string {
+ env := make(map[string]string)
+ for _, kv := range cmd.Env {
+ split := strings.Split(kv, "=")
+ k, v := split[0], split[1]
+ env[k] = v
+ }
+ var quotedArgs []string
+ for _, arg := range args {
+ quotedArgs = append(quotedArgs, strconv.Quote(arg))
}
- return out, nil
+ return fmt.Sprintf("GOROOT=%v GOPATH=%v GO111MODULE=%v PWD=%v go %s", env["GOROOT"], env["GOPATH"], env["GO111MODULE"], env["PWD"], strings.Join(quotedArgs, " "))
}
diff --git a/vendor/golang.org/x/tools/go/packages/golist_fallback.go b/vendor/golang.org/x/tools/go/packages/golist_fallback.go
deleted file mode 100644
index 331bb655..00000000
--- a/vendor/golang.org/x/tools/go/packages/golist_fallback.go
+++ /dev/null
@@ -1,282 +0,0 @@
-// Copyright 2018 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package packages
-
-import (
- "encoding/json"
- "fmt"
-
- "go/build"
- "io/ioutil"
- "os"
- "path/filepath"
- "sort"
- "strings"
-
- "golang.org/x/tools/go/internal/cgo"
-)
-
-// TODO(matloob): Delete this file once Go 1.12 is released.
-
-// This file provides backwards compatibility support for
-// loading for versions of Go earlier than 1.10.4. This support is meant to
-// assist with migration to the Package API until there's
-// widespread adoption of these newer Go versions.
-// This support will be removed once Go 1.12 is released
-// in Q1 2019.
-
-func golistDriverFallback(cfg *Config, words ...string) (*driverResponse, error) {
- original, deps, err := getDeps(cfg, words...)
- if err != nil {
- return nil, err
- }
-
- var tmpdir string // used for generated cgo files
-
- var response driverResponse
- addPackage := func(p *jsonPackage) {
- if p.Name == "" {
- return
- }
-
- id := p.ImportPath
- isRoot := original[id] != nil
- pkgpath := id
-
- if pkgpath == "unsafe" {
- p.GoFiles = nil // ignore fake unsafe.go file
- }
-
- importMap := func(importlist []string) map[string]*Package {
- importMap := make(map[string]*Package)
- for _, id := range importlist {
-
- if id == "C" {
- for _, path := range []string{"unsafe", "syscall", "runtime/cgo"} {
- if pkgpath != path && importMap[path] == nil {
- importMap[path] = &Package{ID: path}
- }
- }
- continue
- }
- importMap[vendorlessPath(id)] = &Package{ID: id}
- }
- return importMap
- }
- compiledGoFiles := absJoin(p.Dir, p.GoFiles)
- // Use a function to simplify control flow. It's just a bunch of gotos.
- var cgoErrors []error
- processCgo := func() bool {
- // Suppress any cgo errors. Any relevant errors will show up in typechecking.
- // TODO(matloob): Skip running cgo if Mode < LoadTypes.
- if tmpdir == "" {
- if tmpdir, err = ioutil.TempDir("", "gopackages"); err != nil {
- cgoErrors = append(cgoErrors, err)
- return false
- }
- }
- outdir := filepath.Join(tmpdir, strings.Replace(p.ImportPath, "/", "_", -1))
- if err := os.Mkdir(outdir, 0755); err != nil {
- cgoErrors = append(cgoErrors, err)
- return false
- }
- files, _, err := runCgo(p.Dir, outdir, cfg.Env)
- if err != nil {
- cgoErrors = append(cgoErrors, err)
- return false
- }
- compiledGoFiles = append(compiledGoFiles, files...)
- return true
- }
- if len(p.CgoFiles) == 0 || !processCgo() {
- compiledGoFiles = append(compiledGoFiles, absJoin(p.Dir, p.CgoFiles)...) // Punt to typechecker.
- }
- if isRoot {
- response.Roots = append(response.Roots, id)
- }
- response.Packages = append(response.Packages, &Package{
- ID: id,
- Name: p.Name,
- GoFiles: absJoin(p.Dir, p.GoFiles, p.CgoFiles),
- CompiledGoFiles: compiledGoFiles,
- OtherFiles: absJoin(p.Dir, otherFiles(p)...),
- PkgPath: pkgpath,
- Imports: importMap(p.Imports),
- // TODO(matloob): set errors on the Package to cgoErrors
- })
- if cfg.Tests {
- testID := fmt.Sprintf("%s [%s.test]", id, id)
- if len(p.TestGoFiles) > 0 || len(p.XTestGoFiles) > 0 {
- if isRoot {
- response.Roots = append(response.Roots, testID)
- }
- response.Packages = append(response.Packages, &Package{
- ID: testID,
- Name: p.Name,
- GoFiles: absJoin(p.Dir, p.GoFiles, p.CgoFiles, p.TestGoFiles),
- CompiledGoFiles: append(compiledGoFiles, absJoin(p.Dir, p.TestGoFiles)...),
- OtherFiles: absJoin(p.Dir, otherFiles(p)...),
- PkgPath: pkgpath,
- Imports: importMap(append(p.Imports, p.TestImports...)),
- // TODO(matloob): set errors on the Package to cgoErrors
- })
- if len(p.XTestGoFiles) > 0 {
- xtestID := fmt.Sprintf("%s_test [%s.test]", id, id)
- if isRoot {
- response.Roots = append(response.Roots, xtestID)
- }
- for i, imp := range p.XTestImports {
- if imp == p.ImportPath {
- p.XTestImports[i] = testID
- break
- }
- }
- response.Packages = append(response.Packages, &Package{
- ID: xtestID,
- Name: p.Name + "_test",
- GoFiles: absJoin(p.Dir, p.XTestGoFiles),
- CompiledGoFiles: absJoin(p.Dir, p.XTestGoFiles),
- PkgPath: pkgpath,
- Imports: importMap(p.XTestImports),
- })
- }
- }
- }
- }
-
- for _, pkg := range original {
- addPackage(pkg)
- }
- if cfg.Mode < LoadImports || len(deps) == 0 {
- return &response, nil
- }
-
- buf, err := golist(cfg, golistArgsFallback(cfg, deps))
- if err != nil {
- return nil, err
- }
-
- // Decode the JSON and convert it to Package form.
- for dec := json.NewDecoder(buf); dec.More(); {
- p := new(jsonPackage)
- if err := dec.Decode(p); err != nil {
- return nil, fmt.Errorf("JSON decoding failed: %v", err)
- }
-
- addPackage(p)
- }
-
- return &response, nil
-}
-
-// vendorlessPath returns the devendorized version of the import path ipath.
-// For example, VendorlessPath("foo/bar/vendor/a/b") returns "a/b".
-// Copied from golang.org/x/tools/imports/fix.go.
-func vendorlessPath(ipath string) string {
- // Devendorize for use in import statement.
- if i := strings.LastIndex(ipath, "/vendor/"); i >= 0 {
- return ipath[i+len("/vendor/"):]
- }
- if strings.HasPrefix(ipath, "vendor/") {
- return ipath[len("vendor/"):]
- }
- return ipath
-}
-
-// getDeps runs an initial go list to determine all the dependency packages.
-func getDeps(cfg *Config, words ...string) (originalSet map[string]*jsonPackage, deps []string, err error) {
- buf, err := golist(cfg, golistArgsFallback(cfg, words))
- if err != nil {
- return nil, nil, err
- }
-
- depsSet := make(map[string]bool)
- originalSet = make(map[string]*jsonPackage)
- var testImports []string
-
- // Extract deps from the JSON.
- for dec := json.NewDecoder(buf); dec.More(); {
- p := new(jsonPackage)
- if err := dec.Decode(p); err != nil {
- return nil, nil, fmt.Errorf("JSON decoding failed: %v", err)
- }
-
- originalSet[p.ImportPath] = p
- for _, dep := range p.Deps {
- depsSet[dep] = true
- }
- if cfg.Tests {
- // collect the additional imports of the test packages.
- pkgTestImports := append(p.TestImports, p.XTestImports...)
- for _, imp := range pkgTestImports {
- if depsSet[imp] {
- continue
- }
- depsSet[imp] = true
- testImports = append(testImports, imp)
- }
- }
- }
- // Get the deps of the packages imported by tests.
- if len(testImports) > 0 {
- buf, err = golist(cfg, golistArgsFallback(cfg, testImports))
- if err != nil {
- return nil, nil, err
- }
- // Extract deps from the JSON.
- for dec := json.NewDecoder(buf); dec.More(); {
- p := new(jsonPackage)
- if err := dec.Decode(p); err != nil {
- return nil, nil, fmt.Errorf("JSON decoding failed: %v", err)
- }
- for _, dep := range p.Deps {
- depsSet[dep] = true
- }
- }
- }
-
- for orig := range originalSet {
- delete(depsSet, orig)
- }
-
- deps = make([]string, 0, len(depsSet))
- for dep := range depsSet {
- deps = append(deps, dep)
- }
- sort.Strings(deps) // ensure output is deterministic
- return originalSet, deps, nil
-}
-
-func golistArgsFallback(cfg *Config, words []string) []string {
- fullargs := []string{"list", "-e", "-json"}
- fullargs = append(fullargs, cfg.Flags...)
- fullargs = append(fullargs, "--")
- fullargs = append(fullargs, words...)
- return fullargs
-}
-
-func runCgo(pkgdir, tmpdir string, env []string) (files, displayfiles []string, err error) {
- // Use go/build to open cgo files and determine the cgo flags, etc, from them.
- // This is tricky so it's best to avoid reimplementing as much as we can, and
- // we plan to delete this support once Go 1.12 is released anyways.
- // TODO(matloob): This isn't completely correct because we're using the Default
- // context. Perhaps we should more accurately fill in the context.
- bp, err := build.ImportDir(pkgdir, build.ImportMode(0))
- if err != nil {
- return nil, nil, err
- }
- for _, ev := range env {
- if v := strings.TrimPrefix(ev, "CGO_CPPFLAGS"); v != ev {
- bp.CgoCPPFLAGS = append(bp.CgoCPPFLAGS, strings.Fields(v)...)
- } else if v := strings.TrimPrefix(ev, "CGO_CFLAGS"); v != ev {
- bp.CgoCFLAGS = append(bp.CgoCFLAGS, strings.Fields(v)...)
- } else if v := strings.TrimPrefix(ev, "CGO_CXXFLAGS"); v != ev {
- bp.CgoCXXFLAGS = append(bp.CgoCXXFLAGS, strings.Fields(v)...)
- } else if v := strings.TrimPrefix(ev, "CGO_LDFLAGS"); v != ev {
- bp.CgoLDFLAGS = append(bp.CgoLDFLAGS, strings.Fields(v)...)
- }
- }
- return cgo.Run(bp, pkgdir, tmpdir, true)
-}
diff --git a/vendor/golang.org/x/tools/go/packages/golist_overlay.go b/vendor/golang.org/x/tools/go/packages/golist_overlay.go
new file mode 100644
index 00000000..71ffcd9d
--- /dev/null
+++ b/vendor/golang.org/x/tools/go/packages/golist_overlay.go
@@ -0,0 +1,104 @@
+package packages
+
+import (
+ "go/parser"
+ "go/token"
+ "path/filepath"
+ "strconv"
+ "strings"
+)
+
+// processGolistOverlay provides rudimentary support for adding
+// files that don't exist on disk to an overlay. The results can be
+// sometimes incorrect.
+// TODO(matloob): Handle unsupported cases, including the following:
+// - test files
+// - adding test and non-test files to test variants of packages
+// - determining the correct package to add given a new import path
+// - creating packages that don't exist
+func processGolistOverlay(cfg *Config, response *driverResponse) (modifiedPkgs, needPkgs []string, err error) {
+ havePkgs := make(map[string]string) // importPath -> non-test package ID
+ needPkgsSet := make(map[string]bool)
+ modifiedPkgsSet := make(map[string]bool)
+
+ for _, pkg := range response.Packages {
+ // This is an approximation of import path to id. This can be
+ // wrong for tests, vendored packages, and a number of other cases.
+ havePkgs[pkg.PkgPath] = pkg.ID
+ }
+
+outer:
+ for path, contents := range cfg.Overlay {
+ base := filepath.Base(path)
+ if strings.HasSuffix(path, "_test.go") {
+ // Overlays don't support adding new test files yet.
+ // TODO(matloob): support adding new test files.
+ continue
+ }
+ dir := filepath.Dir(path)
+ for _, pkg := range response.Packages {
+ var dirContains, fileExists bool
+ for _, f := range pkg.GoFiles {
+ if sameFile(filepath.Dir(f), dir) {
+ dirContains = true
+ }
+ if filepath.Base(f) == base {
+ fileExists = true
+ }
+ }
+ if dirContains {
+ if !fileExists {
+ pkg.GoFiles = append(pkg.GoFiles, path) // TODO(matloob): should the file just be added to GoFiles?
+ pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, path)
+ modifiedPkgsSet[pkg.ID] = true
+ }
+ imports, err := extractImports(path, contents)
+ if err != nil {
+ // Let the parser or type checker report errors later.
+ continue outer
+ }
+ for _, imp := range imports {
+ _, found := pkg.Imports[imp]
+ if !found {
+ needPkgsSet[imp] = true
+ // TODO(matloob): Handle cases when the following block isn't correct.
+ // These include imports of test variants, imports of vendored packages, etc.
+ id, ok := havePkgs[imp]
+ if !ok {
+ id = imp
+ }
+ pkg.Imports[imp] = &Package{ID: id}
+ }
+ }
+ continue outer
+ }
+ }
+ }
+
+ needPkgs = make([]string, 0, len(needPkgsSet))
+ for pkg := range needPkgsSet {
+ needPkgs = append(needPkgs, pkg)
+ }
+ modifiedPkgs = make([]string, 0, len(modifiedPkgsSet))
+ for pkg := range modifiedPkgsSet {
+ modifiedPkgs = append(modifiedPkgs, pkg)
+ }
+ return modifiedPkgs, needPkgs, err
+}
+
+func extractImports(filename string, contents []byte) ([]string, error) {
+ f, err := parser.ParseFile(token.NewFileSet(), filename, contents, parser.ImportsOnly) // TODO(matloob): reuse fileset?
+ if err != nil {
+ return nil, err
+ }
+ var res []string
+ for _, imp := range f.Imports {
+ quotedPath := imp.Path.Value
+ path, err := strconv.Unquote(quotedPath)
+ if err != nil {
+ return nil, err
+ }
+ res = append(res, path)
+ }
+ return res, nil
+}
diff --git a/vendor/golang.org/x/tools/go/packages/packages.go b/vendor/golang.org/x/tools/go/packages/packages.go
index 7e8e4e2e..4639fcdd 100644
--- a/vendor/golang.org/x/tools/go/packages/packages.go
+++ b/vendor/golang.org/x/tools/go/packages/packages.go
@@ -12,49 +12,100 @@ import (
"fmt"
"go/ast"
"go/parser"
+ "go/scanner"
"go/token"
"go/types"
+ "io/ioutil"
"log"
"os"
+ "path/filepath"
+ "strings"
"sync"
"golang.org/x/tools/go/gcexportdata"
)
-// A LoadMode specifies the amount of detail to return when loading packages.
-// The modes are all strictly additive, as you go through the list it increases
-// the amount of information available to you, but may also increase the cost
-// of collecting the information.
-// Load is always allowed to return more information than requested.
+// A LoadMode specifies the amount of detail to return when loading.
+// Higher-numbered modes cause Load to return more information,
+// but may be slower. Load may return more information than requested.
type LoadMode int
const (
+ // The following constants are used to specify which fields of the Package
+ // should be filled when loading is done. As a special case to provide
+ // backwards compatibility, a LoadMode of 0 is equivalent to LoadFiles.
+ // For all other LoadModes, the bits below specify which fields will be filled
+ // in the result packages.
+ // WARNING: This part of the go/packages API is EXPERIMENTAL. It might
+ // be changed or removed up until April 15 2019. After that date it will
+ // be frozen.
+ // TODO(matloob): Remove this comment on April 15.
+
+ // ID and Errors (if present) will always be filled.
+
+ // NeedName adds Name and PkgPath.
+ NeedName LoadMode = 1 << iota
+
+ // NeedFiles adds GoFiles and OtherFiles.
+ NeedFiles
+
+ // NeedCompiledGoFiles adds CompiledGoFiles.
+ NeedCompiledGoFiles
+
+ // NeedImports adds Imports. If NeedDeps is not set, the Imports field will contain
+ // "placeholder" Packages with only the ID set.
+ NeedImports
+
+ // NeedDeps adds the fields requested by the LoadMode in the packages in Imports. If NeedImports
+ // is not set NeedDeps has no effect.
+ NeedDeps
+
+ // NeedExportsFile adds ExportsFile.
+ NeedExportsFile
+
+ // NeedTypes adds Types, Fset, and IllTyped.
+ NeedTypes
+
+ // NeedSyntax adds Syntax.
+ NeedSyntax
+
+ // NeedTypesInfo adds TypesInfo.
+ NeedTypesInfo
+
+ // NeedTypesSizes adds TypesSizes.
+ NeedTypesSizes
+)
+
+const (
// LoadFiles finds the packages and computes their source file lists.
- // Package fields: ID, Name, Errors, GoFiles, OtherFiles.
- LoadFiles LoadMode = iota
+ // Package fields: ID, Name, Errors, GoFiles, CompiledGoFiles, and OtherFiles.
+ LoadFiles = NeedName | NeedFiles | NeedCompiledGoFiles
// LoadImports adds import information for each package
// and its dependencies.
// Package fields added: Imports.
- LoadImports
+ LoadImports = LoadFiles | NeedImports | NeedDeps
- // LoadTypes adds type information for the package's exported symbols.
- // Package fields added: Types, Fset, IllTyped.
- // This will use the type information provided by the build system if
- // possible, and the ExportFile field may be filled in.
- LoadTypes
+ // LoadTypes adds type information for package-level
+ // declarations in the packages matching the patterns.
+ // Package fields added: Types, TypesSizes, Fset, and IllTyped.
+ // This mode uses type information provided by the build system when
+ // possible, and may fill in the ExportFile field.
+ LoadTypes = LoadImports | NeedTypes | NeedTypesSizes
// LoadSyntax adds typed syntax trees for the packages matching the patterns.
- // Package fields added: Syntax, TypesInfo, for direct pattern matches only.
- LoadSyntax
+ // Package fields added: Syntax, and TypesInfo, for direct pattern matches only.
+ LoadSyntax = LoadTypes | NeedSyntax | NeedTypesInfo
// LoadAllSyntax adds typed syntax trees for the packages matching the patterns
// and all dependencies.
- // Package fields added: Syntax, TypesInfo, for all packages in import graph.
- LoadAllSyntax
+ // Package fields added: Types, Fset, IllTyped, Syntax, and TypesInfo,
+ // for all packages in the import graph.
+ LoadAllSyntax = LoadSyntax
)
-// An Config specifies details about how packages should be loaded.
+// A Config specifies details about how packages should be loaded.
+// The zero value is a valid configuration.
// Calls to Load do not modify this struct.
type Config struct {
// Mode controls the level of information returned for each package.
@@ -66,14 +117,14 @@ type Config struct {
// If Context is nil, the load cannot be cancelled.
Context context.Context
- // Dir is the directory in which to run the build system tool
+ // Dir is the directory in which to run the build system's query 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.
+ // Env is the environment to use when invoking the build system's query tool.
// If Env is nil, the current environment is used.
- // Like in os/exec's Cmd, only the last value in the slice for
+ // As 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:
//
@@ -81,22 +132,12 @@ type Config struct {
//
Env []string
- // Flags is a list of command-line flags to be passed through to
- // the underlying query tool.
- Flags []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 source files or
- // type information provided by the build system.
- // If Fset is nil, the loader will create one.
+ // BuildFlags is a list of command-line flags to be passed through to
+ // the build system's query tool.
+ BuildFlags []string
+
+ // Fset provides source position information for syntax trees and types.
+ // If Fset is nil, Load will use a new fileset, but preserve Fset's value.
Fset *token.FileSet
// ParseFile is called to read and parse each file
@@ -104,12 +145,14 @@ type Config struct {
// 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)
+ // ParseFile should parse the source from src and use filename only for
+ // recording position information.
+ //
+ // An application may supply a custom implementation of ParseFile
+ // to change the effective file contents or the behavior of the parser,
+ // or to modify the syntax tree. For example, selectively eliminating
+ // unwanted function bodies can significantly accelerate type checking.
+ ParseFile func(fset *token.FileSet, filename string, src []byte) (*ast.File, error)
// If Tests is set, the loader includes not just the packages
// matching a particular pattern but also any related test packages,
@@ -125,21 +168,13 @@ type Config struct {
// setting Tests may have no effect.
Tests bool
- // TypeChecker provides additional configuration for type-checking syntax trees.
- //
- // It is used for all packages in LoadAllSyntax mode,
- // and for the packages matching the patterns, but not their dependencies,
- // in LoadSyntax mode.
- //
- // The TypeChecker.Error function is ignored:
- // errors are reported using the Error function defined above.
+ // Overlay provides a mapping of absolute file paths to file contents.
+ // If the file with the given path already exists, the parser will use the
+ // alternative file contents provided by the map.
//
- // The TypeChecker.Importer function is ignored:
- // the loader defines an appropriate importer.
- //
- // TODO(rsc): TypeChecker.Sizes should use the same sizes as the main build.
- // Derive them from the runtime?
- TypeChecker types.Config
+ // Overlays provide incomplete support for when a given file doesn't
+ // already exist on disk. See the package doc above for more details.
+ Overlay map[string][]byte
}
// driver is the type for functions that query the build system for the
@@ -148,6 +183,9 @@ type driver func(cfg *Config, patterns ...string) (*driverResponse, error)
// driverResponse contains the results for a driver query.
type driverResponse struct {
+ // Sizes, if not nil, is the types.Sizes to use when type checking.
+ Sizes *types.StdSizes
+
// Roots is the set of package IDs that make up the root packages.
// We have to encode this separately because when we encode a single package
// we cannot know if it is one of the roots as that requires knowledge of the
@@ -171,12 +209,18 @@ type driverResponse struct {
// as defined by the underlying build system.
// It may return an empty list of packages without an error,
// for instance for an empty expansion of a valid wildcard.
+// Errors associated with a particular package are recorded in the
+// corresponding Package's Errors list, and do not cause Load to
+// return an error. Clients may need to handle such errors before
+// proceeding with further analysis. The PrintErrors function is
+// provided for convenient display of all errors.
func Load(cfg *Config, patterns ...string) ([]*Package, error) {
l := newLoader(cfg)
response, err := defaultDriver(&l.Config, patterns...)
if err != nil {
return nil, err
}
+ l.sizes = response.Sizes
return l.refine(response.Roots, response.Packages...)
}
@@ -190,7 +234,7 @@ func defaultDriver(cfg *Config, patterns ...string) (*driverResponse, error) {
return driver(cfg, patterns...)
}
-// A Package describes a single loaded Go package.
+// A Package describes a loaded Go package.
type Package struct {
// ID is a unique identifier for a package,
// in a syntax provided by the underlying build system.
@@ -203,15 +247,12 @@ type Package struct {
// Name is the package name as it appears in the package source code.
Name string
- // This is the package path as used by the types package.
- // This is used to map entries in the type information back to the package
- // they come from.
+ // PkgPath is the package path as used by the go/types package.
PkgPath string
- // Errors lists any errors encountered while loading the package.
- // TODO(rsc): Say something about the errors or at least their Strings,
- // as far as file:line being at the beginning and so on.
- Errors []error
+ // Errors contains any errors encountered querying the metadata
+ // of the package, or while parsing or type-checking its files.
+ Errors []Error
// GoFiles lists the absolute file paths of the package's Go source files.
GoFiles []string
@@ -225,60 +266,79 @@ type Package struct {
// including assembly, C, C++, Fortran, Objective-C, SWIG, and so on.
OtherFiles []string
- // ExportFile is the absolute path to a file containing the type information
- // provided by the build system.
+ // ExportFile is the absolute path to a file containing type
+ // information for the package as provided by the build system.
ExportFile string
// Imports maps import paths appearing in the package's Go source files
// to corresponding loaded Packages.
Imports map[string]*Package
- // Types is the type information for the package.
- // Modes LoadTypes and above set this field for all packages.
- //
- // TODO(adonovan): all packages? In Types mode this entails
- // asymptotically more export data processing than is required
- // to load the requested packages. Is that what we want?
+ // Types provides type information for the package.
+ // Modes LoadTypes and above set this field for packages matching the
+ // patterns; type information for dependencies may be missing or incomplete.
+ // Mode LoadAllSyntax sets this field for all packages, including dependencies.
Types *types.Package
// Fset provides position information for Types, TypesInfo, and Syntax.
- // Modes LoadTypes and above set this field for all packages.
+ // It is set only when Types is set.
Fset *token.FileSet
- // IllTyped indicates whether the package has any type errors.
- // Modes LoadTypes and above set this field for all packages.
+ // IllTyped indicates whether the package or any dependency contains errors.
+ // It is set only when Types is set.
IllTyped bool
- // Syntax is the package's syntax trees, for the files listed in GoFiles.
+ // Syntax is the package's syntax trees, for the files listed in CompiledGoFiles.
//
- // Mode LoadSyntax set this field for packages matching the patterns.
- // Mode LoadSyntaxAll sets this field for all packages, including dependencies.
+ // Mode LoadSyntax sets this field for packages matching the patterns.
+ // Mode LoadAllSyntax sets this field for all packages, including dependencies.
Syntax []*ast.File
- // TypesInfo is the type-checking results for the package's syntax trees.
+ // TypesInfo provides type information about the package's syntax trees.
// It is set only when Syntax is set.
TypesInfo *types.Info
+
+ // TypesSizes provides the effective size function for types in TypesInfo.
+ TypesSizes types.Sizes
}
-// packageError is used to serialize structured errors as much as possible.
-// This has members compatible with the golist error type, and possibly some
-// more if we need other error information to survive.
-type packageError struct {
- Pos string // position of error
- Err string // the error itself
+// An Error describes a problem with a package's metadata, syntax, or types.
+type Error struct {
+ Pos string // "file:line:col" or "file:line" or "" or "-"
+ Msg string
+ Kind ErrorKind
}
-func (e *packageError) Error() string {
- return e.Pos + ": " + e.Err
+// ErrorKind describes the source of the error, allowing the user to
+// differentiate between errors generated by the driver, the parser, or the
+// type-checker.
+type ErrorKind int
+
+const (
+ UnknownError ErrorKind = iota
+ ListError
+ ParseError
+ TypeError
+)
+
+func (err Error) Error() string {
+ pos := err.Pos
+ if pos == "" {
+ pos = "-" // like token.Position{}.String()
+ }
+ return pos + ": " + err.Msg
}
// flatPackage is the JSON form of Package
-// It drops all the type and syntax fields, and transforms the Imports and Errors
+// It drops all the type and syntax fields, and transforms the Imports
+//
+// TODO(adonovan): identify this struct with Package, effectively
+// publishing the JSON protocol.
type flatPackage struct {
ID string
Name string `json:",omitempty"`
PkgPath string `json:",omitempty"`
- Errors []*packageError `json:",omitempty"`
+ Errors []Error `json:",omitempty"`
GoFiles []string `json:",omitempty"`
CompiledGoFiles []string `json:",omitempty"`
OtherFiles []string `json:",omitempty"`
@@ -292,6 +352,7 @@ type flatPackage struct {
// The imports are written out as just a map of path to package id.
// The errors are written using a custom type that tries to preserve the
// structure of error types we know about.
+//
// This method exists to enable support for additional build systems. It is
// not intended for use by clients of the API and we may change the format.
func (p *Package) MarshalJSON() ([]byte, error) {
@@ -299,23 +360,12 @@ func (p *Package) MarshalJSON() ([]byte, error) {
ID: p.ID,
Name: p.Name,
PkgPath: p.PkgPath,
+ Errors: p.Errors,
GoFiles: p.GoFiles,
CompiledGoFiles: p.CompiledGoFiles,
OtherFiles: p.OtherFiles,
ExportFile: p.ExportFile,
}
- if len(p.Errors) > 0 {
- flat.Errors = make([]*packageError, len(p.Errors))
- for i, err := range p.Errors {
- //TODO: best effort mapping of errors to the serialized form
- switch err := err.(type) {
- case *packageError:
- flat.Errors[i] = err
- default:
- flat.Errors[i] = &packageError{Err: err.Error()}
- }
- }
- }
if len(p.Imports) > 0 {
flat.Imports = make(map[string]string, len(p.Imports))
for path, ipkg := range p.Imports {
@@ -336,17 +386,12 @@ func (p *Package) UnmarshalJSON(b []byte) error {
ID: flat.ID,
Name: flat.Name,
PkgPath: flat.PkgPath,
+ Errors: flat.Errors,
GoFiles: flat.GoFiles,
CompiledGoFiles: flat.CompiledGoFiles,
OtherFiles: flat.OtherFiles,
ExportFile: flat.ExportFile,
}
- if len(flat.Errors) > 0 {
- p.Errors = make([]error, len(flat.Errors))
- for i, err := range flat.Errors {
- p.Errors[i] = err
- }
- }
if len(flat.Imports) > 0 {
p.Imports = make(map[string]*Package, len(flat.Imports))
for path, id := range flat.Imports {
@@ -361,17 +406,26 @@ func (p *Package) String() string { return p.ID }
// loaderPackage augments Package with state used during the loading phase
type loaderPackage struct {
*Package
- importErrors map[string]error // maps each bad import to its error
- loadOnce sync.Once
- color uint8 // for cycle detection
- mark, needsrc bool // used when Mode >= LoadTypes
+ importErrors map[string]error // maps each bad import to its error
+ loadOnce sync.Once
+ color uint8 // for cycle detection
+ needsrc bool // load from source (Mode >= LoadTypes)
+ needtypes bool // type information is either requested or depended on
+ initial bool // package was matched by a pattern
}
// loader holds the working state of a single call to load.
type loader struct {
pkgs map[string]*loaderPackage
Config
+ sizes types.Sizes
exportMu sync.Mutex // enforces mutual exclusion of exportdata operations
+
+ // TODO(matloob): Add an implied mode here and use that instead of mode.
+ // Implied mode would contain all the fields we need the data for so we can
+ // get the actually requested fields. We'll zero them out before returning
+ // packages to the user. This will make it easier for us to get the conditions
+ // where we need certain modes right.
}
func newLoader(cfg *Config) *loader {
@@ -379,6 +433,12 @@ func newLoader(cfg *Config) *loader {
if cfg != nil {
ld.Config = *cfg
}
+ if ld.Config.Mode == 0 {
+ ld.Config.Mode = LoadFiles // Preserve zero behavior of Mode for backwards compatibility.
+ }
+ if ld.Config.Env == nil {
+ ld.Config.Env = os.Environ()
+ }
if ld.Context == nil {
ld.Context = context.Background()
}
@@ -388,24 +448,21 @@ func newLoader(cfg *Config) *loader {
}
}
- if ld.Mode >= LoadTypes {
+ if ld.Mode&NeedTypes != 0 {
if ld.Fset == nil {
ld.Fset = token.NewFileSet()
}
- // Error and ParseFile are required even in LoadTypes mode
+ // ParseFile is required even in LoadTypes mode
// because we load source if export data is missing.
-
- 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) {
+ ld.ParseFile = func(fset *token.FileSet, filename string, src []byte) (*ast.File, error) {
+ var isrc interface{}
+ if src != nil {
+ isrc = src
+ }
const mode = parser.AllErrors | parser.ParseComments
- return parser.ParseFile(fset, filename, nil, mode)
+ return parser.ParseFile(fset, filename, isrc, mode)
}
}
}
@@ -415,26 +472,34 @@ func newLoader(cfg *Config) *loader {
// refine connects the supplied packages into a graph and then adds type and
// and syntax information as requested by the LoadMode.
func (ld *loader) refine(roots []string, list ...*Package) ([]*Package, error) {
- if len(list) == 0 {
- return nil, fmt.Errorf("packages not found")
- }
- isRoot := make(map[string]bool, len(roots))
- for _, root := range roots {
- isRoot[root] = true
+ rootMap := make(map[string]int, len(roots))
+ for i, root := range roots {
+ rootMap[root] = i
}
ld.pkgs = make(map[string]*loaderPackage)
// first pass, fixup and build the map and roots
- var initial []*loaderPackage
+ var initial = make([]*loaderPackage, len(roots))
for _, pkg := range list {
+ rootIndex := -1
+ if i, found := rootMap[pkg.ID]; found {
+ rootIndex = i
+ }
lpkg := &loaderPackage{
- Package: pkg,
- needsrc: ld.Mode >= LoadAllSyntax ||
- (ld.Mode >= LoadSyntax && isRoot[pkg.ID]) ||
- (pkg.ExportFile == "" && pkg.PkgPath != "unsafe"),
+ Package: pkg,
+ needtypes: (ld.Mode&(NeedTypes|NeedTypesInfo) != 0 && rootIndex < 0) || rootIndex >= 0,
+ needsrc: (ld.Mode&(NeedSyntax|NeedTypesInfo) != 0 && rootIndex < 0) || rootIndex >= 0 ||
+ len(ld.Overlay) > 0 || // Overlays can invalidate export data. TODO(matloob): make this check fine-grained based on dependencies on overlaid files
+ pkg.ExportFile == "" && pkg.PkgPath != "unsafe",
}
ld.pkgs[lpkg.ID] = lpkg
- if isRoot[lpkg.ID] {
- initial = append(initial, lpkg)
+ if rootIndex >= 0 {
+ initial[rootIndex] = lpkg
+ lpkg.initial = true
+ }
+ }
+ for i, root := range roots {
+ if initial[i] == nil {
+ return nil, fmt.Errorf("root package %v is missing", root)
}
}
@@ -458,6 +523,7 @@ func (ld *loader) refine(roots []string, list ...*Package) ([]*Package, error) {
// for which we load source code.
var stack []*loaderPackage
var visit func(lpkg *loaderPackage) bool
+ var srcPkgs []*loaderPackage
visit = func(lpkg *loaderPackage) bool {
switch lpkg.color {
case black:
@@ -491,15 +557,20 @@ func (ld *loader) refine(roots []string, list ...*Package) ([]*Package, error) {
}
lpkg.Imports[importPath] = imp.Package
}
-
+ if lpkg.needsrc {
+ srcPkgs = append(srcPkgs, lpkg)
+ }
+ if ld.Mode&NeedTypesSizes != 0 {
+ lpkg.TypesSizes = ld.sizes
+ }
stack = stack[:len(stack)-1] // pop
lpkg.color = black
return lpkg.needsrc
}
- if ld.Mode < LoadImports {
- //we do this to drop the stub import packages that we are not even going to try to resolve
+ if ld.Mode&(NeedImports|NeedDeps) == 0 {
+ // We do this to drop the stub import packages that we are not even going to try to resolve.
for _, lpkg := range initial {
lpkg.Imports = nil
}
@@ -509,9 +580,19 @@ func (ld *loader) refine(roots []string, list ...*Package) ([]*Package, error) {
visit(lpkg)
}
}
+ if ld.Mode&NeedDeps != 0 { // TODO(matloob): This is only the case if NeedTypes is also set, right?
+ for _, lpkg := range srcPkgs {
+ // Complete type information is required for the
+ // immediate dependencies of each source package.
+ for _, ipkg := range lpkg.Imports {
+ imp := ld.pkgs[ipkg.ID]
+ imp.needtypes = true
+ }
+ }
+ }
// Load type data if needed, starting at
// the initial packages (roots of the import DAG).
- if ld.Mode >= LoadTypes {
+ if ld.Mode&NeedTypes != 0 {
var wg sync.WaitGroup
for _, lpkg := range initial {
wg.Add(1)
@@ -524,16 +605,61 @@ func (ld *loader) refine(roots []string, list ...*Package) ([]*Package, error) {
}
result := make([]*Package, len(initial))
+ importPlaceholders := make(map[string]*Package)
for i, lpkg := range initial {
result[i] = lpkg.Package
}
+ for i := range ld.pkgs {
+ // Clear all unrequested fields, for extra de-Hyrum-ization.
+ if ld.Mode&NeedName == 0 {
+ ld.pkgs[i].Name = ""
+ ld.pkgs[i].PkgPath = ""
+ }
+ if ld.Mode&NeedFiles == 0 {
+ ld.pkgs[i].GoFiles = nil
+ ld.pkgs[i].OtherFiles = nil
+ }
+ if ld.Mode&NeedCompiledGoFiles == 0 {
+ ld.pkgs[i].CompiledGoFiles = nil
+ }
+ if ld.Mode&NeedImports == 0 {
+ ld.pkgs[i].Imports = nil
+ }
+ if ld.Mode&NeedExportsFile == 0 {
+ ld.pkgs[i].ExportFile = ""
+ }
+ if ld.Mode&NeedTypes == 0 {
+ ld.pkgs[i].Types = nil
+ ld.pkgs[i].Fset = nil
+ ld.pkgs[i].IllTyped = false
+ }
+ if ld.Mode&NeedSyntax == 0 {
+ ld.pkgs[i].Syntax = nil
+ }
+ if ld.Mode&NeedTypesInfo == 0 {
+ ld.pkgs[i].TypesInfo = nil
+ }
+ if ld.Mode&NeedTypesSizes == 0 {
+ ld.pkgs[i].TypesSizes = nil
+ }
+ if ld.Mode&NeedDeps == 0 {
+ for j, pkg := range ld.pkgs[i].Imports {
+ ph, ok := importPlaceholders[pkg.ID]
+ if !ok {
+ ph = &Package{ID: pkg.ID}
+ importPlaceholders[pkg.ID] = ph
+ }
+ ld.pkgs[i].Imports[j] = ph
+ }
+ }
+ }
return result, nil
}
// loadRecursive loads the specified package and its dependencies,
// recursively, in parallel, in topological order.
// It is atomic and idempotent.
-// Precondition: ld.Mode >= LoadTypes.
+// Precondition: ld.Mode&NeedTypes.
func (ld *loader) loadRecursive(lpkg *loaderPackage) {
lpkg.loadOnce.Do(func() {
// Load the direct dependencies, in parallel.
@@ -563,6 +689,7 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) {
lpkg.Fset = ld.Fset
lpkg.Syntax = []*ast.File{}
lpkg.TypesInfo = new(types.Info)
+ lpkg.TypesSizes = ld.sizes
return
}
@@ -570,21 +697,69 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) {
// This avoids skew between golist and go/types when the files'
// package declarations are inconsistent.
lpkg.Types = types.NewPackage(lpkg.PkgPath, lpkg.Name)
+ lpkg.Fset = ld.Fset
+ // Subtle: we populate all Types fields with an empty Package
+ // before loading export data so that export data processing
+ // never has to create a types.Package for an indirect dependency,
+ // which would then require that such created packages be explicitly
+ // inserted back into the Import graph as a final step after export data loading.
+ // The Diamond test exercises this case.
+ if !lpkg.needtypes {
+ return
+ }
if !lpkg.needsrc {
ld.loadFromExportData(lpkg)
return // not a source package, don't get syntax trees
}
- 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
+ // Convert various error types into the one true Error.
+ var errs []Error
+ switch err := err.(type) {
+ case Error:
+ // from driver
+ errs = append(errs, err)
+
+ case *os.PathError:
+ // from parser
+ errs = append(errs, Error{
+ Pos: err.Path + ":1",
+ Msg: err.Err.Error(),
+ Kind: ParseError,
+ })
+
+ case scanner.ErrorList:
+ // from parser
+ for _, err := range err {
+ errs = append(errs, Error{
+ Pos: err.Pos.String(),
+ Msg: err.Msg,
+ Kind: ParseError,
+ })
+ }
+
+ case types.Error:
+ // from type checker
+ errs = append(errs, Error{
+ Pos: err.Fset.Position(err.Pos).String(),
+ Msg: err.Msg,
+ Kind: TypeError,
+ })
+
+ default:
+ // unexpected impoverished error from parser?
+ errs = append(errs, Error{
+ Pos: "-",
+ Msg: err.Error(),
+ Kind: UnknownError,
+ })
+
+ // If you see this error message, please file a bug.
+ log.Printf("internal error: error %q (%T) without position", err, err)
}
- ld.Error(err)
- lpkg.Errors = append(lpkg.Errors, err)
+
+ lpkg.Errors = append(lpkg.Errors, errs...)
}
files, errs := ld.parseFiles(lpkg.CompiledGoFiles)
@@ -592,7 +767,6 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) {
appendError(err)
}
- lpkg.Fset = ld.Fset
lpkg.Syntax = files
lpkg.TypesInfo = &types.Info{
@@ -603,10 +777,9 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) {
Scopes: make(map[ast.Node]*types.Scope),
Selections: make(map[*ast.SelectorExpr]*types.Selection),
}
+ lpkg.TypesSizes = ld.sizes
- // Copy the prototype types.Config as it must vary across Packages.
- tc := ld.TypeChecker // copy
- tc.Importer = importerFunc(func(path string) (*types.Package, error) {
+ importer := importerFunc(func(path string) (*types.Package, error) {
if path == "unsafe" {
return types.Unsafe, nil
}
@@ -630,10 +803,20 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) {
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)
+ tc := &types.Config{
+ Importer: importer,
+
+ // Type-check bodies of functions only in non-initial packages.
+ // Example: for import graph A->B->C and initial packages {A,C},
+ // we can ignore function bodies in B.
+ IgnoreFuncBodies: (ld.Mode&(NeedDeps|NeedTypesInfo) == 0) && !lpkg.initial,
+
+ Error: appendError,
+ Sizes: ld.sizes,
+ }
+ types.NewChecker(tc, ld.Fset, lpkg.Types, lpkg.TypesInfo).Files(lpkg.Syntax)
lpkg.importErrors = nil // no longer needed
@@ -650,8 +833,8 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) {
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())))
+ err := types.Error{Fset: ld.Fset, Pos: imp.Pos(), Msg: `import "C" ignored`}
+ appendError(err)
break outer
}
}
@@ -659,14 +842,16 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) {
}
// Record accumulated errors.
- for _, imp := range lpkg.Imports {
- if imp.IllTyped {
- hardErrors = true
- break
+ illTyped := len(lpkg.Errors) > 0
+ if !illTyped {
+ for _, imp := range lpkg.Imports {
+ if imp.IllTyped {
+ illTyped = true
+ break
+ }
}
}
-
- lpkg.IllTyped = hardErrors
+ lpkg.IllTyped = illTyped
}
// An importFunc is an implementation of the single-method
@@ -692,11 +877,30 @@ func (ld *loader) parseFiles(filenames []string) ([]*ast.File, []error) {
parsed := make([]*ast.File, n)
errors := make([]error, n)
for i, file := range filenames {
+ if ld.Config.Context.Err() != nil {
+ parsed[i] = nil
+ errors[i] = ld.Config.Context.Err()
+ continue
+ }
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)
+ var src []byte
+ for f, contents := range ld.Config.Overlay {
+ if sameFile(f, filename) {
+ src = contents
+ }
+ }
+ var err error
+ if src == nil {
+ src, err = ioutil.ReadFile(filename)
+ }
+ if err != nil {
+ parsed[i], errors[i] = nil, err
+ } else {
+ parsed[i], errors[i] = ld.ParseFile(ld.Fset, filename, src)
+ }
<-ioLimit // signal
wg.Done()
}(i, file)
@@ -725,6 +929,29 @@ func (ld *loader) parseFiles(filenames []string) ([]*ast.File, []error) {
return parsed, errors
}
+// sameFile returns true if x and y have the same basename and denote
+// the same file.
+//
+func sameFile(x, y string) bool {
+ if x == y {
+ // It could be the case that y doesn't exist.
+ // For instance, it may be an overlay file that
+ // hasn't been written to disk. To handle that case
+ // let x == y through. (We added the exact absolute path
+ // string to the CompiledGoFiles list, so the unwritten
+ // overlay case implies x==y.)
+ return true
+ }
+ if strings.EqualFold(filepath.Base(x), filepath.Base(y)) { // (optimisation)
+ if xi, err := os.Stat(x); err == nil {
+ if yi, err := os.Stat(y); err == nil {
+ return os.SameFile(xi, yi)
+ }
+ }
+ }
+ return false
+}
+
// 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) {
@@ -787,7 +1014,11 @@ func (ld *loader) loadFromExportData(lpkg *loaderPackage) (*types.Package, error
// package that might possibly be mentioned by the
// current package---its transitive closure.
//
- // TODO(adonovan): it would be more simpler and more efficient
+ // In loadPackage, we unconditionally create a types.Package for
+ // each dependency so that export data loading does not
+ // create new ones.
+ //
+ // TODO(adonovan): it would be simpler and more efficient
// if the export data machinery invoked a callback to
// get-or-create a package instead of a map.
//
@@ -806,12 +1037,16 @@ func (ld *loader) loadFromExportData(lpkg *loaderPackage) (*types.Package, error
}
visit(lpkg.Imports)
+ viewLen := len(view) + 1 // adding the self package
// Parse the export data.
- // (May create/modify packages in view.)
+ // (May modify incomplete packages in view but not create new ones.)
tpkg, err := gcexportdata.Read(r, ld.Fset, view, lpkg.PkgPath)
if err != nil {
return nil, fmt.Errorf("reading %s: %v", lpkg.ExportFile, err)
}
+ if viewLen != len(view) {
+ log.Fatalf("Unexpected package creation during export data loading")
+ }
lpkg.Types = tpkg
lpkg.IllTyped = false
@@ -820,5 +1055,5 @@ func (ld *loader) loadFromExportData(lpkg *loaderPackage) (*types.Package, error
}
func usesExportData(cfg *Config) bool {
- return LoadTypes <= cfg.Mode && cfg.Mode < LoadAllSyntax
+ return cfg.Mode&NeedExportsFile != 0 || cfg.Mode&NeedTypes != 0 && cfg.Mode&NeedTypesInfo == 0
}
diff --git a/vendor/golang.org/x/tools/go/packages/visit.go b/vendor/golang.org/x/tools/go/packages/visit.go
new file mode 100644
index 00000000..b13cb081
--- /dev/null
+++ b/vendor/golang.org/x/tools/go/packages/visit.go
@@ -0,0 +1,55 @@
+package packages
+
+import (
+ "fmt"
+ "os"
+ "sort"
+)
+
+// Visit visits all the packages in the import graph whose roots are
+// pkgs, calling the optional pre function the first time each package
+// is encountered (preorder), and the optional post function after a
+// package's dependencies have been visited (postorder).
+// The boolean result of pre(pkg) determines whether
+// the imports of package pkg are visited.
+func Visit(pkgs []*Package, pre func(*Package) bool, post func(*Package)) {
+ seen := make(map[*Package]bool)
+ var visit func(*Package)
+ visit = func(pkg *Package) {
+ if !seen[pkg] {
+ seen[pkg] = true
+
+ if pre == nil || pre(pkg) {
+ paths := make([]string, 0, len(pkg.Imports))
+ for path := range pkg.Imports {
+ paths = append(paths, path)
+ }
+ sort.Strings(paths) // Imports is a map, this makes visit stable
+ for _, path := range paths {
+ visit(pkg.Imports[path])
+ }
+ }
+
+ if post != nil {
+ post(pkg)
+ }
+ }
+ }
+ for _, pkg := range pkgs {
+ visit(pkg)
+ }
+}
+
+// PrintErrors prints to os.Stderr the accumulated errors of all
+// packages in the import graph rooted at pkgs, dependencies first.
+// PrintErrors returns the number of errors printed.
+func PrintErrors(pkgs []*Package) int {
+ var n int
+ Visit(pkgs, nil, func(pkg *Package) {
+ for _, err := range pkg.Errors {
+ fmt.Fprintln(os.Stderr, err)
+ n++
+ }
+ })
+ return n
+}