golang.org/x/tools@v0.21.1-0.20240520172518-788d39e776b1/cmd/compilebench/main.go (about) 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 "log" 85 "os" 86 "os/exec" 87 "path/filepath" 88 "regexp" 89 "runtime" 90 "strconv" 91 "strings" 92 "time" 93 ) 94 95 var ( 96 goroot string 97 compiler string 98 assembler string 99 linker string 100 runRE *regexp.Regexp 101 is6g bool 102 needCompilingRuntimeFlag bool 103 ) 104 105 var ( 106 flagGoCmd = flag.String("go", "go", "path to \"go\" command") 107 flagAlloc = flag.Bool("alloc", false, "report allocations") 108 flagObj = flag.Bool("obj", false, "report object file stats") 109 flagCompiler = flag.String("compile", "", "use `exe` as the cmd/compile binary") 110 flagAssembler = flag.String("asm", "", "use `exe` as the cmd/asm binary") 111 flagCompilerFlags = flag.String("compileflags", "", "additional `flags` to pass to compile") 112 flagLinker = flag.String("link", "", "use `exe` as the cmd/link binary") 113 flagLinkerFlags = flag.String("linkflags", "", "additional `flags` to pass to link") 114 flagRun = flag.String("run", "", "run benchmarks matching `regexp`") 115 flagCount = flag.Int("count", 1, "run benchmarks `n` times") 116 flagCpuprofile = flag.String("cpuprofile", "", "write CPU profile to `file`") 117 flagMemprofile = flag.String("memprofile", "", "write memory profile to `file`") 118 flagMemprofilerate = flag.Int64("memprofilerate", -1, "set memory profile `rate`") 119 flagPackage = flag.String("pkg", "", "if set, benchmark the package at path `pkg`") 120 flagShort = flag.Bool("short", false, "skip long-running benchmarks") 121 flagTrace = flag.Bool("trace", false, "debug tracing of builds") 122 ) 123 124 type test struct { 125 name string 126 r runner 127 } 128 129 type runner interface { 130 long() bool 131 run(name string, count int) error 132 } 133 134 var tests = []test{ 135 {"BenchmarkTemplate", compile{"html/template"}}, 136 {"BenchmarkUnicode", compile{"unicode"}}, 137 {"BenchmarkGoTypes", compile{"go/types"}}, 138 {"BenchmarkCompiler", compile{"cmd/compile/internal/gc"}}, 139 {"BenchmarkSSA", compile{"cmd/compile/internal/ssa"}}, 140 {"BenchmarkFlate", compile{"compress/flate"}}, 141 {"BenchmarkGoParser", compile{"go/parser"}}, 142 {"BenchmarkReflect", compile{"reflect"}}, 143 {"BenchmarkTar", compile{"archive/tar"}}, 144 {"BenchmarkXML", compile{"encoding/xml"}}, 145 {"BenchmarkLinkCompiler", link{"cmd/compile", ""}}, 146 {"BenchmarkExternalLinkCompiler", link{"cmd/compile", "-linkmode=external"}}, 147 {"BenchmarkLinkWithoutDebugCompiler", link{"cmd/compile", "-w"}}, 148 {"BenchmarkStdCmd", goBuild{[]string{"std", "cmd"}}}, 149 {"BenchmarkHelloSize", size{"$GOROOT/test/helloworld.go", false}}, 150 {"BenchmarkCmdGoSize", size{"cmd/go", true}}, 151 } 152 153 func usage() { 154 fmt.Fprintf(os.Stderr, "usage: compilebench [options]\n") 155 fmt.Fprintf(os.Stderr, "options:\n") 156 flag.PrintDefaults() 157 os.Exit(2) 158 } 159 160 func main() { 161 log.SetFlags(0) 162 log.SetPrefix("compilebench: ") 163 flag.Usage = usage 164 flag.Parse() 165 if flag.NArg() != 0 { 166 usage() 167 } 168 169 s, err := exec.Command(*flagGoCmd, "env", "GOROOT").CombinedOutput() 170 if err != nil { 171 log.Fatalf("%s env GOROOT: %v", *flagGoCmd, err) 172 } 173 goroot = strings.TrimSpace(string(s)) 174 os.Setenv("GOROOT", goroot) // for any subcommands 175 176 compiler = *flagCompiler 177 if compiler == "" { 178 var foundTool string 179 foundTool, compiler = toolPath("compile", "6g") 180 if foundTool == "6g" { 181 is6g = true 182 } 183 } 184 assembler = *flagAssembler 185 if assembler == "" { 186 _, assembler = toolPath("asm") 187 } 188 if err := checkCompilingRuntimeFlag(assembler); err != nil { 189 log.Fatalf("checkCompilingRuntimeFlag: %v", err) 190 } 191 192 linker = *flagLinker 193 if linker == "" && !is6g { // TODO: Support 6l 194 _, linker = toolPath("link") 195 } 196 197 if is6g { 198 *flagMemprofilerate = -1 199 *flagAlloc = false 200 *flagCpuprofile = "" 201 *flagMemprofile = "" 202 } 203 204 if *flagRun != "" { 205 r, err := regexp.Compile(*flagRun) 206 if err != nil { 207 log.Fatalf("invalid -run argument: %v", err) 208 } 209 runRE = r 210 } 211 212 if *flagPackage != "" { 213 tests = []test{ 214 {"BenchmarkPkg", compile{*flagPackage}}, 215 {"BenchmarkPkgLink", link{*flagPackage, ""}}, 216 } 217 runRE = nil 218 } 219 220 for i := 0; i < *flagCount; i++ { 221 for _, tt := range tests { 222 if tt.r.long() && *flagShort { 223 continue 224 } 225 if runRE == nil || runRE.MatchString(tt.name) { 226 if err := tt.r.run(tt.name, i); err != nil { 227 log.Printf("%s: %v", tt.name, err) 228 } 229 } 230 } 231 } 232 } 233 234 func toolPath(names ...string) (found, path string) { 235 var out1 []byte 236 var err1 error 237 for i, name := range names { 238 out, err := exec.Command(*flagGoCmd, "tool", "-n", name).CombinedOutput() 239 if err == nil { 240 return name, strings.TrimSpace(string(out)) 241 } 242 if i == 0 { 243 out1, err1 = out, err 244 } 245 } 246 log.Fatalf("go tool -n %s: %v\n%s", names[0], err1, out1) 247 return "", "" 248 } 249 250 type Pkg struct { 251 ImportPath string 252 Dir string 253 GoFiles []string 254 SFiles []string 255 } 256 257 func goList(dir string) (*Pkg, error) { 258 var pkg Pkg 259 out, err := exec.Command(*flagGoCmd, "list", "-json", dir).Output() 260 if err != nil { 261 return nil, fmt.Errorf("go list -json %s: %v", dir, err) 262 } 263 if err := json.Unmarshal(out, &pkg); err != nil { 264 return nil, fmt.Errorf("go list -json %s: unmarshal: %v", dir, err) 265 } 266 return &pkg, nil 267 } 268 269 func runCmd(name string, cmd *exec.Cmd) error { 270 start := time.Now() 271 out, err := cmd.CombinedOutput() 272 if err != nil { 273 return fmt.Errorf("%v\n%s", err, out) 274 } 275 fmt.Printf("%s 1 %d ns/op\n", name, time.Since(start).Nanoseconds()) 276 return nil 277 } 278 279 type goBuild struct{ pkgs []string } 280 281 func (goBuild) long() bool { return true } 282 283 func (r goBuild) run(name string, count int) error { 284 args := []string{"build", "-a"} 285 if *flagCompilerFlags != "" { 286 args = append(args, "-gcflags", *flagCompilerFlags) 287 } 288 args = append(args, r.pkgs...) 289 cmd := exec.Command(*flagGoCmd, args...) 290 cmd.Dir = filepath.Join(goroot, "src") 291 return runCmd(name, cmd) 292 } 293 294 type size struct { 295 // path is either a path to a file ("$GOROOT/test/helloworld.go") or a package path ("cmd/go"). 296 path string 297 isLong bool 298 } 299 300 func (r size) long() bool { return r.isLong } 301 302 func (r size) run(name string, count int) error { 303 if strings.HasPrefix(r.path, "$GOROOT/") { 304 r.path = goroot + "/" + r.path[len("$GOROOT/"):] 305 } 306 307 cmd := exec.Command(*flagGoCmd, "build", "-o", "_compilebenchout_", r.path) 308 cmd.Stdout = os.Stderr 309 cmd.Stderr = os.Stderr 310 if err := cmd.Run(); err != nil { 311 return err 312 } 313 defer os.Remove("_compilebenchout_") 314 info, err := os.Stat("_compilebenchout_") 315 if err != nil { 316 return err 317 } 318 out, err := exec.Command("size", "_compilebenchout_").CombinedOutput() 319 if err != nil { 320 return fmt.Errorf("size: %v\n%s", err, out) 321 } 322 lines := strings.Split(string(out), "\n") 323 if len(lines) < 2 { 324 return fmt.Errorf("not enough output from size: %s", out) 325 } 326 f := strings.Fields(lines[1]) 327 if strings.HasPrefix(lines[0], "__TEXT") && len(f) >= 2 { // OS X 328 fmt.Printf("%s 1 %s text-bytes %s data-bytes %v exe-bytes\n", name, f[0], f[1], info.Size()) 329 } else if strings.Contains(lines[0], "bss") && len(f) >= 3 { 330 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()) 331 } 332 return nil 333 } 334 335 type compile struct{ dir string } 336 337 func (compile) long() bool { return false } 338 339 func (c compile) run(name string, count int) error { 340 // Make sure dependencies needed by go tool compile are built. 341 out, err := exec.Command(*flagGoCmd, "build", c.dir).CombinedOutput() 342 if err != nil { 343 return fmt.Errorf("go build %s: %v\n%s", c.dir, err, out) 344 } 345 346 // Find dir and source file list. 347 pkg, err := goList(c.dir) 348 if err != nil { 349 return err 350 } 351 352 importcfg, err := genImportcfgFile(c.dir, "", false) // TODO: pass compiler flags? 353 if err != nil { 354 return err 355 } 356 357 // If this package has assembly files, we'll need to pass a symabis 358 // file to the compiler; call a helper to invoke the assembler 359 // to do that. 360 var symAbisFile string 361 var asmIncFile string 362 if len(pkg.SFiles) != 0 { 363 symAbisFile = filepath.Join(pkg.Dir, "symabis") 364 asmIncFile = filepath.Join(pkg.Dir, "go_asm.h") 365 content := "\n" 366 if err := os.WriteFile(asmIncFile, []byte(content), 0666); err != nil { 367 return fmt.Errorf("os.WriteFile(%s) failed: %v", asmIncFile, err) 368 } 369 defer os.Remove(symAbisFile) 370 defer os.Remove(asmIncFile) 371 if err := genSymAbisFile(pkg, symAbisFile, pkg.Dir); err != nil { 372 return err 373 } 374 } 375 376 args := []string{"-o", "_compilebench_.o", "-p", pkg.ImportPath} 377 args = append(args, strings.Fields(*flagCompilerFlags)...) 378 if symAbisFile != "" { 379 args = append(args, "-symabis", symAbisFile) 380 } 381 if importcfg != "" { 382 args = append(args, "-importcfg", importcfg) 383 defer os.Remove(importcfg) 384 } 385 args = append(args, pkg.GoFiles...) 386 if err := runBuildCmd(name, count, pkg.Dir, compiler, args); err != nil { 387 return err 388 } 389 390 opath := pkg.Dir + "/_compilebench_.o" 391 if *flagObj { 392 // TODO(josharian): object files are big; just read enough to find what we seek. 393 data, err := os.ReadFile(opath) 394 if err != nil { 395 log.Print(err) 396 } 397 // Find start of export data. 398 i := bytes.Index(data, []byte("\n$$B\n")) + len("\n$$B\n") 399 // Count bytes to end of export data. 400 nexport := bytes.Index(data[i:], []byte("\n$$\n")) 401 fmt.Printf(" %d object-bytes %d export-bytes", len(data), nexport) 402 } 403 fmt.Println() 404 405 os.Remove(opath) 406 return nil 407 } 408 409 type link struct{ dir, flags string } 410 411 func (link) long() bool { return false } 412 413 func (r link) run(name string, count int) error { 414 if linker == "" { 415 // No linker. Skip the test. 416 return nil 417 } 418 419 // Build dependencies. 420 ldflags := *flagLinkerFlags 421 if r.flags != "" { 422 if ldflags != "" { 423 ldflags += " " 424 } 425 ldflags += r.flags 426 } 427 out, err := exec.Command(*flagGoCmd, "build", "-o", "/dev/null", "-ldflags="+ldflags, r.dir).CombinedOutput() 428 if err != nil { 429 return fmt.Errorf("go build -a %s: %v\n%s", r.dir, err, out) 430 } 431 432 importcfg, err := genImportcfgFile(r.dir, "-ldflags="+ldflags, true) 433 if err != nil { 434 return err 435 } 436 defer os.Remove(importcfg) 437 438 // Build the main package. 439 pkg, err := goList(r.dir) 440 if err != nil { 441 return err 442 } 443 args := []string{"-o", "_compilebench_.o", "-importcfg", importcfg} 444 args = append(args, pkg.GoFiles...) 445 if *flagTrace { 446 fmt.Fprintf(os.Stderr, "running: %s %+v\n", 447 compiler, args) 448 } 449 cmd := exec.Command(compiler, args...) 450 cmd.Dir = pkg.Dir 451 cmd.Stdout = os.Stderr 452 cmd.Stderr = os.Stderr 453 err = cmd.Run() 454 if err != nil { 455 return fmt.Errorf("compiling: %v", err) 456 } 457 defer os.Remove(pkg.Dir + "/_compilebench_.o") 458 459 // Link the main package. 460 args = []string{"-o", "_compilebench_.exe", "-importcfg", importcfg} 461 args = append(args, strings.Fields(*flagLinkerFlags)...) 462 args = append(args, strings.Fields(r.flags)...) 463 args = append(args, "_compilebench_.o") 464 if err := runBuildCmd(name, count, pkg.Dir, linker, args); err != nil { 465 return err 466 } 467 fmt.Println() 468 defer os.Remove(pkg.Dir + "/_compilebench_.exe") 469 470 return err 471 } 472 473 // runBuildCmd runs "tool args..." in dir, measures standard build 474 // tool metrics, and prints a benchmark line. The caller may print 475 // additional metrics and then must print a newline. 476 // 477 // This assumes tool accepts standard build tool flags like 478 // -memprofilerate, -memprofile, and -cpuprofile. 479 func runBuildCmd(name string, count int, dir, tool string, args []string) error { 480 var preArgs []string 481 if *flagMemprofilerate >= 0 { 482 preArgs = append(preArgs, "-memprofilerate", fmt.Sprint(*flagMemprofilerate)) 483 } 484 if *flagAlloc || *flagCpuprofile != "" || *flagMemprofile != "" { 485 if *flagAlloc || *flagMemprofile != "" { 486 preArgs = append(preArgs, "-memprofile", "_compilebench_.memprof") 487 } 488 if *flagCpuprofile != "" { 489 preArgs = append(preArgs, "-cpuprofile", "_compilebench_.cpuprof") 490 } 491 } 492 if *flagTrace { 493 fmt.Fprintf(os.Stderr, "running: %s %+v\n", 494 tool, append(preArgs, args...)) 495 } 496 cmd := exec.Command(tool, append(preArgs, args...)...) 497 cmd.Dir = dir 498 cmd.Stdout = os.Stderr 499 cmd.Stderr = os.Stderr 500 start := time.Now() 501 err := cmd.Run() 502 if err != nil { 503 return err 504 } 505 end := time.Now() 506 507 haveAllocs, haveRSS := false, false 508 var allocs, allocbytes, rssbytes int64 509 if *flagAlloc || *flagMemprofile != "" { 510 out, err := os.ReadFile(dir + "/_compilebench_.memprof") 511 if err != nil { 512 log.Print("cannot find memory profile after compilation") 513 } 514 for _, line := range strings.Split(string(out), "\n") { 515 f := strings.Fields(line) 516 if len(f) < 4 || f[0] != "#" || f[2] != "=" { 517 continue 518 } 519 val, err := strconv.ParseInt(f[3], 0, 64) 520 if err != nil { 521 continue 522 } 523 haveAllocs = true 524 switch f[1] { 525 case "TotalAlloc": 526 allocbytes = val 527 case "Mallocs": 528 allocs = val 529 case "MaxRSS": 530 haveRSS = true 531 rssbytes = val 532 } 533 } 534 if !haveAllocs { 535 log.Println("missing stats in memprof (golang.org/issue/18641)") 536 } 537 538 if *flagMemprofile != "" { 539 outpath := *flagMemprofile 540 if *flagCount != 1 { 541 outpath = fmt.Sprintf("%s_%d", outpath, count) 542 } 543 if err := os.WriteFile(outpath, out, 0666); err != nil { 544 log.Print(err) 545 } 546 } 547 os.Remove(dir + "/_compilebench_.memprof") 548 } 549 550 if *flagCpuprofile != "" { 551 out, err := os.ReadFile(dir + "/_compilebench_.cpuprof") 552 if err != nil { 553 log.Print(err) 554 } 555 outpath := *flagCpuprofile 556 if *flagCount != 1 { 557 outpath = fmt.Sprintf("%s_%d", outpath, count) 558 } 559 if err := os.WriteFile(outpath, out, 0666); err != nil { 560 log.Print(err) 561 } 562 os.Remove(dir + "/_compilebench_.cpuprof") 563 } 564 565 wallns := end.Sub(start).Nanoseconds() 566 userns := cmd.ProcessState.UserTime().Nanoseconds() 567 568 fmt.Printf("%s 1 %d ns/op %d user-ns/op", name, wallns, userns) 569 if haveAllocs { 570 fmt.Printf(" %d B/op %d allocs/op", allocbytes, allocs) 571 } 572 if haveRSS { 573 fmt.Printf(" %d maxRSS/op", rssbytes) 574 } 575 576 return nil 577 } 578 579 func checkCompilingRuntimeFlag(assembler string) error { 580 td, err := os.MkdirTemp("", "asmsrcd") 581 if err != nil { 582 return fmt.Errorf("MkdirTemp failed: %v", err) 583 } 584 defer os.RemoveAll(td) 585 src := filepath.Join(td, "asm.s") 586 obj := filepath.Join(td, "asm.o") 587 const code = ` 588 TEXT ·foo(SB),$0-0 589 RET 590 ` 591 if err := os.WriteFile(src, []byte(code), 0644); err != nil { 592 return fmt.Errorf("writing %s failed: %v", src, err) 593 } 594 595 // Try compiling the assembly source file passing 596 // -compiling-runtime; if it succeeds, then we'll need it 597 // when doing assembly of the reflect package later on. 598 // If it does not succeed, the assumption is that it's not 599 // needed. 600 args := []string{"-o", obj, "-p", "reflect", "-compiling-runtime", src} 601 cmd := exec.Command(assembler, args...) 602 cmd.Dir = td 603 out, aerr := cmd.CombinedOutput() 604 if aerr != nil { 605 if strings.Contains(string(out), "flag provided but not defined: -compiling-runtime") { 606 // flag not defined: assume we're using a recent assembler, so 607 // don't use -compiling-runtime. 608 return nil 609 } 610 // error is not flag-related; report it. 611 return fmt.Errorf("problems invoking assembler with args %+v: error %v\n%s\n", args, aerr, out) 612 } 613 // asm invocation succeeded -- assume we need the flag. 614 needCompilingRuntimeFlag = true 615 return nil 616 } 617 618 // genSymAbisFile runs the assembler on the target package asm files 619 // with "-gensymabis" to produce a symabis file that will feed into 620 // the Go source compilation. This is fairly hacky in that if the 621 // asm invocation convention changes it will need to be updated 622 // (hopefully that will not be needed too frequently). 623 func genSymAbisFile(pkg *Pkg, symAbisFile, incdir string) error { 624 args := []string{"-gensymabis", "-o", symAbisFile, 625 "-p", pkg.ImportPath, 626 "-I", filepath.Join(goroot, "pkg", "include"), 627 "-I", incdir, 628 "-D", "GOOS_" + runtime.GOOS, 629 "-D", "GOARCH_" + runtime.GOARCH} 630 if pkg.ImportPath == "reflect" && needCompilingRuntimeFlag { 631 args = append(args, "-compiling-runtime") 632 } 633 args = append(args, pkg.SFiles...) 634 if *flagTrace { 635 fmt.Fprintf(os.Stderr, "running: %s %+v\n", 636 assembler, args) 637 } 638 cmd := exec.Command(assembler, args...) 639 cmd.Dir = pkg.Dir 640 cmd.Stdout = os.Stderr 641 cmd.Stderr = os.Stderr 642 err := cmd.Run() 643 if err != nil { 644 return fmt.Errorf("assembling to produce symabis file: %v", err) 645 } 646 return nil 647 } 648 649 // genImportcfgFile generates an importcfg file for building package 650 // dir. Returns the generated importcfg file path (or empty string 651 // if the package has no dependency). 652 func genImportcfgFile(dir string, flags string, full bool) (string, error) { 653 need := "{{.Imports}}" 654 if full { 655 // for linking, we need transitive dependencies 656 need = "{{.Deps}}" 657 } 658 659 if flags == "" { 660 flags = "--" // passing "" to go list, it will match to the current directory 661 } 662 663 // find imported/dependent packages 664 cmd := exec.Command(*flagGoCmd, "list", "-f", need, flags, dir) 665 cmd.Stderr = os.Stderr 666 out, err := cmd.Output() 667 if err != nil { 668 return "", fmt.Errorf("go list -f %s %s: %v", need, dir, err) 669 } 670 // trim [ ]\n 671 if len(out) < 3 || out[0] != '[' || out[len(out)-2] != ']' || out[len(out)-1] != '\n' { 672 return "", fmt.Errorf("unexpected output from go list -f %s %s: %s", need, dir, out) 673 } 674 out = out[1 : len(out)-2] 675 if len(out) == 0 { 676 return "", nil 677 } 678 679 // build importcfg for imported packages 680 cmd = exec.Command(*flagGoCmd, "list", "-export", "-f", "{{if .Export}}packagefile {{.ImportPath}}={{.Export}}{{end}}", flags) 681 cmd.Args = append(cmd.Args, strings.Fields(string(out))...) 682 cmd.Stderr = os.Stderr 683 out, err = cmd.Output() 684 if err != nil { 685 return "", fmt.Errorf("generating importcfg for %s: %s: %v", dir, cmd, err) 686 } 687 688 f, err := os.CreateTemp("", "importcfg") 689 if err != nil { 690 return "", fmt.Errorf("creating tmp importcfg file failed: %v", err) 691 } 692 defer f.Close() 693 if _, err := f.Write(out); err != nil { 694 return "", fmt.Errorf("writing importcfg file %s failed: %v", f.Name(), err) 695 } 696 return f.Name(), nil 697 }