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}