1 | // Copyright 2015 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 | // Compilebench benchmarks the speed of the Go compiler. |
6 | // |
7 | // Usage: |
8 | // |
9 | // compilebench [options] |
10 | // |
11 | // It times the compilation of various packages and prints results in |
12 | // the format used by package testing (and expected by golang.org/x/perf/cmd/benchstat). |
13 | // |
14 | // The options are: |
15 | // |
16 | // -alloc |
17 | // Report allocations. |
18 | // |
19 | // -compile exe |
20 | // Use exe as the path to the cmd/compile binary. |
21 | // |
22 | // -compileflags 'list' |
23 | // Pass the space-separated list of flags to the compilation. |
24 | // |
25 | // -link exe |
26 | // Use exe as the path to the cmd/link binary. |
27 | // |
28 | // -linkflags 'list' |
29 | // Pass the space-separated list of flags to the linker. |
30 | // |
31 | // -count n |
32 | // Run each benchmark n times (default 1). |
33 | // |
34 | // -cpuprofile file |
35 | // Write a CPU profile of the compiler to file. |
36 | // |
37 | // -go path |
38 | // Path to "go" command (default "go"). |
39 | // |
40 | // -memprofile file |
41 | // Write a memory profile of the compiler to file. |
42 | // |
43 | // -memprofilerate rate |
44 | // Set runtime.MemProfileRate during compilation. |
45 | // |
46 | // -obj |
47 | // Report object file statistics. |
48 | // |
49 | // -pkg pkg |
50 | // Benchmark compiling a single package. |
51 | // |
52 | // -run regexp |
53 | // Only run benchmarks with names matching regexp. |
54 | // |
55 | // -short |
56 | // Skip long-running benchmarks. |
57 | // |
58 | // Although -cpuprofile and -memprofile are intended to write a |
59 | // combined profile for all the executed benchmarks to file, |
60 | // today they write only the profile for the last benchmark executed. |
61 | // |
62 | // The default memory profiling rate is one profile sample per 512 kB |
63 | // allocated (see “go doc runtime.MemProfileRate”). |
64 | // Lowering the rate (for example, -memprofilerate 64000) produces |
65 | // a more fine-grained and therefore accurate profile, but it also incurs |
66 | // execution cost. For benchmark comparisons, never use timings |
67 | // obtained with a low -memprofilerate option. |
68 | // |
69 | // # Example |
70 | // |
71 | // Assuming the base version of the compiler has been saved with |
72 | // “toolstash save,” this sequence compares the old and new compiler: |
73 | // |
74 | // compilebench -count 10 -compile $(toolstash -n compile) >old.txt |
75 | // compilebench -count 10 >new.txt |
76 | // benchstat old.txt new.txt |
77 | package main |
78 | |
79 | import ( |
80 | "bytes" |
81 | "encoding/json" |
82 | "flag" |
83 | "fmt" |
84 | "io/ioutil" |
85 | "log" |
86 | "os" |
87 | "path/filepath" |
88 | "regexp" |
89 | "runtime" |
90 | "strconv" |
91 | "strings" |
92 | "time" |
93 | |
94 | exec "golang.org/x/sys/execabs" |
95 | ) |
96 | |
97 | var ( |
98 | goroot string |
99 | compiler string |
100 | assembler string |
101 | linker string |
102 | runRE *regexp.Regexp |
103 | is6g bool |
104 | ) |
105 | |
106 | var ( |
107 | flagGoCmd = flag.String("go", "go", "path to \"go\" command") |
108 | flagAlloc = flag.Bool("alloc", false, "report allocations") |
109 | flagObj = flag.Bool("obj", false, "report object file stats") |
110 | flagCompiler = flag.String("compile", "", "use `exe` as the cmd/compile binary") |
111 | flagAssembler = flag.String("asm", "", "use `exe` as the cmd/asm binary") |
112 | flagCompilerFlags = flag.String("compileflags", "", "additional `flags` to pass to compile") |
113 | flagLinker = flag.String("link", "", "use `exe` as the cmd/link binary") |
114 | flagLinkerFlags = flag.String("linkflags", "", "additional `flags` to pass to link") |
115 | flagRun = flag.String("run", "", "run benchmarks matching `regexp`") |
116 | flagCount = flag.Int("count", 1, "run benchmarks `n` times") |
117 | flagCpuprofile = flag.String("cpuprofile", "", "write CPU profile to `file`") |
118 | flagMemprofile = flag.String("memprofile", "", "write memory profile to `file`") |
119 | flagMemprofilerate = flag.Int64("memprofilerate", -1, "set memory profile `rate`") |
120 | flagPackage = flag.String("pkg", "", "if set, benchmark the package at path `pkg`") |
121 | flagShort = flag.Bool("short", false, "skip long-running benchmarks") |
122 | flagTrace = flag.Bool("trace", false, "debug tracing of builds") |
123 | ) |
124 | |
125 | type test struct { |
126 | name string |
127 | r runner |
128 | } |
129 | |
130 | type runner interface { |
131 | long() bool |
132 | run(name string, count int) error |
133 | } |
134 | |
135 | var tests = []test{ |
136 | {"BenchmarkTemplate", compile{"html/template"}}, |
137 | {"BenchmarkUnicode", compile{"unicode"}}, |
138 | {"BenchmarkGoTypes", compile{"go/types"}}, |
139 | {"BenchmarkCompiler", compile{"cmd/compile/internal/gc"}}, |
140 | {"BenchmarkSSA", compile{"cmd/compile/internal/ssa"}}, |
141 | {"BenchmarkFlate", compile{"compress/flate"}}, |
142 | {"BenchmarkGoParser", compile{"go/parser"}}, |
143 | {"BenchmarkReflect", compile{"reflect"}}, |
144 | {"BenchmarkTar", compile{"archive/tar"}}, |
145 | {"BenchmarkXML", compile{"encoding/xml"}}, |
146 | {"BenchmarkLinkCompiler", link{"cmd/compile", ""}}, |
147 | {"BenchmarkExternalLinkCompiler", link{"cmd/compile", "-linkmode=external"}}, |
148 | {"BenchmarkLinkWithoutDebugCompiler", link{"cmd/compile", "-w"}}, |
149 | {"BenchmarkStdCmd", goBuild{[]string{"std", "cmd"}}}, |
150 | {"BenchmarkHelloSize", size{"$GOROOT/test/helloworld.go", false}}, |
151 | {"BenchmarkCmdGoSize", size{"cmd/go", true}}, |
152 | } |
153 | |
154 | func usage() { |
155 | fmt.Fprintf(os.Stderr, "usage: compilebench [options]\n") |
156 | fmt.Fprintf(os.Stderr, "options:\n") |
157 | flag.PrintDefaults() |
158 | os.Exit(2) |
159 | } |
160 | |
161 | func main() { |
162 | log.SetFlags(0) |
163 | log.SetPrefix("compilebench: ") |
164 | flag.Usage = usage |
165 | flag.Parse() |
166 | if flag.NArg() != 0 { |
167 | usage() |
168 | } |
169 | |
170 | s, err := exec.Command(*flagGoCmd, "env", "GOROOT").CombinedOutput() |
171 | if err != nil { |
172 | log.Fatalf("%s env GOROOT: %v", *flagGoCmd, err) |
173 | } |
174 | goroot = strings.TrimSpace(string(s)) |
175 | os.Setenv("GOROOT", goroot) // for any subcommands |
176 | |
177 | compiler = *flagCompiler |
178 | if compiler == "" { |
179 | var foundTool string |
180 | foundTool, compiler = toolPath("compile", "6g") |
181 | if foundTool == "6g" { |
182 | is6g = true |
183 | } |
184 | } |
185 | assembler = *flagAssembler |
186 | if assembler == "" { |
187 | _, assembler = toolPath("asm") |
188 | } |
189 | |
190 | linker = *flagLinker |
191 | if linker == "" && !is6g { // TODO: Support 6l |
192 | _, linker = toolPath("link") |
193 | } |
194 | |
195 | if is6g { |
196 | *flagMemprofilerate = -1 |
197 | *flagAlloc = false |
198 | *flagCpuprofile = "" |
199 | *flagMemprofile = "" |
200 | } |
201 | |
202 | if *flagRun != "" { |
203 | r, err := regexp.Compile(*flagRun) |
204 | if err != nil { |
205 | log.Fatalf("invalid -run argument: %v", err) |
206 | } |
207 | runRE = r |
208 | } |
209 | |
210 | if *flagPackage != "" { |
211 | tests = []test{ |
212 | {"BenchmarkPkg", compile{*flagPackage}}, |
213 | {"BenchmarkPkgLink", link{*flagPackage, ""}}, |
214 | } |
215 | runRE = nil |
216 | } |
217 | |
218 | for i := 0; i < *flagCount; i++ { |
219 | for _, tt := range tests { |
220 | if tt.r.long() && *flagShort { |
221 | continue |
222 | } |
223 | if runRE == nil || runRE.MatchString(tt.name) { |
224 | if err := tt.r.run(tt.name, i); err != nil { |
225 | log.Printf("%s: %v", tt.name, err) |
226 | } |
227 | } |
228 | } |
229 | } |
230 | } |
231 | |
232 | func toolPath(names ...string) (found, path string) { |
233 | var out1 []byte |
234 | var err1 error |
235 | for i, name := range names { |
236 | out, err := exec.Command(*flagGoCmd, "tool", "-n", name).CombinedOutput() |
237 | if err == nil { |
238 | return name, strings.TrimSpace(string(out)) |
239 | } |
240 | if i == 0 { |
241 | out1, err1 = out, err |
242 | } |
243 | } |
244 | log.Fatalf("go tool -n %s: %v\n%s", names[0], err1, out1) |
245 | return "", "" |
246 | } |
247 | |
248 | type Pkg struct { |
249 | ImportPath string |
250 | Dir string |
251 | GoFiles []string |
252 | SFiles []string |
253 | } |
254 | |
255 | func goList(dir string) (*Pkg, error) { |
256 | var pkg Pkg |
257 | out, err := exec.Command(*flagGoCmd, "list", "-json", dir).Output() |
258 | if err != nil { |
259 | return nil, fmt.Errorf("go list -json %s: %v", dir, err) |
260 | } |
261 | if err := json.Unmarshal(out, &pkg); err != nil { |
262 | return nil, fmt.Errorf("go list -json %s: unmarshal: %v", dir, err) |
263 | } |
264 | return &pkg, nil |
265 | } |
266 | |
267 | func runCmd(name string, cmd *exec.Cmd) error { |
268 | start := time.Now() |
269 | out, err := cmd.CombinedOutput() |
270 | if err != nil { |
271 | return fmt.Errorf("%v\n%s", err, out) |
272 | } |
273 | fmt.Printf("%s 1 %d ns/op\n", name, time.Since(start).Nanoseconds()) |
274 | return nil |
275 | } |
276 | |
277 | type goBuild struct{ pkgs []string } |
278 | |
279 | func (goBuild) long() bool { return true } |
280 | |
281 | func (r goBuild) run(name string, count int) error { |
282 | args := []string{"build", "-a"} |
283 | if *flagCompilerFlags != "" { |
284 | args = append(args, "-gcflags", *flagCompilerFlags) |
285 | } |
286 | args = append(args, r.pkgs...) |
287 | cmd := exec.Command(*flagGoCmd, args...) |
288 | cmd.Dir = filepath.Join(goroot, "src") |
289 | return runCmd(name, cmd) |
290 | } |
291 | |
292 | type size struct { |
293 | // path is either a path to a file ("$GOROOT/test/helloworld.go") or a package path ("cmd/go"). |
294 | path string |
295 | isLong bool |
296 | } |
297 | |
298 | func (r size) long() bool { return r.isLong } |
299 | |
300 | func (r size) run(name string, count int) error { |
301 | if strings.HasPrefix(r.path, "$GOROOT/") { |
302 | r.path = goroot + "/" + r.path[len("$GOROOT/"):] |
303 | } |
304 | |
305 | cmd := exec.Command(*flagGoCmd, "build", "-o", "_compilebenchout_", r.path) |
306 | cmd.Stdout = os.Stderr |
307 | cmd.Stderr = os.Stderr |
308 | if err := cmd.Run(); err != nil { |
309 | return err |
310 | } |
311 | defer os.Remove("_compilebenchout_") |
312 | info, err := os.Stat("_compilebenchout_") |
313 | if err != nil { |
314 | return err |
315 | } |
316 | out, err := exec.Command("size", "_compilebenchout_").CombinedOutput() |
317 | if err != nil { |
318 | return fmt.Errorf("size: %v\n%s", err, out) |
319 | } |
320 | lines := strings.Split(string(out), "\n") |
321 | if len(lines) < 2 { |
322 | return fmt.Errorf("not enough output from size: %s", out) |
323 | } |
324 | f := strings.Fields(lines[1]) |
325 | if strings.HasPrefix(lines[0], "__TEXT") && len(f) >= 2 { // OS X |
326 | fmt.Printf("%s 1 %s text-bytes %s data-bytes %v exe-bytes\n", name, f[0], f[1], info.Size()) |
327 | } else if strings.Contains(lines[0], "bss") && len(f) >= 3 { |
328 | fmt.Printf("%s 1 %s text-bytes %s data-bytes %s bss-bytes %v exe-bytes\n", name, f[0], f[1], f[2], info.Size()) |
329 | } |
330 | return nil |
331 | } |
332 | |
333 | type compile struct{ dir string } |
334 | |
335 | func (compile) long() bool { return false } |
336 | |
337 | func (c compile) run(name string, count int) error { |
338 | // Make sure dependencies needed by go tool compile are built. |
339 | out, err := exec.Command(*flagGoCmd, "build", c.dir).CombinedOutput() |
340 | if err != nil { |
341 | return fmt.Errorf("go build %s: %v\n%s", c.dir, err, out) |
342 | } |
343 | |
344 | // Find dir and source file list. |
345 | pkg, err := goList(c.dir) |
346 | if err != nil { |
347 | return err |
348 | } |
349 | |
350 | importcfg, err := genImportcfgFile(c.dir, false) |
351 | if err != nil { |
352 | return err |
353 | } |
354 | |
355 | // If this package has assembly files, we'll need to pass a symabis |
356 | // file to the compiler; call a helper to invoke the assembler |
357 | // to do that. |
358 | var symAbisFile string |
359 | var asmIncFile string |
360 | if len(pkg.SFiles) != 0 { |
361 | symAbisFile = filepath.Join(pkg.Dir, "symabis") |
362 | asmIncFile = filepath.Join(pkg.Dir, "go_asm.h") |
363 | content := "\n" |
364 | if err := os.WriteFile(asmIncFile, []byte(content), 0666); err != nil { |
365 | return fmt.Errorf("os.WriteFile(%s) failed: %v", asmIncFile, err) |
366 | } |
367 | defer os.Remove(symAbisFile) |
368 | defer os.Remove(asmIncFile) |
369 | if err := genSymAbisFile(pkg, symAbisFile, pkg.Dir); err != nil { |
370 | return err |
371 | } |
372 | } |
373 | |
374 | args := []string{"-o", "_compilebench_.o", "-p", pkg.ImportPath} |
375 | args = append(args, strings.Fields(*flagCompilerFlags)...) |
376 | if symAbisFile != "" { |
377 | args = append(args, "-symabis", symAbisFile) |
378 | } |
379 | if importcfg != "" { |
380 | args = append(args, "-importcfg", importcfg) |
381 | defer os.Remove(importcfg) |
382 | } |
383 | args = append(args, pkg.GoFiles...) |
384 | if err := runBuildCmd(name, count, pkg.Dir, compiler, args); err != nil { |
385 | return err |
386 | } |
387 | |
388 | opath := pkg.Dir + "/_compilebench_.o" |
389 | if *flagObj { |
390 | // TODO(josharian): object files are big; just read enough to find what we seek. |
391 | data, err := ioutil.ReadFile(opath) |
392 | if err != nil { |
393 | log.Print(err) |
394 | } |
395 | // Find start of export data. |
396 | i := bytes.Index(data, []byte("\n$$B\n")) + len("\n$$B\n") |
397 | // Count bytes to end of export data. |
398 | nexport := bytes.Index(data[i:], []byte("\n$$\n")) |
399 | fmt.Printf(" %d object-bytes %d export-bytes", len(data), nexport) |
400 | } |
401 | fmt.Println() |
402 | |
403 | os.Remove(opath) |
404 | return nil |
405 | } |
406 | |
407 | type link struct{ dir, flags string } |
408 | |
409 | func (link) long() bool { return false } |
410 | |
411 | func (r link) run(name string, count int) error { |
412 | if linker == "" { |
413 | // No linker. Skip the test. |
414 | return nil |
415 | } |
416 | |
417 | // Build dependencies. |
418 | out, err := exec.Command(*flagGoCmd, "build", "-o", "/dev/null", r.dir).CombinedOutput() |
419 | if err != nil { |
420 | return fmt.Errorf("go build -a %s: %v\n%s", r.dir, err, out) |
421 | } |
422 | |
423 | importcfg, err := genImportcfgFile(r.dir, true) |
424 | if err != nil { |
425 | return err |
426 | } |
427 | defer os.Remove(importcfg) |
428 | |
429 | // Build the main package. |
430 | pkg, err := goList(r.dir) |
431 | if err != nil { |
432 | return err |
433 | } |
434 | args := []string{"-o", "_compilebench_.o", "-importcfg", importcfg} |
435 | args = append(args, pkg.GoFiles...) |
436 | if *flagTrace { |
437 | fmt.Fprintf(os.Stderr, "running: %s %+v\n", |
438 | compiler, args) |
439 | } |
440 | cmd := exec.Command(compiler, args...) |
441 | cmd.Dir = pkg.Dir |
442 | cmd.Stdout = os.Stderr |
443 | cmd.Stderr = os.Stderr |
444 | err = cmd.Run() |
445 | if err != nil { |
446 | return fmt.Errorf("compiling: %v", err) |
447 | } |
448 | defer os.Remove(pkg.Dir + "/_compilebench_.o") |
449 | |
450 | // Link the main package. |
451 | args = []string{"-o", "_compilebench_.exe", "-importcfg", importcfg} |
452 | args = append(args, strings.Fields(*flagLinkerFlags)...) |
453 | args = append(args, strings.Fields(r.flags)...) |
454 | args = append(args, "_compilebench_.o") |
455 | if err := runBuildCmd(name, count, pkg.Dir, linker, args); err != nil { |
456 | return err |
457 | } |
458 | fmt.Println() |
459 | defer os.Remove(pkg.Dir + "/_compilebench_.exe") |
460 | |
461 | return err |
462 | } |
463 | |
464 | // runBuildCmd runs "tool args..." in dir, measures standard build |
465 | // tool metrics, and prints a benchmark line. The caller may print |
466 | // additional metrics and then must print a newline. |
467 | // |
468 | // This assumes tool accepts standard build tool flags like |
469 | // -memprofilerate, -memprofile, and -cpuprofile. |
470 | func runBuildCmd(name string, count int, dir, tool string, args []string) error { |
471 | var preArgs []string |
472 | if *flagMemprofilerate >= 0 { |
473 | preArgs = append(preArgs, "-memprofilerate", fmt.Sprint(*flagMemprofilerate)) |
474 | } |
475 | if *flagAlloc || *flagCpuprofile != "" || *flagMemprofile != "" { |
476 | if *flagAlloc || *flagMemprofile != "" { |
477 | preArgs = append(preArgs, "-memprofile", "_compilebench_.memprof") |
478 | } |
479 | if *flagCpuprofile != "" { |
480 | preArgs = append(preArgs, "-cpuprofile", "_compilebench_.cpuprof") |
481 | } |
482 | } |
483 | if *flagTrace { |
484 | fmt.Fprintf(os.Stderr, "running: %s %+v\n", |
485 | tool, append(preArgs, args...)) |
486 | } |
487 | cmd := exec.Command(tool, append(preArgs, args...)...) |
488 | cmd.Dir = dir |
489 | cmd.Stdout = os.Stderr |
490 | cmd.Stderr = os.Stderr |
491 | start := time.Now() |
492 | err := cmd.Run() |
493 | if err != nil { |
494 | return err |
495 | } |
496 | end := time.Now() |
497 | |
498 | haveAllocs, haveRSS := false, false |
499 | var allocs, allocbytes, rssbytes int64 |
500 | if *flagAlloc || *flagMemprofile != "" { |
501 | out, err := ioutil.ReadFile(dir + "/_compilebench_.memprof") |
502 | if err != nil { |
503 | log.Print("cannot find memory profile after compilation") |
504 | } |
505 | for _, line := range strings.Split(string(out), "\n") { |
506 | f := strings.Fields(line) |
507 | if len(f) < 4 || f[0] != "#" || f[2] != "=" { |
508 | continue |
509 | } |
510 | val, err := strconv.ParseInt(f[3], 0, 64) |
511 | if err != nil { |
512 | continue |
513 | } |
514 | haveAllocs = true |
515 | switch f[1] { |
516 | case "TotalAlloc": |
517 | allocbytes = val |
518 | case "Mallocs": |
519 | allocs = val |
520 | case "MaxRSS": |
521 | haveRSS = true |
522 | rssbytes = val |
523 | } |
524 | } |
525 | if !haveAllocs { |
526 | log.Println("missing stats in memprof (golang.org/issue/18641)") |
527 | } |
528 | |
529 | if *flagMemprofile != "" { |
530 | outpath := *flagMemprofile |
531 | if *flagCount != 1 { |
532 | outpath = fmt.Sprintf("%s_%d", outpath, count) |
533 | } |
534 | if err := ioutil.WriteFile(outpath, out, 0666); err != nil { |
535 | log.Print(err) |
536 | } |
537 | } |
538 | os.Remove(dir + "/_compilebench_.memprof") |
539 | } |
540 | |
541 | if *flagCpuprofile != "" { |
542 | out, err := ioutil.ReadFile(dir + "/_compilebench_.cpuprof") |
543 | if err != nil { |
544 | log.Print(err) |
545 | } |
546 | outpath := *flagCpuprofile |
547 | if *flagCount != 1 { |
548 | outpath = fmt.Sprintf("%s_%d", outpath, count) |
549 | } |
550 | if err := ioutil.WriteFile(outpath, out, 0666); err != nil { |
551 | log.Print(err) |
552 | } |
553 | os.Remove(dir + "/_compilebench_.cpuprof") |
554 | } |
555 | |
556 | wallns := end.Sub(start).Nanoseconds() |
557 | userns := cmd.ProcessState.UserTime().Nanoseconds() |
558 | |
559 | fmt.Printf("%s 1 %d ns/op %d user-ns/op", name, wallns, userns) |
560 | if haveAllocs { |
561 | fmt.Printf(" %d B/op %d allocs/op", allocbytes, allocs) |
562 | } |
563 | if haveRSS { |
564 | fmt.Printf(" %d maxRSS/op", rssbytes) |
565 | } |
566 | |
567 | return nil |
568 | } |
569 | |
570 | // genSymAbisFile runs the assembler on the target packge asm files |
571 | // with "-gensymabis" to produce a symabis file that will feed into |
572 | // the Go source compilation. This is fairly hacky in that if the |
573 | // asm invocation convenion changes it will need to be updated |
574 | // (hopefully that will not be needed too frequently). |
575 | func genSymAbisFile(pkg *Pkg, symAbisFile, incdir string) error { |
576 | args := []string{"-gensymabis", "-o", symAbisFile, |
577 | "-p", pkg.ImportPath, |
578 | "-I", filepath.Join(goroot, "pkg", "include"), |
579 | "-I", incdir, |
580 | "-D", "GOOS_" + runtime.GOOS, |
581 | "-D", "GOARCH_" + runtime.GOARCH} |
582 | if pkg.ImportPath == "reflect" { |
583 | args = append(args, "-compiling-runtime") |
584 | } |
585 | args = append(args, pkg.SFiles...) |
586 | if *flagTrace { |
587 | fmt.Fprintf(os.Stderr, "running: %s %+v\n", |
588 | assembler, args) |
589 | } |
590 | cmd := exec.Command(assembler, args...) |
591 | cmd.Dir = pkg.Dir |
592 | cmd.Stdout = os.Stderr |
593 | cmd.Stderr = os.Stderr |
594 | err := cmd.Run() |
595 | if err != nil { |
596 | return fmt.Errorf("assembling to produce symabis file: %v", err) |
597 | } |
598 | return nil |
599 | } |
600 | |
601 | // genImportcfgFile generates an importcfg file for building package |
602 | // dir. Returns the generated importcfg file path (or empty string |
603 | // if the package has no dependency). |
604 | func genImportcfgFile(dir string, full bool) (string, error) { |
605 | need := "{{.Imports}}" |
606 | if full { |
607 | // for linking, we need transitive dependencies |
608 | need = "{{.Deps}}" |
609 | } |
610 | |
611 | // find imported/dependent packages |
612 | cmd := exec.Command(*flagGoCmd, "list", "-f", need, dir) |
613 | cmd.Stderr = os.Stderr |
614 | out, err := cmd.Output() |
615 | if err != nil { |
616 | return "", fmt.Errorf("go list -f %s %s: %v", need, dir, err) |
617 | } |
618 | // trim [ ]\n |
619 | if len(out) < 3 || out[0] != '[' || out[len(out)-2] != ']' || out[len(out)-1] != '\n' { |
620 | return "", fmt.Errorf("unexpected output from go list -f %s %s: %s", need, dir, out) |
621 | } |
622 | out = out[1 : len(out)-2] |
623 | if len(out) == 0 { |
624 | return "", nil |
625 | } |
626 | |
627 | // build importcfg for imported packages |
628 | cmd = exec.Command(*flagGoCmd, "list", "-export", "-f", "{{if .Export}}packagefile {{.ImportPath}}={{.Export}}{{end}}") |
629 | cmd.Args = append(cmd.Args, strings.Fields(string(out))...) |
630 | cmd.Stderr = os.Stderr |
631 | out, err = cmd.Output() |
632 | if err != nil { |
633 | return "", fmt.Errorf("generating importcfg for %s: %s: %v", dir, cmd, err) |
634 | } |
635 | |
636 | f, err := os.CreateTemp("", "importcfg") |
637 | if err != nil { |
638 | return "", fmt.Errorf("creating tmp importcfg file failed: %v", err) |
639 | } |
640 | defer f.Close() |
641 | if _, err := f.Write(out); err != nil { |
642 | return "", fmt.Errorf("writing importcfg file %s failed: %v", f.Name(), err) |
643 | } |
644 | return f.Name(), nil |
645 | } |
646 |
Members