Skip to content

Commit 7dea509

Browse files
committed
cmd/go: switch to entirely content-based staleness determination
This CL changes the go command to base all its rebuilding decisions on the content of the files being processed and not their file system modification times. It also eliminates the special handling of release toolchains, which were previously considered always up-to-date because modification time order could not be trusted when unpacking a pre-built release. The go command previously tracked "build IDs" as a backup to modification times, to catch changes not reflected in modification times. For example, if you remove one .go file in a package with multiple .go files, there is no modification time remaining in the system that indicates that the installed package is out of date. The old build ID was the hash of a list of file names and a few other factors, expected to change if those factors changed. This CL moves to using this kind of build ID as the only way to detect staleness, making sure that the build ID hash includes all possible factors that need to influence the rebuild decision. One such factor is the compiler flags. As of this CL, if you run go build -gcflags -N cmd/gofmt you will get a gofmt where every package is built with -N, regardless of what may or may not be installed already. Another such factor is the linker flags. As of this CL, if you run go install myprog go install -ldflags=-s myprog the second go install will now correctly build a new myprog with the updated linker flags. (Previously the installed myprog appeared up-to-date, because the ldflags were not included in the build ID.) Because we have more precise information we can also validate whether the target of a "go test -c" operation is already the right binary and therefore can avoid a rebuild. This CL sets us up for having a more general build artifact cache, maybe even a step toward not having a pkg directory with .a files, but this CL does not take that step. For now the result of go install is the same as it ever was; we just do a better job of what needs to be installed. This CL does slow down builds a small amount by reading all the dependent source files in full. (The go command already read the beginning of every dependent source file to discover build tags and imports.) On my MacBook Pro, before this CL all.bash takes 3m58s, while after this CL and a few optimizations stacked above it all.bash takes 4m28s. Given that CL 73850 cut 1m43s off the all.bash time earlier today, we can afford adding 30s back for now. More optimizations are planned that should make the go command more efficient than it was even before this CL. Fixes #15799. Fixes #18369. Fixes #19340. Fixes #21477. Change-Id: I10d7ca0e31ca3f58aabb9b1f11e2e3d9d18f0bc9 Reviewed-on: https://go-review.googlesource.com/73212 Run-TryBot: Russ Cox <rsc@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: David Crawshaw <crawshaw@golang.org>
1 parent 4b5018c commit 7dea509

File tree

18 files changed

+1003
-921
lines changed

18 files changed

+1003
-921
lines changed

Diff for: misc/cgo/testshared/shared_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,7 @@ func TestThreeGopathShlibs(t *testing.T) {
598598
// If gccgo is not available or not new enough call t.Skip. Otherwise,
599599
// return a build.Context that is set up for gccgo.
600600
func prepGccgo(t *testing.T) build.Context {
601+
t.Skip("golang.org/issue/22472")
601602
gccgoName := os.Getenv("GCCGO")
602603
if gccgoName == "" {
603604
gccgoName = "gccgo"

Diff for: src/cmd/dist/build.go

+112-44
Original file line numberDiff line numberDiff line change
@@ -1018,7 +1018,9 @@ func cmdenv() {
10181018
// that setting, not the new one.
10191019
func cmdbootstrap() {
10201020
var noBanner bool
1021+
var debug bool
10211022
flag.BoolVar(&rebuildall, "a", rebuildall, "rebuild all")
1023+
flag.BoolVar(&debug, "d", debug, "enable debugging of bootstrap process")
10221024
flag.BoolVar(&noBanner, "no-banner", noBanner, "do not print banner")
10231025

10241026
xflagparse(0)
@@ -1055,31 +1057,7 @@ func cmdbootstrap() {
10551057
os.Setenv("GOARCH", goarch)
10561058
os.Setenv("GOOS", goos)
10571059

1058-
// TODO(rsc): Enable when appropriate.
1059-
// This step is only needed if we believe that the Go compiler built from Go 1.4
1060-
// will produce different object files than the Go compiler built from itself.
1061-
// In the absence of bugs, that should not happen.
1062-
// And if there are bugs, they're more likely in the current development tree
1063-
// than in a standard release like Go 1.4, so don't do this rebuild by default.
1064-
if false {
1065-
xprintf("##### Building Go toolchain using itself.\n")
1066-
for _, dir := range buildlist {
1067-
installed[dir] = make(chan struct{})
1068-
}
1069-
var wg sync.WaitGroup
1070-
for _, dir := range builddeps["cmd/go"] {
1071-
wg.Add(1)
1072-
dir := dir
1073-
go func() {
1074-
defer wg.Done()
1075-
install(dir)
1076-
}()
1077-
}
1078-
wg.Wait()
1079-
xprintf("\n")
1080-
}
1081-
1082-
xprintf("##### Building go_bootstrap for host, %s/%s.\n", gohostos, gohostarch)
1060+
xprintf("##### Building go_bootstrap.\n")
10831061
for _, dir := range buildlist {
10841062
installed[dir] = make(chan struct{})
10851063
}
@@ -1091,20 +1069,97 @@ func cmdbootstrap() {
10911069

10921070
gogcflags = os.Getenv("GO_GCFLAGS") // we were using $BOOT_GO_GCFLAGS until now
10931071
goldflags = os.Getenv("GO_LDFLAGS")
1094-
1095-
// Build full toolchain for host and (if different) for target.
1096-
if goos != oldgoos || goarch != oldgoarch {
1097-
os.Setenv("CC", defaultcc)
1098-
buildAll()
1099-
xprintf("\n")
1072+
goBootstrap := pathf("%s/go_bootstrap", tooldir)
1073+
cmdGo := pathf("%s/go", gobin)
1074+
if debug {
1075+
run("", ShowOutput|CheckExit, pathf("%s/compile", tooldir), "-V=full")
1076+
copyfile(pathf("%s/compile1", tooldir), pathf("%s/compile", tooldir), writeExec)
1077+
}
1078+
1079+
// To recap, so far we have built the new toolchain
1080+
// (cmd/asm, cmd/cgo, cmd/compile, cmd/link)
1081+
// using Go 1.4's toolchain and go command.
1082+
// Then we built the new go command (as go_bootstrap)
1083+
// using the new toolchain and our own build logic (above).
1084+
//
1085+
// toolchain1 = mk(new toolchain, go1.4 toolchain, go1.4 cmd/go)
1086+
// go_bootstrap = mk(new cmd/go, toolchain1, cmd/dist)
1087+
//
1088+
// The toolchain1 we built earlier is built from the new sources,
1089+
// but because it was built using cmd/go it has no build IDs.
1090+
// The eventually installed toolchain needs build IDs, so we need
1091+
// to do another round:
1092+
//
1093+
// toolchain2 = mk(new toolchain, toolchain1, go_bootstrap)
1094+
//
1095+
xprintf("\n##### Building Go toolchain2 using go_bootstrap and Go toolchain1.\n")
1096+
os.Setenv("CC", defaultcc)
1097+
if goos == oldgoos && goarch == oldgoarch {
1098+
// Host and target are same, and we have historically
1099+
// chosen $CC_FOR_TARGET in this case.
1100+
os.Setenv("CC", defaultcctarget)
1101+
}
1102+
toolchain := []string{"cmd/asm", "cmd/cgo", "cmd/compile", "cmd/link", "cmd/buildid"}
1103+
goInstall(toolchain...)
1104+
if debug {
1105+
run("", ShowOutput|CheckExit, pathf("%s/compile", tooldir), "-V=full")
1106+
run("", ShowOutput|CheckExit, pathf("%s/buildid", tooldir), pathf("%s/../../darwin_amd64/runtime/internal/sys.a", tooldir))
1107+
copyfile(pathf("%s/compile2", tooldir), pathf("%s/compile", tooldir), writeExec)
1108+
}
1109+
1110+
// Toolchain2 should be semantically equivalent to toolchain1,
1111+
// but it was built using the new compilers instead of the Go 1.4 compilers,
1112+
// so it should at the least run faster. Also, toolchain1 had no build IDs
1113+
// in the binaries, while toolchain2 does. In non-release builds, the
1114+
// toolchain's build IDs feed into constructing the build IDs of built targets,
1115+
// so in non-release builds, everything now looks out-of-date due to
1116+
// toolchain2 having build IDs - that is, due to the go command seeing
1117+
// that there are new compilers. In release builds, the toolchain's reported
1118+
// version is used in place of the build ID, and the go command does not
1119+
// see that change from toolchain1 to toolchain2, so in release builds,
1120+
// nothing looks out of date.
1121+
// To keep the behavior the same in both non-release and release builds,
1122+
// we force-install everything here.
1123+
//
1124+
// toolchain3 = mk(new toolchain, toolchain2, go_bootstrap)
1125+
//
1126+
xprintf("\n##### Building Go toolchain3 using go_bootstrap and Go toolchain2.\n")
1127+
goInstall(append([]string{"-a"}, toolchain...)...)
1128+
if debug {
1129+
run("", ShowOutput|CheckExit, pathf("%s/compile", tooldir), "-V=full")
1130+
run("", ShowOutput|CheckExit, pathf("%s/buildid", tooldir), pathf("%s/../../darwin_amd64/runtime/internal/sys.a", tooldir))
1131+
copyfile(pathf("%s/compile3", tooldir), pathf("%s/compile", tooldir), writeExec)
1132+
}
1133+
checkNotStale(goBootstrap, append(toolchain, "runtime/internal/sys")...)
1134+
1135+
if goos == oldgoos && goarch == oldgoarch {
1136+
// Common case - not setting up for cross-compilation.
1137+
xprintf("\n##### Building packages and commands for %s/%s\n", goos, goarch)
1138+
} else {
1139+
// GOOS/GOARCH does not match GOHOSTOS/GOHOSTARCH.
1140+
// Finish GOHOSTOS/GOHOSTARCH installation and then
1141+
// run GOOS/GOARCH installation.
1142+
xprintf("\n##### Building packages and commands for host, %s/%s\n", goos, goarch)
1143+
goInstall("std", "cmd")
1144+
checkNotStale(goBootstrap, "std", "cmd")
1145+
checkNotStale(cmdGo, "std", "cmd")
1146+
1147+
xprintf("\n##### Building packages and commands for target, %s/%s\n", goos, goarch)
11001148
goos = oldgoos
11011149
goarch = oldgoarch
11021150
os.Setenv("GOOS", goos)
11031151
os.Setenv("GOARCH", goarch)
1152+
os.Setenv("CC", defaultcctarget)
1153+
}
1154+
goInstall("std", "cmd")
1155+
checkNotStale(goBootstrap, "std", "cmd")
1156+
checkNotStale(cmdGo, "std", "cmd")
1157+
if debug {
1158+
run("", ShowOutput|CheckExit, pathf("%s/compile", tooldir), "-V=full")
1159+
run("", ShowOutput|CheckExit, pathf("%s/buildid", tooldir), pathf("%s/../../darwin_amd64/runtime/internal/sys.a", tooldir))
1160+
checkNotStale(goBootstrap, append(toolchain, "runtime/internal/sys")...)
1161+
copyfile(pathf("%s/compile4", tooldir), pathf("%s/compile", tooldir), writeExec)
11041162
}
1105-
1106-
os.Setenv("CC", defaultcctarget)
1107-
buildAll()
11081163

11091164
// Check that there are no new files in $GOROOT/bin other than
11101165
// go and gofmt and $GOOS_$GOARCH (target bin when cross-compiling).
@@ -1129,21 +1184,34 @@ func cmdbootstrap() {
11291184
}
11301185
}
11311186

1132-
func buildAll() {
1133-
desc := ""
1134-
if oldgoos != goos || oldgoarch != goarch {
1135-
desc = " host,"
1136-
}
1137-
xprintf("##### Building packages and commands for%s %s/%s.\n", desc, goos, goarch)
1138-
go_bootstrap := pathf("%s/go_bootstrap", tooldir)
1139-
go_install := []string{go_bootstrap, "install", "-v", "-gcflags=" + gogcflags, "-ldflags=" + goldflags}
1187+
func goInstall(args ...string) {
1188+
installCmd := []string{pathf("%s/go_bootstrap", tooldir), "install", "-v", "-gcflags=" + gogcflags, "-ldflags=" + goldflags}
11401189

11411190
// Force only one process at a time on vx32 emulation.
11421191
if gohostos == "plan9" && os.Getenv("sysname") == "vx32" {
1143-
go_install = append(go_install, "-p=1")
1192+
installCmd = append(installCmd, "-p=1")
11441193
}
11451194

1146-
run(pathf("%s/src", goroot), ShowOutput|CheckExit, append(go_install, "std", "cmd")...)
1195+
run(goroot, ShowOutput|CheckExit, append(installCmd, args...)...)
1196+
}
1197+
1198+
func checkNotStale(goBinary string, targets ...string) {
1199+
out := run(goroot, CheckExit,
1200+
append([]string{
1201+
goBinary,
1202+
"list", "-gcflags=" + gogcflags, "-ldflags=" + goldflags,
1203+
"-f={{if .Stale}}\t{{.ImportPath}}: {{.StaleReason}}{{end}}",
1204+
}, targets...)...)
1205+
if out != "" {
1206+
os.Setenv("GOCMDDEBUGHASH", "1")
1207+
for _, target := range []string{"runtime/internal/sys", "cmd/dist", "cmd/link"} {
1208+
if strings.Contains(out, target) {
1209+
run(goroot, ShowOutput|CheckExit, goBinary, "list", "-f={{.ImportPath}} {{.Stale}}", target)
1210+
break
1211+
}
1212+
}
1213+
fatalf("unexpected stale targets reported by %s list -gcflags=\"%s\" -ldflags=\"%s\" for %v:\n%s", goBinary, gogcflags, goldflags, targets, out)
1214+
}
11471215
}
11481216

11491217
// Cannot use go/build directly because cmd/dist for a new release

Diff for: src/cmd/dist/buildtool.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ func bootstrapBuildTools() {
106106
if goroot_bootstrap == "" {
107107
goroot_bootstrap = pathf("%s/go1.4", os.Getenv("HOME"))
108108
}
109-
xprintf("##### Building Go toolchain using %s.\n", goroot_bootstrap)
109+
xprintf("##### Building Go toolchain1 using %s.\n", goroot_bootstrap)
110110

111111
mkzbootstrap(pathf("%s/src/cmd/internal/objabi/zbootstrap.go", goroot))
112112

Diff for: src/cmd/dist/deps.go

+48-44
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: src/cmd/dist/test.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import (
2323
)
2424

2525
func cmdtest() {
26+
gogcflags = os.Getenv("GO_GCFLAGS")
27+
2628
var t tester
2729
var noRebuild bool
2830
flag.BoolVar(&t.listMode, "list", false, "list available tests")
@@ -272,7 +274,7 @@ func (t *tester) registerStdTest(pkg string) {
272274
"-short",
273275
t.tags(),
274276
t.timeout(180),
275-
"-gcflags=" + os.Getenv("GO_GCFLAGS"),
277+
"-gcflags=" + gogcflags,
276278
}
277279
if t.race {
278280
args = append(args, "-race")
@@ -936,6 +938,7 @@ func (t *tester) cgoTest(dt *distTest) error {
936938
// running in parallel with earlier tests, or if it has some other reason
937939
// for needing the earlier tests to be done.
938940
func (t *tester) runPending(nextTest *distTest) {
941+
checkNotStale("go", "std", "cmd")
939942
worklist := t.worklist
940943
t.worklist = nil
941944
for _, w := range worklist {
@@ -985,6 +988,7 @@ func (t *tester) runPending(nextTest *distTest) {
985988
log.Printf("Failed: %v", w.err)
986989
t.failed = true
987990
}
991+
checkNotStale("go", "std", "cmd")
988992
}
989993
if t.failed && !t.keepGoing {
990994
log.Fatal("FAILED")

0 commit comments

Comments
 (0)