github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/go/work/gc.go (about)

     1  // Copyright 2011 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 work
     6  
     7  import (
     8  	"bufio"
     9  	"bytes"
    10  	"fmt"
    11  	"io"
    12  	"log"
    13  	"os"
    14  	"path/filepath"
    15  	"runtime"
    16  	"strings"
    17  
    18  	"github.com/go-asm/go/platform"
    19  
    20  	"crypto/sha1"
    21  
    22  	"github.com/go-asm/go/cmd/go/base"
    23  	"github.com/go-asm/go/cmd/go/cfg"
    24  	"github.com/go-asm/go/cmd/go/fsys"
    25  	"github.com/go-asm/go/cmd/go/gover"
    26  	"github.com/go-asm/go/cmd/go/load"
    27  	"github.com/go-asm/go/cmd/go/str"
    28  	"github.com/go-asm/go/cmd/quoted"
    29  )
    30  
    31  // Tests can override this by setting $TESTGO_TOOLCHAIN_VERSION.
    32  var ToolchainVersion = runtime.Version()
    33  
    34  // The 'path' used for GOROOT_FINAL when -trimpath is specified
    35  const trimPathGoRootFinal string = "$GOROOT"
    36  
    37  // The Go toolchain.
    38  
    39  type gcToolchain struct{}
    40  
    41  func (gcToolchain) compiler() string {
    42  	return base.Tool("compile")
    43  }
    44  
    45  func (gcToolchain) linker() string {
    46  	return base.Tool("link")
    47  }
    48  
    49  func pkgPath(a *Action) string {
    50  	p := a.Package
    51  	ppath := p.ImportPath
    52  	if cfg.BuildBuildmode == "plugin" {
    53  		ppath = pluginPath(a)
    54  	} else if p.Name == "main" && !p.Internal.ForceLibrary {
    55  		ppath = "main"
    56  	}
    57  	return ppath
    58  }
    59  
    60  func (gcToolchain) gc(b *Builder, a *Action, archive string, importcfg, embedcfg []byte, symabis string, asmhdr bool, gofiles []string) (ofile string, output []byte, err error) {
    61  	p := a.Package
    62  	sh := b.Shell(a)
    63  	objdir := a.Objdir
    64  	if archive != "" {
    65  		ofile = archive
    66  	} else {
    67  		out := "_go_.o"
    68  		ofile = objdir + out
    69  	}
    70  
    71  	pkgpath := pkgPath(a)
    72  	defaultGcFlags := []string{"-p", pkgpath}
    73  	if p.Module != nil {
    74  		v := p.Module.GoVersion
    75  		if v == "" {
    76  			v = gover.DefaultGoModVersion
    77  		}
    78  		if allowedVersion(v) {
    79  			defaultGcFlags = append(defaultGcFlags, "-lang=go"+gover.Lang(v))
    80  		}
    81  	}
    82  	if p.Standard {
    83  		defaultGcFlags = append(defaultGcFlags, "-std")
    84  	}
    85  
    86  	// If we're giving the compiler the entire package (no C etc files), tell it that,
    87  	// so that it can give good error messages about forward declarations.
    88  	// Exceptions: a few standard packages have forward declarations for
    89  	// pieces supplied behind-the-scenes by package runtime.
    90  	extFiles := len(p.CgoFiles) + len(p.CFiles) + len(p.CXXFiles) + len(p.MFiles) + len(p.FFiles) + len(p.SFiles) + len(p.SysoFiles) + len(p.SwigFiles) + len(p.SwigCXXFiles)
    91  	if p.Standard {
    92  		switch p.ImportPath {
    93  		case "bytes", "github.com/go-asm/go/poll", "net", "os":
    94  			fallthrough
    95  		case "runtime/metrics", "runtime/pprof", "runtime/trace":
    96  			fallthrough
    97  		case "sync", "syscall", "time":
    98  			extFiles++
    99  		}
   100  	}
   101  	if extFiles == 0 {
   102  		defaultGcFlags = append(defaultGcFlags, "-complete")
   103  	}
   104  	if cfg.BuildContext.InstallSuffix != "" {
   105  		defaultGcFlags = append(defaultGcFlags, "-installsuffix", cfg.BuildContext.InstallSuffix)
   106  	}
   107  	if a.buildID != "" {
   108  		defaultGcFlags = append(defaultGcFlags, "-buildid", a.buildID)
   109  	}
   110  	if p.Internal.OmitDebug || cfg.Goos == "plan9" || cfg.Goarch == "wasm" {
   111  		defaultGcFlags = append(defaultGcFlags, "-dwarf=false")
   112  	}
   113  	if strings.HasPrefix(ToolchainVersion, "go1") && !strings.Contains(os.Args[0], "go_bootstrap") {
   114  		defaultGcFlags = append(defaultGcFlags, "-goversion", ToolchainVersion)
   115  	}
   116  	if p.Internal.Cover.Cfg != "" {
   117  		defaultGcFlags = append(defaultGcFlags, "-coveragecfg="+p.Internal.Cover.Cfg)
   118  	}
   119  	if p.Internal.PGOProfile != "" {
   120  		defaultGcFlags = append(defaultGcFlags, "-pgoprofile="+p.Internal.PGOProfile)
   121  	}
   122  	if symabis != "" {
   123  		defaultGcFlags = append(defaultGcFlags, "-symabis", symabis)
   124  	}
   125  
   126  	gcflags := str.StringList(forcedGcflags, p.Internal.Gcflags)
   127  	if p.Internal.FuzzInstrument {
   128  		gcflags = append(gcflags, fuzzInstrumentFlags()...)
   129  	}
   130  	// Add -c=N to use concurrent backend compilation, if possible.
   131  	if c := gcBackendConcurrency(gcflags); c > 1 {
   132  		defaultGcFlags = append(defaultGcFlags, fmt.Sprintf("-c=%d", c))
   133  	}
   134  
   135  	args := []any{cfg.BuildToolexec, base.Tool("compile"), "-o", ofile, "-trimpath", a.trimpath(), defaultGcFlags, gcflags}
   136  	if p.Internal.LocalPrefix == "" {
   137  		args = append(args, "-nolocalimports")
   138  	} else {
   139  		args = append(args, "-D", p.Internal.LocalPrefix)
   140  	}
   141  	if importcfg != nil {
   142  		if err := sh.writeFile(objdir+"importcfg", importcfg); err != nil {
   143  			return "", nil, err
   144  		}
   145  		args = append(args, "-importcfg", objdir+"importcfg")
   146  	}
   147  	if embedcfg != nil {
   148  		if err := sh.writeFile(objdir+"embedcfg", embedcfg); err != nil {
   149  			return "", nil, err
   150  		}
   151  		args = append(args, "-embedcfg", objdir+"embedcfg")
   152  	}
   153  	if ofile == archive {
   154  		args = append(args, "-pack")
   155  	}
   156  	if asmhdr {
   157  		args = append(args, "-asmhdr", objdir+"go_asm.h")
   158  	}
   159  
   160  	for _, f := range gofiles {
   161  		f := mkAbs(p.Dir, f)
   162  
   163  		// Handle overlays. Convert path names using OverlayPath
   164  		// so these paths can be handed directly to tools.
   165  		// Deleted files won't show up in when scanning directories earlier,
   166  		// so OverlayPath will never return "" (meaning a deleted file) here.
   167  		// TODO(#39958): Handle cases where the package directory
   168  		// doesn't exist on disk (this can happen when all the package's
   169  		// files are in an overlay): the code expects the package directory
   170  		// to exist and runs some tools in that directory.
   171  		// TODO(#39958): Process the overlays when the
   172  		// gofiles, cgofiles, cfiles, sfiles, and cxxfiles variables are
   173  		// created in (*Builder).build. Doing that requires rewriting the
   174  		// code that uses those values to expect absolute paths.
   175  		f, _ = fsys.OverlayPath(f)
   176  
   177  		args = append(args, f)
   178  	}
   179  
   180  	output, err = sh.runOut(base.Cwd(), nil, args...)
   181  	return ofile, output, err
   182  }
   183  
   184  // gcBackendConcurrency returns the backend compiler concurrency level for a package compilation.
   185  func gcBackendConcurrency(gcflags []string) int {
   186  	// First, check whether we can use -c at all for this compilation.
   187  	canDashC := concurrentGCBackendCompilationEnabledByDefault
   188  
   189  	switch e := os.Getenv("GO19CONCURRENTCOMPILATION"); e {
   190  	case "0":
   191  		canDashC = false
   192  	case "1":
   193  		canDashC = true
   194  	case "":
   195  		// Not set. Use default.
   196  	default:
   197  		log.Fatalf("GO19CONCURRENTCOMPILATION must be 0, 1, or unset, got %q", e)
   198  	}
   199  
   200  	// TODO: Test and delete these conditions.
   201  	if cfg.ExperimentErr != nil || cfg.Experiment.FieldTrack || cfg.Experiment.PreemptibleLoops {
   202  		canDashC = false
   203  	}
   204  
   205  	if !canDashC {
   206  		return 1
   207  	}
   208  
   209  	// Decide how many concurrent backend compilations to allow.
   210  	//
   211  	// If we allow too many, in theory we might end up with p concurrent processes,
   212  	// each with c concurrent backend compiles, all fighting over the same resources.
   213  	// However, in practice, that seems not to happen too much.
   214  	// Most build graphs are surprisingly serial, so p==1 for much of the build.
   215  	// Furthermore, concurrent backend compilation is only enabled for a part
   216  	// of the overall compiler execution, so c==1 for much of the build.
   217  	// So don't worry too much about that interaction for now.
   218  	//
   219  	// However, in practice, setting c above 4 tends not to help very much.
   220  	// See the analysis in CL 41192.
   221  	//
   222  	// TODO(josharian): attempt to detect whether this particular compilation
   223  	// is likely to be a bottleneck, e.g. when:
   224  	//   - it has no successor packages to compile (usually package main)
   225  	//   - all paths through the build graph pass through it
   226  	//   - critical path scheduling says it is high priority
   227  	// and in such a case, set c to runtime.GOMAXPROCS(0).
   228  	// By default this is the same as runtime.NumCPU.
   229  	// We do this now when p==1.
   230  	// To limit parallelism, set GOMAXPROCS below numCPU; this may be useful
   231  	// on a low-memory builder, or if a deterministic build order is required.
   232  	c := runtime.GOMAXPROCS(0)
   233  	if cfg.BuildP == 1 {
   234  		// No process parallelism, do not cap compiler parallelism.
   235  		return c
   236  	}
   237  	// Some process parallelism. Set c to min(4, maxprocs).
   238  	if c > 4 {
   239  		c = 4
   240  	}
   241  	return c
   242  }
   243  
   244  // trimpath returns the -trimpath argument to use
   245  // when compiling the action.
   246  func (a *Action) trimpath() string {
   247  	// Keep in sync with Builder.ccompile
   248  	// The trimmed paths are a little different, but we need to trim in the
   249  	// same situations.
   250  
   251  	// Strip the object directory entirely.
   252  	objdir := a.Objdir
   253  	if len(objdir) > 1 && objdir[len(objdir)-1] == filepath.Separator {
   254  		objdir = objdir[:len(objdir)-1]
   255  	}
   256  	rewrite := ""
   257  
   258  	rewriteDir := a.Package.Dir
   259  	if cfg.BuildTrimpath {
   260  		importPath := a.Package.Internal.OrigImportPath
   261  		if m := a.Package.Module; m != nil && m.Version != "" {
   262  			rewriteDir = m.Path + "@" + m.Version + strings.TrimPrefix(importPath, m.Path)
   263  		} else {
   264  			rewriteDir = importPath
   265  		}
   266  		rewrite += a.Package.Dir + "=>" + rewriteDir + ";"
   267  	}
   268  
   269  	// Add rewrites for overlays. The 'from' and 'to' paths in overlays don't need to have
   270  	// same basename, so go from the overlay contents file path (passed to the compiler)
   271  	// to the path the disk path would be rewritten to.
   272  
   273  	cgoFiles := make(map[string]bool)
   274  	for _, f := range a.Package.CgoFiles {
   275  		cgoFiles[f] = true
   276  	}
   277  
   278  	// TODO(matloob): Higher up in the stack, when the logic for deciding when to make copies
   279  	// of c/c++/m/f/hfiles is consolidated, use the same logic that Build uses to determine
   280  	// whether to create the copies in objdir to decide whether to rewrite objdir to the
   281  	// package directory here.
   282  	var overlayNonGoRewrites string // rewrites for non-go files
   283  	hasCgoOverlay := false
   284  	if fsys.OverlayFile != "" {
   285  		for _, filename := range a.Package.AllFiles() {
   286  			path := filename
   287  			if !filepath.IsAbs(path) {
   288  				path = filepath.Join(a.Package.Dir, path)
   289  			}
   290  			base := filepath.Base(path)
   291  			isGo := strings.HasSuffix(filename, ".go") || strings.HasSuffix(filename, ".s")
   292  			isCgo := cgoFiles[filename] || !isGo
   293  			overlayPath, isOverlay := fsys.OverlayPath(path)
   294  			if isCgo && isOverlay {
   295  				hasCgoOverlay = true
   296  			}
   297  			if !isCgo && isOverlay {
   298  				rewrite += overlayPath + "=>" + filepath.Join(rewriteDir, base) + ";"
   299  			} else if isCgo {
   300  				// Generate rewrites for non-Go files copied to files in objdir.
   301  				if filepath.Dir(path) == a.Package.Dir {
   302  					// This is a file copied to objdir.
   303  					overlayNonGoRewrites += filepath.Join(objdir, base) + "=>" + filepath.Join(rewriteDir, base) + ";"
   304  				}
   305  			} else {
   306  				// Non-overlay Go files are covered by the a.Package.Dir rewrite rule above.
   307  			}
   308  		}
   309  	}
   310  	if hasCgoOverlay {
   311  		rewrite += overlayNonGoRewrites
   312  	}
   313  	rewrite += objdir + "=>"
   314  
   315  	return rewrite
   316  }
   317  
   318  func asmArgs(a *Action, p *load.Package) []any {
   319  	// Add -I pkg/GOOS_GOARCH so #include "textflag.h" works in .s files.
   320  	inc := filepath.Join(cfg.GOROOT, "pkg", "include")
   321  	pkgpath := pkgPath(a)
   322  	args := []any{cfg.BuildToolexec, base.Tool("asm"), "-p", pkgpath, "-trimpath", a.trimpath(), "-I", a.Objdir, "-I", inc, "-D", "GOOS_" + cfg.Goos, "-D", "GOARCH_" + cfg.Goarch, forcedAsmflags, p.Internal.Asmflags}
   323  	if p.ImportPath == "runtime" && cfg.Goarch == "386" {
   324  		for _, arg := range forcedAsmflags {
   325  			if arg == "-dynlink" {
   326  				args = append(args, "-D=GOBUILDMODE_shared=1")
   327  			}
   328  		}
   329  	}
   330  
   331  	if cfg.Goarch == "386" {
   332  		// Define GO386_value from cfg.GO386.
   333  		args = append(args, "-D", "GO386_"+cfg.GO386)
   334  	}
   335  
   336  	if cfg.Goarch == "amd64" {
   337  		// Define GOAMD64_value from cfg.GOAMD64.
   338  		args = append(args, "-D", "GOAMD64_"+cfg.GOAMD64)
   339  	}
   340  
   341  	if cfg.Goarch == "mips" || cfg.Goarch == "mipsle" {
   342  		// Define GOMIPS_value from cfg.GOMIPS.
   343  		args = append(args, "-D", "GOMIPS_"+cfg.GOMIPS)
   344  	}
   345  
   346  	if cfg.Goarch == "mips64" || cfg.Goarch == "mips64le" {
   347  		// Define GOMIPS64_value from cfg.GOMIPS64.
   348  		args = append(args, "-D", "GOMIPS64_"+cfg.GOMIPS64)
   349  	}
   350  
   351  	if cfg.Goarch == "ppc64" || cfg.Goarch == "ppc64le" {
   352  		// Define GOPPC64_power8..N from cfg.PPC64.
   353  		// We treat each powerpc version as a superset of functionality.
   354  		switch cfg.GOPPC64 {
   355  		case "power10":
   356  			args = append(args, "-D", "GOPPC64_power10")
   357  			fallthrough
   358  		case "power9":
   359  			args = append(args, "-D", "GOPPC64_power9")
   360  			fallthrough
   361  		default: // This should always be power8.
   362  			args = append(args, "-D", "GOPPC64_power8")
   363  		}
   364  	}
   365  
   366  	if cfg.Goarch == "arm" {
   367  		// Define GOARM_value from cfg.GOARM.
   368  		switch cfg.GOARM {
   369  		case "7":
   370  			args = append(args, "-D", "GOARM_7")
   371  			fallthrough
   372  		case "6":
   373  			args = append(args, "-D", "GOARM_6")
   374  			fallthrough
   375  		default:
   376  			args = append(args, "-D", "GOARM_5")
   377  		}
   378  	}
   379  
   380  	return args
   381  }
   382  
   383  func (gcToolchain) asm(b *Builder, a *Action, sfiles []string) ([]string, error) {
   384  	p := a.Package
   385  	args := asmArgs(a, p)
   386  
   387  	var ofiles []string
   388  	for _, sfile := range sfiles {
   389  		overlayPath, _ := fsys.OverlayPath(mkAbs(p.Dir, sfile))
   390  		ofile := a.Objdir + sfile[:len(sfile)-len(".s")] + ".o"
   391  		ofiles = append(ofiles, ofile)
   392  		args1 := append(args, "-o", ofile, overlayPath)
   393  		if err := b.Shell(a).run(p.Dir, p.ImportPath, nil, args1...); err != nil {
   394  			return nil, err
   395  		}
   396  	}
   397  	return ofiles, nil
   398  }
   399  
   400  func (gcToolchain) symabis(b *Builder, a *Action, sfiles []string) (string, error) {
   401  	sh := b.Shell(a)
   402  
   403  	mkSymabis := func(p *load.Package, sfiles []string, path string) error {
   404  		args := asmArgs(a, p)
   405  		args = append(args, "-gensymabis", "-o", path)
   406  		for _, sfile := range sfiles {
   407  			if p.ImportPath == "runtime/cgo" && strings.HasPrefix(sfile, "gcc_") {
   408  				continue
   409  			}
   410  			op, _ := fsys.OverlayPath(mkAbs(p.Dir, sfile))
   411  			args = append(args, op)
   412  		}
   413  
   414  		// Supply an empty go_asm.h as if the compiler had been run.
   415  		// -gensymabis parsing is lax enough that we don't need the
   416  		// actual definitions that would appear in go_asm.h.
   417  		if err := sh.writeFile(a.Objdir+"go_asm.h", nil); err != nil {
   418  			return err
   419  		}
   420  
   421  		return sh.run(p.Dir, p.ImportPath, nil, args...)
   422  	}
   423  
   424  	var symabis string // Only set if we actually create the file
   425  	p := a.Package
   426  	if len(sfiles) != 0 {
   427  		symabis = a.Objdir + "symabis"
   428  		if err := mkSymabis(p, sfiles, symabis); err != nil {
   429  			return "", err
   430  		}
   431  	}
   432  
   433  	return symabis, nil
   434  }
   435  
   436  // toolVerify checks that the command line args writes the same output file
   437  // if run using newTool instead.
   438  // Unused now but kept around for future use.
   439  func toolVerify(a *Action, b *Builder, p *load.Package, newTool string, ofile string, args []any) error {
   440  	newArgs := make([]any, len(args))
   441  	copy(newArgs, args)
   442  	newArgs[1] = base.Tool(newTool)
   443  	newArgs[3] = ofile + ".new" // x.6 becomes x.6.new
   444  	if err := b.Shell(a).run(p.Dir, p.ImportPath, nil, newArgs...); err != nil {
   445  		return err
   446  	}
   447  	data1, err := os.ReadFile(ofile)
   448  	if err != nil {
   449  		return err
   450  	}
   451  	data2, err := os.ReadFile(ofile + ".new")
   452  	if err != nil {
   453  		return err
   454  	}
   455  	if !bytes.Equal(data1, data2) {
   456  		return fmt.Errorf("%s and %s produced different output files:\n%s\n%s", filepath.Base(args[1].(string)), newTool, strings.Join(str.StringList(args...), " "), strings.Join(str.StringList(newArgs...), " "))
   457  	}
   458  	os.Remove(ofile + ".new")
   459  	return nil
   460  }
   461  
   462  func (gcToolchain) pack(b *Builder, a *Action, afile string, ofiles []string) error {
   463  	var absOfiles []string
   464  	for _, f := range ofiles {
   465  		absOfiles = append(absOfiles, mkAbs(a.Objdir, f))
   466  	}
   467  	absAfile := mkAbs(a.Objdir, afile)
   468  
   469  	// The archive file should have been created by the compiler.
   470  	// Since it used to not work that way, verify.
   471  	if !cfg.BuildN {
   472  		if _, err := os.Stat(absAfile); err != nil {
   473  			base.Fatalf("os.Stat of archive file failed: %v", err)
   474  		}
   475  	}
   476  
   477  	p := a.Package
   478  	sh := b.Shell(a)
   479  	if cfg.BuildN || cfg.BuildX {
   480  		cmdline := str.StringList(base.Tool("pack"), "r", absAfile, absOfiles)
   481  		sh.ShowCmd(p.Dir, "%s # internal", joinUnambiguously(cmdline))
   482  	}
   483  	if cfg.BuildN {
   484  		return nil
   485  	}
   486  	if err := packInternal(absAfile, absOfiles); err != nil {
   487  		return sh.reportCmd("", "", nil, err)
   488  	}
   489  	return nil
   490  }
   491  
   492  func packInternal(afile string, ofiles []string) error {
   493  	dst, err := os.OpenFile(afile, os.O_WRONLY|os.O_APPEND, 0)
   494  	if err != nil {
   495  		return err
   496  	}
   497  	defer dst.Close() // only for error returns or panics
   498  	w := bufio.NewWriter(dst)
   499  
   500  	for _, ofile := range ofiles {
   501  		src, err := os.Open(ofile)
   502  		if err != nil {
   503  			return err
   504  		}
   505  		fi, err := src.Stat()
   506  		if err != nil {
   507  			src.Close()
   508  			return err
   509  		}
   510  		// Note: Not using %-16.16s format because we care
   511  		// about bytes, not runes.
   512  		name := fi.Name()
   513  		if len(name) > 16 {
   514  			name = name[:16]
   515  		} else {
   516  			name += strings.Repeat(" ", 16-len(name))
   517  		}
   518  		size := fi.Size()
   519  		fmt.Fprintf(w, "%s%-12d%-6d%-6d%-8o%-10d`\n",
   520  			name, 0, 0, 0, 0644, size)
   521  		n, err := io.Copy(w, src)
   522  		src.Close()
   523  		if err == nil && n < size {
   524  			err = io.ErrUnexpectedEOF
   525  		} else if err == nil && n > size {
   526  			err = fmt.Errorf("file larger than size reported by stat")
   527  		}
   528  		if err != nil {
   529  			return fmt.Errorf("copying %s to %s: %v", ofile, afile, err)
   530  		}
   531  		if size&1 != 0 {
   532  			w.WriteByte(0)
   533  		}
   534  	}
   535  
   536  	if err := w.Flush(); err != nil {
   537  		return err
   538  	}
   539  	return dst.Close()
   540  }
   541  
   542  // setextld sets the appropriate linker flags for the specified compiler.
   543  func setextld(ldflags []string, compiler []string) ([]string, error) {
   544  	for _, f := range ldflags {
   545  		if f == "-extld" || strings.HasPrefix(f, "-extld=") {
   546  			// don't override -extld if supplied
   547  			return ldflags, nil
   548  		}
   549  	}
   550  	joined, err := quoted.Join(compiler)
   551  	if err != nil {
   552  		return nil, err
   553  	}
   554  	return append(ldflags, "-extld="+joined), nil
   555  }
   556  
   557  // pluginPath computes the package path for a plugin main package.
   558  //
   559  // This is typically the import path of the main package p, unless the
   560  // plugin is being built directly from source files. In that case we
   561  // combine the package build ID with the contents of the main package
   562  // source files. This allows us to identify two different plugins
   563  // built from two source files with the same name.
   564  func pluginPath(a *Action) string {
   565  	p := a.Package
   566  	if p.ImportPath != "command-line-arguments" {
   567  		return p.ImportPath
   568  	}
   569  	h := sha1.New()
   570  	buildID := a.buildID
   571  	if a.Mode == "link" {
   572  		// For linking, use the main package's build ID instead of
   573  		// the binary's build ID, so it is the same hash used in
   574  		// compiling and linking.
   575  		// When compiling, we use actionID/actionID (instead of
   576  		// actionID/contentID) as a temporary build ID to compute
   577  		// the hash. Do the same here. (See buildid.go:useCache)
   578  		// The build ID matters because it affects the overall hash
   579  		// in the plugin's pseudo-import path returned below.
   580  		// We need to use the same import path when compiling and linking.
   581  		id := strings.Split(buildID, buildIDSeparator)
   582  		buildID = id[1] + buildIDSeparator + id[1]
   583  	}
   584  	fmt.Fprintf(h, "build ID: %s\n", buildID)
   585  	for _, file := range str.StringList(p.GoFiles, p.CgoFiles, p.SFiles) {
   586  		data, err := os.ReadFile(filepath.Join(p.Dir, file))
   587  		if err != nil {
   588  			base.Fatalf("go: %s", err)
   589  		}
   590  		h.Write(data)
   591  	}
   592  	return fmt.Sprintf("plugin/unnamed-%x", h.Sum(nil))
   593  }
   594  
   595  func (gcToolchain) ld(b *Builder, root *Action, targetPath, importcfg, mainpkg string) error {
   596  	cxx := len(root.Package.CXXFiles) > 0 || len(root.Package.SwigCXXFiles) > 0
   597  	for _, a := range root.Deps {
   598  		if a.Package != nil && (len(a.Package.CXXFiles) > 0 || len(a.Package.SwigCXXFiles) > 0) {
   599  			cxx = true
   600  		}
   601  	}
   602  	var ldflags []string
   603  	if cfg.BuildContext.InstallSuffix != "" {
   604  		ldflags = append(ldflags, "-installsuffix", cfg.BuildContext.InstallSuffix)
   605  	}
   606  	if root.Package.Internal.OmitDebug {
   607  		ldflags = append(ldflags, "-s", "-w")
   608  	}
   609  	if cfg.BuildBuildmode == "plugin" {
   610  		ldflags = append(ldflags, "-pluginpath", pluginPath(root))
   611  	}
   612  
   613  	// Store BuildID inside toolchain binaries as a unique identifier of the
   614  	// tool being run, for use by content-based staleness determination.
   615  	if root.Package.Goroot && strings.HasPrefix(root.Package.ImportPath, "cmd/") {
   616  		// External linking will include our build id in the external
   617  		// linker's build id, which will cause our build id to not
   618  		// match the next time the tool is built.
   619  		// Rely on the external build id instead.
   620  		if !platform.MustLinkExternal(cfg.Goos, cfg.Goarch, false) {
   621  			ldflags = append(ldflags, "-X=github.com/go-asm/go/cmd/objabi.buildID="+root.buildID)
   622  		}
   623  	}
   624  
   625  	// Store default GODEBUG in binaries.
   626  	if root.Package.DefaultGODEBUG != "" {
   627  		ldflags = append(ldflags, "-X=runtime.godebugDefault="+root.Package.DefaultGODEBUG)
   628  	}
   629  
   630  	// If the user has not specified the -extld option, then specify the
   631  	// appropriate linker. In case of C++ code, use the compiler named
   632  	// by the CXX environment variable or defaultCXX if CXX is not set.
   633  	// Else, use the CC environment variable and defaultCC as fallback.
   634  	var compiler []string
   635  	if cxx {
   636  		compiler = envList("CXX", cfg.DefaultCXX(cfg.Goos, cfg.Goarch))
   637  	} else {
   638  		compiler = envList("CC", cfg.DefaultCC(cfg.Goos, cfg.Goarch))
   639  	}
   640  	ldflags = append(ldflags, "-buildmode="+ldBuildmode)
   641  	if root.buildID != "" {
   642  		ldflags = append(ldflags, "-buildid="+root.buildID)
   643  	}
   644  	ldflags = append(ldflags, forcedLdflags...)
   645  	ldflags = append(ldflags, root.Package.Internal.Ldflags...)
   646  	ldflags, err := setextld(ldflags, compiler)
   647  	if err != nil {
   648  		return err
   649  	}
   650  
   651  	// On OS X when using external linking to build a shared library,
   652  	// the argument passed here to -o ends up recorded in the final
   653  	// shared library in the LC_ID_DYLIB load command.
   654  	// To avoid putting the temporary output directory name there
   655  	// (and making the resulting shared library useless),
   656  	// run the link in the output directory so that -o can name
   657  	// just the final path element.
   658  	// On Windows, DLL file name is recorded in PE file
   659  	// export section, so do like on OS X.
   660  	// On Linux, for a shared object, at least with the Gold linker,
   661  	// the output file path is recorded in the .gnu.version_d section.
   662  	dir := "."
   663  	if cfg.BuildBuildmode == "c-shared" || cfg.BuildBuildmode == "plugin" {
   664  		dir, targetPath = filepath.Split(targetPath)
   665  	}
   666  
   667  	env := []string{}
   668  	if cfg.BuildTrimpath {
   669  		env = append(env, "GOROOT_FINAL="+trimPathGoRootFinal)
   670  	}
   671  	return b.Shell(root).run(dir, root.Package.ImportPath, env, cfg.BuildToolexec, base.Tool("link"), "-o", targetPath, "-importcfg", importcfg, ldflags, mainpkg)
   672  }
   673  
   674  func (gcToolchain) ldShared(b *Builder, root *Action, toplevelactions []*Action, targetPath, importcfg string, allactions []*Action) error {
   675  	ldflags := []string{"-installsuffix", cfg.BuildContext.InstallSuffix}
   676  	ldflags = append(ldflags, "-buildmode=shared")
   677  	ldflags = append(ldflags, forcedLdflags...)
   678  	ldflags = append(ldflags, root.Package.Internal.Ldflags...)
   679  	cxx := false
   680  	for _, a := range allactions {
   681  		if a.Package != nil && (len(a.Package.CXXFiles) > 0 || len(a.Package.SwigCXXFiles) > 0) {
   682  			cxx = true
   683  		}
   684  	}
   685  	// If the user has not specified the -extld option, then specify the
   686  	// appropriate linker. In case of C++ code, use the compiler named
   687  	// by the CXX environment variable or defaultCXX if CXX is not set.
   688  	// Else, use the CC environment variable and defaultCC as fallback.
   689  	var compiler []string
   690  	if cxx {
   691  		compiler = envList("CXX", cfg.DefaultCXX(cfg.Goos, cfg.Goarch))
   692  	} else {
   693  		compiler = envList("CC", cfg.DefaultCC(cfg.Goos, cfg.Goarch))
   694  	}
   695  	ldflags, err := setextld(ldflags, compiler)
   696  	if err != nil {
   697  		return err
   698  	}
   699  	for _, d := range toplevelactions {
   700  		if !strings.HasSuffix(d.Target, ".a") { // omit unsafe etc and actions for other shared libraries
   701  			continue
   702  		}
   703  		ldflags = append(ldflags, d.Package.ImportPath+"="+d.Target)
   704  	}
   705  	return b.Shell(root).run(".", targetPath, nil, cfg.BuildToolexec, base.Tool("link"), "-o", targetPath, "-importcfg", importcfg, ldflags)
   706  }
   707  
   708  func (gcToolchain) cc(b *Builder, a *Action, ofile, cfile string) error {
   709  	return fmt.Errorf("%s: C source files not supported without cgo", mkAbs(a.Package.Dir, cfile))
   710  }