Skip to content

Commit 0b5fbf7

Browse files
committed
cmd/go: add Package.StaleReason for debugging with go list
It comes up every few months that we can't understand why the go command is rebuilding some package. Add diagnostics so that the go command can explain itself if asked. For golang#2775, golang#3506, golang#12074. Change-Id: I1c73b492589b49886bf31a8f9d05514adbd6ed70 Reviewed-on: https://go-review.googlesource.com/22432 Reviewed-by: Rob Pike <[email protected]>
1 parent 525ae3f commit 0b5fbf7

File tree

6 files changed

+82
-59
lines changed

6 files changed

+82
-59
lines changed

src/cmd/go/alldocs.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,7 @@ syntax of package template. The default output is equivalent to -f
568568
Goroot bool // is this package in the Go root?
569569
Standard bool // is this package part of the standard Go library?
570570
Stale bool // would 'go install' do anything for this package?
571+
StaleReason string // explanation for Stale==true
571572
Root string // Go root or Go path dir containing this package
572573
573574
// Source files

src/cmd/go/build.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,7 @@ func runBuild(cmd *Command, args []string) {
465465
p := pkgs[0]
466466
p.target = *buildO
467467
p.Stale = true // must build - not up to date
468+
p.StaleReason = "build -o flag in use"
468469
a := b.action(modeInstall, depMode, p)
469470
b.do(a)
470471
return
@@ -836,6 +837,7 @@ func goFilesPackage(gofiles []string) *Package {
836837

837838
pkg.Target = pkg.target
838839
pkg.Stale = true
840+
pkg.StaleReason = "files named on command line"
839841

840842
computeStale(pkg)
841843
return pkg

src/cmd/go/go_test.go

Lines changed: 51 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -524,32 +524,43 @@ func (tg *testgoData) wantArchive(path string) {
524524
}
525525
}
526526

527-
// isStale returns whether pkg is stale.
528-
func (tg *testgoData) isStale(pkg string) bool {
529-
tg.run("list", "-f", "{{.Stale}}", pkg)
530-
switch v := strings.TrimSpace(tg.getStdout()); v {
531-
case "true":
532-
return true
533-
case "false":
534-
return false
535-
default:
536-
tg.t.Fatalf("unexpected output checking staleness of package %v: %v", pkg, v)
537-
panic("unreachable")
527+
// isStale reports whether pkg is stale, and why
528+
func (tg *testgoData) isStale(pkg string) (bool, string) {
529+
tg.run("list", "-f", "{{.Stale}}:{{.StaleReason}}", pkg)
530+
v := strings.TrimSpace(tg.getStdout())
531+
f := strings.SplitN(v, ":", 2)
532+
if len(f) == 2 {
533+
switch f[0] {
534+
case "true":
535+
return true, f[1]
536+
case "false":
537+
return false, f[1]
538+
}
538539
}
540+
tg.t.Fatalf("unexpected output checking staleness of package %v: %v", pkg, v)
541+
panic("unreachable")
539542
}
540543

541544
// wantStale fails with msg if pkg is not stale.
542-
func (tg *testgoData) wantStale(pkg, msg string) {
543-
if !tg.isStale(pkg) {
545+
func (tg *testgoData) wantStale(pkg, reason, msg string) {
546+
stale, why := tg.isStale(pkg)
547+
if !stale {
544548
tg.t.Fatal(msg)
545549
}
550+
if reason == "" && why != "" || !strings.Contains(why, reason) {
551+
tg.t.Errorf("wrong reason for Stale=true: %q, want %q", why, reason)
552+
}
546553
}
547554

548555
// wantNotStale fails with msg if pkg is stale.
549-
func (tg *testgoData) wantNotStale(pkg, msg string) {
550-
if tg.isStale(pkg) {
556+
func (tg *testgoData) wantNotStale(pkg, reason, msg string) {
557+
stale, why := tg.isStale(pkg)
558+
if stale {
551559
tg.t.Fatal(msg)
552560
}
561+
if reason == "" && why != "" || !strings.Contains(why, reason) {
562+
tg.t.Errorf("wrong reason for Stale=false: %q, want %q", why, reason)
563+
}
553564
}
554565

555566
// cleanup cleans up a test that runs testgo.
@@ -708,7 +719,7 @@ func TestNewReleaseRebuildsStalePackagesInGOPATH(t *testing.T) {
708719
tg.tempFile("d1/src/p1/p1.go", `package p1`)
709720
tg.setenv("GOPATH", tg.path("d1"))
710721
tg.run("install", "-a", "p1")
711-
tg.wantNotStale("p1", "./testgo list claims p1 is stale, incorrectly")
722+
tg.wantNotStale("p1", "", "./testgo list claims p1 is stale, incorrectly")
712723
tg.sleep()
713724

714725
// Changing mtime and content of runtime/internal/sys/sys.go
@@ -717,28 +728,28 @@ func TestNewReleaseRebuildsStalePackagesInGOPATH(t *testing.T) {
717728
sys := runtime.GOROOT() + "/src/runtime/internal/sys/sys.go"
718729
restore := addNL(sys)
719730
defer restore()
720-
tg.wantNotStale("p1", "./testgo list claims p1 is stale, incorrectly, after updating runtime/internal/sys/sys.go")
731+
tg.wantNotStale("p1", "", "./testgo list claims p1 is stale, incorrectly, after updating runtime/internal/sys/sys.go")
721732
restore()
722-
tg.wantNotStale("p1", "./testgo list claims p1 is stale, incorrectly, after restoring runtime/internal/sys/sys.go")
733+
tg.wantNotStale("p1", "", "./testgo list claims p1 is stale, incorrectly, after restoring runtime/internal/sys/sys.go")
723734

724735
// But changing runtime/internal/sys/zversion.go should have an effect:
725736
// that's how we tell when we flip from one release to another.
726737
zversion := runtime.GOROOT() + "/src/runtime/internal/sys/zversion.go"
727738
restore = addNL(zversion)
728739
defer restore()
729-
tg.wantStale("p1", "./testgo list claims p1 is NOT stale, incorrectly, after changing to new release")
740+
tg.wantStale("p1", "build ID mismatch", "./testgo list claims p1 is NOT stale, incorrectly, after changing to new release")
730741
restore()
731-
tg.wantNotStale("p1", "./testgo list claims p1 is stale, incorrectly, after changing back to old release")
742+
tg.wantNotStale("p1", "", "./testgo list claims p1 is stale, incorrectly, after changing back to old release")
732743
addNL(zversion)
733-
tg.wantStale("p1", "./testgo list claims p1 is NOT stale, incorrectly, after changing again to new release")
744+
tg.wantStale("p1", "build ID mismatch", "./testgo list claims p1 is NOT stale, incorrectly, after changing again to new release")
734745
tg.run("install", "p1")
735-
tg.wantNotStale("p1", "./testgo list claims p1 is stale after building with new release")
746+
tg.wantNotStale("p1", "", "./testgo list claims p1 is stale after building with new release")
736747

737748
// Restore to "old" release.
738749
restore()
739-
tg.wantStale("p1", "./testgo list claims p1 is NOT stale, incorrectly, after changing to old release after new build")
750+
tg.wantStale("p1", "build ID mismatch", "./testgo list claims p1 is NOT stale, incorrectly, after changing to old release after new build")
740751
tg.run("install", "p1")
741-
tg.wantNotStale("p1", "./testgo list claims p1 is stale after building with old release")
752+
tg.wantNotStale("p1", "", "./testgo list claims p1 is stale after building with old release")
742753

743754
// Everything is out of date. Rebuild to leave things in a better state.
744755
tg.run("install", "std")
@@ -821,8 +832,8 @@ func TestGoInstallRebuildsStalePackagesInOtherGOPATH(t *testing.T) {
821832
sep := string(filepath.ListSeparator)
822833
tg.setenv("GOPATH", tg.path("d1")+sep+tg.path("d2"))
823834
tg.run("install", "p1")
824-
tg.wantNotStale("p1", "./testgo list claims p1 is stale, incorrectly")
825-
tg.wantNotStale("p2", "./testgo list claims p2 is stale, incorrectly")
835+
tg.wantNotStale("p1", "", "./testgo list claims p1 is stale, incorrectly")
836+
tg.wantNotStale("p2", "", "./testgo list claims p2 is stale, incorrectly")
826837
tg.sleep()
827838
if f, err := os.OpenFile(tg.path("d2/src/p2/p2.go"), os.O_WRONLY|os.O_APPEND, 0); err != nil {
828839
t.Fatal(err)
@@ -831,12 +842,12 @@ func TestGoInstallRebuildsStalePackagesInOtherGOPATH(t *testing.T) {
831842
} else {
832843
tg.must(f.Close())
833844
}
834-
tg.wantStale("p2", "./testgo list claims p2 is NOT stale, incorrectly")
835-
tg.wantStale("p1", "./testgo list claims p1 is NOT stale, incorrectly")
845+
tg.wantStale("p2", "newer source file", "./testgo list claims p2 is NOT stale, incorrectly")
846+
tg.wantStale("p1", "stale dependency", "./testgo list claims p1 is NOT stale, incorrectly")
836847

837848
tg.run("install", "p1")
838-
tg.wantNotStale("p2", "./testgo list claims p2 is stale after reinstall, incorrectly")
839-
tg.wantNotStale("p1", "./testgo list claims p1 is stale after reinstall, incorrectly")
849+
tg.wantNotStale("p2", "", "./testgo list claims p2 is stale after reinstall, incorrectly")
850+
tg.wantNotStale("p1", "", "./testgo list claims p1 is stale after reinstall, incorrectly")
840851
}
841852

842853
func TestGoInstallDetectsRemovedFiles(t *testing.T) {
@@ -850,13 +861,13 @@ func TestGoInstallDetectsRemovedFiles(t *testing.T) {
850861
package mypkg`)
851862
tg.setenv("GOPATH", tg.path("."))
852863
tg.run("install", "mypkg")
853-
tg.wantNotStale("mypkg", "./testgo list mypkg claims mypkg is stale, incorrectly")
864+
tg.wantNotStale("mypkg", "", "./testgo list mypkg claims mypkg is stale, incorrectly")
854865
// z.go was not part of the build; removing it is okay.
855866
tg.must(os.Remove(tg.path("src/mypkg/z.go")))
856-
tg.wantNotStale("mypkg", "./testgo list mypkg claims mypkg is stale after removing z.go; should not be stale")
867+
tg.wantNotStale("mypkg", "", "./testgo list mypkg claims mypkg is stale after removing z.go; should not be stale")
857868
// y.go was part of the package; removing it should be detected.
858869
tg.must(os.Remove(tg.path("src/mypkg/y.go")))
859-
tg.wantStale("mypkg", "./testgo list mypkg claims mypkg is NOT stale after removing y.go; should be stale")
870+
tg.wantStale("mypkg", "build ID mismatch", "./testgo list mypkg claims mypkg is NOT stale after removing y.go; should be stale")
860871
}
861872

862873
func TestWildcardMatchesSyntaxErrorDirs(t *testing.T) {
@@ -919,13 +930,13 @@ func TestGoInstallDetectsRemovedFilesInPackageMain(t *testing.T) {
919930
package main`)
920931
tg.setenv("GOPATH", tg.path("."))
921932
tg.run("install", "mycmd")
922-
tg.wantNotStale("mycmd", "./testgo list mypkg claims mycmd is stale, incorrectly")
933+
tg.wantNotStale("mycmd", "", "./testgo list mypkg claims mycmd is stale, incorrectly")
923934
// z.go was not part of the build; removing it is okay.
924935
tg.must(os.Remove(tg.path("src/mycmd/z.go")))
925-
tg.wantNotStale("mycmd", "./testgo list mycmd claims mycmd is stale after removing z.go; should not be stale")
936+
tg.wantNotStale("mycmd", "", "./testgo list mycmd claims mycmd is stale after removing z.go; should not be stale")
926937
// y.go was part of the package; removing it should be detected.
927938
tg.must(os.Remove(tg.path("src/mycmd/y.go")))
928-
tg.wantStale("mycmd", "./testgo list mycmd claims mycmd is NOT stale after removing y.go; should be stale")
939+
tg.wantStale("mycmd", "build ID mismatch", "./testgo list mycmd claims mycmd is NOT stale after removing y.go; should be stale")
929940
}
930941

931942
func testLocalRun(tg *testgoData, exepath, local, match string) {
@@ -1317,7 +1328,7 @@ func TestPackageMainTestImportsArchiveNotBinary(t *testing.T) {
13171328
tg.sleep()
13181329
tg.run("test", "main_test")
13191330
tg.run("install", "main_test")
1320-
tg.wantNotStale("main_test", "after go install, main listed as stale")
1331+
tg.wantNotStale("main_test", "", "after go install, main listed as stale")
13211332
tg.run("test", "main_test")
13221333
}
13231334

@@ -1327,9 +1338,9 @@ func TestPackageNotStaleWithTrailingSlash(t *testing.T) {
13271338
defer tg.cleanup()
13281339
goroot := runtime.GOROOT()
13291340
tg.setenv("GOROOT", goroot+"/")
1330-
tg.wantNotStale("runtime", "with trailing slash in GOROOT, runtime listed as stale")
1331-
tg.wantNotStale("os", "with trailing slash in GOROOT, os listed as stale")
1332-
tg.wantNotStale("io", "with trailing slash in GOROOT, io listed as stale")
1341+
tg.wantNotStale("runtime", "", "with trailing slash in GOROOT, runtime listed as stale")
1342+
tg.wantNotStale("os", "", "with trailing slash in GOROOT, os listed as stale")
1343+
tg.wantNotStale("io", "", "with trailing slash in GOROOT, io listed as stale")
13331344
}
13341345

13351346
// With $GOBIN set, binaries get installed to $GOBIN.

src/cmd/go/list.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ syntax of package template. The default output is equivalent to -f
4141
Goroot bool // is this package in the Go root?
4242
Standard bool // is this package part of the standard Go library?
4343
Stale bool // would 'go install' do anything for this package?
44+
StaleReason string // explanation for Stale==true
4445
Root string // Go root or Go path dir containing this package
4546
4647
// Source files

src/cmd/go/pkg.go

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ type Package struct {
3939
Goroot bool `json:",omitempty"` // is this package found in the Go root?
4040
Standard bool `json:",omitempty"` // is this package part of the standard Go library?
4141
Stale bool `json:",omitempty"` // would 'go install' do anything for this package?
42+
StaleReason string `json:",omitempty"` // why is Stale true?
4243
Root string `json:",omitempty"` // Go root or Go path dir containing this package
4344
ConflictDir string `json:",omitempty"` // Dir is hidden by this other directory
4445

@@ -1085,7 +1086,7 @@ func packageList(roots []*Package) []*Package {
10851086
// at the named pkgs (command-line arguments).
10861087
func computeStale(pkgs ...*Package) {
10871088
for _, p := range packageList(pkgs) {
1088-
p.Stale = isStale(p)
1089+
p.Stale, p.StaleReason = isStale(p)
10891090
}
10901091
}
10911092

@@ -1356,14 +1357,15 @@ var isGoRelease = strings.HasPrefix(runtime.Version(), "go1")
13561357
// standard library, even in release versions. This makes
13571358
// 'go build -tags netgo' work, among other things.
13581359

1359-
// isStale reports whether package p needs to be rebuilt.
1360-
func isStale(p *Package) bool {
1360+
// isStale reports whether package p needs to be rebuilt,
1361+
// along with the reason why.
1362+
func isStale(p *Package) (bool, string) {
13611363
if p.Standard && (p.ImportPath == "unsafe" || buildContext.Compiler == "gccgo") {
13621364
// fake, builtin package
1363-
return false
1365+
return false, "builtin package"
13641366
}
13651367
if p.Error != nil {
1366-
return true
1368+
return true, "errors loading package"
13671369
}
13681370

13691371
// A package without Go sources means we only found
@@ -1373,23 +1375,26 @@ func isStale(p *Package) bool {
13731375
// only useful with the specific version of the toolchain that
13741376
// created them.
13751377
if len(p.gofiles) == 0 && !p.usesSwig() {
1376-
return false
1378+
return false, "no source files"
13771379
}
13781380

13791381
// If the -a flag is given, rebuild everything.
13801382
if buildA {
1381-
return true
1383+
return true, "build -a flag in use"
13821384
}
13831385

13841386
// If there's no install target or it's already marked stale, we have to rebuild.
1385-
if p.target == "" || p.Stale {
1386-
return true
1387+
if p.target == "" {
1388+
return true, "no install target"
1389+
}
1390+
if p.Stale {
1391+
return true, p.StaleReason
13871392
}
13881393

13891394
// Package is stale if completely unbuilt.
13901395
fi, err := os.Stat(p.target)
13911396
if err != nil {
1392-
return true
1397+
return true, "cannot stat install target"
13931398
}
13941399

13951400
// Package is stale if the expected build ID differs from the
@@ -1402,13 +1407,13 @@ func isStale(p *Package) bool {
14021407
// See issue 8290 and issue 10702.
14031408
targetBuildID, err := readBuildID(p)
14041409
if err == nil && targetBuildID != p.buildID {
1405-
return true
1410+
return true, "build ID mismatch"
14061411
}
14071412

14081413
// Package is stale if a dependency is.
14091414
for _, p1 := range p.deps {
14101415
if p1.Stale {
1411-
return true
1416+
return true, "stale dependency"
14121417
}
14131418
}
14141419

@@ -1431,7 +1436,7 @@ func isStale(p *Package) bool {
14311436
// install is to run make.bash, which will remove the old package archives
14321437
// before rebuilding.)
14331438
if p.Standard && isGoRelease {
1434-
return false
1439+
return false, "standard package in Go release distribution"
14351440
}
14361441

14371442
// Time-based staleness.
@@ -1446,7 +1451,7 @@ func isStale(p *Package) bool {
14461451
// Package is stale if a dependency is, or if a dependency is newer.
14471452
for _, p1 := range p.deps {
14481453
if p1.target != "" && olderThan(p1.target) {
1449-
return true
1454+
return true, "newer dependency"
14501455
}
14511456
}
14521457

@@ -1465,10 +1470,10 @@ func isStale(p *Package) bool {
14651470
// taken care of above (at least when the installed Go is a released version).
14661471
if p.Root != goroot {
14671472
if olderThan(buildToolchain.compiler()) {
1468-
return true
1473+
return true, "newer compiler"
14691474
}
14701475
if p.build.IsCommand() && olderThan(buildToolchain.linker()) {
1471-
return true
1476+
return true, "newer linker"
14721477
}
14731478
}
14741479

@@ -1513,11 +1518,11 @@ func isStale(p *Package) bool {
15131518
srcs := stringList(p.GoFiles, p.CFiles, p.CXXFiles, p.MFiles, p.HFiles, p.FFiles, p.SFiles, p.CgoFiles, p.SysoFiles, p.SwigFiles, p.SwigCXXFiles)
15141519
for _, src := range srcs {
15151520
if olderThan(filepath.Join(p.Dir, src)) {
1516-
return true
1521+
return true, "newer source file"
15171522
}
15181523
}
15191524

1520-
return false
1525+
return false, ""
15211526
}
15221527

15231528
// computeBuildID computes the build ID for p, leaving it in p.buildID.

src/cmd/go/test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,8 @@ func runTest(cmd *Command, args []string) {
512512
continue
513513
}
514514
p.Stale = true // rebuild
515-
p.fake = true // do not warn about rebuild
515+
p.StaleReason = "rebuild for coverage"
516+
p.fake = true // do not warn about rebuild
516517
p.coverMode = testCoverMode
517518
var coverFiles []string
518519
coverFiles = append(coverFiles, p.GoFiles...)
@@ -749,6 +750,7 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,
749750
ptest.fake = true
750751
ptest.forceLibrary = true
751752
ptest.Stale = true
753+
ptest.StaleReason = "rebuild for test"
752754
ptest.build = new(build.Package)
753755
*ptest.build = *p.build
754756
m := map[string][]token.Position{}
@@ -1027,6 +1029,7 @@ func recompileForTest(pmain, preal, ptest *Package, testDir string) {
10271029
p.target = ""
10281030
p.fake = true
10291031
p.Stale = true
1032+
p.StaleReason = "depends on package being tested"
10301033
}
10311034
}
10321035

0 commit comments

Comments
 (0)