Skip to content

Commit 8a27154

Browse files
rscgopherbot
authored andcommitted
cmd/dist: make toolchain build reproducible
- Build cmd with CGO_ENABLED=0. Doing so removes the C compiler toolchain from the reproducibility perimeter and also results in cmd/go and cmd/pprof binaries that are statically linked, so that they will run on a wider variety of systems. In particular the Linux versions will run on Alpine and NixOS without needing a simulation of libc.so.6. The potential downside of disabling cgo is that cmd/go and cmd/pprof use the pure Go network resolver instead of the host resolver on Unix systems. This means they will not be able to use non-DNS resolver mechanisms that may be specified in /etc/resolv.conf, such as mDNS. Neither program seems likely to need non-DNS names like those, however. macOS and Windows systems still use the host resolver, which they access without cgo. - Build cmd with -trimpath when building a release. Doing so removes $GOPATH from the file name prefixes stored in the binary, so that the build directory does not leak into the final artifacts. - When CC and CXX are empty, do not pick values to hard-code into the source tree and binaries. Instead, emit code that makes the right decision at runtime. In addition to reproducibility, this makes cross-compiled toolchains work better. A macOS toolchain cross-compiled on Linux will now correctly look for clang, instead of looking for gcc because it was built on Linux. - Convert \ to / in file names stored in .a files. These are converted to / in the final binaries, but the hashes of the .a files affect the final build ID of the binaries. Without this change, builds of a Windows toolchain on Windows and non-Windows machines produce identical binaries except for the input hash part of the build ID. - Due to the conversion of \ to / in .a files, convert back when reading inline bodies on Windows to preserve output file names in error messages. Combined, these four changes (along with Go 1.20's removal of installed pkg/**.a files and conversion of macOS net away from cgo) make the output of make.bash fully reproducible, even when cross-compiling: a released macOS toolchain built on Linux or Windows will contain exactly the same bits as a released macOS toolchain built on macOS. The word "released" in the previous sentence is important. For the build IDs in the binaries to work out the same on both systems, a VERSION file must exist to provide a consistent compiler build ID (instead of using a content hash of the binary). For #24904. Fixes #57007. Change-Id: I665e1ef4ff207d6ff469452347dca5bfc81050e6 Reviewed-on: https://go-review.googlesource.com/c/go/+/454836 Reviewed-by: Bryan Mills <[email protected]> Run-TryBot: Russ Cox <[email protected]> Auto-Submit: Russ Cox <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent dbe327a commit 8a27154

File tree

9 files changed

+165
-67
lines changed

9 files changed

+165
-67
lines changed

src/cmd/compile/internal/noder/reader.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"go/constant"
1010
"internal/buildcfg"
1111
"internal/pkgbits"
12+
"path/filepath"
1213
"strings"
1314

1415
"cmd/compile/internal/base"
@@ -268,13 +269,14 @@ func (pr *pkgReader) posBaseIdx(idx pkgbits.Index) *src.PosBase {
268269
// "$GOROOT" to buildcfg.GOROOT is a close-enough approximation to
269270
// satisfy this.
270271
//
271-
// TODO(mdempsky): De-duplicate this logic with similar logic in
272-
// cmd/link/internal/ld's expandGoroot. However, this will probably
273-
// require being more consistent about when we use native vs UNIX
274-
// file paths.
272+
// The export data format only ever uses slash paths
273+
// (for cross-operating-system reproducible builds),
274+
// but error messages need to use native paths (backslash on Windows)
275+
// as if they had been specified on the command line.
276+
// (The go command always passes native paths to the compiler.)
275277
const dollarGOROOT = "$GOROOT"
276278
if buildcfg.GOROOT != "" && strings.HasPrefix(filename, dollarGOROOT) {
277-
filename = buildcfg.GOROOT + filename[len(dollarGOROOT):]
279+
filename = filepath.FromSlash(buildcfg.GOROOT + filename[len(dollarGOROOT):])
278280
}
279281

280282
if r.Bool() {

src/cmd/compile/internal/ssa/debug_lines_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ func testInlineStack(t *testing.T, file, function string, wantStacks [][]int) {
222222
sortInlineStacks(gotStacks)
223223
sortInlineStacks(wantStacks)
224224
if !reflect.DeepEqual(wantStacks, gotStacks) {
225-
t.Errorf("wanted inlines %+v but got %+v", wantStacks, gotStacks)
225+
t.Errorf("wanted inlines %+v but got %+v\n%s", wantStacks, gotStacks, dumpBytes)
226226
}
227227

228228
}

src/cmd/dist/build.go

Lines changed: 87 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,8 @@ var (
5252
defaultpkgconfig string
5353
defaultldso string
5454

55-
rebuildall bool
56-
defaultclang bool
57-
noOpt bool
55+
rebuildall bool
56+
noOpt bool
5857

5958
vflag int // verbosity
6059
)
@@ -210,12 +209,8 @@ func xinit() {
210209
gogcflags = os.Getenv("BOOT_GO_GCFLAGS")
211210
goldflags = os.Getenv("BOOT_GO_LDFLAGS")
212211

213-
cc, cxx := "gcc", "g++"
214-
if defaultclang {
215-
cc, cxx = "clang", "clang++"
216-
}
217-
defaultcc = compilerEnv("CC", cc)
218-
defaultcxx = compilerEnv("CXX", cxx)
212+
defaultcc = compilerEnv("CC", "")
213+
defaultcxx = compilerEnv("CXX", "")
219214

220215
b = os.Getenv("PKG_CONFIG")
221216
if b == "" {
@@ -308,12 +303,34 @@ func compilerEnv(envName, def string) map[string]string {
308303
return m
309304
}
310305

306+
// clangos lists the operating systems where we prefer clang to gcc.
307+
var clangos = []string{
308+
"darwin", // macOS 10.9 and later require clang
309+
"freebsd", // FreeBSD 10 and later do not ship gcc
310+
"openbsd", // OpenBSD ships with GCC 4.2, which is now quite old.
311+
}
312+
311313
// compilerEnvLookup returns the compiler settings for goos/goarch in map m.
312-
func compilerEnvLookup(m map[string]string, goos, goarch string) string {
314+
// kind is "CC" or "CXX".
315+
func compilerEnvLookup(kind string, m map[string]string, goos, goarch string) string {
313316
if cc := m[goos+"/"+goarch]; cc != "" {
314317
return cc
315318
}
316-
return m[""]
319+
if cc := m[""]; cc != "" {
320+
return cc
321+
}
322+
for _, os := range clangos {
323+
if goos == os {
324+
if kind == "CXX" {
325+
return "clang++"
326+
}
327+
return "clang"
328+
}
329+
}
330+
if kind == "CXX" {
331+
return "g++"
332+
}
333+
return "gcc"
317334
}
318335

319336
// rmworkdir deletes the work directory.
@@ -524,15 +541,25 @@ func setup() {
524541
xremove(pathf("%s/bin/%s", goroot, old))
525542
}
526543

527-
// For release, make sure excluded things are excluded.
544+
// Special release-specific setup.
528545
goversion := findgoversion()
529-
if strings.HasPrefix(goversion, "release.") || (strings.HasPrefix(goversion, "go") && !strings.Contains(goversion, "beta")) {
546+
isRelease := strings.HasPrefix(goversion, "release.") || (strings.HasPrefix(goversion, "go") && !strings.Contains(goversion, "beta"))
547+
if isRelease {
548+
// Make sure release-excluded things are excluded.
530549
for _, dir := range unreleased {
531550
if p := pathf("%s/%s", goroot, dir); isdir(p) {
532551
fatalf("%s should not exist in release build", p)
533552
}
534553
}
535554
}
555+
if isRelease || os.Getenv("GO_BUILDER_NAME") != "" {
556+
// Add -trimpath for reproducible builds of releases.
557+
// Include builders so that -trimpath is well-tested ahead of releases.
558+
// Do not include local development, so that people working in the
559+
// main branch for day-to-day work on the Go toolchain itself can
560+
// still have full paths for stack traces for compiler crashes and the like.
561+
// toolenv = append(toolenv, "GOFLAGS=-trimpath")
562+
}
536563
}
537564

538565
/*
@@ -675,7 +702,7 @@ func runInstall(pkg string, ch chan struct{}) {
675702
if goldflags != "" {
676703
link = append(link, goldflags)
677704
}
678-
link = append(link, "-extld="+compilerEnvLookup(defaultcc, goos, goarch))
705+
link = append(link, "-extld="+compilerEnvLookup("CC", defaultcc, goos, goarch))
679706
link = append(link, "-L="+pathf("%s/pkg/obj/go-bootstrap/%s_%s", goroot, goos, goarch))
680707
link = append(link, "-o", pathf("%s/%s%s", tooldir, elem, exe))
681708
targ = len(link) - 1
@@ -1263,6 +1290,15 @@ func timelog(op, name string) {
12631290
fmt.Fprintf(timeLogFile, "%s %+.1fs %s %s\n", t.Format(time.UnixDate), t.Sub(timeLogStart).Seconds(), op, name)
12641291
}
12651292

1293+
// toolenv is the environment to use when building cmd.
1294+
// We disable cgo to get static binaries for cmd/go and cmd/pprof,
1295+
// so that they work on systems without the same dynamic libraries
1296+
// as the original build system.
1297+
// In release branches, we add -trimpath for reproducible builds.
1298+
// In the main branch we leave it off, so that compiler crashes and
1299+
// the like have full path names for easier navigation to source files.
1300+
var toolenv = []string{"CGO_ENABLED=0"}
1301+
12661302
var toolchain = []string{"cmd/asm", "cmd/cgo", "cmd/compile", "cmd/link"}
12671303

12681304
// The bootstrap command runs a build from scratch,
@@ -1388,10 +1424,10 @@ func cmdbootstrap() {
13881424
xprintf("\n")
13891425
}
13901426
xprintf("Building Go toolchain2 using go_bootstrap and Go toolchain1.\n")
1391-
os.Setenv("CC", compilerEnvLookup(defaultcc, goos, goarch))
1427+
os.Setenv("CC", compilerEnvLookup("CC", defaultcc, goos, goarch))
13921428
// Now that cmd/go is in charge of the build process, enable GOEXPERIMENT.
13931429
os.Setenv("GOEXPERIMENT", goexperiment)
1394-
goInstall(goBootstrap, toolchain...)
1430+
goInstall(toolenv, goBootstrap, toolchain...)
13951431
if debug {
13961432
run("", ShowOutput|CheckExit, pathf("%s/compile", tooldir), "-V=full")
13971433
copyfile(pathf("%s/compile2", tooldir), pathf("%s/compile", tooldir), writeExec)
@@ -1418,7 +1454,7 @@ func cmdbootstrap() {
14181454
xprintf("\n")
14191455
}
14201456
xprintf("Building Go toolchain3 using go_bootstrap and Go toolchain2.\n")
1421-
goInstall(goBootstrap, append([]string{"-a"}, toolchain...)...)
1457+
goInstall(toolenv, goBootstrap, append([]string{"-a"}, toolchain...)...)
14221458
if debug {
14231459
run("", ShowOutput|CheckExit, pathf("%s/compile", tooldir), "-V=full")
14241460
copyfile(pathf("%s/compile3", tooldir), pathf("%s/compile", tooldir), writeExec)
@@ -1440,9 +1476,12 @@ func cmdbootstrap() {
14401476
xprintf("\n")
14411477
}
14421478
xprintf("Building packages and commands for host, %s/%s.\n", goos, goarch)
1443-
goInstall(goBootstrap, "std", "cmd")
1444-
checkNotStale(goBootstrap, "std", "cmd")
1445-
checkNotStale(cmdGo, "std", "cmd")
1479+
goInstall(nil, goBootstrap, "std")
1480+
goInstall(toolenv, goBootstrap, "cmd")
1481+
checkNotStale(nil, goBootstrap, "std")
1482+
checkNotStale(toolenv, goBootstrap, "cmd")
1483+
checkNotStale(nil, cmdGo, "std")
1484+
checkNotStale(toolenv, cmdGo, "cmd")
14461485

14471486
timelog("build", "target toolchain")
14481487
if vflag > 0 {
@@ -1452,17 +1491,19 @@ func cmdbootstrap() {
14521491
goarch = oldgoarch
14531492
os.Setenv("GOOS", goos)
14541493
os.Setenv("GOARCH", goarch)
1455-
os.Setenv("CC", compilerEnvLookup(defaultcc, goos, goarch))
1494+
os.Setenv("CC", compilerEnvLookup("CC", defaultcc, goos, goarch))
14561495
xprintf("Building packages and commands for target, %s/%s.\n", goos, goarch)
14571496
}
1458-
targets := []string{"std", "cmd"}
1459-
goInstall(goBootstrap, targets...)
1460-
checkNotStale(goBootstrap, append(toolchain, "runtime/internal/sys")...)
1461-
checkNotStale(goBootstrap, targets...)
1462-
checkNotStale(cmdGo, targets...)
1497+
goInstall(nil, goBootstrap, "std")
1498+
goInstall(toolenv, goBootstrap, "cmd")
1499+
checkNotStale(toolenv, goBootstrap, append(toolchain, "runtime/internal/sys")...)
1500+
checkNotStale(nil, goBootstrap, "std")
1501+
checkNotStale(toolenv, goBootstrap, "cmd")
1502+
checkNotStale(nil, cmdGo, "std")
1503+
checkNotStale(toolenv, cmdGo, "cmd")
14631504
if debug {
14641505
run("", ShowOutput|CheckExit, pathf("%s/compile", tooldir), "-V=full")
1465-
checkNotStale(goBootstrap, append(toolchain, "runtime/internal/sys")...)
1506+
checkNotStale(toolenv, goBootstrap, append(toolchain, "runtime/internal/sys")...)
14661507
copyfile(pathf("%s/compile4", tooldir), pathf("%s/compile", tooldir), writeExec)
14671508
}
14681509

@@ -1492,8 +1533,8 @@ func cmdbootstrap() {
14921533
oldcc := os.Getenv("CC")
14931534
os.Setenv("GOOS", gohostos)
14941535
os.Setenv("GOARCH", gohostarch)
1495-
os.Setenv("CC", compilerEnvLookup(defaultcc, gohostos, gohostarch))
1496-
goCmd(cmdGo, "build", "-o", pathf("%s/go_%s_%s_exec%s", gorootBin, goos, goarch, exe), wrapperPath)
1536+
os.Setenv("CC", compilerEnvLookup("CC", defaultcc, gohostos, gohostarch))
1537+
goCmd(nil, cmdGo, "build", "-o", pathf("%s/go_%s_%s_exec%s", gorootBin, goos, goarch, exe), wrapperPath)
14971538
// Restore environment.
14981539
// TODO(elias.naur): support environment variables in goCmd?
14991540
os.Setenv("GOOS", goos)
@@ -1521,8 +1562,8 @@ func wrapperPathFor(goos, goarch string) string {
15211562
return ""
15221563
}
15231564

1524-
func goInstall(goBinary string, args ...string) {
1525-
goCmd(goBinary, "install", args...)
1565+
func goInstall(env []string, goBinary string, args ...string) {
1566+
goCmd(env, goBinary, "install", args...)
15261567
}
15271568

15281569
func appendCompilerFlags(args []string) []string {
@@ -1535,7 +1576,7 @@ func appendCompilerFlags(args []string) []string {
15351576
return args
15361577
}
15371578

1538-
func goCmd(goBinary string, cmd string, args ...string) {
1579+
func goCmd(env []string, goBinary string, cmd string, args ...string) {
15391580
goCmd := []string{goBinary, cmd}
15401581
if noOpt {
15411582
goCmd = append(goCmd, "-tags=noopt")
@@ -1550,18 +1591,18 @@ func goCmd(goBinary string, cmd string, args ...string) {
15501591
goCmd = append(goCmd, "-p=1")
15511592
}
15521593

1553-
run(workdir, ShowOutput|CheckExit, append(goCmd, args...)...)
1594+
runEnv(workdir, ShowOutput|CheckExit, env, append(goCmd, args...)...)
15541595
}
15551596

1556-
func checkNotStale(goBinary string, targets ...string) {
1597+
func checkNotStale(env []string, goBinary string, targets ...string) {
15571598
goCmd := []string{goBinary, "list"}
15581599
if noOpt {
15591600
goCmd = append(goCmd, "-tags=noopt")
15601601
}
15611602
goCmd = appendCompilerFlags(goCmd)
15621603
goCmd = append(goCmd, "-f={{if .Stale}}\tSTALE {{.ImportPath}}: {{.StaleReason}}{{end}}")
15631604

1564-
out := run(workdir, CheckExit, append(goCmd, targets...)...)
1605+
out := runEnv(workdir, CheckExit, env, append(goCmd, targets...)...)
15651606
if strings.Contains(out, "\tSTALE ") {
15661607
os.Setenv("GODEBUG", "gocachehash=1")
15671608
for _, target := range []string{"runtime/internal/sys", "cmd/dist", "cmd/link"} {
@@ -1664,7 +1705,17 @@ func checkCC() {
16641705
if !needCC() {
16651706
return
16661707
}
1667-
cc, err := quotedSplit(defaultcc[""])
1708+
cc1 := defaultcc[""]
1709+
if cc1 == "" {
1710+
cc1 = "gcc"
1711+
for _, os := range clangos {
1712+
if gohostos == os {
1713+
cc1 = "clang"
1714+
break
1715+
}
1716+
}
1717+
}
1718+
cc, err := quotedSplit(cc1)
16681719
if err != nil {
16691720
fatalf("split CC: %v", err)
16701721
}

src/cmd/dist/buildgo.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,26 @@ func defaultCCFunc(name string, defaultcc map[string]string) string {
6666
fmt.Fprintf(&buf, "\tcase %q:\n\t\treturn %q\n", k, defaultcc[k])
6767
}
6868
fmt.Fprintf(&buf, "\t}\n")
69-
fmt.Fprintf(&buf, "\treturn %q\n", defaultcc[""])
69+
if cc := defaultcc[""]; cc != "" {
70+
fmt.Fprintf(&buf, "\treturn %q\n", cc)
71+
} else {
72+
clang, gcc := "clang", "gcc"
73+
if strings.HasSuffix(name, "CXX") {
74+
clang, gcc = "clang++", "g++"
75+
}
76+
fmt.Fprintf(&buf, "\tswitch goos {\n")
77+
fmt.Fprintf(&buf, "\tcase ")
78+
for i, os := range clangos {
79+
if i > 0 {
80+
fmt.Fprintf(&buf, ", ")
81+
}
82+
fmt.Fprintf(&buf, "%q", os)
83+
}
84+
fmt.Fprintf(&buf, ":\n")
85+
fmt.Fprintf(&buf, "\t\treturn %q\n", clang)
86+
fmt.Fprintf(&buf, "\t}\n")
87+
fmt.Fprintf(&buf, "\treturn %q\n", gcc)
88+
}
7089
fmt.Fprintf(&buf, "}\n")
7190

7291
return buf.String()

src/cmd/dist/main.go

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,6 @@ func main() {
5959
case "aix":
6060
// uname -m doesn't work under AIX
6161
gohostarch = "ppc64"
62-
case "darwin":
63-
// macOS 10.9 and later require clang
64-
defaultclang = true
65-
case "freebsd":
66-
// Since FreeBSD 10 gcc is no longer part of the base system.
67-
defaultclang = true
68-
case "openbsd":
69-
// OpenBSD ships with GCC 4.2, which is now quite old.
70-
defaultclang = true
7162
case "plan9":
7263
gohostarch = os.Getenv("objtype")
7364
if gohostarch == "" {

0 commit comments

Comments
 (0)