diff --git a/src/cmd/go/internal/vet/vetflag.go b/src/cmd/go/internal/vet/vetflag.go index 50eac425ec3693331e1d93cf933f7448296e7c4e..cfa4352cb99e13ea373358e9c7dbcdcdd7135889 100644 --- a/src/cmd/go/internal/vet/vetflag.go +++ b/src/cmd/go/internal/vet/vetflag.go @@ -5,9 +5,14 @@ package vet import ( + "bytes" + "encoding/json" "flag" "fmt" + "log" "os" + "os/exec" + "path/filepath" "strings" "cmd/go/internal/base" @@ -16,72 +21,94 @@ import ( "cmd/go/internal/work" ) -const cmd = "vet" +// go vet flag processing +// +// We query the flags of the tool specified by GOVETTOOL (default: +// cmd/vet) and accept any of those flags plus any flag valid for 'go +// build'. The tool must support -flags, which prints a description of +// its flags in JSON to stdout. -// vetFlagDefn is the set of flags we process. -var vetFlagDefn = []*cmdflag.Defn{ - // Note: Some flags, in particular -tags and -v, are known to - // vet but also defined as build flags. This works fine, so we - // don't define them here but use AddBuildFlags to init them. - // However some, like -x, are known to the build but not - // to vet. We handle them in vetFlags. +// GOVETTOOL specifies the vet command to run. +// This must be an environment variable because +// we need it before flag processing, as we execute +// $GOVETTOOL to discover the set of flags it supports. +// +// Using an environment variable also makes it easy for users to opt in +// to (and later, opt out of) the new cmd/vet analysis driver during the +// transition. It is also used by tests. +var vetTool = os.Getenv("GOVETTOOL") - // local. - {Name: "all", BoolVar: new(bool), PassToTest: true}, - {Name: "asmdecl", BoolVar: new(bool), PassToTest: true}, - {Name: "assign", BoolVar: new(bool), PassToTest: true}, - {Name: "atomic", BoolVar: new(bool), PassToTest: true}, - {Name: "bool", BoolVar: new(bool), PassToTest: true}, - {Name: "buildtags", BoolVar: new(bool), PassToTest: true}, - {Name: "cgocall", BoolVar: new(bool), PassToTest: true}, - {Name: "composites", BoolVar: new(bool), PassToTest: true}, - {Name: "copylocks", BoolVar: new(bool), PassToTest: true}, - {Name: "httpresponse", BoolVar: new(bool), PassToTest: true}, - {Name: "lostcancel", BoolVar: new(bool), PassToTest: true}, - {Name: "methods", BoolVar: new(bool), PassToTest: true}, - {Name: "nilfunc", BoolVar: new(bool), PassToTest: true}, - {Name: "printf", BoolVar: new(bool), PassToTest: true}, - {Name: "printfuncs", PassToTest: true}, - {Name: "rangeloops", BoolVar: new(bool), PassToTest: true}, - {Name: "shadow", BoolVar: new(bool), PassToTest: true}, - {Name: "shadowstrict", BoolVar: new(bool), PassToTest: true}, - {Name: "shift", BoolVar: new(bool), PassToTest: true}, - {Name: "source", BoolVar: new(bool), PassToTest: true}, - {Name: "structtags", BoolVar: new(bool), PassToTest: true}, - {Name: "tests", BoolVar: new(bool), PassToTest: true}, - {Name: "unreachable", BoolVar: new(bool), PassToTest: true}, - {Name: "unsafeptr", BoolVar: new(bool), PassToTest: true}, - {Name: "unusedfuncs", PassToTest: true}, - {Name: "unusedresult", BoolVar: new(bool), PassToTest: true}, - {Name: "unusedstringmethods", PassToTest: true}, -} +// vetFlags processes the command line, splitting it at the first non-flag +// into the list of flags and list of packages. +func vetFlags(args []string) (passToVet, packageNames []string) { + // Query the vet command for its flags. + tool := vetTool + if tool != "" { + var err error + tool, err = filepath.Abs(tool) + if err != nil { + log.Fatal(err) + } + } else { + tool = base.Tool("vet") + } + out := new(bytes.Buffer) + vetcmd := exec.Command(tool, "-flags") + vetcmd.Stdout = out + if err := vetcmd.Run(); err != nil { + fmt.Fprintf(os.Stderr, "go vet: can't execute %s -flags: %v\n", tool, err) + os.Exit(2) + } + var analysisFlags []struct { + Name string + Bool bool + Usage string + } + if err := json.Unmarshal(out.Bytes(), &analysisFlags); err != nil { + fmt.Fprintf(os.Stderr, "go vet: can't unmarshal JSON from %s -flags: %v", tool, err) + os.Exit(2) + } -var vetTool string + // Add vet's flags to vetflagDefn. + // + // Some flags, in particular -tags and -v, are known to vet but + // also defined as build flags. This works fine, so we don't + // define them here but use AddBuildFlags to init them. + // However some, like -x, are known to the build but not to vet. + var vetFlagDefn []*cmdflag.Defn + for _, f := range analysisFlags { + switch f.Name { + case "tags", "v": + continue + } + defn := &cmdflag.Defn{ + Name: f.Name, + PassToTest: true, + } + if f.Bool { + defn.BoolVar = new(bool) + } + vetFlagDefn = append(vetFlagDefn, defn) + } -// add build flags to vetFlagDefn. -func init() { - cmdflag.AddKnownFlags("vet", vetFlagDefn) + // Add build flags to vetFlagDefn. var cmd base.Command work.AddBuildFlags(&cmd) - cmd.Flag.StringVar(&vetTool, "vettool", "", "path to vet tool binary") // for cmd/vet tests; undocumented for now cmd.Flag.VisitAll(func(f *flag.Flag) { vetFlagDefn = append(vetFlagDefn, &cmdflag.Defn{ Name: f.Name, Value: f.Value, }) }) -} -// vetFlags processes the command line, splitting it at the first non-flag -// into the list of flags and list of packages. -func vetFlags(args []string) (passToVet, packageNames []string) { + // Process args. args = str.StringList(cmdflag.FindGOFLAGS(vetFlagDefn), args) for i := 0; i < len(args); i++ { if !strings.HasPrefix(args[i], "-") { return args[:i], args[i:] } - f, value, extraWord := cmdflag.Parse(cmd, vetFlagDefn, args, i) + f, value, extraWord := cmdflag.Parse("vet", vetFlagDefn, args, i) if f == nil { fmt.Fprintf(os.Stderr, "vet: flag %q not defined\n", args[i]) fmt.Fprintf(os.Stderr, "Run \"go help vet\" for more information\n") diff --git a/src/cmd/go/internal/work/buildid.go b/src/cmd/go/internal/work/buildid.go index 8b97e8b75b88535b2d51fe8e77e6b0cd01016307..af3183ae9a061fc656c9225248309404e61badc5 100644 --- a/src/cmd/go/internal/work/buildid.go +++ b/src/cmd/go/internal/work/buildid.go @@ -178,7 +178,7 @@ func (b *Builder) toolID(name string) string { path := base.Tool(name) desc := "go tool " + name - // Special case: undocumented -vettool overrides usual vet, for testing vet. + // Special case: undocumented $GOVETTOOL overrides usual vet, for testing vet. if name == "vet" && VetTool != "" { path = VetTool desc = VetTool diff --git a/src/cmd/vet/main.go b/src/cmd/vet/main.go index 6e885121c8cd581f1850c852725f557d230d193a..cf91e4d59679fe5b4eb20f4d3b15470be4f57dc0 100644 --- a/src/cmd/vet/main.go +++ b/src/cmd/vet/main.go @@ -22,6 +22,7 @@ import ( "go/types" "io" "io/ioutil" + "log" "os" "path/filepath" "sort" @@ -31,10 +32,9 @@ import ( "cmd/internal/objabi" ) -// Important! If you add flags here, make sure to update cmd/go/internal/vet/vetflag.go. - var ( verbose = flag.Bool("v", false, "verbose") + flags = flag.Bool("flags", false, "print flags in JSON") source = flag.Bool("source", false, "import from source instead of compiled object files") tags = flag.String("tags", "", "space-separated list of build tags to apply when parsing") tagList = []string{} // exploded version of tags flag; set in main @@ -259,6 +259,32 @@ func main() { flag.Usage = Usage flag.Parse() + // -flags: print flags as JSON. Used by go vet. + if *flags { + type jsonFlag struct { + Name string + Bool bool + Usage string + } + var jsonFlags []jsonFlag + flag.VisitAll(func(f *flag.Flag) { + isBool := false + switch v := f.Value.(type) { + case interface{ BoolFlag() bool }: + isBool = v.BoolFlag() + case *triState: + isBool = true // go vet should treat it as boolean + } + jsonFlags = append(jsonFlags, jsonFlag{f.Name, isBool, f.Usage}) + }) + data, err := json.MarshalIndent(jsonFlags, "", "\t") + if err != nil { + log.Fatal(err) + } + os.Stdout.Write(data) + os.Exit(0) + } + // If any flag is set, we run only those checks requested. // If all flag is set true or if no flags are set true, set all the non-experimental ones // not explicitly set (in effect, set the "-all" flag). diff --git a/src/cmd/vet/vet_test.go b/src/cmd/vet/vet_test.go index 6b2125924d7a8ae34074f0379fa9d88bf4e03adf..da5a6ed87c7034440a0c81359941f0578c228271 100644 --- a/src/cmd/vet/vet_test.go +++ b/src/cmd/vet/vet_test.go @@ -118,11 +118,12 @@ func TestVetPrint(t *testing.T) { Build(t) file := filepath.Join("testdata", "print.go") cmd := exec.Command( - "go", "vet", "-vettool="+binary, + "go", "vet", "-printf", "-printfuncs=Warn:1,Warnf:1", file, ) + cmd.Env = append(os.Environ(), "GOVETTOOL="+binary) errchk(cmd, []string{file}, t) }