summaryrefslogtreecommitdiffstats
path: root/src/cmd/go/internal/work/buildid.go
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:23:18 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:23:18 +0000
commit43a123c1ae6613b3efeed291fa552ecd909d3acf (patch)
treefd92518b7024bc74031f78a1cf9e454b65e73665 /src/cmd/go/internal/work/buildid.go
parentInitial commit. (diff)
downloadgolang-1.20-43a123c1ae6613b3efeed291fa552ecd909d3acf.tar.xz
golang-1.20-43a123c1ae6613b3efeed291fa552ecd909d3acf.zip
Adding upstream version 1.20.14.upstream/1.20.14upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/cmd/go/internal/work/buildid.go')
-rw-r--r--src/cmd/go/internal/work/buildid.go707
1 files changed, 707 insertions, 0 deletions
diff --git a/src/cmd/go/internal/work/buildid.go b/src/cmd/go/internal/work/buildid.go
new file mode 100644
index 0000000..db56714
--- /dev/null
+++ b/src/cmd/go/internal/work/buildid.go
@@ -0,0 +1,707 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package work
+
+import (
+ "bytes"
+ "fmt"
+ "os"
+ "os/exec"
+ "strings"
+
+ "cmd/go/internal/base"
+ "cmd/go/internal/cache"
+ "cmd/go/internal/cfg"
+ "cmd/go/internal/fsys"
+ "cmd/go/internal/str"
+ "cmd/internal/buildid"
+ "cmd/internal/quoted"
+)
+
+// Build IDs
+//
+// Go packages and binaries are stamped with build IDs that record both
+// the action ID, which is a hash of the inputs to the action that produced
+// the packages or binary, and the content ID, which is a hash of the action
+// output, namely the archive or binary itself. The hash is the same one
+// used by the build artifact cache (see cmd/go/internal/cache), but
+// truncated when stored in packages and binaries, as the full length is not
+// needed and is a bit unwieldy. The precise form is
+//
+// actionID/[.../]contentID
+//
+// where the actionID and contentID are prepared by buildid.HashToString below.
+// and are found by looking for the first or last slash.
+// Usually the buildID is simply actionID/contentID, but see below for an
+// exception.
+//
+// The build ID serves two primary purposes.
+//
+// 1. The action ID half allows installed packages and binaries to serve as
+// one-element cache entries. If we intend to build math.a with a given
+// set of inputs summarized in the action ID, and the installed math.a already
+// has that action ID, we can reuse the installed math.a instead of rebuilding it.
+//
+// 2. The content ID half allows the easy preparation of action IDs for steps
+// that consume a particular package or binary. The content hash of every
+// input file for a given action must be included in the action ID hash.
+// Storing the content ID in the build ID lets us read it from the file with
+// minimal I/O, instead of reading and hashing the entire file.
+// This is especially effective since packages and binaries are typically
+// the largest inputs to an action.
+//
+// Separating action ID from content ID is important for reproducible builds.
+// The compiler is compiled with itself. If an output were represented by its
+// own action ID (instead of content ID) when computing the action ID of
+// the next step in the build process, then the compiler could never have its
+// own input action ID as its output action ID (short of a miraculous hash collision).
+// Instead we use the content IDs to compute the next action ID, and because
+// the content IDs converge, so too do the action IDs and therefore the
+// build IDs and the overall compiler binary. See cmd/dist's cmdbootstrap
+// for the actual convergence sequence.
+//
+// The “one-element cache” purpose is a bit more complex for installed
+// binaries. For a binary, like cmd/gofmt, there are two steps: compile
+// cmd/gofmt/*.go into main.a, and then link main.a into the gofmt binary.
+// We do not install gofmt's main.a, only the gofmt binary. Being able to
+// decide that the gofmt binary is up-to-date means computing the action ID
+// for the final link of the gofmt binary and comparing it against the
+// already-installed gofmt binary. But computing the action ID for the link
+// means knowing the content ID of main.a, which we did not keep.
+// To sidestep this problem, each binary actually stores an expanded build ID:
+//
+// actionID(binary)/actionID(main.a)/contentID(main.a)/contentID(binary)
+//
+// (Note that this can be viewed equivalently as:
+//
+// actionID(binary)/buildID(main.a)/contentID(binary)
+//
+// Storing the buildID(main.a) in the middle lets the computations that care
+// about the prefix or suffix halves ignore the middle and preserves the
+// original build ID as a contiguous string.)
+//
+// During the build, when it's time to build main.a, the gofmt binary has the
+// information needed to decide whether the eventual link would produce
+// the same binary: if the action ID for main.a's inputs matches and then
+// the action ID for the link step matches when assuming the given main.a
+// content ID, then the binary as a whole is up-to-date and need not be rebuilt.
+//
+// This is all a bit complex and may be simplified once we can rely on the
+// main cache, but at least at the start we will be using the content-based
+// staleness determination without a cache beyond the usual installed
+// package and binary locations.
+
+const buildIDSeparator = "/"
+
+// actionID returns the action ID half of a build ID.
+func actionID(buildID string) string {
+ i := strings.Index(buildID, buildIDSeparator)
+ if i < 0 {
+ return buildID
+ }
+ return buildID[:i]
+}
+
+// contentID returns the content ID half of a build ID.
+func contentID(buildID string) string {
+ return buildID[strings.LastIndex(buildID, buildIDSeparator)+1:]
+}
+
+// toolID returns the unique ID to use for the current copy of the
+// named tool (asm, compile, cover, link).
+//
+// It is important that if the tool changes (for example a compiler bug is fixed
+// and the compiler reinstalled), toolID returns a different string, so that old
+// package archives look stale and are rebuilt (with the fixed compiler).
+// This suggests using a content hash of the tool binary, as stored in the build ID.
+//
+// Unfortunately, we can't just open the tool binary, because the tool might be
+// invoked via a wrapper program specified by -toolexec and we don't know
+// what the wrapper program does. In particular, we want "-toolexec toolstash"
+// to continue working: it does no good if "-toolexec toolstash" is executing a
+// stashed copy of the compiler but the go command is acting as if it will run
+// the standard copy of the compiler. The solution is to ask the tool binary to tell
+// us its own build ID using the "-V=full" flag now supported by all tools.
+// Then we know we're getting the build ID of the compiler that will actually run
+// during the build. (How does the compiler binary know its own content hash?
+// We store it there using updateBuildID after the standard link step.)
+//
+// A final twist is that we'd prefer to have reproducible builds for release toolchains.
+// It should be possible to cross-compile for Windows from either Linux or Mac
+// or Windows itself and produce the same binaries, bit for bit. If the tool ID,
+// which influences the action ID half of the build ID, is based on the content ID,
+// then the Linux compiler binary and Mac compiler binary will have different tool IDs
+// and therefore produce executables with different action IDs.
+// To avoid this problem, for releases we use the release version string instead
+// of the compiler binary's content hash. This assumes that all compilers built
+// on all different systems are semantically equivalent, which is of course only true
+// modulo bugs. (Producing the exact same executables also requires that the different
+// build setups agree on details like $GOROOT and file name paths, but at least the
+// tool IDs do not make it impossible.)
+func (b *Builder) toolID(name string) string {
+ b.id.Lock()
+ id := b.toolIDCache[name]
+ b.id.Unlock()
+
+ if id != "" {
+ return id
+ }
+
+ path := base.Tool(name)
+ desc := "go tool " + name
+
+ // Special case: undocumented -vettool overrides usual vet,
+ // for testing vet or supplying an alternative analysis tool.
+ if name == "vet" && VetTool != "" {
+ path = VetTool
+ desc = VetTool
+ }
+
+ cmdline := str.StringList(cfg.BuildToolexec, path, "-V=full")
+ cmd := exec.Command(cmdline[0], cmdline[1:]...)
+ var stdout, stderr strings.Builder
+ cmd.Stdout = &stdout
+ cmd.Stderr = &stderr
+ if err := cmd.Run(); err != nil {
+ if stderr.Len() > 0 {
+ os.Stderr.WriteString(stderr.String())
+ }
+ base.Fatalf("go: error obtaining buildID for %s: %v", desc, err)
+ }
+
+ line := stdout.String()
+ f := strings.Fields(line)
+ if len(f) < 3 || f[0] != name && path != VetTool || f[1] != "version" || f[2] == "devel" && !strings.HasPrefix(f[len(f)-1], "buildID=") {
+ base.Fatalf("go: parsing buildID from %s -V=full: unexpected output:\n\t%s", desc, line)
+ }
+ if f[2] == "devel" {
+ // On the development branch, use the content ID part of the build ID.
+ id = contentID(f[len(f)-1])
+ } else {
+ // For a release, the output is like: "compile version go1.9.1 X:framepointer".
+ // Use the whole line.
+ id = strings.TrimSpace(line)
+ }
+
+ b.id.Lock()
+ b.toolIDCache[name] = id
+ b.id.Unlock()
+
+ return id
+}
+
+// gccToolID returns the unique ID to use for a tool that is invoked
+// by the GCC driver. This is used particularly for gccgo, but this can also
+// be used for gcc, g++, gfortran, etc.; those tools all use the GCC
+// driver under different names. The approach used here should also
+// work for sufficiently new versions of clang. Unlike toolID, the
+// name argument is the program to run. The language argument is the
+// type of input file as passed to the GCC driver's -x option.
+//
+// For these tools we have no -V=full option to dump the build ID,
+// but we can run the tool with -v -### to reliably get the compiler proper
+// and hash that. That will work in the presence of -toolexec.
+//
+// In order to get reproducible builds for released compilers, we
+// detect a released compiler by the absence of "experimental" in the
+// --version output, and in that case we just use the version string.
+//
+// gccToolID also returns the underlying executable for the compiler.
+// The caller assumes that stat of the exe can be used, combined with the id,
+// to detect changes in the underlying compiler. The returned exe can be empty,
+// which means to rely only on the id.
+func (b *Builder) gccToolID(name, language string) (id, exe string, err error) {
+ key := name + "." + language
+ b.id.Lock()
+ id = b.toolIDCache[key]
+ exe = b.toolIDCache[key+".exe"]
+ b.id.Unlock()
+
+ if id != "" {
+ return id, exe, nil
+ }
+
+ // Invoke the driver with -### to see the subcommands and the
+ // version strings. Use -x to set the language. Pretend to
+ // compile an empty file on standard input.
+ cmdline := str.StringList(cfg.BuildToolexec, name, "-###", "-x", language, "-c", "-")
+ cmd := exec.Command(cmdline[0], cmdline[1:]...)
+ // Force untranslated output so that we see the string "version".
+ cmd.Env = append(os.Environ(), "LC_ALL=C")
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ return "", "", fmt.Errorf("%s: %v; output: %q", name, err, out)
+ }
+
+ version := ""
+ lines := strings.Split(string(out), "\n")
+ for _, line := range lines {
+ if fields := strings.Fields(line); len(fields) > 1 && fields[1] == "version" || len(fields) > 2 && fields[2] == "version" {
+ version = line
+ break
+ }
+ }
+ if version == "" {
+ return "", "", fmt.Errorf("%s: can not find version number in %q", name, out)
+ }
+
+ if !strings.Contains(version, "experimental") {
+ // This is a release. Use this line as the tool ID.
+ id = version
+ } else {
+ // This is a development version. The first line with
+ // a leading space is the compiler proper.
+ compiler := ""
+ for _, line := range lines {
+ if strings.HasPrefix(line, " ") && !strings.HasPrefix(line, " (in-process)") {
+ compiler = line
+ break
+ }
+ }
+ if compiler == "" {
+ return "", "", fmt.Errorf("%s: can not find compilation command in %q", name, out)
+ }
+
+ fields, _ := quoted.Split(compiler)
+ if len(fields) == 0 {
+ return "", "", fmt.Errorf("%s: compilation command confusion %q", name, out)
+ }
+ exe = fields[0]
+ if !strings.ContainsAny(exe, `/\`) {
+ if lp, err := exec.LookPath(exe); err == nil {
+ exe = lp
+ }
+ }
+ id, err = buildid.ReadFile(exe)
+ if err != nil {
+ return "", "", err
+ }
+
+ // If we can't find a build ID, use a hash.
+ if id == "" {
+ id = b.fileHash(exe)
+ }
+ }
+
+ b.id.Lock()
+ b.toolIDCache[key] = id
+ b.toolIDCache[key+".exe"] = exe
+ b.id.Unlock()
+
+ return id, exe, nil
+}
+
+// Check if assembler used by gccgo is GNU as.
+func assemblerIsGas() bool {
+ cmd := exec.Command(BuildToolchain.compiler(), "-print-prog-name=as")
+ assembler, err := cmd.Output()
+ if err == nil {
+ cmd := exec.Command(strings.TrimSpace(string(assembler)), "--version")
+ out, err := cmd.Output()
+ return err == nil && strings.Contains(string(out), "GNU")
+ } else {
+ return false
+ }
+}
+
+// gccgoBuildIDFile creates an assembler file that records the
+// action's build ID in an SHF_EXCLUDE section for ELF files or
+// in a CSECT in XCOFF files.
+func (b *Builder) gccgoBuildIDFile(a *Action) (string, error) {
+ sfile := a.Objdir + "_buildid.s"
+
+ var buf bytes.Buffer
+ if cfg.Goos == "aix" {
+ fmt.Fprintf(&buf, "\t.csect .go.buildid[XO]\n")
+ } else if (cfg.Goos != "solaris" && cfg.Goos != "illumos") || assemblerIsGas() {
+ fmt.Fprintf(&buf, "\t"+`.section .go.buildid,"e"`+"\n")
+ } else if cfg.Goarch == "sparc" || cfg.Goarch == "sparc64" {
+ fmt.Fprintf(&buf, "\t"+`.section ".go.buildid",#exclude`+"\n")
+ } else { // cfg.Goarch == "386" || cfg.Goarch == "amd64"
+ fmt.Fprintf(&buf, "\t"+`.section .go.buildid,#exclude`+"\n")
+ }
+ fmt.Fprintf(&buf, "\t.byte ")
+ for i := 0; i < len(a.buildID); i++ {
+ if i > 0 {
+ if i%8 == 0 {
+ fmt.Fprintf(&buf, "\n\t.byte ")
+ } else {
+ fmt.Fprintf(&buf, ",")
+ }
+ }
+ fmt.Fprintf(&buf, "%#02x", a.buildID[i])
+ }
+ fmt.Fprintf(&buf, "\n")
+ if cfg.Goos != "solaris" && cfg.Goos != "illumos" && cfg.Goos != "aix" {
+ secType := "@progbits"
+ if cfg.Goarch == "arm" {
+ secType = "%progbits"
+ }
+ fmt.Fprintf(&buf, "\t"+`.section .note.GNU-stack,"",%s`+"\n", secType)
+ fmt.Fprintf(&buf, "\t"+`.section .note.GNU-split-stack,"",%s`+"\n", secType)
+ }
+
+ if cfg.BuildN || cfg.BuildX {
+ for _, line := range bytes.Split(buf.Bytes(), []byte("\n")) {
+ b.Showcmd("", "echo '%s' >> %s", line, sfile)
+ }
+ if cfg.BuildN {
+ return sfile, nil
+ }
+ }
+
+ if err := os.WriteFile(sfile, buf.Bytes(), 0666); err != nil {
+ return "", err
+ }
+
+ return sfile, nil
+}
+
+// buildID returns the build ID found in the given file.
+// If no build ID is found, buildID returns the content hash of the file.
+func (b *Builder) buildID(file string) string {
+ b.id.Lock()
+ id := b.buildIDCache[file]
+ b.id.Unlock()
+
+ if id != "" {
+ return id
+ }
+
+ id, err := buildid.ReadFile(file)
+ if err != nil {
+ id = b.fileHash(file)
+ }
+
+ b.id.Lock()
+ b.buildIDCache[file] = id
+ b.id.Unlock()
+
+ return id
+}
+
+// fileHash returns the content hash of the named file.
+func (b *Builder) fileHash(file string) string {
+ file, _ = fsys.OverlayPath(file)
+ sum, err := cache.FileHash(file)
+ if err != nil {
+ return ""
+ }
+ return buildid.HashToString(sum)
+}
+
+// useCache tries to satisfy the action a, which has action ID actionHash,
+// by using a cached result from an earlier build. At the moment, the only
+// cached result is the installed package or binary at target.
+// If useCache decides that the cache can be used, it sets a.buildID
+// and a.built for use by parent actions and then returns true.
+// Otherwise it sets a.buildID to a temporary build ID for use in the build
+// and returns false. When useCache returns false the expectation is that
+// the caller will build the target and then call updateBuildID to finish the
+// build ID computation.
+// When useCache returns false, it may have initiated buffering of output
+// during a's work. The caller should defer b.flushOutput(a), to make sure
+// that flushOutput is eventually called regardless of whether the action
+// succeeds. The flushOutput call must happen after updateBuildID.
+func (b *Builder) useCache(a *Action, actionHash cache.ActionID, target string, printOutput bool) bool {
+ // The second half of the build ID here is a placeholder for the content hash.
+ // It's important that the overall buildID be unlikely verging on impossible
+ // to appear in the output by chance, but that should be taken care of by
+ // the actionID half; if it also appeared in the input that would be like an
+ // engineered 120-bit partial SHA256 collision.
+ a.actionID = actionHash
+ actionID := buildid.HashToString(actionHash)
+ if a.json != nil {
+ a.json.ActionID = actionID
+ }
+ contentID := actionID // temporary placeholder, likely unique
+ a.buildID = actionID + buildIDSeparator + contentID
+
+ // Executable binaries also record the main build ID in the middle.
+ // See "Build IDs" comment above.
+ if a.Mode == "link" {
+ mainpkg := a.Deps[0]
+ a.buildID = actionID + buildIDSeparator + mainpkg.buildID + buildIDSeparator + contentID
+ }
+
+ // If user requested -a, we force a rebuild, so don't use the cache.
+ if cfg.BuildA {
+ if p := a.Package; p != nil && !p.Stale {
+ p.Stale = true
+ p.StaleReason = "build -a flag in use"
+ }
+ // Begin saving output for later writing to cache.
+ a.output = []byte{}
+ return false
+ }
+
+ if target != "" {
+ buildID, _ := buildid.ReadFile(target)
+ if strings.HasPrefix(buildID, actionID+buildIDSeparator) {
+ a.buildID = buildID
+ if a.json != nil {
+ a.json.BuildID = a.buildID
+ }
+ a.built = target
+ // Poison a.Target to catch uses later in the build.
+ a.Target = "DO NOT USE - " + a.Mode
+ return true
+ }
+ // Special case for building a main package: if the only thing we
+ // want the package for is to link a binary, and the binary is
+ // already up-to-date, then to avoid a rebuild, report the package
+ // as up-to-date as well. See "Build IDs" comment above.
+ // TODO(rsc): Rewrite this code to use a TryCache func on the link action.
+ if !b.NeedExport && a.Mode == "build" && len(a.triggers) == 1 && a.triggers[0].Mode == "link" {
+ if id := strings.Split(buildID, buildIDSeparator); len(id) == 4 && id[1] == actionID {
+ // Temporarily assume a.buildID is the package build ID
+ // stored in the installed binary, and see if that makes
+ // the upcoming link action ID a match. If so, report that
+ // we built the package, safe in the knowledge that the
+ // link step will not ask us for the actual package file.
+ // Note that (*Builder).LinkAction arranged that all of
+ // a.triggers[0]'s dependencies other than a are also
+ // dependencies of a, so that we can be sure that,
+ // other than a.buildID, b.linkActionID is only accessing
+ // build IDs of completed actions.
+ oldBuildID := a.buildID
+ a.buildID = id[1] + buildIDSeparator + id[2]
+ linkID := buildid.HashToString(b.linkActionID(a.triggers[0]))
+ if id[0] == linkID {
+ // Best effort attempt to display output from the compile and link steps.
+ // If it doesn't work, it doesn't work: reusing the cached binary is more
+ // important than reprinting diagnostic information.
+ if printOutput {
+ if c := cache.Default(); c != nil {
+ showStdout(b, c, a.actionID, "stdout") // compile output
+ showStdout(b, c, a.actionID, "link-stdout") // link output
+ }
+ }
+
+ // Poison a.Target to catch uses later in the build.
+ a.Target = "DO NOT USE - main build pseudo-cache Target"
+ a.built = "DO NOT USE - main build pseudo-cache built"
+ if a.json != nil {
+ a.json.BuildID = a.buildID
+ }
+ return true
+ }
+ // Otherwise restore old build ID for main build.
+ a.buildID = oldBuildID
+ }
+ }
+ }
+
+ // Special case for linking a test binary: if the only thing we
+ // want the binary for is to run the test, and the test result is cached,
+ // then to avoid the link step, report the link as up-to-date.
+ // We avoid the nested build ID problem in the previous special case
+ // by recording the test results in the cache under the action ID half.
+ if len(a.triggers) == 1 && a.triggers[0].TryCache != nil && a.triggers[0].TryCache(b, a.triggers[0]) {
+ // Best effort attempt to display output from the compile and link steps.
+ // If it doesn't work, it doesn't work: reusing the test result is more
+ // important than reprinting diagnostic information.
+ if printOutput {
+ if c := cache.Default(); c != nil {
+ showStdout(b, c, a.Deps[0].actionID, "stdout") // compile output
+ showStdout(b, c, a.Deps[0].actionID, "link-stdout") // link output
+ }
+ }
+
+ // Poison a.Target to catch uses later in the build.
+ a.Target = "DO NOT USE - pseudo-cache Target"
+ a.built = "DO NOT USE - pseudo-cache built"
+ return true
+ }
+
+ // Check to see if the action output is cached.
+ if c := cache.Default(); c != nil {
+ if file, _, err := c.GetFile(actionHash); err == nil {
+ if buildID, err := buildid.ReadFile(file); err == nil {
+ if printOutput {
+ showStdout(b, c, a.actionID, "stdout")
+ }
+ a.built = file
+ a.Target = "DO NOT USE - using cache"
+ a.buildID = buildID
+ if a.json != nil {
+ a.json.BuildID = a.buildID
+ }
+ if p := a.Package; p != nil && target != "" {
+ p.Stale = true
+ // Clearer than explaining that something else is stale.
+ p.StaleReason = "not installed but available in build cache"
+ }
+ return true
+ }
+ }
+ }
+
+ // If we've reached this point, we can't use the cache for the action.
+ if p := a.Package; p != nil && !p.Stale {
+ p.Stale = true
+ p.StaleReason = "build ID mismatch"
+ if b.IsCmdList {
+ // Since we may end up printing StaleReason, include more detail.
+ for _, p1 := range p.Internal.Imports {
+ if p1.Stale && p1.StaleReason != "" {
+ if strings.HasPrefix(p1.StaleReason, "stale dependency: ") {
+ p.StaleReason = p1.StaleReason
+ break
+ }
+ if strings.HasPrefix(p.StaleReason, "build ID mismatch") {
+ p.StaleReason = "stale dependency: " + p1.ImportPath
+ }
+ }
+ }
+ }
+ }
+
+ // Begin saving output for later writing to cache.
+ a.output = []byte{}
+ return false
+}
+
+func showStdout(b *Builder, c *cache.Cache, actionID cache.ActionID, key string) error {
+ stdout, stdoutEntry, err := c.GetBytes(cache.Subkey(actionID, key))
+ if err != nil {
+ return err
+ }
+
+ if len(stdout) > 0 {
+ if cfg.BuildX || cfg.BuildN {
+ b.Showcmd("", "%s # internal", joinUnambiguously(str.StringList("cat", c.OutputFile(stdoutEntry.OutputID))))
+ }
+ if !cfg.BuildN {
+ b.output.Lock()
+ defer b.output.Unlock()
+ b.Print(string(stdout))
+ }
+ }
+ return nil
+}
+
+// flushOutput flushes the output being queued in a.
+func (b *Builder) flushOutput(a *Action) {
+ b.output.Lock()
+ defer b.output.Unlock()
+ b.Print(string(a.output))
+ a.output = nil
+}
+
+// updateBuildID updates the build ID in the target written by action a.
+// It requires that useCache was called for action a and returned false,
+// and that the build was then carried out and given the temporary
+// a.buildID to record as the build ID in the resulting package or binary.
+// updateBuildID computes the final content ID and updates the build IDs
+// in the binary.
+//
+// Keep in sync with src/cmd/buildid/buildid.go
+func (b *Builder) updateBuildID(a *Action, target string, rewrite bool) error {
+ if cfg.BuildX || cfg.BuildN {
+ if rewrite {
+ b.Showcmd("", "%s # internal", joinUnambiguously(str.StringList(base.Tool("buildid"), "-w", target)))
+ }
+ if cfg.BuildN {
+ return nil
+ }
+ }
+
+ // Cache output from compile/link, even if we don't do the rest.
+ if c := cache.Default(); c != nil {
+ switch a.Mode {
+ case "build":
+ c.PutBytes(cache.Subkey(a.actionID, "stdout"), a.output)
+ case "link":
+ // Even though we don't cache the binary, cache the linker text output.
+ // We might notice that an installed binary is up-to-date but still
+ // want to pretend to have run the linker.
+ // Store it under the main package's action ID
+ // to make it easier to find when that's all we have.
+ for _, a1 := range a.Deps {
+ if p1 := a1.Package; p1 != nil && p1.Name == "main" {
+ c.PutBytes(cache.Subkey(a1.actionID, "link-stdout"), a.output)
+ break
+ }
+ }
+ }
+ }
+
+ // Find occurrences of old ID and compute new content-based ID.
+ r, err := os.Open(target)
+ if err != nil {
+ return err
+ }
+ matches, hash, err := buildid.FindAndHash(r, a.buildID, 0)
+ r.Close()
+ if err != nil {
+ return err
+ }
+ newID := a.buildID[:strings.LastIndex(a.buildID, buildIDSeparator)] + buildIDSeparator + buildid.HashToString(hash)
+ if len(newID) != len(a.buildID) {
+ return fmt.Errorf("internal error: build ID length mismatch %q vs %q", a.buildID, newID)
+ }
+
+ // Replace with new content-based ID.
+ a.buildID = newID
+ if a.json != nil {
+ a.json.BuildID = a.buildID
+ }
+ if len(matches) == 0 {
+ // Assume the user specified -buildid= to override what we were going to choose.
+ return nil
+ }
+
+ if rewrite {
+ w, err := os.OpenFile(target, os.O_RDWR, 0)
+ if err != nil {
+ return err
+ }
+ err = buildid.Rewrite(w, matches, newID)
+ if err != nil {
+ w.Close()
+ return err
+ }
+ if err := w.Close(); err != nil {
+ return err
+ }
+ }
+
+ // Cache package builds, but not binaries (link steps).
+ // The expectation is that binaries are not reused
+ // nearly as often as individual packages, and they're
+ // much larger, so the cache-footprint-to-utility ratio
+ // of binaries is much lower for binaries.
+ // Not caching the link step also makes sure that repeated "go run" at least
+ // always rerun the linker, so that they don't get too fast.
+ // (We don't want people thinking go is a scripting language.)
+ // Note also that if we start caching binaries, then we will
+ // copy the binaries out of the cache to run them, and then
+ // that will mean the go process is itself writing a binary
+ // and then executing it, so we will need to defend against
+ // ETXTBSY problems as discussed in exec.go and golang.org/issue/22220.
+ if c := cache.Default(); c != nil && a.Mode == "build" {
+ r, err := os.Open(target)
+ if err == nil {
+ if a.output == nil {
+ panic("internal error: a.output not set")
+ }
+ outputID, _, err := c.Put(a.actionID, r)
+ r.Close()
+ if err == nil && cfg.BuildX {
+ b.Showcmd("", "%s # internal", joinUnambiguously(str.StringList("cp", target, c.OutputFile(outputID))))
+ }
+ if b.NeedExport {
+ if err != nil {
+ return err
+ }
+ a.Package.Export = c.OutputFile(outputID)
+ a.Package.BuildID = a.buildID
+ }
+ }
+ }
+
+ return nil
+}