]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/go: add go list -cgo and -export
authorRuss Cox <rsc@golang.org>
Thu, 19 Apr 2018 14:54:12 +0000 (10:54 -0400)
committerRuss Cox <rsc@golang.org>
Thu, 7 Jun 2018 14:25:31 +0000 (14:25 +0000)
We want package analysis tools to be able to ask cmd/go for
cgo-translated files and for the names of files holding export
type information, instead of reinventing that logic themselves.
Allow them to do so, with the new list -cgo and -export flags.

Change-Id: I860df530d8dcc130f1f328413381b5664cc81c3b
Reviewed-on: https://go-review.googlesource.com/108156
Run-TryBot: Russ Cox <rsc@golang.org>
Reviewed-by: Bryan C. Mills <bcmills@google.com>
src/cmd/go/alldocs.go
src/cmd/go/go_test.go
src/cmd/go/internal/list/list.go
src/cmd/go/internal/load/pkg.go
src/cmd/go/internal/work/action.go
src/cmd/go/internal/work/buildid.go
src/cmd/go/internal/work/exec.go

index 8cccbf4de00ea4a8ddb082db0b77cadfbc2f968d..e1cdbe3fa3c92bf984d80ed4406fa3e7feac4ab1 100644 (file)
 //
 // Usage:
 //
-//     go list [-deps] [-e] [-f format] [-json] [-test] [build flags] [packages]
+//     go list [-cgo] [-deps] [-e] [-f format] [-json] [-list] [-test] [build flags] [packages]
 //
 // List lists the packages named by the import paths, one per line.
 //
 //         Root          string // Go root or Go path dir containing this package
 //         ConflictDir   string // this directory shadows Dir in $GOPATH
 //         BinaryOnly    bool   // binary-only package: cannot be recompiled from sources
+//         ForTest       string // package is only for use in named test
+//         DepOnly       bool   // package is only a dependency, not explicitly listed
+//         Export        string // file containing export data (when using -export)
 //
 //         // Source files
 //         GoFiles        []string // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles)
 // The -json flag causes the package data to be printed in JSON format
 // instead of using the template format.
 //
+// The -cgo flag causes list to set CgoFiles not to the original *.go files
+// importing "C" but instead to the translated files generated by the cgo
+// command.
+//
 // The -deps flag causes list to iterate over not just the named packages
 // but also all their dependencies. It visits them in a depth-first post-order
 // traversal, so that a package is listed only after all its dependencies.
+// Packages not explicitly listed on the command line will have the DepOnly
+// field set to true.
 //
 // The -e flag changes the handling of erroneous packages, those that
 // cannot be found or are malformed. By default, the list command
 // a non-nil Error field; other information may or may not be missing
 // (zeroed).
 //
+// The -export flag causes list to set the package's Export field to
+// the name of a file containing up-to-date export information for
+// the given package.
+//
 // The -test flag causes list to report not only the named packages
 // but also their test binaries (for packages with tests), to convey to
 // source code analysis tools exactly how test binaries are constructed.
 // package itself). The reported import path of a package recompiled
 // for a particular test binary is followed by a space and the name of
 // the test binary in brackets, as in "math/rand [math/rand.test]"
-// or "regexp [sort.test]".
+// or "regexp [sort.test]". The ForTest field is also set to the name
+// of the package being tested ("math/rand" or "sort" in the previous
+// examples).
+//
+// The Dir, Target, Shlib, Root, ConflictDir, and Export file paths
+// are all absolute paths.
+//
+// By default, the lists GoFiles, CgoFiles, and so on hold names of files in Dir
+// (that is, paths relative to Dir, not absolute paths).
+// The extra entries added by the -cgo and -test flags are absolute paths
+// referring to cached copies of generated Go source files.
+// Although they are Go source files, the paths may not end in ".go".
 //
 // For more about build flags, see 'go help build'.
 //
index ba5bba6123deb1c1a9d4489e6786ef74662d945b..f1fbf6cb693e1e4f6906e8fc3a8aada2e1f0a228 100644 (file)
@@ -1977,6 +1977,55 @@ func TestGoListTest(t *testing.T) {
        tg.grepStdout(`^runtime/cgo$`, "missing runtime/cgo")
 }
 
+func TestGoListCgo(t *testing.T) {
+       tg := testgo(t)
+       defer tg.cleanup()
+       tg.parallel()
+       tg.makeTempdir()
+       tg.setenv("GOCACHE", tg.tempdir)
+
+       tg.run("list", "-f", `{{join .CgoFiles "\n"}}`, "net")
+       if tg.stdout.String() == "" {
+               t.Skip("net does not use cgo")
+       }
+       if strings.Contains(tg.stdout.String(), tg.tempdir) {
+               t.Fatalf(".CgoFiles without -cgo unexpectedly mentioned cache %s", tg.tempdir)
+       }
+       tg.run("list", "-cgo", "-f", `{{join .CgoFiles "\n"}}`, "net")
+       if !strings.Contains(tg.stdout.String(), tg.tempdir) {
+               t.Fatalf(".CgoFiles with -cgo did not mention cache %s", tg.tempdir)
+       }
+       for _, file := range strings.Split(tg.stdout.String(), "\n") {
+               if file == "" {
+                       continue
+               }
+               if _, err := os.Stat(file); err != nil {
+                       t.Fatalf("cannot find .CgoFiles result %s: %v", file, err)
+               }
+       }
+}
+
+func TestGoListExport(t *testing.T) {
+       tg := testgo(t)
+       defer tg.cleanup()
+       tg.parallel()
+       tg.makeTempdir()
+       tg.setenv("GOCACHE", tg.tempdir)
+
+       tg.run("list", "-f", "{{.Export}}", "strings")
+       if tg.stdout.String() != "" {
+               t.Fatalf(".Export without -export unexpectedly set")
+       }
+       tg.run("list", "-export", "-f", "{{.Export}}", "strings")
+       file := strings.TrimSpace(tg.stdout.String())
+       if file == "" {
+               t.Fatalf(".Export with -export was empty")
+       }
+       if _, err := os.Stat(file); err != nil {
+               t.Fatalf("cannot find .Export result %s: %v", file, err)
+       }
+}
+
 // Issue 4096. Validate the output of unsuccessful go install foo/quxx.
 func TestUnsuccessfulGoInstallShouldMentionMissingPackage(t *testing.T) {
        tg := testgo(t)
index 70bbf4bf3291b49ba30363c2404f7c0ade12a9d9..06a7cf126d078aee424ebc71d9cf2dc81f37fbfc 100644 (file)
@@ -23,7 +23,7 @@ import (
 )
 
 var CmdList = &base.Command{
-       UsageLine: "list [-deps] [-e] [-f format] [-json] [-test] [build flags] [packages]",
+       UsageLine: "list [-cgo] [-deps] [-e] [-f format] [-json] [-list] [-test] [build flags] [packages]",
        Short:     "list packages",
        Long: `
 List lists the packages named by the import paths, one per line.
@@ -54,6 +54,9 @@ syntax of package template. The default output is equivalent to -f
         Root          string // Go root or Go path dir containing this package
         ConflictDir   string // this directory shadows Dir in $GOPATH
         BinaryOnly    bool   // binary-only package: cannot be recompiled from sources
+        ForTest       string // package is only for use in named test
+        DepOnly       bool   // package is only a dependency, not explicitly listed
+        Export        string // file containing export data (when using -export)
 
         // Source files
         GoFiles        []string // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles)
@@ -128,9 +131,15 @@ for the go/build package's Context type.
 The -json flag causes the package data to be printed in JSON format
 instead of using the template format.
 
+The -cgo flag causes list to set CgoFiles not to the original *.go files
+importing "C" but instead to the translated files generated by the cgo
+command.
+
 The -deps flag causes list to iterate over not just the named packages
 but also all their dependencies. It visits them in a depth-first post-order
 traversal, so that a package is listed only after all its dependencies.
+Packages not explicitly listed on the command line will have the DepOnly
+field set to true.
 
 The -e flag changes the handling of erroneous packages, those that
 cannot be found or are malformed. By default, the list command
@@ -142,6 +151,10 @@ printing. Erroneous packages will have a non-empty ImportPath and
 a non-nil Error field; other information may or may not be missing
 (zeroed).
 
+The -export flag causes list to set the package's Export field to
+the name of a file containing up-to-date export information for
+the given package.
+
 The -test flag causes list to report not only the named packages
 but also their test binaries (for packages with tests), to convey to
 source code analysis tools exactly how test binaries are constructed.
@@ -152,7 +165,18 @@ dependencies specially for that test (most commonly the tested
 package itself). The reported import path of a package recompiled
 for a particular test binary is followed by a space and the name of
 the test binary in brackets, as in "math/rand [math/rand.test]"
-or "regexp [sort.test]".
+or "regexp [sort.test]". The ForTest field is also set to the name
+of the package being tested ("math/rand" or "sort" in the previous
+examples).
+
+The Dir, Target, Shlib, Root, ConflictDir, and Export file paths
+are all absolute paths.
+
+By default, the lists GoFiles, CgoFiles, and so on hold names of files in Dir
+(that is, paths relative to Dir, not absolute paths).
+The extra entries added by the -cgo and -test flags are absolute paths
+referring to cached copies of generated Go source files.
+Although they are Go source files, the paths may not end in ".go".
 
 For more about build flags, see 'go help build'.
 
@@ -165,8 +189,10 @@ func init() {
        work.AddBuildFlags(CmdList)
 }
 
+var listCgo = CmdList.Flag.Bool("cgo", false, "")
 var listDeps = CmdList.Flag.Bool("deps", false, "")
 var listE = CmdList.Flag.Bool("e", false, "")
+var listExport = CmdList.Flag.Bool("export", false, "")
 var listFmt = CmdList.Flag.String("f", "{{.ImportPath}}", "")
 var listJson = CmdList.Flag.Bool("json", false, "")
 var listTest = CmdList.Flag.Bool("test", false, "")
@@ -222,11 +248,22 @@ func runList(cmd *base.Command, args []string) {
                pkgs = load.Packages(args)
        }
 
-       if *listTest {
-               c := cache.Default()
-               if c == nil {
+       if cache.Default() == nil {
+               // These flags return file names pointing into the build cache,
+               // so the build cache must exist.
+               if *listCgo {
+                       base.Fatalf("go list -cgo requires build cache")
+               }
+               if *listExport {
+                       base.Fatalf("go list -export requires build cache")
+               }
+               if *listTest {
                        base.Fatalf("go list -test requires build cache")
                }
+       }
+
+       if *listTest {
+               c := cache.Default()
                // Add test binaries to packages to be listed.
                for _, p := range pkgs {
                        if p.Error != nil {
@@ -279,13 +316,14 @@ func runList(cmd *base.Command, args []string) {
                pkgs = load.PackageList(pkgs)
        }
 
-       // Estimate whether staleness information is needed,
-       // since it's a little bit of work to compute.
+       // Do we need to run a build to gather information?
        needStale := *listJson || strings.Contains(*listFmt, ".Stale")
-       if needStale {
+       if needStale || *listExport || *listCgo {
                var b work.Builder
                b.Init()
-               b.ComputeStaleOnly = true
+               b.IsCmdList = true
+               b.NeedExport = *listExport
+               b.NeedCgoFiles = *listCgo
                a := &work.Action{}
                // TODO: Use pkgsFilter?
                for _, p := range pkgs {
index 6ab8260daef7046713836091d0e56c8d3d2b6850..693d862214345301e1901dd66053819e129b9a0b 100644 (file)
@@ -51,6 +51,7 @@ type PackagePublic struct {
        BinaryOnly    bool   `json:",omitempty"` // package cannot be recompiled
        ForTest       string `json:",omitempty"` // package is only for use in named test
        DepOnly       bool   `json:",omitempty"` // package is only as a dependency, not explicitly listed
+       Export        string `json:",omitempty"` // file containing export data (set by go list -export)
 
        // Stale and StaleReason remain here *only* for the list command.
        // They are only initialized in preparation for list execution.
index c83fe4e58d282a688e20e25bd38437509a6aa5e0..3b5c4d65fd69322c6f2499d8fa7a14d01d4d12e8 100644 (file)
@@ -36,7 +36,10 @@ type Builder struct {
        flagCache   map[[2]string]bool   // a cache of supported compiler flags
        Print       func(args ...interface{}) (int, error)
 
-       ComputeStaleOnly bool // compute staleness for go list; no actual build
+       IsCmdList    bool // running as part of go list; set p.Stale and additional fields below
+       NeedError    bool // list needs p.Error
+       NeedExport   bool // list needs p.Export
+       NeedCgoFiles bool // list needs p.CgoFiles to cgo-generated files, not originals
 
        objdirSeq int // counter for NewObjdir
        pkgSeq    int
index 94a06ff68fabb7b1cdd9bd8bb075e524f68c5ace..f1c538c3093e9c4aafff566fd0c3bb3fde2395aa 100644 (file)
@@ -414,7 +414,7 @@ func (b *Builder) useCache(a *Action, p *load.Package, actionHash cache.ActionID
        // already up-to-date, then to avoid a rebuild, report the package
        // as up-to-date as well. See "Build IDs" comment above.
        // TODO(rsc): Rewrite this code to use a TryCache func on the link action.
-       if target != "" && !cfg.BuildA && a.Mode == "build" && len(a.triggers) == 1 && a.triggers[0].Mode == "link" {
+       if target != "" && !cfg.BuildA && !b.NeedExport && a.Mode == "build" && len(a.triggers) == 1 && a.triggers[0].Mode == "link" {
                buildID, err := buildid.ReadFile(target)
                if err == nil {
                        id := strings.Split(buildID, buildIDSeparator)
@@ -455,8 +455,8 @@ func (b *Builder) useCache(a *Action, p *load.Package, actionHash cache.ActionID
                return true
        }
 
-       if b.ComputeStaleOnly {
-               // Invoked during go list only to compute and record staleness.
+       if b.IsCmdList {
+               // Invoked during go list to compute and record staleness.
                if p := a.Package; p != nil && !p.Stale {
                        p.Stale = true
                        if cfg.BuildA {
@@ -521,10 +521,6 @@ func (b *Builder) useCache(a *Action, p *load.Package, actionHash cache.ActionID
                a.output = []byte{}
        }
 
-       if b.ComputeStaleOnly {
-               return true
-       }
-
        return false
 }
 
@@ -609,11 +605,17 @@ func (b *Builder) updateBuildID(a *Action, target string, rewrite bool) error {
                                panic("internal error: a.output not set")
                        }
                        outputID, _, err := c.Put(a.actionID, r)
+                       r.Close()
                        if err == nil && cfg.BuildX {
                                b.Showcmd("", "%s # internal", joinUnambiguously(str.StringList("cp", target, c.OutputFile(outputID))))
                        }
+                       if b.NeedExport {
+                               if err != nil {
+                                       return err
+                               }
+                               a.Package.Export = c.OutputFile(outputID)
+                       }
                        c.PutBytes(cache.Subkey(a.actionID, "stdout"), a.output)
-                       r.Close()
                }
        }
 
index 072e2904c14672af75a1b3c42e06cc78a6f342da..1b4d43c46200adba49e3729d1a362d98ad338d84 100644 (file)
@@ -54,7 +54,7 @@ func actionList(root *Action) []*Action {
 
 // do runs the action graph rooted at root.
 func (b *Builder) Do(root *Action) {
-       if c := cache.Default(); c != nil && !b.ComputeStaleOnly {
+       if c := cache.Default(); c != nil && !b.IsCmdList {
                // If we're doing real work, take time at the end to trim the cache.
                defer c.Trim()
        }
@@ -296,11 +296,11 @@ func (b *Builder) buildActionID(a *Action) cache.ActionID {
        return h.Sum()
 }
 
-// needCgoHeader reports whether the actions triggered by this one
+// needCgoHdr reports whether the actions triggered by this one
 // expect to be able to access the cgo-generated header file.
-func needCgoHeader(a *Action) bool {
+func (b *Builder) needCgoHdr(a *Action) bool {
        // If this build triggers a header install, run cgo to get the header.
-       if (a.Package.UsesCgo() || a.Package.UsesSwig()) && (cfg.BuildBuildmode == "c-archive" || cfg.BuildBuildmode == "c-shared") {
+       if !b.IsCmdList && (a.Package.UsesCgo() || a.Package.UsesSwig()) && (cfg.BuildBuildmode == "c-archive" || cfg.BuildBuildmode == "c-shared") {
                for _, t1 := range a.triggers {
                        if t1.Mode == "install header" {
                                return true
@@ -317,19 +317,54 @@ func needCgoHeader(a *Action) bool {
        return false
 }
 
+const (
+       needBuild uint32 = 1 << iota
+       needCgoHdr
+       needVet
+       needCgoFiles
+       needStale
+)
+
 // build is the action for building a single package.
 // Note that any new influence on this logic must be reported in b.buildActionID above as well.
 func (b *Builder) build(a *Action) (err error) {
        p := a.Package
+
+       bit := func(x uint32, b bool) uint32 {
+               if b {
+                       return x
+               }
+               return 0
+       }
+
        cached := false
-       needCgo := needCgoHeader(a)
+       need := bit(needBuild, !b.IsCmdList || b.NeedExport) |
+               bit(needCgoHdr, b.needCgoHdr(a)) |
+               bit(needVet, a.needVet) |
+               bit(needCgoFiles, b.NeedCgoFiles && (p.UsesCgo() || p.UsesSwig()))
+
+       // Save p.CgoFiles now, because we may modify it for go list.
+       cgofiles := append([]string{}, p.CgoFiles...)
 
        if !p.BinaryOnly {
                if b.useCache(a, p, b.buildActionID(a), p.Target) {
-                       if b.ComputeStaleOnly || !needCgo && !a.needVet {
-                               return nil
+                       // We found the main output in the cache.
+                       // If we don't need any other outputs, we can stop.
+                       need &^= needBuild
+                       if b.NeedExport {
+                               p.Export = a.built
                        }
+                       if need&needCgoFiles != 0 && b.loadCachedCgoFiles(a) {
+                               need &^= needCgoFiles
+                       }
+                       // Otherwise, we need to write files to a.Objdir (needVet, needCgoHdr).
+                       // Remember that we might have them in cache
+                       // and check again after we create a.Objdir.
                        cached = true
+                       a.output = []byte{} // start saving output in case we miss any cache results
+               }
+               if need == 0 {
+                       return nil
                }
                defer b.flushOutput(a)
        }
@@ -338,6 +373,9 @@ func (b *Builder) build(a *Action) (err error) {
                if err != nil && err != errPrintedOutput {
                        err = fmt.Errorf("go build %s: %v", a.Package.ImportPath, err)
                }
+               if err != nil && b.IsCmdList && b.NeedError && p.Error == nil {
+                       p.Error = &load.PackageError{Err: err.Error()}
+               }
        }()
        if cfg.BuildN {
                // In -n mode, print a banner between packages.
@@ -357,16 +395,16 @@ func (b *Builder) build(a *Action) (err error) {
                if err == nil {
                        a.built = a.Package.Target
                        a.Target = a.Package.Target
+                       if b.NeedExport {
+                               a.Package.Export = a.Package.Target
+                       }
                        a.buildID = b.fileHash(a.Package.Target)
                        a.Package.Stale = false
                        a.Package.StaleReason = "binary-only package"
                        return nil
                }
-               if b.ComputeStaleOnly {
-                       a.Package.Stale = true
-                       a.Package.StaleReason = "missing or invalid binary-only package"
-                       return nil
-               }
+               a.Package.Stale = true
+               a.Package.StaleReason = "missing or invalid binary-only package"
                return fmt.Errorf("missing or invalid binary-only package")
        }
 
@@ -375,8 +413,21 @@ func (b *Builder) build(a *Action) (err error) {
        }
        objdir := a.Objdir
 
-       if cached && (!needCgo || b.loadCachedCgo(a)) && (!a.needVet || b.loadCachedVet(a)) {
-               return nil
+       if cached {
+               if need&needCgoHdr != 0 && b.loadCachedCgoHdr(a) {
+                       need &^= needCgoHdr
+               }
+
+               // Load cached vet config, but only if that's all we have left
+               // (need == needVet, not testing just the one bit).
+               // If we are going to do a full build anyway,
+               // we're going to regenerate the files below anyway.
+               if need == needVet && b.loadCachedVet(a) {
+                       need &^= needVet
+               }
+               if need == 0 {
+                       return nil
+               }
        }
 
        // make target directory
@@ -387,9 +438,8 @@ func (b *Builder) build(a *Action) (err error) {
                }
        }
 
-       var gofiles, cgofiles, cfiles, sfiles, cxxfiles, objects, cgoObjects, pcCFLAGS, pcLDFLAGS []string
+       var gofiles, cfiles, sfiles, cxxfiles, objects, cgoObjects, pcCFLAGS, pcLDFLAGS []string
        gofiles = append(gofiles, a.Package.GoFiles...)
-       cgofiles = append(cgofiles, a.Package.CgoFiles...)
        cfiles = append(cfiles, a.Package.CFiles...)
        sfiles = append(sfiles, a.Package.SFiles...)
        cxxfiles = append(cxxfiles, a.Package.CXXFiles...)
@@ -500,19 +550,27 @@ func (b *Builder) build(a *Action) (err error) {
        }
        b.cacheGofiles(a, gofiles)
 
+       // Running cgo generated the cgo header.
+       need &^= needCgoHdr
+
        // Sanity check only, since Package.load already checked as well.
        if len(gofiles) == 0 {
                return &load.NoGoError{Package: a.Package}
        }
 
        // Prepare Go vet config if needed.
-       if a.needVet {
+       if need&needVet != 0 {
                buildVetConfig(a, gofiles)
+               need &^= needVet
        }
-       if cached {
-               // The cached package file is OK, so we don't need to run the compile.
-               // We've only gone this far in order to prepare the vet configuration
-               // or cgo header, and now we have.
+       if need&needCgoFiles != 0 {
+               if !b.loadCachedCgoFiles(a) {
+                       return fmt.Errorf("failed to cache translated CgoFiles")
+               }
+               need &^= needCgoFiles
+       }
+       if need == 0 {
+               // Nothing left to do.
                return nil
        }
 
@@ -656,17 +714,25 @@ func (b *Builder) cacheObjdirFile(a *Action, c *cache.Cache, name string) error
        return err
 }
 
-func (b *Builder) loadCachedObjdirFile(a *Action, c *cache.Cache, name string) error {
+func (b *Builder) findCachedObjdirFile(a *Action, c *cache.Cache, name string) (string, error) {
        entry, err := c.Get(cache.Subkey(a.actionID, name))
        if err != nil {
-               return err
+               return "", err
        }
        out := c.OutputFile(entry.OutputID)
        info, err := os.Stat(out)
        if err != nil || info.Size() != entry.Size {
-               return fmt.Errorf("not in cache")
+               return "", fmt.Errorf("not in cache")
+       }
+       return out, nil
+}
+
+func (b *Builder) loadCachedObjdirFile(a *Action, c *cache.Cache, name string) error {
+       cached, err := b.findCachedObjdirFile(a, c, name)
+       if err != nil {
+               return err
        }
-       return b.copyFile(a.Objdir+name, out, 0666, true)
+       return b.copyFile(a.Objdir+name, cached, 0666, true)
 }
 
 func (b *Builder) cacheCgoHdr(a *Action) {
@@ -677,7 +743,7 @@ func (b *Builder) cacheCgoHdr(a *Action) {
        b.cacheObjdirFile(a, c, "_cgo_install.h")
 }
 
-func (b *Builder) loadCachedCgo(a *Action) bool {
+func (b *Builder) loadCachedCgoHdr(a *Action) bool {
        c := cache.Default()
        if c == nil {
                return false
@@ -737,6 +803,33 @@ func (b *Builder) loadCachedVet(a *Action) bool {
        return true
 }
 
+func (b *Builder) loadCachedCgoFiles(a *Action) bool {
+       c := cache.Default()
+       if c == nil {
+               return false
+       }
+       list, _, err := c.GetBytes(cache.Subkey(a.actionID, "gofiles"))
+       if err != nil {
+               return false
+       }
+       var files []string
+       for _, name := range strings.Split(string(list), "\n") {
+               if name == "" { // end of list
+                       continue
+               }
+               if strings.HasPrefix(name, "./") {
+                       continue
+               }
+               file, err := b.findCachedObjdirFile(a, c, name)
+               if err != nil {
+                       return false
+               }
+               files = append(files, file)
+       }
+       a.Package.CgoFiles = files
+       return true
+}
+
 type vetConfig struct {
        Compiler    string
        Dir         string
@@ -939,7 +1032,7 @@ func (b *Builder) printLinkerConfig(h io.Writer, p *load.Package) {
 // link is the action for linking a single command.
 // Note that any new influence on this logic must be reported in b.linkActionID above as well.
 func (b *Builder) link(a *Action) (err error) {
-       if b.useCache(a, a.Package, b.linkActionID(a), a.Package.Target) {
+       if b.useCache(a, a.Package, b.linkActionID(a), a.Package.Target) || b.IsCmdList {
                return nil
        }
        defer b.flushOutput(a)
@@ -1172,7 +1265,7 @@ func (b *Builder) linkSharedActionID(a *Action) cache.ActionID {
 }
 
 func (b *Builder) linkShared(a *Action) (err error) {
-       if b.useCache(a, nil, b.linkSharedActionID(a), a.Target) {
+       if b.useCache(a, nil, b.linkSharedActionID(a), a.Target) || b.IsCmdList {
                return nil
        }
        defer b.flushOutput(a)
@@ -1236,13 +1329,17 @@ func BuildInstallFunc(b *Builder, a *Action) (err error) {
                // We want to hide that awful detail as much as possible, so don't
                // advertise it by touching the mtimes (usually the libraries are up
                // to date).
-               if !a.buggyInstall && !b.ComputeStaleOnly {
+               if !a.buggyInstall && !b.IsCmdList {
                        now := time.Now()
                        os.Chtimes(a.Target, now, now)
                }
                return nil
        }
-       if b.ComputeStaleOnly {
+
+       // If we're building for go list -export,
+       // never install anything; just keep the cache reference.
+       if b.IsCmdList {
+               a.built = a1.built
                return nil
        }