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 | |
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 | // debug controls verbose logging. |
32 | var debug, _ = strconv.ParseBool(os.Getenv("GOPACKAGESDEBUG")) |
33 | |
34 | // A goTooOldError reports that the go command |
35 | // found by exec.LookPath is too old to use the new go list behavior. |
36 | type goTooOldError struct { |
37 | error |
38 | } |
39 | |
40 | // responseDeduper wraps a driverResponse, deduplicating its contents. |
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 | // addAll fills in r with a driverResponse. |
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 // The X in Go 1.X. |
97 | |
98 | // vendorDirs caches the (non)existence of vendor directories. |
99 | vendorDirs map[string]bool |
100 | } |
101 | |
102 | // getEnv returns Go environment variables. Only specific variables are |
103 | // populated -- computing all of them is slow. |
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 | // mustGetEnv is a convenience function that can be used if getEnv has already succeeded. |
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 | // goListDriver uses the go list command to interpret the patterns and produce |
131 | // the build system package structure. |
132 | // See driver for more details. |
133 | func goListDriver(cfg *Config, patterns ...string) (*driverResponse, error) { |
134 | // Make sure that any asynchronous go commands are killed when we return. |
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 | // Fill in response.Sizes asynchronously if necessary. |
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 | // types.SizesFor always returns nil or a *types.StdSizes. |
159 | response.dr.Sizes, _ = sizes.(*types.StdSizes) |
160 | sizeswg.Done() |
161 | }() |
162 | } |
163 | |
164 | // Determine files requested in contains patterns |
165 | var containFiles []string |
166 | restPatterns := make([]string, 0, len(patterns)) |
167 | // Extract file= and other [querytype]= patterns. Report an error if querytype |
168 | // doesn't exist. |
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 "": // not a reserved query |
182 | restPatterns = append(restPatterns, pattern) |
183 | default: |
184 | for _, rune := range query { |
185 | if rune < 'a' || rune > 'z' { // not a reserved query |
186 | restPatterns = append(restPatterns, pattern) |
187 | continue extractQueries |
188 | } |
189 | } |
190 | // Reject all other patterns containing "=" |
191 | return nil, fmt.Errorf("invalid query type %q in query pattern %q", query, pattern) |
192 | } |
193 | } |
194 | } |
195 | |
196 | // See if we have any patterns to pass through to go list. Zero initial |
197 | // patterns also requires a go list call, since it's the equivalent of |
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 | // Only use go/packages' overlay processing if we're using a Go version |
214 | // below 1.16. Otherwise, go list handles it. |
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 | // Check candidate packages for containFiles. |
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 | // Add root for any package that matches a pattern. This applies only to |
253 | // packages that are modified by overlays, since they are not added as |
254 | // roots automatically. |
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 | // TODO(matloob): Do only one query per directory. |
297 | fdir := filepath.Dir(query) |
298 | // Pass absolute path of directory to go list so that it knows to treat it as a directory, |
299 | // not a package path. |
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 | // If there was an error loading the package, or no packages are returned, |
307 | // or the package is returned with errors, try to load the file as an |
308 | // ad-hoc package. |
309 | // Usually the error will appear in a returned package, but may not if we're |
310 | // in module mode and the ad-hoc is located outside a module. |
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 // return the original error |
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 | // Add any new packages to the main set |
324 | // We don't bother to filter packages that will be dropped by the changes of roots, |
325 | // that will happen anyway during graph construction outside this function. |
326 | // Over-reporting packages is not a problem. |
327 | response.addPackage(pkg) |
328 | // if the package was not a root one, it cannot have the file |
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 | // adhocPackage attempts to load or construct an ad-hoc package for a given |
344 | // query, if the original call to the driver produced inadequate results. |
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 | // If we get nothing back from `go list`, |
351 | // try to make this file into its own ad-hoc package. |
352 | // TODO(rstambler): Should this check against the original response? |
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 | // Handle special cases. |
364 | if len(response.Packages) == 1 { |
365 | // golang/go#33482: If this is a file= query for ad-hoc packages where |
366 | // the file only exists on an overlay, and exists outside of a module, |
367 | // add the file to the package and remove the errors. |
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)) // avoid recomputing abspath |
372 | // TODO(matloob): check if the file is outside of a root dir? |
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 | // Fields must match go list; |
387 | // see $GOROOT/src/cmd/go/internal/load/pkg.go. |
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 // q in a "p [q.test]" package, else "" |
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 | // createDriverResponse uses the "go list" command to expand the pattern |
435 | // words and return a response for the specified packages. |
436 | func (state *golistState) createDriverResponse(words ...string) (*driverResponse, error) { |
437 | // go list uses the following identifiers in ImportPath and Imports: |
438 | // |
439 | // "p" -- importable package or main (command) |
440 | // "q.test" -- q's test executable |
441 | // "p [q.test]" -- variant of p as built for q's test executable |
442 | // "q_test [q.test]" -- q's external test package |
443 | // |
444 | // The packages p that are built differently for a test q.test |
445 | // are q itself, plus any helpers used by the external test q_test, |
446 | // typically including "testing" and all its dependencies. |
447 | |
448 | // Run "go list" for complete |
449 | // information on the specified packages. |
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 | // Decode the JSON and convert it to Package form. |
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 | // The documentation for go list says that “[e]rroneous packages will have |
474 | // a non-empty ImportPath”. If for some reason it comes back empty, we |
475 | // prefer to error out rather than silently discarding data or handing |
476 | // back a package without any way to refer to it. |
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 | // Work around https://golang.org/issue/33157: |
487 | // go list -e, when given an absolute path, will find the package contained at |
488 | // that directory. But when no package exists there, it will return a fake package |
489 | // with an error and the ImportPath set to the absolute path provided to go list. |
490 | // Try to convert that absolute path to what its package path would be if it's |
491 | // contained in a known module or GOPATH entry. This will allow the package to be |
492 | // properly "reclaimed" when overlays are processed. |
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 | // If one version of the package has an error, and the other doesn't, assume |
505 | // that this is a case where go list is reporting a fake dependency variant |
506 | // of the imported package: When a package tries to invalidly import another |
507 | // package, go list emits a variant of the imported package (with the same |
508 | // import path, but with an error on it, and the package will have a |
509 | // DepError set on it). An example of when this can happen is for imports of |
510 | // main packages: main packages can not be imported, but they may be |
511 | // separately matched and listed by another pattern. |
512 | // See golang.org/issue/36188 for more details. |
513 | |
514 | // The plan is that eventually, hopefully in Go 1.15, the error will be |
515 | // reported on the importing package rather than the duplicate "fake" |
516 | // version of the imported package. Once all supported versions of Go |
517 | // have the new behavior this logic can be deleted. |
518 | // TODO(matloob): delete the workaround logic once all supported versions of |
519 | // Go return the errors on the proper package. |
520 | |
521 | // There should be exactly one version of a package that doesn't have an |
522 | // error. |
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 | // Determine if this package's error needs to be bubbled up. |
531 | // This is a hack, and we expect for go list to eventually set the error |
532 | // on the package. |
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 | // Using an older version of Go which put this package itself on top of import |
547 | // stack, instead of the importer. Look for importer in second from top |
548 | // position. |
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 | // Make sure that if there's a version of the package without an error, |
563 | // that's the one reported to the user. |
564 | if old.Error == nil { |
565 | continue |
566 | } |
567 | |
568 | // This package will replace the old one at the end of the loop. |
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 | // We need the cgo definitions, which are in the first |
589 | // CompiledGoFile after the non-cgo ones. This is a hack but there |
590 | // isn't currently a better way to find it. We also need the pure |
591 | // Go files and unprocessed cgo files, all of which are already |
592 | // in pkg.GoFiles. |
593 | cgoTypes := p.CompiledGoFiles[len(p.GoFiles)] |
594 | pkg.CompiledGoFiles = append([]string{cgoTypes}, pkg.GoFiles...) |
595 | } else { |
596 | // golang/go#38990: go list silently fails to do cgo processing |
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 | // Work around https://golang.org/issue/28749: |
606 | // cmd/go puts assembly, C, and C++ files in CompiledGoFiles. |
607 | // Remove files from CompiledGoFiles that are non-go files |
608 | // (or are not files that look like they are from the cache). |
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 != "" { // ext == "" means the file is from the cache, so probably cgo-processed file |
613 | continue |
614 | } |
615 | out = append(out, f) |
616 | } |
617 | pkg.CompiledGoFiles = out |
618 | } |
619 | |
620 | // Extract the PkgPath from the package's ID. |
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 // ignore fake unsafe.go file |
629 | } |
630 | |
631 | // Assume go list emits only absolute paths for Dir. |
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 | // imports |
643 | // |
644 | // Imports contains the IDs of all imported packages. |
645 | // ImportsMap records (path, ID) only where they differ. |
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} // non-identity import |
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} // identity import |
661 | } |
662 | if !p.DepOnly { |
663 | response.Roots = append(response.Roots, pkg.ID) |
664 | } |
665 | |
666 | // Work around for pre-go.1.11 versions of go list. |
667 | // TODO(matloob): they should be handled by the fallback. |
668 | // Can we delete this? |
669 | if len(pkg.CompiledGoFiles) == 0 { |
670 | pkg.CompiledGoFiles = pkg.GoFiles |
671 | } |
672 | |
673 | // Temporary work-around for golang/go#39986. Parse filenames out of |
674 | // error messages. This happens if there are unrecoverable syntax |
675 | // errors in the source, so we can't match on a specific error message. |
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 | // In some cases, go list only reports the error position in the |
699 | // error text, not the error position. One such case is when the |
700 | // file's package name is a keyword (see golang.org/issue/39763). |
701 | if !found { |
702 | addFilenameFromPos(err.Err) |
703 | } |
704 | } |
705 | |
706 | if p.Error != nil { |
707 | msg := strings.TrimSpace(p.Error.Err) // Trim to work around golang.org/issue/32363. |
708 | // Address golang.org/issue/35964 by appending import stack to error message. |
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 | // On Go 1.14 and earlier, only add filenames from errors if the import stack is empty. |
746 | // The import stack behaves differently for these versions than newer Go versions. |
747 | if goV < 15 { |
748 | return len(p.Error.ImportStack) == 0 |
749 | } |
750 | |
751 | // On Go 1.15 and later, only parse filenames out of error if there's no import stack, |
752 | // or the current package is at the top of the import stack. This is not guaranteed |
753 | // to work perfectly, but should avoid some cases where files in errors don't belong to this |
754 | // package. |
755 | return len(p.Error.ImportStack) == 0 || p.Error.ImportStack[len(p.Error.ImportStack)-1] == p.ImportPath |
756 | } |
757 | |
758 | // getGoVersion returns the effective minor version of the go command. |
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 | // getPkgPath finds the package path of a directory if it's relative to a root |
767 | // directory. |
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 | // Make sure that the directory is in the module, |
780 | // to avoid creating a path relative to another module. |
781 | if !strings.HasPrefix(absDir, rdir) { |
782 | continue |
783 | } |
784 | // TODO(matloob): This doesn't properly handle symlinks. |
785 | r, err := filepath.Rel(rdir, dir) |
786 | if err != nil { |
787 | continue |
788 | } |
789 | if rpath != "" { |
790 | // We choose only one root even though the directory even it can belong in multiple modules |
791 | // or GOPATH entries. This is okay because we only need to work with absolute dirs when a |
792 | // file is missing from disk, for instance when gopls calls go/packages in an overlay. |
793 | // Once the file is saved, gopls, or the next invocation of the tool will get the correct |
794 | // result straight from golist. |
795 | // TODO(matloob): Implement module tiebreaking? |
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 | // absJoin absolutizes and flattens the lists of files. |
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") // These fields are always needed |
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 | // CompiledGoFiles seems to be required for the test case TestCgoNoSyntax, |
841 | // even when -compiled isn't passed in. |
842 | // TODO(#52435): Should we make the test ask for -compiled, or automatically |
843 | // request CompiledGoFiles in certain circumstances? |
844 | addFields("Dir", "CompiledGoFiles") |
845 | } |
846 | if cfg.Mode&NeedCompiledGoFiles != 0 { |
847 | addFields("Dir", "CompiledGoFiles", "Export") |
848 | } |
849 | if cfg.Mode&NeedImports != 0 { |
850 | // When imports are requested, DepOnly is used to distinguish between packages |
851 | // explicitly requested and transitive imports of those packages. |
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 | // Request Dir in the unlikely case Export is not absolute. |
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 | // go list doesn't let you pass -test and -find together, |
891 | // probably because you'd just get the TestMain. |
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 | // cfgInvocation returns an Invocation that reflects cfg's settings. |
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 | // invokeGo returns the stdout of a go command invocation. |
915 | func (state *golistState) invokeGo(verb string, args ...string) (*bytes.Buffer, error) { |
916 | cfg := state.cfg |
917 | |
918 | inv := state.cfgInvocation() |
919 | |
920 | // For Go versions 1.16 and above, `go list` accepts overlays directly via |
921 | // the -overlay flag. Set it, if it's available. |
922 | // |
923 | // The check for "list" is not necessarily required, but we should avoid |
924 | // getting the go version if possible. |
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 | // Check for 'go' executable not being found. |
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 | // Catastrophic error: |
955 | // - context cancellation |
956 | return nil, fmt.Errorf("couldn't run 'go': %w", err) |
957 | } |
958 | |
959 | // Old go version? |
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 | // Related to #24854 |
965 | if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "unexpected directory layout") { |
966 | return nil, friendlyErr |
967 | } |
968 | |
969 | // Is there an error running the C compiler in cgo? This will be reported in the "Error" field |
970 | // and should be suppressed by go list -e. |
971 | // |
972 | // This condition is not perfect yet because the error message can include other error messages than runtime/cgo. |
973 | isPkgPathRune := func(r rune) bool { |
974 | // From https://golang.org/ref/spec#Import_declarations: |
975 | // Implementation restriction: A compiler may restrict ImportPaths to non-empty strings |
976 | // using only characters belonging to Unicode's L, M, N, P, and S general categories |
977 | // (the Graphic characters without spaces) and may also exclude the |
978 | // characters !"#$%&'()*,:;<=>?[\]^`{|} and the Unicode replacement character U+FFFD. |
979 | return unicode.IsOneOf([]*unicode.RangeTable{unicode.L, unicode.M, unicode.N, unicode.P, unicode.S}, r) && |
980 | !strings.ContainsRune("!\"#$%&'()*,:;<=>?[\\]^`{|}\uFFFD", r) |
981 | } |
982 | // golang/go#36770: Handle case where cmd/go prints module download messages before the error. |
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 | // Treat pkg-config errors as a special case (golang.org/issue/36770). |
993 | if strings.HasPrefix(msg, "pkg-config") { |
994 | return stdout, nil |
995 | } |
996 | } |
997 | |
998 | // This error only appears in stderr. See golang.org/cl/166398 for a fix in go list to show |
999 | // the error in the Err section of stdout in case -e option is provided. |
1000 | // This fix is provided for backwards compatibility. |
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 | // Similar to the previous error, but currently lacks a fix in Go. |
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 | // Backwards compatibility for Go 1.11 because 1.12 and 1.13 put the directory in the ImportPath. |
1015 | // If the package doesn't exist, put the absolute path of the directory into the error message, |
1016 | // as Go 1.13 list does. |
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 | // Workaround for #29280: go list -e has incorrect behavior when an ad-hoc package doesn't exist. |
1027 | // Note that the error message we look for in this case is different that the one looked for above. |
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 | // Workaround for #34273. go list -e with GO111MODULE=on has incorrect behavior when listing a |
1035 | // directory outside any module. |
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 | // TODO(matloob): command-line-arguments isn't correct here. |
1039 | "command-line-arguments", strings.Trim(stderr.String(), "\n")) |
1040 | return bytes.NewBufferString(output), nil |
1041 | } |
1042 | |
1043 | // Another variation of the previous error |
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 | // TODO(matloob): command-line-arguments isn't correct here. |
1047 | "command-line-arguments", strings.Trim(stderr.String(), "\n")) |
1048 | return bytes.NewBufferString(output), nil |
1049 | } |
1050 | |
1051 | // Workaround for an instance of golang.org/issue/26755: go list -e will return a non-zero exit |
1052 | // status if there's a dependency on a package that doesn't exist. But it should return |
1053 | // a zero exit status and set an error on that package. |
1054 | if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "no Go files in") { |
1055 | // Don't clobber stdout if `go list` actually returned something. |
1056 | if len(stdout.String()) > 0 { |
1057 | return stdout, nil |
1058 | } |
1059 | // try to extract package name from string |
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 | // Export mode entails a build. |
1072 | // If that build fails, errors appear on stderr |
1073 | // (despite the -e flag) and the Export field is blank. |
1074 | // Do not fail in that case. |
1075 | // The same is true if an ad-hoc package given to go list doesn't exist. |
1076 | // TODO(matloob): Remove these once we can depend on go list to exit with a zero status with -e even when |
1077 | // packages don't exist or a build fails. |
1078 | if !usesExportData(cfg) && !containsGoFile(args) { |
1079 | return nil, friendlyErr |
1080 | } |
1081 | } |
1082 | return stdout, nil |
1083 | } |
1084 | |
1085 | // OverlayJSON is the format overlay files are expected to be in. |
1086 | // The Replace map maps from overlaid paths to replacement paths: |
1087 | // the Go command will forward all reads trying to open |
1088 | // each overlaid path to its replacement path, or consider the overlaid |
1089 | // path not to exist if the replacement path is empty. |
1090 | // |
1091 | // From golang/go#39958. |
1092 | type OverlayJSON struct { |
1093 | Replace map[string]string `json:"replace,omitempty"` |
1094 | } |
1095 | |
1096 | // writeOverlays writes out files for go list's -overlay flag, as described |
1097 | // above. |
1098 | func (state *golistState) writeOverlays() (filename string, cleanup func(), err error) { |
1099 | // Do nothing if there are no overlays in the config. |
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 | // The caller must clean up this directory, unless this function returns an |
1108 | // error. |
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 | // Create a unique filename for the overlaid files, to avoid |
1120 | // creating nested directories. |
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 | // Write out the overlay file that contains the filepath mappings. |
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 |
Members