Commit baf399b0 authored by Russ Cox's avatar Russ Cox

cmd/go: add go list -cgo and -export

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: 's avatarBryan C. Mills <bcmills@google.com>
parent 5f2b4f26
......@@ -579,7 +579,7 @@
//
// 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.
//
......@@ -609,6 +609,9 @@
// 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)
......@@ -683,9 +686,15 @@
// 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
......@@ -697,6 +706,10 @@
// 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.
......@@ -707,7 +720,18 @@
// 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'.
//
......
......@@ -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)
......
......@@ -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 {
......
......@@ -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.
......
......@@ -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
......
......@@ -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()
}
}
......
......@@ -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
}
......
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