aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/golang.org/x/tools/go/packages/golist.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/golang.org/x/tools/go/packages/golist.go')
-rw-r--r--vendor/golang.org/x/tools/go/packages/golist.go337
1 files changed, 337 insertions, 0 deletions
diff --git a/vendor/golang.org/x/tools/go/packages/golist.go b/vendor/golang.org/x/tools/go/packages/golist.go
new file mode 100644
index 00000000..26d62771
--- /dev/null
+++ b/vendor/golang.org/x/tools/go/packages/golist.go
@@ -0,0 +1,337 @@
+// 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 (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "log"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+)
+
+// 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
+}
+
+// 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) {
+ // Determine files requested in contains patterns
+ var containFiles []string
+ restPatterns := make([]string, 0, len(patterns))
+ for _, pattern := range patterns {
+ if strings.HasPrefix(pattern, "contains:") {
+ containFile := strings.TrimPrefix(pattern, "contains:")
+ containFiles = append(containFiles, containFile)
+ } else {
+ restPatterns = append(restPatterns, 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
+ var err error
+
+ // see if we have any patterns to pass through to go list.
+ if len(patterns) > 0 {
+ response, err = listfunc(cfg, patterns...)
+ if err != nil {
+ return nil, err
+ }
+ } else {
+ response = &driverResponse{}
+ }
+
+ // Run go list for contains: patterns.
+ seenPkgs := make(map[string]*Package) // for deduplication. different containing queries could produce same packages
+ if len(containFiles) > 0 {
+ for _, pkg := range response.Packages {
+ seenPkgs[pkg.ID] = pkg
+ }
+ }
+ for _, f := range containFiles {
+ // TODO(matloob): Do only one query per directory.
+ fdir := filepath.Dir(f)
+ cfg.Dir = fdir
+ dirResponse, err := listfunc(cfg, ".")
+ if err != nil {
+ return nil, err
+ }
+ isRoot := make(map[string]bool, len(dirResponse.Roots))
+ for _, root := range dirResponse.Roots {
+ isRoot[root] = true
+ }
+ for _, pkg := range dirResponse.Packages {
+ // Add any new packages to the main set
+ // 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)
+ }
+ // 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)
+ break
+ }
+ }
+ }
+ }
+ return response, nil
+}
+
+// Fields must match go list;
+// see $GOROOT/src/cmd/go/internal/load/pkg.go.
+type jsonPackage struct {
+ ImportPath string
+ Dir string
+ Name string
+ Export string
+ GoFiles []string
+ CompiledGoFiles []string
+ CFiles []string
+ CgoFiles []string
+ CXXFiles []string
+ MFiles []string
+ HFiles []string
+ FFiles []string
+ SFiles []string
+ SwigFiles []string
+ SwigCXXFiles []string
+ SysoFiles []string
+ Imports []string
+ ImportMap map[string]string
+ Deps []string
+ TestGoFiles []string
+ TestImports []string
+ XTestGoFiles []string
+ XTestImports []string
+ ForTest string // q in a "p [q.test]" package, else ""
+ DepOnly bool
+}
+
+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) {
+ // go list uses the following identifiers in ImportPath and Imports:
+ //
+ // "p" -- importable package or main (command)
+ // "q.test" -- q's test executable
+ // "p [q.test]" -- variant of p as built for q's test executable
+ // "q_test [q.test]" -- q's external test package
+ //
+ // The packages p that are built differently for a test q.test
+ // are q itself, plus any helpers used by the external test q_test,
+ // typically including "testing" and all its dependencies.
+
+ // Run "go list" for complete
+ // information on the specified packages.
+ buf, err := golist(cfg, golistargs(cfg, words))
+ if err != nil {
+ return nil, err
+ }
+ // Decode the JSON and convert it to Package form.
+ var response driverResponse
+ 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)
+ }
+
+ // 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.
+ continue
+ }
+
+ id := p.ImportPath
+
+ // Extract the PkgPath from the package's ID.
+ pkgpath := id
+ if i := strings.IndexByte(id, ' '); i >= 0 {
+ pkgpath = id[:i]
+ }
+
+ if pkgpath == "unsafe" {
+ p.GoFiles = nil // ignore fake unsafe.go file
+ }
+
+ // Assume go list emits only absolute paths for Dir.
+ if !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)
+ }
+
+ // imports
+ //
+ // Imports contains the IDs of all imported packages.
+ // ImportsMap records (path, ID) only where they differ.
+ ids := make(map[string]bool)
+ for _, id := range p.Imports {
+ ids[id] = true
+ }
+ imports := make(map[string]*Package)
+ for path, id := range p.ImportMap {
+ imports[path] = &Package{ID: id} // non-identity import
+ delete(ids, id)
+ }
+ for id := range ids {
+ if id == "C" {
+ continue
+ }
+
+ 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,
+ }
+ // TODO(matloob): Temporary hack since CompiledGoFiles isn't always set.
+ if len(pkg.CompiledGoFiles) == 0 {
+ pkg.CompiledGoFiles = pkg.GoFiles
+ }
+ response.Packages = append(response.Packages, pkg)
+ }
+
+ return &response, nil
+}
+
+// absJoin absolutizes and flattens the lists of files.
+func absJoin(dir string, fileses ...[]string) (res []string) {
+ for _, files := range fileses {
+ for _, file := range files {
+ if !filepath.IsAbs(file) {
+ file = filepath.Join(dir, file)
+ }
+ res = append(res, file)
+ }
+ }
+ return res
+}
+
+func golistargs(cfg *Config, words []string) []string {
+ fullargs := []string{
+ "list", "-e", "-json", "-compiled",
+ fmt.Sprintf("-test=%t", cfg.Tests),
+ fmt.Sprintf("-export=%t", usesExportData(cfg)),
+ fmt.Sprintf("-deps=%t", cfg.Mode >= LoadImports),
+ }
+ fullargs = append(fullargs, cfg.Flags...)
+ 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)
+ cmd := exec.CommandContext(cfg.Context, "go", args...)
+ cmd.Env = cfg.Env
+ cmd.Dir = cfg.Dir
+ cmd.Stdout = out
+ cmd.Stderr = new(bytes.Buffer)
+ if err := cmd.Run(); err != nil {
+ 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)
+ }
+
+ // 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)}
+ }
+
+ // 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)
+ }
+ }
+
+ // 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)
+ }
+
+ // debugging
+ if false {
+ fmt.Fprintln(os.Stderr, out)
+ }
+
+ return out, nil
+}