Commit 398b54df authored by Alan Donovan's avatar Alan Donovan

cmd/go: make go vet query cmd/vet for its flags

Add -flags flag to cmd/vet that causes it to describe its flags as JSON.

go vet's "-vettool" flag has been replaced with an environment
variable, GOVETTOOL, for two reasons:

  1) we need its value before flag processing,
     because we must run vet to discover its flags.

  2) users may change the env var to opt in/out of the new vet tool
     during the upcoming transition to vet based on the analysis API.

Change-Id: I5d8f90817623022f4170b88fab3c92c9b2fbdc37
Reviewed-on: https://go-review.googlesource.com/c/142617
Run-TryBot: Alan Donovan <adonovan@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: 's avatarBryan C. Mills <bcmills@google.com>
parent 4bea6c65
......@@ -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")
......
......@@ -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
......
......@@ -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).
......
......@@ -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)
}
......
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