]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/go: add Package.StaleReason for debugging with go list
authorRuss Cox <rsc@golang.org>
Mon, 25 Apr 2016 14:51:26 +0000 (10:51 -0400)
committerRuss Cox <rsc@golang.org>
Wed, 27 Apr 2016 01:58:24 +0000 (01:58 +0000)
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 #2775, #3506, #12074.

Change-Id: I1c73b492589b49886bf31a8f9d05514adbd6ed70
Reviewed-on: https://go-review.googlesource.com/22432
Reviewed-by: Rob Pike <r@golang.org>
src/cmd/go/alldocs.go
src/cmd/go/build.go
src/cmd/go/go_test.go
src/cmd/go/list.go
src/cmd/go/pkg.go
src/cmd/go/test.go

index aa1f0299392f6aa49c40ca0c3d2369c79f19e764..2b74cb59e35c58fa53da34c17c1ab09c5f018c16 100644 (file)
@@ -568,6 +568,7 @@ syntax of package template.  The default output is equivalent to -f
         Goroot        bool   // is this package in the Go root?
         Standard      bool   // is this package part of the standard Go library?
         Stale         bool   // would 'go install' do anything for this package?
+        StaleReason   string // explanation for Stale==true
         Root          string // Go root or Go path dir containing this package
 
         // Source files
index 0d01ee1f73669600e33b05247e20a7e08f775f5c..4aaad04b3abf909edb4537ae77e2ef05309521ea 100644 (file)
@@ -465,6 +465,7 @@ func runBuild(cmd *Command, args []string) {
                p := pkgs[0]
                p.target = *buildO
                p.Stale = true // must build - not up to date
+               p.StaleReason = "build -o flag in use"
                a := b.action(modeInstall, depMode, p)
                b.do(a)
                return
@@ -836,6 +837,7 @@ func goFilesPackage(gofiles []string) *Package {
 
        pkg.Target = pkg.target
        pkg.Stale = true
+       pkg.StaleReason = "files named on command line"
 
        computeStale(pkg)
        return pkg
index 9d840baae2682d7708fa7aae362b01fb11f598fe..fe3d47752cdbcfecea96c1645bd279beeacfb18c 100644 (file)
@@ -524,32 +524,43 @@ func (tg *testgoData) wantArchive(path string) {
        }
 }
 
-// isStale returns whether pkg is stale.
-func (tg *testgoData) isStale(pkg string) bool {
-       tg.run("list", "-f", "{{.Stale}}", pkg)
-       switch v := strings.TrimSpace(tg.getStdout()); v {
-       case "true":
-               return true
-       case "false":
-               return false
-       default:
-               tg.t.Fatalf("unexpected output checking staleness of package %v: %v", pkg, v)
-               panic("unreachable")
+// isStale reports whether pkg is stale, and why
+func (tg *testgoData) isStale(pkg string) (bool, string) {
+       tg.run("list", "-f", "{{.Stale}}:{{.StaleReason}}", pkg)
+       v := strings.TrimSpace(tg.getStdout())
+       f := strings.SplitN(v, ":", 2)
+       if len(f) == 2 {
+               switch f[0] {
+               case "true":
+                       return true, f[1]
+               case "false":
+                       return false, f[1]
+               }
        }
+       tg.t.Fatalf("unexpected output checking staleness of package %v: %v", pkg, v)
+       panic("unreachable")
 }
 
 // wantStale fails with msg if pkg is not stale.
-func (tg *testgoData) wantStale(pkg, msg string) {
-       if !tg.isStale(pkg) {
+func (tg *testgoData) wantStale(pkg, reason, msg string) {
+       stale, why := tg.isStale(pkg)
+       if !stale {
                tg.t.Fatal(msg)
        }
+       if reason == "" && why != "" || !strings.Contains(why, reason) {
+               tg.t.Errorf("wrong reason for Stale=true: %q, want %q", why, reason)
+       }
 }
 
 // wantNotStale fails with msg if pkg is stale.
-func (tg *testgoData) wantNotStale(pkg, msg string) {
-       if tg.isStale(pkg) {
+func (tg *testgoData) wantNotStale(pkg, reason, msg string) {
+       stale, why := tg.isStale(pkg)
+       if stale {
                tg.t.Fatal(msg)
        }
+       if reason == "" && why != "" || !strings.Contains(why, reason) {
+               tg.t.Errorf("wrong reason for Stale=false: %q, want %q", why, reason)
+       }
 }
 
 // cleanup cleans up a test that runs testgo.
@@ -708,7 +719,7 @@ func TestNewReleaseRebuildsStalePackagesInGOPATH(t *testing.T) {
        tg.tempFile("d1/src/p1/p1.go", `package p1`)
        tg.setenv("GOPATH", tg.path("d1"))
        tg.run("install", "-a", "p1")
-       tg.wantNotStale("p1", "./testgo list claims p1 is stale, incorrectly")
+       tg.wantNotStale("p1", "", "./testgo list claims p1 is stale, incorrectly")
        tg.sleep()
 
        // Changing mtime and content of runtime/internal/sys/sys.go
@@ -717,28 +728,28 @@ func TestNewReleaseRebuildsStalePackagesInGOPATH(t *testing.T) {
        sys := runtime.GOROOT() + "/src/runtime/internal/sys/sys.go"
        restore := addNL(sys)
        defer restore()
-       tg.wantNotStale("p1", "./testgo list claims p1 is stale, incorrectly, after updating runtime/internal/sys/sys.go")
+       tg.wantNotStale("p1", "", "./testgo list claims p1 is stale, incorrectly, after updating runtime/internal/sys/sys.go")
        restore()
-       tg.wantNotStale("p1", "./testgo list claims p1 is stale, incorrectly, after restoring runtime/internal/sys/sys.go")
+       tg.wantNotStale("p1", "", "./testgo list claims p1 is stale, incorrectly, after restoring runtime/internal/sys/sys.go")
 
        // But changing runtime/internal/sys/zversion.go should have an effect:
        // that's how we tell when we flip from one release to another.
        zversion := runtime.GOROOT() + "/src/runtime/internal/sys/zversion.go"
        restore = addNL(zversion)
        defer restore()
-       tg.wantStale("p1", "./testgo list claims p1 is NOT stale, incorrectly, after changing to new release")
+       tg.wantStale("p1", "build ID mismatch", "./testgo list claims p1 is NOT stale, incorrectly, after changing to new release")
        restore()
-       tg.wantNotStale("p1", "./testgo list claims p1 is stale, incorrectly, after changing back to old release")
+       tg.wantNotStale("p1", "", "./testgo list claims p1 is stale, incorrectly, after changing back to old release")
        addNL(zversion)
-       tg.wantStale("p1", "./testgo list claims p1 is NOT stale, incorrectly, after changing again to new release")
+       tg.wantStale("p1", "build ID mismatch", "./testgo list claims p1 is NOT stale, incorrectly, after changing again to new release")
        tg.run("install", "p1")
-       tg.wantNotStale("p1", "./testgo list claims p1 is stale after building with new release")
+       tg.wantNotStale("p1", "", "./testgo list claims p1 is stale after building with new release")
 
        // Restore to "old" release.
        restore()
-       tg.wantStale("p1", "./testgo list claims p1 is NOT stale, incorrectly, after changing to old release after new build")
+       tg.wantStale("p1", "build ID mismatch", "./testgo list claims p1 is NOT stale, incorrectly, after changing to old release after new build")
        tg.run("install", "p1")
-       tg.wantNotStale("p1", "./testgo list claims p1 is stale after building with old release")
+       tg.wantNotStale("p1", "", "./testgo list claims p1 is stale after building with old release")
 
        // Everything is out of date. Rebuild to leave things in a better state.
        tg.run("install", "std")
@@ -821,8 +832,8 @@ func TestGoInstallRebuildsStalePackagesInOtherGOPATH(t *testing.T) {
        sep := string(filepath.ListSeparator)
        tg.setenv("GOPATH", tg.path("d1")+sep+tg.path("d2"))
        tg.run("install", "p1")
-       tg.wantNotStale("p1", "./testgo list claims p1 is stale, incorrectly")
-       tg.wantNotStale("p2", "./testgo list claims p2 is stale, incorrectly")
+       tg.wantNotStale("p1", "", "./testgo list claims p1 is stale, incorrectly")
+       tg.wantNotStale("p2", "", "./testgo list claims p2 is stale, incorrectly")
        tg.sleep()
        if f, err := os.OpenFile(tg.path("d2/src/p2/p2.go"), os.O_WRONLY|os.O_APPEND, 0); err != nil {
                t.Fatal(err)
@@ -831,12 +842,12 @@ func TestGoInstallRebuildsStalePackagesInOtherGOPATH(t *testing.T) {
        } else {
                tg.must(f.Close())
        }
-       tg.wantStale("p2", "./testgo list claims p2 is NOT stale, incorrectly")
-       tg.wantStale("p1", "./testgo list claims p1 is NOT stale, incorrectly")
+       tg.wantStale("p2", "newer source file", "./testgo list claims p2 is NOT stale, incorrectly")
+       tg.wantStale("p1", "stale dependency", "./testgo list claims p1 is NOT stale, incorrectly")
 
        tg.run("install", "p1")
-       tg.wantNotStale("p2", "./testgo list claims p2 is stale after reinstall, incorrectly")
-       tg.wantNotStale("p1", "./testgo list claims p1 is stale after reinstall, incorrectly")
+       tg.wantNotStale("p2", "", "./testgo list claims p2 is stale after reinstall, incorrectly")
+       tg.wantNotStale("p1", "", "./testgo list claims p1 is stale after reinstall, incorrectly")
 }
 
 func TestGoInstallDetectsRemovedFiles(t *testing.T) {
@@ -850,13 +861,13 @@ func TestGoInstallDetectsRemovedFiles(t *testing.T) {
                package mypkg`)
        tg.setenv("GOPATH", tg.path("."))
        tg.run("install", "mypkg")
-       tg.wantNotStale("mypkg", "./testgo list mypkg claims mypkg is stale, incorrectly")
+       tg.wantNotStale("mypkg", "", "./testgo list mypkg claims mypkg is stale, incorrectly")
        // z.go was not part of the build; removing it is okay.
        tg.must(os.Remove(tg.path("src/mypkg/z.go")))
-       tg.wantNotStale("mypkg", "./testgo list mypkg claims mypkg is stale after removing z.go; should not be stale")
+       tg.wantNotStale("mypkg", "", "./testgo list mypkg claims mypkg is stale after removing z.go; should not be stale")
        // y.go was part of the package; removing it should be detected.
        tg.must(os.Remove(tg.path("src/mypkg/y.go")))
-       tg.wantStale("mypkg", "./testgo list mypkg claims mypkg is NOT stale after removing y.go; should be stale")
+       tg.wantStale("mypkg", "build ID mismatch", "./testgo list mypkg claims mypkg is NOT stale after removing y.go; should be stale")
 }
 
 func TestWildcardMatchesSyntaxErrorDirs(t *testing.T) {
@@ -919,13 +930,13 @@ func TestGoInstallDetectsRemovedFilesInPackageMain(t *testing.T) {
                package main`)
        tg.setenv("GOPATH", tg.path("."))
        tg.run("install", "mycmd")
-       tg.wantNotStale("mycmd", "./testgo list mypkg claims mycmd is stale, incorrectly")
+       tg.wantNotStale("mycmd", "", "./testgo list mypkg claims mycmd is stale, incorrectly")
        // z.go was not part of the build; removing it is okay.
        tg.must(os.Remove(tg.path("src/mycmd/z.go")))
-       tg.wantNotStale("mycmd", "./testgo list mycmd claims mycmd is stale after removing z.go; should not be stale")
+       tg.wantNotStale("mycmd", "", "./testgo list mycmd claims mycmd is stale after removing z.go; should not be stale")
        // y.go was part of the package; removing it should be detected.
        tg.must(os.Remove(tg.path("src/mycmd/y.go")))
-       tg.wantStale("mycmd", "./testgo list mycmd claims mycmd is NOT stale after removing y.go; should be stale")
+       tg.wantStale("mycmd", "build ID mismatch", "./testgo list mycmd claims mycmd is NOT stale after removing y.go; should be stale")
 }
 
 func testLocalRun(tg *testgoData, exepath, local, match string) {
@@ -1317,7 +1328,7 @@ func TestPackageMainTestImportsArchiveNotBinary(t *testing.T) {
        tg.sleep()
        tg.run("test", "main_test")
        tg.run("install", "main_test")
-       tg.wantNotStale("main_test", "after go install, main listed as stale")
+       tg.wantNotStale("main_test", "", "after go install, main listed as stale")
        tg.run("test", "main_test")
 }
 
@@ -1327,9 +1338,9 @@ func TestPackageNotStaleWithTrailingSlash(t *testing.T) {
        defer tg.cleanup()
        goroot := runtime.GOROOT()
        tg.setenv("GOROOT", goroot+"/")
-       tg.wantNotStale("runtime", "with trailing slash in GOROOT, runtime listed as stale")
-       tg.wantNotStale("os", "with trailing slash in GOROOT, os listed as stale")
-       tg.wantNotStale("io", "with trailing slash in GOROOT, io listed as stale")
+       tg.wantNotStale("runtime", "", "with trailing slash in GOROOT, runtime listed as stale")
+       tg.wantNotStale("os", "", "with trailing slash in GOROOT, os listed as stale")
+       tg.wantNotStale("io", "", "with trailing slash in GOROOT, io listed as stale")
 }
 
 // With $GOBIN set, binaries get installed to $GOBIN.
index 9409f3715467b3134395f7a20869a995617d9279..49a63425bf598b5cc5150541544cf94b8a89be11 100644 (file)
@@ -41,6 +41,7 @@ syntax of package template.  The default output is equivalent to -f
         Goroot        bool   // is this package in the Go root?
         Standard      bool   // is this package part of the standard Go library?
         Stale         bool   // would 'go install' do anything for this package?
+        StaleReason   string // explanation for Stale==true
         Root          string // Go root or Go path dir containing this package
 
         // Source files
index ef7fd124a9a432b8d9d17f0ff071f78d351d79ee..00e0d7315363fc8be281b47ba7d0b1b70b1780bf 100644 (file)
@@ -39,6 +39,7 @@ type Package struct {
        Goroot        bool   `json:",omitempty"` // is this package found in the Go root?
        Standard      bool   `json:",omitempty"` // is this package part of the standard Go library?
        Stale         bool   `json:",omitempty"` // would 'go install' do anything for this package?
+       StaleReason   string `json:",omitempty"` // why is Stale true?
        Root          string `json:",omitempty"` // Go root or Go path dir containing this package
        ConflictDir   string `json:",omitempty"` // Dir is hidden by this other directory
 
@@ -1085,7 +1086,7 @@ func packageList(roots []*Package) []*Package {
 // at the named pkgs (command-line arguments).
 func computeStale(pkgs ...*Package) {
        for _, p := range packageList(pkgs) {
-               p.Stale = isStale(p)
+               p.Stale, p.StaleReason = isStale(p)
        }
 }
 
@@ -1356,14 +1357,15 @@ var isGoRelease = strings.HasPrefix(runtime.Version(), "go1")
 // standard library, even in release versions. This makes
 // 'go build -tags netgo' work, among other things.
 
-// isStale reports whether package p needs to be rebuilt.
-func isStale(p *Package) bool {
+// isStale reports whether package p needs to be rebuilt,
+// along with the reason why.
+func isStale(p *Package) (bool, string) {
        if p.Standard && (p.ImportPath == "unsafe" || buildContext.Compiler == "gccgo") {
                // fake, builtin package
-               return false
+               return false, "builtin package"
        }
        if p.Error != nil {
-               return true
+               return true, "errors loading package"
        }
 
        // A package without Go sources means we only found
@@ -1373,23 +1375,26 @@ func isStale(p *Package) bool {
        // only useful with the specific version of the toolchain that
        // created them.
        if len(p.gofiles) == 0 && !p.usesSwig() {
-               return false
+               return false, "no source files"
        }
 
        // If the -a flag is given, rebuild everything.
        if buildA {
-               return true
+               return true, "build -a flag in use"
        }
 
        // If there's no install target or it's already marked stale, we have to rebuild.
-       if p.target == "" || p.Stale {
-               return true
+       if p.target == "" {
+               return true, "no install target"
+       }
+       if p.Stale {
+               return true, p.StaleReason
        }
 
        // Package is stale if completely unbuilt.
        fi, err := os.Stat(p.target)
        if err != nil {
-               return true
+               return true, "cannot stat install target"
        }
 
        // Package is stale if the expected build ID differs from the
@@ -1402,13 +1407,13 @@ func isStale(p *Package) bool {
        // See issue 8290 and issue 10702.
        targetBuildID, err := readBuildID(p)
        if err == nil && targetBuildID != p.buildID {
-               return true
+               return true, "build ID mismatch"
        }
 
        // Package is stale if a dependency is.
        for _, p1 := range p.deps {
                if p1.Stale {
-                       return true
+                       return true, "stale dependency"
                }
        }
 
@@ -1431,7 +1436,7 @@ func isStale(p *Package) bool {
        // install is to run make.bash, which will remove the old package archives
        // before rebuilding.)
        if p.Standard && isGoRelease {
-               return false
+               return false, "standard package in Go release distribution"
        }
 
        // Time-based staleness.
@@ -1446,7 +1451,7 @@ func isStale(p *Package) bool {
        // Package is stale if a dependency is, or if a dependency is newer.
        for _, p1 := range p.deps {
                if p1.target != "" && olderThan(p1.target) {
-                       return true
+                       return true, "newer dependency"
                }
        }
 
@@ -1465,10 +1470,10 @@ func isStale(p *Package) bool {
        // taken care of above (at least when the installed Go is a released version).
        if p.Root != goroot {
                if olderThan(buildToolchain.compiler()) {
-                       return true
+                       return true, "newer compiler"
                }
                if p.build.IsCommand() && olderThan(buildToolchain.linker()) {
-                       return true
+                       return true, "newer linker"
                }
        }
 
@@ -1513,11 +1518,11 @@ func isStale(p *Package) bool {
        srcs := stringList(p.GoFiles, p.CFiles, p.CXXFiles, p.MFiles, p.HFiles, p.FFiles, p.SFiles, p.CgoFiles, p.SysoFiles, p.SwigFiles, p.SwigCXXFiles)
        for _, src := range srcs {
                if olderThan(filepath.Join(p.Dir, src)) {
-                       return true
+                       return true, "newer source file"
                }
        }
 
-       return false
+       return false, ""
 }
 
 // computeBuildID computes the build ID for p, leaving it in p.buildID.
index 0c87fca556d2166a7ce45de20126a91db8bb52a0..5c21de5d9ba558696ea3edffd20b738a6840f0e3 100644 (file)
@@ -512,7 +512,8 @@ func runTest(cmd *Command, args []string) {
                                continue
                        }
                        p.Stale = true // rebuild
-                       p.fake = true  // do not warn about rebuild
+                       p.StaleReason = "rebuild for coverage"
+                       p.fake = true // do not warn about rebuild
                        p.coverMode = testCoverMode
                        var coverFiles []string
                        coverFiles = append(coverFiles, p.GoFiles...)
@@ -749,6 +750,7 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,
                ptest.fake = true
                ptest.forceLibrary = true
                ptest.Stale = true
+               ptest.StaleReason = "rebuild for test"
                ptest.build = new(build.Package)
                *ptest.build = *p.build
                m := map[string][]token.Position{}
@@ -1027,6 +1029,7 @@ func recompileForTest(pmain, preal, ptest *Package, testDir string) {
                                p.target = ""
                                p.fake = true
                                p.Stale = true
+                               p.StaleReason = "depends on package being tested"
                        }
                }