1// Copyright 2018 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package packages
6
7import (
8	"bytes"
9	"encoding/json"
10	"fmt"
11	"go/types"
12	"io/ioutil"
13	"log"
14	"os"
15	"os/exec"
16	"path/filepath"
17	"reflect"
18	"regexp"
19	"strconv"
20	"strings"
21	"sync"
22	"time"
23
24	"golang.org/x/tools/go/internal/packagesdriver"
25	"golang.org/x/tools/internal/gopathwalk"
26	"golang.org/x/tools/internal/semver"
27)
28
29// debug controls verbose logging.
30var debug, _ = strconv.ParseBool(os.Getenv("GOPACKAGESDEBUG"))
31
32// A goTooOldError reports that the go command
33// found by exec.LookPath is too old to use the new go list behavior.
34type goTooOldError struct {
35	error
36}
37
38// responseDeduper wraps a driverResponse, deduplicating its contents.
39type responseDeduper struct {
40	seenRoots    map[string]bool
41	seenPackages map[string]*Package
42	dr           *driverResponse
43}
44
45// init fills in r with a driverResponse.
46func (r *responseDeduper) init(dr *driverResponse) {
47	r.dr = dr
48	r.seenRoots = map[string]bool{}
49	r.seenPackages = map[string]*Package{}
50	for _, pkg := range dr.Packages {
51		r.seenPackages[pkg.ID] = pkg
52	}
53	for _, root := range dr.Roots {
54		r.seenRoots[root] = true
55	}
56}
57
58func (r *responseDeduper) addPackage(p *Package) {
59	if r.seenPackages[p.ID] != nil {
60		return
61	}
62	r.seenPackages[p.ID] = p
63	r.dr.Packages = append(r.dr.Packages, p)
64}
65
66func (r *responseDeduper) addRoot(id string) {
67	if r.seenRoots[id] {
68		return
69	}
70	r.seenRoots[id] = true
71	r.dr.Roots = append(r.dr.Roots, id)
72}
73
74// goListDriver uses the go list command to interpret the patterns and produce
75// the build system package structure.
76// See driver for more details.
77func goListDriver(cfg *Config, patterns ...string) (*driverResponse, error) {
78	var sizes types.Sizes
79	var sizeserr error
80	var sizeswg sync.WaitGroup
81	if cfg.Mode >= LoadTypes {
82		sizeswg.Add(1)
83		go func() {
84			sizes, sizeserr = getSizes(cfg)
85			sizeswg.Done()
86		}()
87	}
88
89	// Determine files requested in contains patterns
90	var containFiles []string
91	var packagesNamed []string
92	restPatterns := make([]string, 0, len(patterns))
93	// Extract file= and other [querytype]= patterns. Report an error if querytype
94	// doesn't exist.
95extractQueries:
96	for _, pattern := range patterns {
97		eqidx := strings.Index(pattern, "=")
98		if eqidx < 0 {
99			restPatterns = append(restPatterns, pattern)
100		} else {
101			query, value := pattern[:eqidx], pattern[eqidx+len("="):]
102			switch query {
103			case "file":
104				containFiles = append(containFiles, value)
105			case "pattern":
106				restPatterns = append(restPatterns, value)
107			case "iamashamedtousethedisabledqueryname":
108				packagesNamed = append(packagesNamed, value)
109			case "": // not a reserved query
110				restPatterns = append(restPatterns, pattern)
111			default:
112				for _, rune := range query {
113					if rune < 'a' || rune > 'z' { // not a reserved query
114						restPatterns = append(restPatterns, pattern)
115						continue extractQueries
116					}
117				}
118				// Reject all other patterns containing "="
119				return nil, fmt.Errorf("invalid query type %q in query pattern %q", query, pattern)
120			}
121		}
122	}
123
124	// TODO(matloob): Remove the definition of listfunc and just use golistPackages once go1.12 is released.
125	var listfunc driver
126	var isFallback bool
127	listfunc = func(cfg *Config, words ...string) (*driverResponse, error) {
128		response, err := golistDriverCurrent(cfg, words...)
129		if _, ok := err.(goTooOldError); ok {
130			isFallback = true
131			listfunc = golistDriverFallback
132			return listfunc(cfg, words...)
133		}
134		listfunc = golistDriverCurrent
135		return response, err
136	}
137
138	response := &responseDeduper{}
139	var err error
140
141	// See if we have any patterns to pass through to go list. Zero initial
142	// patterns also requires a go list call, since it's the equivalent of
143	// ".".
144	if len(restPatterns) > 0 || len(patterns) == 0 {
145		dr, err := listfunc(cfg, restPatterns...)
146		if err != nil {
147			return nil, err
148		}
149		response.init(dr)
150	} else {
151		response.init(&driverResponse{})
152	}
153
154	sizeswg.Wait()
155	if sizeserr != nil {
156		return nil, sizeserr
157	}
158	// types.SizesFor always returns nil or a *types.StdSizes
159	response.dr.Sizes, _ = sizes.(*types.StdSizes)
160
161	var containsCandidates []string
162
163	if len(containFiles) != 0 {
164		if err := runContainsQueries(cfg, listfunc, isFallback, response, containFiles); err != nil {
165			return nil, err
166		}
167	}
168
169	if len(packagesNamed) != 0 {
170		if err := runNamedQueries(cfg, listfunc, response, packagesNamed); err != nil {
171			return nil, err
172		}
173	}
174
175	modifiedPkgs, needPkgs, err := processGolistOverlay(cfg, response.dr)
176	if err != nil {
177		return nil, err
178	}
179	if len(containFiles) > 0 {
180		containsCandidates = append(containsCandidates, modifiedPkgs...)
181		containsCandidates = append(containsCandidates, needPkgs...)
182	}
183
184	if len(needPkgs) > 0 {
185		addNeededOverlayPackages(cfg, listfunc, response, needPkgs)
186		if err != nil {
187			return nil, err
188		}
189	}
190	// Check candidate packages for containFiles.
191	if len(containFiles) > 0 {
192		for _, id := range containsCandidates {
193			pkg := response.seenPackages[id]
194			for _, f := range containFiles {
195				for _, g := range pkg.GoFiles {
196					if sameFile(f, g) {
197						response.addRoot(id)
198					}
199				}
200			}
201		}
202	}
203
204	return response.dr, nil
205}
206
207func addNeededOverlayPackages(cfg *Config, driver driver, response *responseDeduper, pkgs []string) error {
208	dr, err := driver(cfg, pkgs...)
209	if err != nil {
210		return err
211	}
212	for _, pkg := range dr.Packages {
213		response.addPackage(pkg)
214	}
215	return nil
216}
217
218func runContainsQueries(cfg *Config, driver driver, isFallback bool, response *responseDeduper, queries []string) error {
219	for _, query := range queries {
220		// TODO(matloob): Do only one query per directory.
221		fdir := filepath.Dir(query)
222		// Pass absolute path of directory to go list so that it knows to treat it as a directory,
223		// not a package path.
224		pattern, err := filepath.Abs(fdir)
225		if err != nil {
226			return fmt.Errorf("could not determine absolute path of file= query path %q: %v", query, err)
227		}
228		if isFallback {
229			pattern = "."
230			cfg.Dir = fdir
231		}
232
233		dirResponse, err := driver(cfg, pattern)
234		if err != nil {
235			return err
236		}
237		isRoot := make(map[string]bool, len(dirResponse.Roots))
238		for _, root := range dirResponse.Roots {
239			isRoot[root] = true
240		}
241		for _, pkg := range dirResponse.Packages {
242			// Add any new packages to the main set
243			// We don't bother to filter packages that will be dropped by the changes of roots,
244			// that will happen anyway during graph construction outside this function.
245			// Over-reporting packages is not a problem.
246			response.addPackage(pkg)
247			// if the package was not a root one, it cannot have the file
248			if !isRoot[pkg.ID] {
249				continue
250			}
251			for _, pkgFile := range pkg.GoFiles {
252				if filepath.Base(query) == filepath.Base(pkgFile) {
253					response.addRoot(pkg.ID)
254					break
255				}
256			}
257		}
258	}
259	return nil
260}
261
262// modCacheRegexp splits a path in a module cache into module, module version, and package.
263var modCacheRegexp = regexp.MustCompile(`(.*)@([^/\\]*)(.*)`)
264
265func runNamedQueries(cfg *Config, driver driver, response *responseDeduper, queries []string) error {
266	// calling `go env` isn't free; bail out if there's nothing to do.
267	if len(queries) == 0 {
268		return nil
269	}
270	// Determine which directories are relevant to scan.
271	roots, modRoot, err := roots(cfg)
272	if err != nil {
273		return err
274	}
275
276	// Scan the selected directories. Simple matches, from GOPATH/GOROOT
277	// or the local module, can simply be "go list"ed. Matches from the
278	// module cache need special treatment.
279	var matchesMu sync.Mutex
280	var simpleMatches, modCacheMatches []string
281	add := func(root gopathwalk.Root, dir string) {
282		// Walk calls this concurrently; protect the result slices.
283		matchesMu.Lock()
284		defer matchesMu.Unlock()
285
286		path := dir
287		if dir != root.Path {
288			path = dir[len(root.Path)+1:]
289		}
290		if pathMatchesQueries(path, queries) {
291			switch root.Type {
292			case gopathwalk.RootModuleCache:
293				modCacheMatches = append(modCacheMatches, path)
294			case gopathwalk.RootCurrentModule:
295				// We'd need to read go.mod to find the full
296				// import path. Relative's easier.
297				rel, err := filepath.Rel(cfg.Dir, dir)
298				if err != nil {
299					// This ought to be impossible, since
300					// we found dir in the current module.
301					panic(err)
302				}
303				simpleMatches = append(simpleMatches, "./"+rel)
304			case gopathwalk.RootGOPATH, gopathwalk.RootGOROOT:
305				simpleMatches = append(simpleMatches, path)
306			}
307		}
308	}
309
310	startWalk := time.Now()
311	gopathwalk.Walk(roots, add, gopathwalk.Options{ModulesEnabled: modRoot != "", Debug: debug})
312	if debug {
313		log.Printf("%v for walk", time.Since(startWalk))
314	}
315
316	// Weird special case: the top-level package in a module will be in
317	// whatever directory the user checked the repository out into. It's
318	// more reasonable for that to not match the package name. So, if there
319	// are any Go files in the mod root, query it just to be safe.
320	if modRoot != "" {
321		rel, err := filepath.Rel(cfg.Dir, modRoot)
322		if err != nil {
323			panic(err) // See above.
324		}
325
326		files, err := ioutil.ReadDir(modRoot)
327		for _, f := range files {
328			if strings.HasSuffix(f.Name(), ".go") {
329				simpleMatches = append(simpleMatches, rel)
330				break
331			}
332		}
333	}
334
335	addResponse := func(r *driverResponse) {
336		for _, pkg := range r.Packages {
337			response.addPackage(pkg)
338			for _, name := range queries {
339				if pkg.Name == name {
340					response.addRoot(pkg.ID)
341					break
342				}
343			}
344		}
345	}
346
347	if len(simpleMatches) != 0 {
348		resp, err := driver(cfg, simpleMatches...)
349		if err != nil {
350			return err
351		}
352		addResponse(resp)
353	}
354
355	// Module cache matches are tricky. We want to avoid downloading new
356	// versions of things, so we need to use the ones present in the cache.
357	// go list doesn't accept version specifiers, so we have to write out a
358	// temporary module, and do the list in that module.
359	if len(modCacheMatches) != 0 {
360		// Collect all the matches, deduplicating by major version
361		// and preferring the newest.
362		type modInfo struct {
363			mod   string
364			major string
365		}
366		mods := make(map[modInfo]string)
367		var imports []string
368		for _, modPath := range modCacheMatches {
369			matches := modCacheRegexp.FindStringSubmatch(modPath)
370			mod, ver := filepath.ToSlash(matches[1]), matches[2]
371			importPath := filepath.ToSlash(filepath.Join(matches[1], matches[3]))
372
373			major := semver.Major(ver)
374			if prevVer, ok := mods[modInfo{mod, major}]; !ok || semver.Compare(ver, prevVer) > 0 {
375				mods[modInfo{mod, major}] = ver
376			}
377
378			imports = append(imports, importPath)
379		}
380
381		// Build the temporary module.
382		var gomod bytes.Buffer
383		gomod.WriteString("module modquery\nrequire (\n")
384		for mod, version := range mods {
385			gomod.WriteString("\t" + mod.mod + " " + version + "\n")
386		}
387		gomod.WriteString(")\n")
388
389		tmpCfg := *cfg
390
391		// We're only trying to look at stuff in the module cache, so
392		// disable the network. This should speed things up, and has
393		// prevented errors in at least one case, #28518.
394		tmpCfg.Env = append(append([]string{"GOPROXY=off"}, cfg.Env...))
395
396		var err error
397		tmpCfg.Dir, err = ioutil.TempDir("", "gopackages-modquery")
398		if err != nil {
399			return err
400		}
401		defer os.RemoveAll(tmpCfg.Dir)
402
403		if err := ioutil.WriteFile(filepath.Join(tmpCfg.Dir, "go.mod"), gomod.Bytes(), 0777); err != nil {
404			return fmt.Errorf("writing go.mod for module cache query: %v", err)
405		}
406
407		// Run the query, using the import paths calculated from the matches above.
408		resp, err := driver(&tmpCfg, imports...)
409		if err != nil {
410			return fmt.Errorf("querying module cache matches: %v", err)
411		}
412		addResponse(resp)
413	}
414
415	return nil
416}
417
418func getSizes(cfg *Config) (types.Sizes, error) {
419	return packagesdriver.GetSizesGolist(cfg.Context, cfg.BuildFlags, cfg.Env, cfg.Dir, usesExportData(cfg))
420}
421
422// roots selects the appropriate paths to walk based on the passed-in configuration,
423// particularly the environment and the presence of a go.mod in cfg.Dir's parents.
424func roots(cfg *Config) ([]gopathwalk.Root, string, error) {
425	stdout, err := invokeGo(cfg, "env", "GOROOT", "GOPATH", "GOMOD")
426	if err != nil {
427		return nil, "", err
428	}
429
430	fields := strings.Split(stdout.String(), "\n")
431	if len(fields) != 4 || len(fields[3]) != 0 {
432		return nil, "", fmt.Errorf("go env returned unexpected output: %q", stdout.String())
433	}
434	goroot, gopath, gomod := fields[0], filepath.SplitList(fields[1]), fields[2]
435	var modDir string
436	if gomod != "" {
437		modDir = filepath.Dir(gomod)
438	}
439
440	var roots []gopathwalk.Root
441	// Always add GOROOT.
442	roots = append(roots, gopathwalk.Root{filepath.Join(goroot, "/src"), gopathwalk.RootGOROOT})
443	// If modules are enabled, scan the module dir.
444	if modDir != "" {
445		roots = append(roots, gopathwalk.Root{modDir, gopathwalk.RootCurrentModule})
446	}
447	// Add either GOPATH/src or GOPATH/pkg/mod, depending on module mode.
448	for _, p := range gopath {
449		if modDir != "" {
450			roots = append(roots, gopathwalk.Root{filepath.Join(p, "/pkg/mod"), gopathwalk.RootModuleCache})
451		} else {
452			roots = append(roots, gopathwalk.Root{filepath.Join(p, "/src"), gopathwalk.RootGOPATH})
453		}
454	}
455
456	return roots, modDir, nil
457}
458
459// These functions were copied from goimports. See further documentation there.
460
461// pathMatchesQueries is adapted from pkgIsCandidate.
462// TODO: is it reasonable to do Contains here, rather than an exact match on a path component?
463func pathMatchesQueries(path string, queries []string) bool {
464	lastTwo := lastTwoComponents(path)
465	for _, query := range queries {
466		if strings.Contains(lastTwo, query) {
467			return true
468		}
469		if hasHyphenOrUpperASCII(lastTwo) && !hasHyphenOrUpperASCII(query) {
470			lastTwo = lowerASCIIAndRemoveHyphen(lastTwo)
471			if strings.Contains(lastTwo, query) {
472				return true
473			}
474		}
475	}
476	return false
477}
478
479// lastTwoComponents returns at most the last two path components
480// of v, using either / or \ as the path separator.
481func lastTwoComponents(v string) string {
482	nslash := 0
483	for i := len(v) - 1; i >= 0; i-- {
484		if v[i] == '/' || v[i] == '\\' {
485			nslash++
486			if nslash == 2 {
487				return v[i:]
488			}
489		}
490	}
491	return v
492}
493
494func hasHyphenOrUpperASCII(s string) bool {
495	for i := 0; i < len(s); i++ {
496		b := s[i]
497		if b == '-' || ('A' <= b && b <= 'Z') {
498			return true
499		}
500	}
501	return false
502}
503
504func lowerASCIIAndRemoveHyphen(s string) (ret string) {
505	buf := make([]byte, 0, len(s))
506	for i := 0; i < len(s); i++ {
507		b := s[i]
508		switch {
509		case b == '-':
510			continue
511		case 'A' <= b && b <= 'Z':
512			buf = append(buf, b+('a'-'A'))
513		default:
514			buf = append(buf, b)
515		}
516	}
517	return string(buf)
518}
519
520// Fields must match go list;
521// see $GOROOT/src/cmd/go/internal/load/pkg.go.
522type jsonPackage struct {
523	ImportPath      string
524	Dir             string
525	Name            string
526	Export          string
527	GoFiles         []string
528	CompiledGoFiles []string
529	CFiles          []string
530	CgoFiles        []string
531	CXXFiles        []string
532	MFiles          []string
533	HFiles          []string
534	FFiles          []string
535	SFiles          []string
536	SwigFiles       []string
537	SwigCXXFiles    []string
538	SysoFiles       []string
539	Imports         []string
540	ImportMap       map[string]string
541	Deps            []string
542	TestGoFiles     []string
543	TestImports     []string
544	XTestGoFiles    []string
545	XTestImports    []string
546	ForTest         string // q in a "p [q.test]" package, else ""
547	DepOnly         bool
548
549	Error *jsonPackageError
550}
551
552type jsonPackageError struct {
553	ImportStack []string
554	Pos         string
555	Err         string
556}
557
558func otherFiles(p *jsonPackage) [][]string {
559	return [][]string{p.CFiles, p.CXXFiles, p.MFiles, p.HFiles, p.FFiles, p.SFiles, p.SwigFiles, p.SwigCXXFiles, p.SysoFiles}
560}
561
562// golistDriverCurrent uses the "go list" command to expand the
563// pattern words and return metadata for the specified packages.
564// dir may be "" and env may be nil, as per os/exec.Command.
565func golistDriverCurrent(cfg *Config, words ...string) (*driverResponse, error) {
566	// go list uses the following identifiers in ImportPath and Imports:
567	//
568	// 	"p"			-- importable package or main (command)
569	//      "q.test"		-- q's test executable
570	// 	"p [q.test]"		-- variant of p as built for q's test executable
571	//	"q_test [q.test]"	-- q's external test package
572	//
573	// The packages p that are built differently for a test q.test
574	// are q itself, plus any helpers used by the external test q_test,
575	// typically including "testing" and all its dependencies.
576
577	// Run "go list" for complete
578	// information on the specified packages.
579	buf, err := invokeGo(cfg, golistargs(cfg, words)...)
580	if err != nil {
581		return nil, err
582	}
583	seen := make(map[string]*jsonPackage)
584	// Decode the JSON and convert it to Package form.
585	var response driverResponse
586	for dec := json.NewDecoder(buf); dec.More(); {
587		p := new(jsonPackage)
588		if err := dec.Decode(p); err != nil {
589			return nil, fmt.Errorf("JSON decoding failed: %v", err)
590		}
591
592		if p.ImportPath == "" {
593			// The documentation for go list says that “[e]rroneous packages will have
594			// a non-empty ImportPath”. If for some reason it comes back empty, we
595			// prefer to error out rather than silently discarding data or handing
596			// back a package without any way to refer to it.
597			if p.Error != nil {
598				return nil, Error{
599					Pos: p.Error.Pos,
600					Msg: p.Error.Err,
601				}
602			}
603			return nil, fmt.Errorf("package missing import path: %+v", p)
604		}
605
606		if old, found := seen[p.ImportPath]; found {
607			if !reflect.DeepEqual(p, old) {
608				return nil, fmt.Errorf("go list repeated package %v with different values", p.ImportPath)
609			}
610			// skip the duplicate
611			continue
612		}
613		seen[p.ImportPath] = p
614
615		pkg := &Package{
616			Name:            p.Name,
617			ID:              p.ImportPath,
618			GoFiles:         absJoin(p.Dir, p.GoFiles, p.CgoFiles),
619			CompiledGoFiles: absJoin(p.Dir, p.CompiledGoFiles),
620			OtherFiles:      absJoin(p.Dir, otherFiles(p)...),
621		}
622
623		// Work around https://golang.org/issue/28749:
624		// cmd/go puts assembly, C, and C++ files in CompiledGoFiles.
625		// Filter out any elements of CompiledGoFiles that are also in OtherFiles.
626		// We have to keep this workaround in place until go1.12 is a distant memory.
627		if len(pkg.OtherFiles) > 0 {
628			other := make(map[string]bool, len(pkg.OtherFiles))
629			for _, f := range pkg.OtherFiles {
630				other[f] = true
631			}
632
633			out := pkg.CompiledGoFiles[:0]
634			for _, f := range pkg.CompiledGoFiles {
635				if other[f] {
636					continue
637				}
638				out = append(out, f)
639			}
640			pkg.CompiledGoFiles = out
641		}
642
643		// Extract the PkgPath from the package's ID.
644		if i := strings.IndexByte(pkg.ID, ' '); i >= 0 {
645			pkg.PkgPath = pkg.ID[:i]
646		} else {
647			pkg.PkgPath = pkg.ID
648		}
649
650		if pkg.PkgPath == "unsafe" {
651			pkg.GoFiles = nil // ignore fake unsafe.go file
652		}
653
654		// Assume go list emits only absolute paths for Dir.
655		if p.Dir != "" && !filepath.IsAbs(p.Dir) {
656			log.Fatalf("internal error: go list returned non-absolute Package.Dir: %s", p.Dir)
657		}
658
659		if p.Export != "" && !filepath.IsAbs(p.Export) {
660			pkg.ExportFile = filepath.Join(p.Dir, p.Export)
661		} else {
662			pkg.ExportFile = p.Export
663		}
664
665		// imports
666		//
667		// Imports contains the IDs of all imported packages.
668		// ImportsMap records (path, ID) only where they differ.
669		ids := make(map[string]bool)
670		for _, id := range p.Imports {
671			ids[id] = true
672		}
673		pkg.Imports = make(map[string]*Package)
674		for path, id := range p.ImportMap {
675			pkg.Imports[path] = &Package{ID: id} // non-identity import
676			delete(ids, id)
677		}
678		for id := range ids {
679			if id == "C" {
680				continue
681			}
682
683			pkg.Imports[id] = &Package{ID: id} // identity import
684		}
685		if !p.DepOnly {
686			response.Roots = append(response.Roots, pkg.ID)
687		}
688
689		// Work around for pre-go.1.11 versions of go list.
690		// TODO(matloob): they should be handled by the fallback.
691		// Can we delete this?
692		if len(pkg.CompiledGoFiles) == 0 {
693			pkg.CompiledGoFiles = pkg.GoFiles
694		}
695
696		if p.Error != nil {
697			pkg.Errors = append(pkg.Errors, Error{
698				Pos: p.Error.Pos,
699				Msg: p.Error.Err,
700			})
701		}
702
703		response.Packages = append(response.Packages, pkg)
704	}
705
706	return &response, nil
707}
708
709// absJoin absolutizes and flattens the lists of files.
710func absJoin(dir string, fileses ...[]string) (res []string) {
711	for _, files := range fileses {
712		for _, file := range files {
713			if !filepath.IsAbs(file) {
714				file = filepath.Join(dir, file)
715			}
716			res = append(res, file)
717		}
718	}
719	return res
720}
721
722func golistargs(cfg *Config, words []string) []string {
723	fullargs := []string{
724		"list", "-e", "-json", "-compiled",
725		fmt.Sprintf("-test=%t", cfg.Tests),
726		fmt.Sprintf("-export=%t", usesExportData(cfg)),
727		fmt.Sprintf("-deps=%t", cfg.Mode >= LoadImports),
728		// go list doesn't let you pass -test and -find together,
729		// probably because you'd just get the TestMain.
730		fmt.Sprintf("-find=%t", cfg.Mode < LoadImports && !cfg.Tests),
731	}
732	fullargs = append(fullargs, cfg.BuildFlags...)
733	fullargs = append(fullargs, "--")
734	fullargs = append(fullargs, words...)
735	return fullargs
736}
737
738// invokeGo returns the stdout of a go command invocation.
739func invokeGo(cfg *Config, args ...string) (*bytes.Buffer, error) {
740	stdout := new(bytes.Buffer)
741	stderr := new(bytes.Buffer)
742	cmd := exec.CommandContext(cfg.Context, "go", args...)
743	// On darwin the cwd gets resolved to the real path, which breaks anything that
744	// expects the working directory to keep the original path, including the
745	// go command when dealing with modules.
746	// The Go stdlib has a special feature where if the cwd and the PWD are the
747	// same node then it trusts the PWD, so by setting it in the env for the child
748	// process we fix up all the paths returned by the go command.
749	cmd.Env = append(append([]string{}, cfg.Env...), "PWD="+cfg.Dir)
750	cmd.Dir = cfg.Dir
751	cmd.Stdout = stdout
752	cmd.Stderr = stderr
753	if debug {
754		defer func(start time.Time) {
755			log.Printf("%s for %v, stderr: <<%s>>\n", time.Since(start), cmdDebugStr(cmd, args...), stderr)
756		}(time.Now())
757	}
758
759	if err := cmd.Run(); err != nil {
760		exitErr, ok := err.(*exec.ExitError)
761		if !ok {
762			// Catastrophic error:
763			// - executable not found
764			// - context cancellation
765			return nil, fmt.Errorf("couldn't exec 'go %v': %s %T", args, err, err)
766		}
767
768		// Old go version?
769		if strings.Contains(stderr.String(), "flag provided but not defined") {
770			return nil, goTooOldError{fmt.Errorf("unsupported version of go: %s: %s", exitErr, stderr)}
771		}
772
773		// Export mode entails a build.
774		// If that build fails, errors appear on stderr
775		// (despite the -e flag) and the Export field is blank.
776		// Do not fail in that case.
777		// The same is true if an ad-hoc package given to go list doesn't exist.
778		// TODO(matloob): Remove these once we can depend on go list to exit with a zero status with -e even when
779		// packages don't exist or a build fails.
780		if !usesExportData(cfg) && !containsGoFile(args) {
781			return nil, fmt.Errorf("go %v: %s: %s", args, exitErr, stderr)
782		}
783	}
784
785	// As of writing, go list -export prints some non-fatal compilation
786	// errors to stderr, even with -e set. We would prefer that it put
787	// them in the Package.Error JSON (see https://golang.org/issue/26319).
788	// In the meantime, there's nowhere good to put them, but they can
789	// be useful for debugging. Print them if $GOPACKAGESPRINTGOLISTERRORS
790	// is set.
791	if len(stderr.Bytes()) != 0 && os.Getenv("GOPACKAGESPRINTGOLISTERRORS") != "" {
792		fmt.Fprintf(os.Stderr, "%s stderr: <<%s>>\n", cmdDebugStr(cmd, args...), stderr)
793	}
794
795	// debugging
796	if false {
797		fmt.Fprintf(os.Stderr, "%s stdout: <<%s>>\n", cmdDebugStr(cmd, args...), stdout)
798	}
799
800	return stdout, nil
801}
802
803func containsGoFile(s []string) bool {
804	for _, f := range s {
805		if strings.HasSuffix(f, ".go") {
806			return true
807		}
808	}
809	return false
810}
811
812func cmdDebugStr(cmd *exec.Cmd, args ...string) string {
813	env := make(map[string]string)
814	for _, kv := range cmd.Env {
815		split := strings.Split(kv, "=")
816		k, v := split[0], split[1]
817		env[k] = v
818	}
819	var quotedArgs []string
820	for _, arg := range args {
821		quotedArgs = append(quotedArgs, strconv.Quote(arg))
822	}
823
824	return fmt.Sprintf("GOROOT=%v GOPATH=%v GO111MODULE=%v PWD=%v go %s", env["GOROOT"], env["GOPATH"], env["GO111MODULE"], env["PWD"], strings.Join(quotedArgs, " "))
825}
826