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