...

Source file src/golang.org/x/tools/cmd/compilebench/main.go

Documentation: golang.org/x/tools/cmd/compilebench

     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  }
   698  

View as plain text