Commit ccf04c60 authored by Russ Cox's avatar Russ Cox

cmd/go: display cached compiler output more often

CL 77110 arranged for caching and redisplaying compiler output
when reusing a compile artifact from the build cache.

It neglected to redisplay compiler and linker output when avoiding
the compile and link steps by reusing the target output binary
as a cached result. It also neglected to redisplay compiler and linker
output when avoiding the compile and link (and test) steps by reusing
cached test output.

This CL brings back the compiler and linker output in those two cases,
provided it can be found in the build cache. If it can't be found in the
build cache, then the go command still reuses the binaries and avoids
the compile/link/test steps. (It's not worth doing all that work again
just to repeat diagnostic output.)

Fixes #23877.

Change-Id: I25bc054d93a88c039bcb8c5683fe4ac5cb1ee544
Reviewed-on: https://go-review.googlesource.com/128903
Run-TryBot: Russ Cox <rsc@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: 's avatarBryan C. Mills <bcmills@google.com>
parent dce644d9
...@@ -469,6 +469,14 @@ func (b *Builder) useCache(a *Action, p *load.Package, actionHash cache.ActionID ...@@ -469,6 +469,14 @@ func (b *Builder) useCache(a *Action, p *load.Package, actionHash cache.ActionID
a.buildID = id[1] + buildIDSeparator + id[2] a.buildID = id[1] + buildIDSeparator + id[2]
linkID := hashToString(b.linkActionID(a.triggers[0])) linkID := hashToString(b.linkActionID(a.triggers[0]))
if id[0] == linkID { 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. // Poison a.Target to catch uses later in the build.
a.Target = "DO NOT USE - main build pseudo-cache Target" a.Target = "DO NOT USE - main build pseudo-cache Target"
a.built = "DO NOT USE - main build pseudo-cache built" a.built = "DO NOT USE - main build pseudo-cache built"
...@@ -486,6 +494,15 @@ func (b *Builder) useCache(a *Action, p *load.Package, actionHash cache.ActionID ...@@ -486,6 +494,15 @@ func (b *Builder) useCache(a *Action, p *load.Package, actionHash cache.ActionID
// We avoid the nested build ID problem in the previous special case // 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. // 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]) { 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.Target = "DO NOT USE - pseudo-cache Target"
a.built = "DO NOT USE - pseudo-cache built" a.built = "DO NOT USE - pseudo-cache built"
return true return true
...@@ -526,15 +543,7 @@ func (b *Builder) useCache(a *Action, p *load.Package, actionHash cache.ActionID ...@@ -526,15 +543,7 @@ func (b *Builder) useCache(a *Action, p *load.Package, actionHash cache.ActionID
if !cfg.BuildA { if !cfg.BuildA {
if file, _, err := c.GetFile(actionHash); err == nil { if file, _, err := c.GetFile(actionHash); err == nil {
if buildID, err := buildid.ReadFile(file); err == nil { if buildID, err := buildid.ReadFile(file); err == nil {
if stdout, stdoutEntry, err := c.GetBytes(cache.Subkey(a.actionID, "stdout")); err == nil { if err := showStdout(b, c, a.actionID, "stdout"); err == nil {
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.Print(string(stdout))
}
}
a.built = file a.built = file
a.Target = "DO NOT USE - using cache" a.Target = "DO NOT USE - using cache"
a.buildID = buildID a.buildID = buildID
...@@ -555,6 +564,23 @@ func (b *Builder) useCache(a *Action, p *load.Package, actionHash cache.ActionID ...@@ -555,6 +564,23 @@ func (b *Builder) useCache(a *Action, p *load.Package, actionHash cache.ActionID
return false 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.Print(string(stdout))
}
}
return nil
}
// flushOutput flushes the output being queued in a. // flushOutput flushes the output being queued in a.
func (b *Builder) flushOutput(a *Action) { func (b *Builder) flushOutput(a *Action) {
b.Print(string(a.output)) b.Print(string(a.output))
...@@ -579,6 +605,26 @@ func (b *Builder) updateBuildID(a *Action, target string, rewrite bool) error { ...@@ -579,6 +605,26 @@ func (b *Builder) updateBuildID(a *Action, target string, rewrite bool) error {
} }
} }
// 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. // Find occurrences of old ID and compute new content-based ID.
r, err := os.Open(target) r, err := os.Open(target)
if err != nil { if err != nil {
...@@ -646,7 +692,6 @@ func (b *Builder) updateBuildID(a *Action, target string, rewrite bool) error { ...@@ -646,7 +692,6 @@ func (b *Builder) updateBuildID(a *Action, target string, rewrite bool) error {
} }
a.Package.Export = c.OutputFile(outputID) a.Package.Export = c.OutputFile(outputID)
} }
c.PutBytes(cache.Subkey(a.actionID, "stdout"), a.output)
} }
} }
......
...@@ -1155,11 +1155,11 @@ func (b *Builder) link(a *Action) (err error) { ...@@ -1155,11 +1155,11 @@ func (b *Builder) link(a *Action) (err error) {
// We still call updateBuildID to update a.buildID, which is important // We still call updateBuildID to update a.buildID, which is important
// for test result caching, but passing rewrite=false (final arg) // for test result caching, but passing rewrite=false (final arg)
// means we don't actually rewrite the binary, nor store the // means we don't actually rewrite the binary, nor store the
// result into the cache. // result into the cache. That's probably a net win:
// Not calling updateBuildID means we also don't insert these
// binaries into the build object cache. That's probably a net win:
// less cache space wasted on large binaries we are not likely to // less cache space wasted on large binaries we are not likely to
// need again. (On the other hand it does make repeated go test slower.) // need again. (On the other hand it does make repeated go test slower.)
// It also makes repeated go run slower, which is a win in itself:
// we don't want people to treat go run like a scripting environment.
if err := b.updateBuildID(a, a.Target, !a.Package.Internal.OmitDebug); err != nil { if err := b.updateBuildID(a, a.Target, !a.Package.Internal.OmitDebug); err != nil {
return err return err
} }
......
...@@ -6,14 +6,58 @@ mkdir $GOCACHE ...@@ -6,14 +6,58 @@ mkdir $GOCACHE
# Building a trivial non-main package should run compiler the first time. # Building a trivial non-main package should run compiler the first time.
go build -x -gcflags=-m lib.go go build -x -gcflags=-m lib.go
stderr 'compile( |\.exe)' stderr 'compile( |\.exe"?)'
stderr 'lib.go:2.* can inline f' stderr 'lib.go:2.* can inline f'
# ... but not the second, even though it still prints the compiler output. # ... but not the second, even though it still prints the compiler output.
go build -x -gcflags=-m lib.go go build -x -gcflags=-m lib.go
! stderr 'compile( |\.exe)' ! stderr 'compile( |\.exe"?)'
stderr 'lib.go:2.* can inline f' stderr 'lib.go:2.* can inline f'
# Building a trivial main package should run the compiler and linker the first time.
go build -x -gcflags=-m -ldflags='-v -w' main.go
stderr 'compile( |\.exe"?)'
stderr 'main.go:2.* can inline main' # from compiler
stderr 'link(\.exe"?)? -'
stderr '\d+ symbols' # from linker
# ... but not the second, even though it still prints the compiler and linker output.
go build -x -gcflags=-m -ldflags='-v -w' main.go
! stderr 'compile( |\.exe"?)'
stderr 'main.go:2.* can inline main' # from compiler
! stderr 'link(\.exe"?)? -'
stderr '\d+ symbols' # from linker
# Running a test should run the compiler, linker, and the test the first time.
go test -v -x -gcflags=-m -ldflags=-v p_test.go
stderr 'compile( |\.exe"?)'
stderr 'p_test.go:.*can inline Test' # from compile of p_test
stderr 'testmain\.go:.*inlin' # from compile of testmain
stderr 'link(\.exe"?)? -'
stderr '\d+ symbols' # from linker
stderr 'p\.test( |\.exe"?)'
stdout 'TEST' # from test
# ... but not the second, even though it still prints the compiler, linker, and test output.
go test -v -x -gcflags=-m -ldflags=-v p_test.go
! stderr 'compile( |\.exe"?)'
stderr 'p_test.go:.*can inline Test' # from compile of p_test
stderr 'testmain\.go:.*inlin' # from compile of testmain
! stderr 'link(\.exe"?)? -'
stderr '\d+ symbols' # from linker
! stderr 'p\.test( |\.exe"?)'
stdout 'TEST' # from test
-- lib.go -- -- lib.go --
package p package p
func f(x *int) *int { return x } func f(x *int) *int { return x }
-- main.go --
package main
func main() {}
-- p_test.go --
package p
import "testing"
func Test(t *testing.T) {println("TEST")}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment