aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/go/internal/work/buildid.go
blob: ac98aa344c8bfc2647f3c479864318c04d0597e9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
// 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"
	exec "internal/execabs"
	"os"
	"strings"

	"cmd/go/internal/base"
	"cmd/go/internal/cache"
	"cmd/go/internal/cfg"
	"cmd/go/internal/fsys"
	"cmd/go/internal/str"
	"cmd/internal/buildid"
)

// 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 bytes.Buffer
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr
	if err := cmd.Run(); err != nil {
		base.Fatalf("%s: %v\n%s%s", desc, err, stdout.Bytes(), stderr.Bytes())
	}

	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("%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.
func (b *Builder) gccToolID(name, language string) (string, error) {
	key := name + "." + language
	b.id.Lock()
	id := b.toolIDCache[key]
	b.id.Unlock()

	if id != "" {
		return id, 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" {
			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 len(line) > 1 && line[0] == ' ' {
				compiler = line
				break
			}
		}
		if compiler == "" {
			return "", fmt.Errorf("%s: can not find compilation command in %q", name, out)
		}

		fields := strings.Fields(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.id.Unlock()

	return id, 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) 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
	}

	// Check to see if target exists and matches the expected action ID.
	// If so, it's up to date and we can reuse it instead of rebuilding it.
	var buildID string
	if target != "" && !cfg.BuildA {
		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 target != "" && !cfg.BuildA && !b.NeedExport && a.Mode == "build" && len(a.triggers) == 1 && a.triggers[0].Mode == "link" {
		buildID, err := buildid.ReadFile(target)
		if err == nil {
			id := strings.Split(buildID, buildIDSeparator)
			if 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 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 !cfg.BuildA && 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 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
	}

	if b.IsCmdList {
		// Invoked during go list to compute and record staleness.
		if p := a.Package; p != nil && !p.Stale {
			p.Stale = true
			if cfg.BuildA {
				p.StaleReason = "build -a flag in use"
			} else {
				p.StaleReason = "build ID mismatch"
				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
						}
					}
				}
			}
		}

		// Fall through to update a.buildID from the build artifact cache,
		// which will affect the computation of buildIDs for targets
		// higher up in the dependency graph.
	}

	// Check the build artifact cache.
	// We treat hits in this cache as being "stale" for the purposes of go list
	// (in effect, "stale" means whether p.Target is up-to-date),
	// but we're still happy to use results from the build artifact cache.
	if c := cache.Default(); c != nil {
		if !cfg.BuildA {
			if file, _, err := c.GetFile(actionHash); err == nil {
				if buildID, err := buildid.ReadFile(file); err == nil {
					if err := showStdout(b, c, a.actionID, "stdout"); err == nil {
						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 {
							// Clearer than explaining that something else is stale.
							p.StaleReason = "not installed but available in build cache"
						}
						return true
					}
				}
			}
		}

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