1
2
3
4
5 package packages
6
7 import (
8 "bytes"
9 "context"
10 "encoding/json"
11 "fmt"
12 "go/types"
13 "io/ioutil"
14 "log"
15 "os"
16 "path"
17 "path/filepath"
18 "reflect"
19 "sort"
20 "strconv"
21 "strings"
22 "sync"
23 "unicode"
24
25 exec "golang.org/x/sys/execabs"
26 "golang.org/x/tools/go/internal/packagesdriver"
27 "golang.org/x/tools/internal/gocommand"
28 "golang.org/x/tools/internal/packagesinternal"
29 )
30
31
32 var debug, _ = strconv.ParseBool(os.Getenv("GOPACKAGESDEBUG"))
33
34
35
36 type goTooOldError struct {
37 error
38 }
39
40
41 type responseDeduper struct {
42 seenRoots map[string]bool
43 seenPackages map[string]*Package
44 dr *driverResponse
45 }
46
47 func newDeduper() *responseDeduper {
48 return &responseDeduper{
49 dr: &driverResponse{},
50 seenRoots: map[string]bool{},
51 seenPackages: map[string]*Package{},
52 }
53 }
54
55
56 func (r *responseDeduper) addAll(dr *driverResponse) {
57 for _, pkg := range dr.Packages {
58 r.addPackage(pkg)
59 }
60 for _, root := range dr.Roots {
61 r.addRoot(root)
62 }
63 r.dr.GoVersion = dr.GoVersion
64 }
65
66 func (r *responseDeduper) addPackage(p *Package) {
67 if r.seenPackages[p.ID] != nil {
68 return
69 }
70 r.seenPackages[p.ID] = p
71 r.dr.Packages = append(r.dr.Packages, p)
72 }
73
74 func (r *responseDeduper) addRoot(id string) {
75 if r.seenRoots[id] {
76 return
77 }
78 r.seenRoots[id] = true
79 r.dr.Roots = append(r.dr.Roots, id)
80 }
81
82 type golistState struct {
83 cfg *Config
84 ctx context.Context
85
86 envOnce sync.Once
87 goEnvError error
88 goEnv map[string]string
89
90 rootsOnce sync.Once
91 rootDirsError error
92 rootDirs map[string]string
93
94 goVersionOnce sync.Once
95 goVersionError error
96 goVersion int
97
98
99 vendorDirs map[string]bool
100 }
101
102
103
104 func (state *golistState) getEnv() (map[string]string, error) {
105 state.envOnce.Do(func() {
106 var b *bytes.Buffer
107 b, state.goEnvError = state.invokeGo("env", "-json", "GOMOD", "GOPATH")
108 if state.goEnvError != nil {
109 return
110 }
111
112 state.goEnv = make(map[string]string)
113 decoder := json.NewDecoder(b)
114 if state.goEnvError = decoder.Decode(&state.goEnv); state.goEnvError != nil {
115 return
116 }
117 })
118 return state.goEnv, state.goEnvError
119 }
120
121
122 func (state *golistState) mustGetEnv() map[string]string {
123 env, err := state.getEnv()
124 if err != nil {
125 panic(fmt.Sprintf("mustGetEnv: %v", err))
126 }
127 return env
128 }
129
130
131
132
133 func goListDriver(cfg *Config, patterns ...string) (*driverResponse, error) {
134
135 parentCtx := cfg.Context
136 if parentCtx == nil {
137 parentCtx = context.Background()
138 }
139 ctx, cancel := context.WithCancel(parentCtx)
140 defer cancel()
141
142 response := newDeduper()
143
144 state := &golistState{
145 cfg: cfg,
146 ctx: ctx,
147 vendorDirs: map[string]bool{},
148 }
149
150
151 var sizeserr error
152 var sizeswg sync.WaitGroup
153 if cfg.Mode&NeedTypesSizes != 0 || cfg.Mode&NeedTypes != 0 {
154 sizeswg.Add(1)
155 go func() {
156 var sizes types.Sizes
157 sizes, sizeserr = packagesdriver.GetSizesGolist(ctx, state.cfgInvocation(), cfg.gocmdRunner)
158
159 response.dr.Sizes, _ = sizes.(*types.StdSizes)
160 sizeswg.Done()
161 }()
162 }
163
164
165 var containFiles []string
166 restPatterns := make([]string, 0, len(patterns))
167
168
169 extractQueries:
170 for _, pattern := range patterns {
171 eqidx := strings.Index(pattern, "=")
172 if eqidx < 0 {
173 restPatterns = append(restPatterns, pattern)
174 } else {
175 query, value := pattern[:eqidx], pattern[eqidx+len("="):]
176 switch query {
177 case "file":
178 containFiles = append(containFiles, value)
179 case "pattern":
180 restPatterns = append(restPatterns, value)
181 case "":
182 restPatterns = append(restPatterns, pattern)
183 default:
184 for _, rune := range query {
185 if rune < 'a' || rune > 'z' {
186 restPatterns = append(restPatterns, pattern)
187 continue extractQueries
188 }
189 }
190
191 return nil, fmt.Errorf("invalid query type %q in query pattern %q", query, pattern)
192 }
193 }
194 }
195
196
197
198
199 if len(restPatterns) > 0 || len(patterns) == 0 {
200 dr, err := state.createDriverResponse(restPatterns...)
201 if err != nil {
202 return nil, err
203 }
204 response.addAll(dr)
205 }
206
207 if len(containFiles) != 0 {
208 if err := state.runContainsQueries(response, containFiles); err != nil {
209 return nil, err
210 }
211 }
212
213
214
215 if goVersion, err := state.getGoVersion(); err == nil && goVersion < 16 {
216 modifiedPkgs, needPkgs, err := state.processGolistOverlay(response)
217 if err != nil {
218 return nil, err
219 }
220
221 var containsCandidates []string
222 if len(containFiles) > 0 {
223 containsCandidates = append(containsCandidates, modifiedPkgs...)
224 containsCandidates = append(containsCandidates, needPkgs...)
225 }
226 if err := state.addNeededOverlayPackages(response, needPkgs); err != nil {
227 return nil, err
228 }
229
230 if len(containFiles) > 0 {
231 for _, id := range containsCandidates {
232 pkg, ok := response.seenPackages[id]
233 if !ok {
234 response.addPackage(&Package{
235 ID: id,
236 Errors: []Error{{
237 Kind: ListError,
238 Msg: fmt.Sprintf("package %s expected but not seen", id),
239 }},
240 })
241 continue
242 }
243 for _, f := range containFiles {
244 for _, g := range pkg.GoFiles {
245 if sameFile(f, g) {
246 response.addRoot(id)
247 }
248 }
249 }
250 }
251 }
252
253
254
255 for _, pattern := range restPatterns {
256 match := matchPattern(pattern)
257 for _, pkgID := range modifiedPkgs {
258 pkg, ok := response.seenPackages[pkgID]
259 if !ok {
260 continue
261 }
262 if match(pkg.PkgPath) {
263 response.addRoot(pkg.ID)
264 }
265 }
266 }
267 }
268
269 sizeswg.Wait()
270 if sizeserr != nil {
271 return nil, sizeserr
272 }
273 return response.dr, nil
274 }
275
276 func (state *golistState) addNeededOverlayPackages(response *responseDeduper, pkgs []string) error {
277 if len(pkgs) == 0 {
278 return nil
279 }
280 dr, err := state.createDriverResponse(pkgs...)
281 if err != nil {
282 return err
283 }
284 for _, pkg := range dr.Packages {
285 response.addPackage(pkg)
286 }
287 _, needPkgs, err := state.processGolistOverlay(response)
288 if err != nil {
289 return err
290 }
291 return state.addNeededOverlayPackages(response, needPkgs)
292 }
293
294 func (state *golistState) runContainsQueries(response *responseDeduper, queries []string) error {
295 for _, query := range queries {
296
297 fdir := filepath.Dir(query)
298
299
300 pattern, err := filepath.Abs(fdir)
301 if err != nil {
302 return fmt.Errorf("could not determine absolute path of file= query path %q: %v", query, err)
303 }
304 dirResponse, err := state.createDriverResponse(pattern)
305
306
307
308
309
310
311 if err != nil || len(dirResponse.Packages) == 0 || len(dirResponse.Packages) == 1 && len(dirResponse.Packages[0].GoFiles) == 0 &&
312 len(dirResponse.Packages[0].Errors) == 1 {
313 var queryErr error
314 if dirResponse, queryErr = state.adhocPackage(pattern, query); queryErr != nil {
315 return err
316 }
317 }
318 isRoot := make(map[string]bool, len(dirResponse.Roots))
319 for _, root := range dirResponse.Roots {
320 isRoot[root] = true
321 }
322 for _, pkg := range dirResponse.Packages {
323
324
325
326
327 response.addPackage(pkg)
328
329 if !isRoot[pkg.ID] {
330 continue
331 }
332 for _, pkgFile := range pkg.GoFiles {
333 if filepath.Base(query) == filepath.Base(pkgFile) {
334 response.addRoot(pkg.ID)
335 break
336 }
337 }
338 }
339 }
340 return nil
341 }
342
343
344
345 func (state *golistState) adhocPackage(pattern, query string) (*driverResponse, error) {
346 response, err := state.createDriverResponse(query)
347 if err != nil {
348 return nil, err
349 }
350
351
352
353 if len(response.Packages) == 0 {
354 response.Packages = append(response.Packages, &Package{
355 ID: "command-line-arguments",
356 PkgPath: query,
357 GoFiles: []string{query},
358 CompiledGoFiles: []string{query},
359 Imports: make(map[string]*Package),
360 })
361 response.Roots = append(response.Roots, "command-line-arguments")
362 }
363
364 if len(response.Packages) == 1 {
365
366
367
368 if response.Packages[0].ID == "command-line-arguments" ||
369 filepath.ToSlash(response.Packages[0].PkgPath) == filepath.ToSlash(query) {
370 if len(response.Packages[0].GoFiles) == 0 {
371 filename := filepath.Join(pattern, filepath.Base(query))
372
373 for path := range state.cfg.Overlay {
374 if path == filename {
375 response.Packages[0].Errors = nil
376 response.Packages[0].GoFiles = []string{path}
377 response.Packages[0].CompiledGoFiles = []string{path}
378 }
379 }
380 }
381 }
382 }
383 return response, nil
384 }
385
386
387
388 type jsonPackage struct {
389 ImportPath string
390 Dir string
391 Name string
392 Export string
393 GoFiles []string
394 CompiledGoFiles []string
395 IgnoredGoFiles []string
396 IgnoredOtherFiles []string
397 EmbedPatterns []string
398 EmbedFiles []string
399 CFiles []string
400 CgoFiles []string
401 CXXFiles []string
402 MFiles []string
403 HFiles []string
404 FFiles []string
405 SFiles []string
406 SwigFiles []string
407 SwigCXXFiles []string
408 SysoFiles []string
409 Imports []string
410 ImportMap map[string]string
411 Deps []string
412 Module *Module
413 TestGoFiles []string
414 TestImports []string
415 XTestGoFiles []string
416 XTestImports []string
417 ForTest string
418 DepOnly bool
419
420 Error *packagesinternal.PackageError
421 DepsErrors []*packagesinternal.PackageError
422 }
423
424 type jsonPackageError struct {
425 ImportStack []string
426 Pos string
427 Err string
428 }
429
430 func otherFiles(p *jsonPackage) [][]string {
431 return [][]string{p.CFiles, p.CXXFiles, p.MFiles, p.HFiles, p.FFiles, p.SFiles, p.SwigFiles, p.SwigCXXFiles, p.SysoFiles}
432 }
433
434
435
436 func (state *golistState) createDriverResponse(words ...string) (*driverResponse, error) {
437
438
439
440
441
442
443
444
445
446
447
448
449
450 goVersion, err := state.getGoVersion()
451 if err != nil {
452 return nil, err
453 }
454 buf, err := state.invokeGo("list", golistargs(state.cfg, words, goVersion)...)
455 if err != nil {
456 return nil, err
457 }
458
459 seen := make(map[string]*jsonPackage)
460 pkgs := make(map[string]*Package)
461 additionalErrors := make(map[string][]Error)
462
463 response := &driverResponse{
464 GoVersion: goVersion,
465 }
466 for dec := json.NewDecoder(buf); dec.More(); {
467 p := new(jsonPackage)
468 if err := dec.Decode(p); err != nil {
469 return nil, fmt.Errorf("JSON decoding failed: %v", err)
470 }
471
472 if p.ImportPath == "" {
473
474
475
476
477 if p.Error != nil {
478 return nil, Error{
479 Pos: p.Error.Pos,
480 Msg: p.Error.Err,
481 }
482 }
483 return nil, fmt.Errorf("package missing import path: %+v", p)
484 }
485
486
487
488
489
490
491
492
493 if filepath.IsAbs(p.ImportPath) && p.Error != nil {
494 pkgPath, ok, err := state.getPkgPath(p.ImportPath)
495 if err != nil {
496 return nil, err
497 }
498 if ok {
499 p.ImportPath = pkgPath
500 }
501 }
502
503 if old, found := seen[p.ImportPath]; found {
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523 if old.Error == nil && p.Error == nil {
524 if !reflect.DeepEqual(p, old) {
525 return nil, fmt.Errorf("internal error: go list gives conflicting information for package %v", p.ImportPath)
526 }
527 continue
528 }
529
530
531
532
533 if old.Error != nil {
534 var errkind string
535 if strings.Contains(old.Error.Err, "not an importable package") {
536 errkind = "not an importable package"
537 } else if strings.Contains(old.Error.Err, "use of internal package") && strings.Contains(old.Error.Err, "not allowed") {
538 errkind = "use of internal package not allowed"
539 }
540 if errkind != "" {
541 if len(old.Error.ImportStack) < 1 {
542 return nil, fmt.Errorf(`internal error: go list gave a %q error with empty import stack`, errkind)
543 }
544 importingPkg := old.Error.ImportStack[len(old.Error.ImportStack)-1]
545 if importingPkg == old.ImportPath {
546
547
548
549 if len(old.Error.ImportStack) < 2 {
550 return nil, fmt.Errorf(`internal error: go list gave a %q error with an import stack without importing package`, errkind)
551 }
552 importingPkg = old.Error.ImportStack[len(old.Error.ImportStack)-2]
553 }
554 additionalErrors[importingPkg] = append(additionalErrors[importingPkg], Error{
555 Pos: old.Error.Pos,
556 Msg: old.Error.Err,
557 Kind: ListError,
558 })
559 }
560 }
561
562
563
564 if old.Error == nil {
565 continue
566 }
567
568
569 }
570 seen[p.ImportPath] = p
571
572 pkg := &Package{
573 Name: p.Name,
574 ID: p.ImportPath,
575 GoFiles: absJoin(p.Dir, p.GoFiles, p.CgoFiles),
576 CompiledGoFiles: absJoin(p.Dir, p.CompiledGoFiles),
577 OtherFiles: absJoin(p.Dir, otherFiles(p)...),
578 EmbedFiles: absJoin(p.Dir, p.EmbedFiles),
579 EmbedPatterns: absJoin(p.Dir, p.EmbedPatterns),
580 IgnoredFiles: absJoin(p.Dir, p.IgnoredGoFiles, p.IgnoredOtherFiles),
581 forTest: p.ForTest,
582 depsErrors: p.DepsErrors,
583 Module: p.Module,
584 }
585
586 if (state.cfg.Mode&typecheckCgo) != 0 && len(p.CgoFiles) != 0 {
587 if len(p.CompiledGoFiles) > len(p.GoFiles) {
588
589
590
591
592
593 cgoTypes := p.CompiledGoFiles[len(p.GoFiles)]
594 pkg.CompiledGoFiles = append([]string{cgoTypes}, pkg.GoFiles...)
595 } else {
596
597 pkg.CompiledGoFiles = nil
598 pkg.Errors = append(pkg.Errors, Error{
599 Msg: "go list failed to return CompiledGoFiles. This may indicate failure to perform cgo processing; try building at the command line. See https://golang.org/issue/38990.",
600 Kind: ListError,
601 })
602 }
603 }
604
605
606
607
608
609 if len(pkg.CompiledGoFiles) > 0 {
610 out := pkg.CompiledGoFiles[:0]
611 for _, f := range pkg.CompiledGoFiles {
612 if ext := filepath.Ext(f); ext != ".go" && ext != "" {
613 continue
614 }
615 out = append(out, f)
616 }
617 pkg.CompiledGoFiles = out
618 }
619
620
621 if i := strings.IndexByte(pkg.ID, ' '); i >= 0 {
622 pkg.PkgPath = pkg.ID[:i]
623 } else {
624 pkg.PkgPath = pkg.ID
625 }
626
627 if pkg.PkgPath == "unsafe" {
628 pkg.GoFiles = nil
629 }
630
631
632 if p.Dir != "" && !filepath.IsAbs(p.Dir) {
633 log.Fatalf("internal error: go list returned non-absolute Package.Dir: %s", p.Dir)
634 }
635
636 if p.Export != "" && !filepath.IsAbs(p.Export) {
637 pkg.ExportFile = filepath.Join(p.Dir, p.Export)
638 } else {
639 pkg.ExportFile = p.Export
640 }
641
642
643
644
645
646 ids := make(map[string]bool)
647 for _, id := range p.Imports {
648 ids[id] = true
649 }
650 pkg.Imports = make(map[string]*Package)
651 for path, id := range p.ImportMap {
652 pkg.Imports[path] = &Package{ID: id}
653 delete(ids, id)
654 }
655 for id := range ids {
656 if id == "C" {
657 continue
658 }
659
660 pkg.Imports[id] = &Package{ID: id}
661 }
662 if !p.DepOnly {
663 response.Roots = append(response.Roots, pkg.ID)
664 }
665
666
667
668
669 if len(pkg.CompiledGoFiles) == 0 {
670 pkg.CompiledGoFiles = pkg.GoFiles
671 }
672
673
674
675
676 if err := p.Error; err != nil && state.shouldAddFilenameFromError(p) {
677 addFilenameFromPos := func(pos string) bool {
678 split := strings.Split(pos, ":")
679 if len(split) < 1 {
680 return false
681 }
682 filename := strings.TrimSpace(split[0])
683 if filename == "" {
684 return false
685 }
686 if !filepath.IsAbs(filename) {
687 filename = filepath.Join(state.cfg.Dir, filename)
688 }
689 info, _ := os.Stat(filename)
690 if info == nil {
691 return false
692 }
693 pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, filename)
694 pkg.GoFiles = append(pkg.GoFiles, filename)
695 return true
696 }
697 found := addFilenameFromPos(err.Pos)
698
699
700
701 if !found {
702 addFilenameFromPos(err.Err)
703 }
704 }
705
706 if p.Error != nil {
707 msg := strings.TrimSpace(p.Error.Err)
708
709 if msg == "import cycle not allowed" && len(p.Error.ImportStack) != 0 {
710 msg += fmt.Sprintf(": import stack: %v", p.Error.ImportStack)
711 }
712 pkg.Errors = append(pkg.Errors, Error{
713 Pos: p.Error.Pos,
714 Msg: msg,
715 Kind: ListError,
716 })
717 }
718
719 pkgs[pkg.ID] = pkg
720 }
721
722 for id, errs := range additionalErrors {
723 if p, ok := pkgs[id]; ok {
724 p.Errors = append(p.Errors, errs...)
725 }
726 }
727 for _, pkg := range pkgs {
728 response.Packages = append(response.Packages, pkg)
729 }
730 sort.Slice(response.Packages, func(i, j int) bool { return response.Packages[i].ID < response.Packages[j].ID })
731
732 return response, nil
733 }
734
735 func (state *golistState) shouldAddFilenameFromError(p *jsonPackage) bool {
736 if len(p.GoFiles) > 0 || len(p.CompiledGoFiles) > 0 {
737 return false
738 }
739
740 goV, err := state.getGoVersion()
741 if err != nil {
742 return false
743 }
744
745
746
747 if goV < 15 {
748 return len(p.Error.ImportStack) == 0
749 }
750
751
752
753
754
755 return len(p.Error.ImportStack) == 0 || p.Error.ImportStack[len(p.Error.ImportStack)-1] == p.ImportPath
756 }
757
758
759 func (state *golistState) getGoVersion() (int, error) {
760 state.goVersionOnce.Do(func() {
761 state.goVersion, state.goVersionError = gocommand.GoVersion(state.ctx, state.cfgInvocation(), state.cfg.gocmdRunner)
762 })
763 return state.goVersion, state.goVersionError
764 }
765
766
767
768 func (state *golistState) getPkgPath(dir string) (string, bool, error) {
769 absDir, err := filepath.Abs(dir)
770 if err != nil {
771 return "", false, err
772 }
773 roots, err := state.determineRootDirs()
774 if err != nil {
775 return "", false, err
776 }
777
778 for rdir, rpath := range roots {
779
780
781 if !strings.HasPrefix(absDir, rdir) {
782 continue
783 }
784
785 r, err := filepath.Rel(rdir, dir)
786 if err != nil {
787 continue
788 }
789 if rpath != "" {
790
791
792
793
794
795
796 return path.Join(rpath, filepath.ToSlash(r)), true, nil
797 }
798 return filepath.ToSlash(r), true, nil
799 }
800 return "", false, nil
801 }
802
803
804 func absJoin(dir string, fileses ...[]string) (res []string) {
805 for _, files := range fileses {
806 for _, file := range files {
807 if !filepath.IsAbs(file) {
808 file = filepath.Join(dir, file)
809 }
810 res = append(res, file)
811 }
812 }
813 return res
814 }
815
816 func jsonFlag(cfg *Config, goVersion int) string {
817 if goVersion < 19 {
818 return "-json"
819 }
820 var fields []string
821 added := make(map[string]bool)
822 addFields := func(fs ...string) {
823 for _, f := range fs {
824 if !added[f] {
825 added[f] = true
826 fields = append(fields, f)
827 }
828 }
829 }
830 addFields("Name", "ImportPath", "Error")
831 if cfg.Mode&NeedFiles != 0 || cfg.Mode&NeedTypes != 0 {
832 addFields("Dir", "GoFiles", "IgnoredGoFiles", "IgnoredOtherFiles", "CFiles",
833 "CgoFiles", "CXXFiles", "MFiles", "HFiles", "FFiles", "SFiles",
834 "SwigFiles", "SwigCXXFiles", "SysoFiles")
835 if cfg.Tests {
836 addFields("TestGoFiles", "XTestGoFiles")
837 }
838 }
839 if cfg.Mode&NeedTypes != 0 {
840
841
842
843
844 addFields("Dir", "CompiledGoFiles")
845 }
846 if cfg.Mode&NeedCompiledGoFiles != 0 {
847 addFields("Dir", "CompiledGoFiles", "Export")
848 }
849 if cfg.Mode&NeedImports != 0 {
850
851
852 addFields("DepOnly", "Imports", "ImportMap")
853 if cfg.Tests {
854 addFields("TestImports", "XTestImports")
855 }
856 }
857 if cfg.Mode&NeedDeps != 0 {
858 addFields("DepOnly")
859 }
860 if usesExportData(cfg) {
861
862 addFields("Dir", "Export")
863 }
864 if cfg.Mode&needInternalForTest != 0 {
865 addFields("ForTest")
866 }
867 if cfg.Mode&needInternalDepsErrors != 0 {
868 addFields("DepsErrors")
869 }
870 if cfg.Mode&NeedModule != 0 {
871 addFields("Module")
872 }
873 if cfg.Mode&NeedEmbedFiles != 0 {
874 addFields("EmbedFiles")
875 }
876 if cfg.Mode&NeedEmbedPatterns != 0 {
877 addFields("EmbedPatterns")
878 }
879 return "-json=" + strings.Join(fields, ",")
880 }
881
882 func golistargs(cfg *Config, words []string, goVersion int) []string {
883 const findFlags = NeedImports | NeedTypes | NeedSyntax | NeedTypesInfo
884 fullargs := []string{
885 "-e", jsonFlag(cfg, goVersion),
886 fmt.Sprintf("-compiled=%t", cfg.Mode&(NeedCompiledGoFiles|NeedSyntax|NeedTypes|NeedTypesInfo|NeedTypesSizes) != 0),
887 fmt.Sprintf("-test=%t", cfg.Tests),
888 fmt.Sprintf("-export=%t", usesExportData(cfg)),
889 fmt.Sprintf("-deps=%t", cfg.Mode&NeedImports != 0),
890
891
892 fmt.Sprintf("-find=%t", !cfg.Tests && cfg.Mode&findFlags == 0 && !usesExportData(cfg)),
893 }
894 fullargs = append(fullargs, cfg.BuildFlags...)
895 fullargs = append(fullargs, "--")
896 fullargs = append(fullargs, words...)
897 return fullargs
898 }
899
900
901 func (state *golistState) cfgInvocation() gocommand.Invocation {
902 cfg := state.cfg
903 return gocommand.Invocation{
904 BuildFlags: cfg.BuildFlags,
905 ModFile: cfg.modFile,
906 ModFlag: cfg.modFlag,
907 CleanEnv: cfg.Env != nil,
908 Env: cfg.Env,
909 Logf: cfg.Logf,
910 WorkingDir: cfg.Dir,
911 }
912 }
913
914
915 func (state *golistState) invokeGo(verb string, args ...string) (*bytes.Buffer, error) {
916 cfg := state.cfg
917
918 inv := state.cfgInvocation()
919
920
921
922
923
924
925 if verb == "list" {
926 goVersion, err := state.getGoVersion()
927 if err != nil {
928 return nil, err
929 }
930 if goVersion >= 16 {
931 filename, cleanup, err := state.writeOverlays()
932 if err != nil {
933 return nil, err
934 }
935 defer cleanup()
936 inv.Overlay = filename
937 }
938 }
939 inv.Verb = verb
940 inv.Args = args
941 gocmdRunner := cfg.gocmdRunner
942 if gocmdRunner == nil {
943 gocmdRunner = &gocommand.Runner{}
944 }
945 stdout, stderr, friendlyErr, err := gocmdRunner.RunRaw(cfg.Context, inv)
946 if err != nil {
947
948 if ee, ok := err.(*exec.Error); ok && ee.Err == exec.ErrNotFound {
949 return nil, fmt.Errorf("'go list' driver requires 'go', but %s", exec.ErrNotFound)
950 }
951
952 exitErr, ok := err.(*exec.ExitError)
953 if !ok {
954
955
956 return nil, fmt.Errorf("couldn't run 'go': %w", err)
957 }
958
959
960 if strings.Contains(stderr.String(), "flag provided but not defined") {
961 return nil, goTooOldError{fmt.Errorf("unsupported version of go: %s: %s", exitErr, stderr)}
962 }
963
964
965 if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "unexpected directory layout") {
966 return nil, friendlyErr
967 }
968
969
970
971
972
973 isPkgPathRune := func(r rune) bool {
974
975
976
977
978
979 return unicode.IsOneOf([]*unicode.RangeTable{unicode.L, unicode.M, unicode.N, unicode.P, unicode.S}, r) &&
980 !strings.ContainsRune("!\"#$%&'()*,:;<=>?[\\]^`{|}\uFFFD", r)
981 }
982
983 msg := stderr.String()
984 for strings.HasPrefix(msg, "go: downloading") {
985 msg = msg[strings.IndexRune(msg, '\n')+1:]
986 }
987 if len(stderr.String()) > 0 && strings.HasPrefix(stderr.String(), "# ") {
988 msg := msg[len("# "):]
989 if strings.HasPrefix(strings.TrimLeftFunc(msg, isPkgPathRune), "\n") {
990 return stdout, nil
991 }
992
993 if strings.HasPrefix(msg, "pkg-config") {
994 return stdout, nil
995 }
996 }
997
998
999
1000
1001 if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "named files must be .go files") {
1002 output := fmt.Sprintf(`{"ImportPath": "command-line-arguments","Incomplete": true,"Error": {"Pos": "","Err": %q}}`,
1003 strings.Trim(stderr.String(), "\n"))
1004 return bytes.NewBufferString(output), nil
1005 }
1006
1007
1008 if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "named files must all be in one directory") {
1009 output := fmt.Sprintf(`{"ImportPath": "command-line-arguments","Incomplete": true,"Error": {"Pos": "","Err": %q}}`,
1010 strings.Trim(stderr.String(), "\n"))
1011 return bytes.NewBufferString(output), nil
1012 }
1013
1014
1015
1016
1017 const noSuchDirectory = "no such directory"
1018 if len(stderr.String()) > 0 && strings.Contains(stderr.String(), noSuchDirectory) {
1019 errstr := stderr.String()
1020 abspath := strings.TrimSpace(errstr[strings.Index(errstr, noSuchDirectory)+len(noSuchDirectory):])
1021 output := fmt.Sprintf(`{"ImportPath": %q,"Incomplete": true,"Error": {"Pos": "","Err": %q}}`,
1022 abspath, strings.Trim(stderr.String(), "\n"))
1023 return bytes.NewBufferString(output), nil
1024 }
1025
1026
1027
1028 if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "no such file or directory") {
1029 output := fmt.Sprintf(`{"ImportPath": "command-line-arguments","Incomplete": true,"Error": {"Pos": "","Err": %q}}`,
1030 strings.Trim(stderr.String(), "\n"))
1031 return bytes.NewBufferString(output), nil
1032 }
1033
1034
1035
1036 if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "outside available modules") {
1037 output := fmt.Sprintf(`{"ImportPath": %q,"Incomplete": true,"Error": {"Pos": "","Err": %q}}`,
1038
1039 "command-line-arguments", strings.Trim(stderr.String(), "\n"))
1040 return bytes.NewBufferString(output), nil
1041 }
1042
1043
1044 if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "outside module root") {
1045 output := fmt.Sprintf(`{"ImportPath": %q,"Incomplete": true,"Error": {"Pos": "","Err": %q}}`,
1046
1047 "command-line-arguments", strings.Trim(stderr.String(), "\n"))
1048 return bytes.NewBufferString(output), nil
1049 }
1050
1051
1052
1053
1054 if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "no Go files in") {
1055
1056 if len(stdout.String()) > 0 {
1057 return stdout, nil
1058 }
1059
1060 stderrStr := stderr.String()
1061 var importPath string
1062 colon := strings.Index(stderrStr, ":")
1063 if colon > 0 && strings.HasPrefix(stderrStr, "go build ") {
1064 importPath = stderrStr[len("go build "):colon]
1065 }
1066 output := fmt.Sprintf(`{"ImportPath": %q,"Incomplete": true,"Error": {"Pos": "","Err": %q}}`,
1067 importPath, strings.Trim(stderrStr, "\n"))
1068 return bytes.NewBufferString(output), nil
1069 }
1070
1071
1072
1073
1074
1075
1076
1077
1078 if !usesExportData(cfg) && !containsGoFile(args) {
1079 return nil, friendlyErr
1080 }
1081 }
1082 return stdout, nil
1083 }
1084
1085
1086
1087
1088
1089
1090
1091
1092 type OverlayJSON struct {
1093 Replace map[string]string `json:"replace,omitempty"`
1094 }
1095
1096
1097
1098 func (state *golistState) writeOverlays() (filename string, cleanup func(), err error) {
1099
1100 if len(state.cfg.Overlay) == 0 {
1101 return "", func() {}, nil
1102 }
1103 dir, err := ioutil.TempDir("", "gopackages-*")
1104 if err != nil {
1105 return "", nil, err
1106 }
1107
1108
1109 cleanup = func() {
1110 os.RemoveAll(dir)
1111 }
1112 defer func() {
1113 if err != nil {
1114 cleanup()
1115 }
1116 }()
1117 overlays := map[string]string{}
1118 for k, v := range state.cfg.Overlay {
1119
1120
1121 noSeparator := strings.Join(strings.Split(filepath.ToSlash(k), "/"), "")
1122 f, err := ioutil.TempFile(dir, fmt.Sprintf("*-%s", noSeparator))
1123 if err != nil {
1124 return "", func() {}, err
1125 }
1126 if _, err := f.Write(v); err != nil {
1127 return "", func() {}, err
1128 }
1129 if err := f.Close(); err != nil {
1130 return "", func() {}, err
1131 }
1132 overlays[k] = f.Name()
1133 }
1134 b, err := json.Marshal(OverlayJSON{Replace: overlays})
1135 if err != nil {
1136 return "", func() {}, err
1137 }
1138
1139 filename = filepath.Join(dir, "overlay.json")
1140 if err := ioutil.WriteFile(filename, b, 0665); err != nil {
1141 return "", func() {}, err
1142 }
1143 return filename, cleanup, nil
1144 }
1145
1146 func containsGoFile(s []string) bool {
1147 for _, f := range s {
1148 if strings.HasSuffix(f, ".go") {
1149 return true
1150 }
1151 }
1152 return false
1153 }
1154
1155 func cmdDebugStr(cmd *exec.Cmd) string {
1156 env := make(map[string]string)
1157 for _, kv := range cmd.Env {
1158 split := strings.SplitN(kv, "=", 2)
1159 k, v := split[0], split[1]
1160 env[k] = v
1161 }
1162
1163 var args []string
1164 for _, arg := range cmd.Args {
1165 quoted := strconv.Quote(arg)
1166 if quoted[1:len(quoted)-1] != arg || strings.Contains(arg, " ") {
1167 args = append(args, quoted)
1168 } else {
1169 args = append(args, arg)
1170 }
1171 }
1172 return fmt.Sprintf("GOROOT=%v GOPATH=%v GO111MODULE=%v GOPROXY=%v PWD=%v %v", env["GOROOT"], env["GOPATH"], env["GO111MODULE"], env["GOPROXY"], env["PWD"], strings.Join(args, " "))
1173 }
1174
View as plain text