diff --git a/src/cmd/go/alldocs.go b/src/cmd/go/alldocs.go index 5d7dea81fb..242e00fbe8 100644 --- a/src/cmd/go/alldocs.go +++ b/src/cmd/go/alldocs.go @@ -1394,6 +1394,10 @@ // GOCACHE // The directory where the go command will store cached // information for reuse in future builds. +// GOFLAGS +// A space-separated list of -flag=value settings to apply +// to go commands by default (when the given flag is known by +// the current command). // GOOS // The operating system for which to compile code. // Examples are linux, darwin, windows, netbsd. diff --git a/src/cmd/go/internal/base/goflags.go b/src/cmd/go/internal/base/goflags.go new file mode 100644 index 0000000000..2f50b50bfc --- /dev/null +++ b/src/cmd/go/internal/base/goflags.go @@ -0,0 +1,152 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package base + +import ( + "flag" + "fmt" + "os" + "runtime" + "strings" + + "cmd/go/internal/cfg" +) + +var ( + goflags []string // cached $GOFLAGS list; can be -x or --x form + knownFlag = make(map[string]bool) // flags allowed to appear in $GOFLAGS; no leading dashes +) + +// AddKnownFlag adds name to the list of known flags for use in $GOFLAGS. +func AddKnownFlag(name string) { + knownFlag[name] = true +} + +// GOFLAGS returns the flags from $GOFLAGS. +// The list can be assumed to contain one string per flag, +// with each string either beginning with -name or --name. +func GOFLAGS() []string { + InitGOFLAGS() + return goflags +} + +// InitGOFLAGS initializes the goflags list from $GOFLAGS. +// If goflags is already initialized, it does nothing. +func InitGOFLAGS() { + if goflags != nil { // already initialized + return + } + + // Build list of all flags for all commands. + // If no command has that flag, then we report the problem. + // This catches typos while still letting users record flags in GOFLAGS + // that only apply to a subset of go commands. + // Commands using CustomFlags can report their flag names + // by calling AddKnownFlag instead. + var walkFlags func(*Command) + walkFlags = func(cmd *Command) { + for _, sub := range cmd.Commands { + walkFlags(sub) + } + cmd.Flag.VisitAll(func(f *flag.Flag) { + knownFlag[f.Name] = true + }) + } + walkFlags(Go) + + // Ignore bad flag in go env and go bug, because + // they are what people reach for when debugging + // a problem, and maybe they're debugging GOFLAGS. + // (Both will show the GOFLAGS setting if let succeed.) + hideErrors := cfg.CmdName == "env" || cfg.CmdName == "bug" + + goflags = strings.Fields(os.Getenv("GOFLAGS")) + if goflags == nil { + goflags = []string{} // avoid work on later InitGOFLAGS call + } + + // Each of the words returned by strings.Fields must be its own flag. + // To set flag arguments use -x=value instead of -x value. + // For boolean flags, -x is fine instead of -x=true. + for _, f := range goflags { + // Check that every flag looks like -x --x -x=value or --x=value. + if !strings.HasPrefix(f, "-") || f == "-" || f == "--" || strings.HasPrefix(f, "---") || strings.HasPrefix(f, "-=") || strings.HasPrefix(f, "--=") { + if hideErrors { + continue + } + Fatalf("go: parsing $GOFLAGS: non-flag %q", f) + } + + name := f[1:] + if name[0] == '-' { + name = name[1:] + } + if i := strings.Index(name, "="); i >= 0 { + name = name[:i] + } + if !knownFlag[name] { + if hideErrors { + continue + } + Fatalf("go: parsing $GOFLAGS: unknown flag -%s", name) + } + } +} + +// boolFlag is the optional interface for flag.Value known to the flag package. +// (It is not clear why package flag does not export this interface.) +type boolFlag interface { + flag.Value + IsBoolFlag() bool +} + +// SetFromGOFLAGS sets the flags in the given flag set using settings in $GOFLAGS. +func SetFromGOFLAGS(flags flag.FlagSet) { + InitGOFLAGS() + + // This loop is similar to flag.Parse except that it ignores + // unknown flags found in goflags, so that setting, say, GOFLAGS=-ldflags=-w + // does not break commands that don't have a -ldflags. + // It also adjusts the output to be clear that the reported problem is from $GOFLAGS. + where := "$GOFLAGS" + if runtime.GOOS == "windows" { + where = "%GOFLAGS%" + } + for _, goflag := range goflags { + name, value, hasValue := goflag, "", false + if i := strings.Index(goflag, "="); i >= 0 { + name, value, hasValue = goflag[:i], goflag[i+1:], true + } + if strings.HasPrefix(name, "--") { + name = name[1:] + } + f := flags.Lookup(name[1:]) + if f == nil { + continue + } + if fb, ok := f.Value.(boolFlag); ok && fb.IsBoolFlag() { + if hasValue { + if err := fb.Set(value); err != nil { + fmt.Fprintf(flags.Output(), "go: invalid boolean value %q for flag %s (from %s): %v\n", value, name, where, err) + flags.Usage() + } + } else { + if err := fb.Set("true"); err != nil { + fmt.Fprintf(flags.Output(), "go: invalid boolean flag %s (from %s): %v\n", name, where, err) + flags.Usage() + } + } + } else { + if !hasValue { + fmt.Fprintf(flags.Output(), "go: flag needs an argument: %s (from %s)\n", name, where) + flags.Usage() + } + if err := f.Value.Set(value); err != nil { + fmt.Fprintf(flags.Output(), "go: invalid value %q for flag %s (from %s): %v\n", value, name, where, err) + flags.Usage() + } + } + } +} diff --git a/src/cmd/go/internal/cmdflag/flag.go b/src/cmd/go/internal/cmdflag/flag.go index 7ab3022127..b2a67e6f74 100644 --- a/src/cmd/go/internal/cmdflag/flag.go +++ b/src/cmd/go/internal/cmdflag/flag.go @@ -69,6 +69,14 @@ func SyntaxError(cmd, msg string) { os.Exit(2) } +// AddKnownFlags registers the flags in defns with base.AddKnownFlag. +func AddKnownFlags(cmd string, defns []*Defn) { + for _, f := range defns { + base.AddKnownFlag(f.Name) + base.AddKnownFlag(cmd + "." + f.Name) + } +} + // Parse sees if argument i is present in the definitions and if so, // returns its definition, value, and whether it consumed an extra word. // If the flag begins (cmd+".") it is ignored for the purpose of this function. @@ -121,3 +129,31 @@ func Parse(cmd string, defns []*Defn, args []string, i int) (f *Defn, value stri f = nil return } + +// FindGOFLAGS extracts and returns the flags matching defns from GOFLAGS. +// Ideally the caller would mention that the flags were from GOFLAGS +// when reporting errors, but that's too hard for now. +func FindGOFLAGS(defns []*Defn) []string { + var flags []string + for _, flag := range base.GOFLAGS() { + // Flags returned by base.GOFLAGS are well-formed, one of: + // -x + // --x + // -x=value + // --x=value + if strings.HasPrefix(flag, "--") { + flag = flag[1:] + } + name := flag[1:] + if i := strings.Index(name, "="); i >= 0 { + name = name[:i] + } + for _, f := range defns { + if name == f.Name { + flags = append(flags, flag) + break + } + } + } + return flags +} diff --git a/src/cmd/go/internal/envcmd/env.go b/src/cmd/go/internal/envcmd/env.go index 1f458483b1..afadbade38 100644 --- a/src/cmd/go/internal/envcmd/env.go +++ b/src/cmd/go/internal/envcmd/env.go @@ -54,6 +54,7 @@ func MkEnv() []cfg.EnvVar { {Name: "GOBIN", Value: cfg.GOBIN}, {Name: "GOCACHE", Value: cache.DefaultDir()}, {Name: "GOEXE", Value: cfg.ExeSuffix}, + {Name: "GOFLAGS", Value: os.Getenv("GOFLAGS")}, {Name: "GOHOSTARCH", Value: runtime.GOARCH}, {Name: "GOHOSTOS", Value: runtime.GOOS}, {Name: "GOOS", Value: cfg.Goos}, diff --git a/src/cmd/go/internal/help/helpdoc.go b/src/cmd/go/internal/help/helpdoc.go index b5fab2f21e..179812083a 100644 --- a/src/cmd/go/internal/help/helpdoc.go +++ b/src/cmd/go/internal/help/helpdoc.go @@ -486,6 +486,11 @@ General-purpose environment variables: GOCACHE The directory where the go command will store cached information for reuse in future builds. + GOFLAGS + A space-separated list of -flag=value settings to apply + to go commands by default, when the given flag is known by + the current command. Flags listed on the command-line + are applied after this list and therefore override it. GOOS The operating system for which to compile code. Examples are linux, darwin, windows, netbsd. diff --git a/src/cmd/go/internal/test/testflag.go b/src/cmd/go/internal/test/testflag.go index 8a686b7125..73f8c69d9e 100644 --- a/src/cmd/go/internal/test/testflag.go +++ b/src/cmd/go/internal/test/testflag.go @@ -63,6 +63,7 @@ var testFlagDefn = []*cmdflag.Defn{ // add build flags to testFlagDefn func init() { + cmdflag.AddKnownFlags("test", testFlagDefn) var cmd base.Command work.AddBuildFlags(&cmd) cmd.Flag.VisitAll(func(f *flag.Flag) { @@ -87,6 +88,7 @@ func init() { // go test fmt -custom-flag-for-fmt-test // go test -x math func testFlags(args []string) (packageNames, passToTest []string) { + args = str.StringList(cmdflag.FindGOFLAGS(testFlagDefn), args) inPkg := false var explicitArgs []string for i := 0; i < len(args); i++ { diff --git a/src/cmd/go/internal/vet/vetflag.go b/src/cmd/go/internal/vet/vetflag.go index bdfe033018..6cf2a8ca67 100644 --- a/src/cmd/go/internal/vet/vetflag.go +++ b/src/cmd/go/internal/vet/vetflag.go @@ -12,6 +12,7 @@ import ( "cmd/go/internal/base" "cmd/go/internal/cmdflag" + "cmd/go/internal/str" "cmd/go/internal/work" ) @@ -59,6 +60,7 @@ var vetTool string // add build flags to vetFlagDefn. func init() { + cmdflag.AddKnownFlags("vet", 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 @@ -73,6 +75,7 @@ func init() { // 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) { + args = str.StringList(cmdflag.FindGOFLAGS(vetFlagDefn), args) for i := 0; i < len(args); i++ { if !strings.HasPrefix(args[i], "-") { return args[:i], args[i:] diff --git a/src/cmd/go/main.go b/src/cmd/go/main.go index 0743b996a7..25dfe8ffa4 100644 --- a/src/cmd/go/main.go +++ b/src/cmd/go/main.go @@ -209,6 +209,7 @@ BigCmdLoop: if cmd.CustomFlags { args = args[1:] } else { + base.SetFromGOFLAGS(cmd.Flag) cmd.Flag.Parse(args[1:]) args = cmd.Flag.Args() } diff --git a/src/cmd/go/testdata/script/goflags.txt b/src/cmd/go/testdata/script/goflags.txt new file mode 100644 index 0000000000..20de325ac2 --- /dev/null +++ b/src/cmd/go/testdata/script/goflags.txt @@ -0,0 +1,49 @@ +# GOFLAGS sets flags for commands + +env GOFLAGS='-e -f={{.Dir}} --test.benchtime=1s -count=10' +go list asdfasdfasdf # succeeds because of -e +go list runtime +stdout '[\\/]runtime$' + +env GOFLAGS=-race OLDGOARCH=$GOARCH OLDGOOS=$GOOS GOARCH=386 GOOS=linux +! go list runtime +stderr 'race is only supported on' + +env GOARCH=$OLDGOARCH GOOS=$OLDGOOS + +# go env succeeds even though -f={{.Dir}} is inappropriate +go env + +# bad flags are diagnosed +env GOFLAGS=-typoflag +! go list runtime +stderr 'unknown flag -typoflag' + +env GOFLAGS=- +! go list runtime +stderr '^go: parsing \$GOFLAGS: non-flag "-"' + +env GOFLAGS=-- +! go list runtime +stderr '^go: parsing \$GOFLAGS: non-flag "--"' + +env GOFLAGS=---oops +! go list runtime +stderr '^go: parsing \$GOFLAGS: non-flag "---oops"' + +env GOFLAGS=-=noname +! go list runtime +stderr '^go: parsing \$GOFLAGS: non-flag "-=noname"' + +env GOFLAGS=-f +! go list runtime +stderr '^go: flag needs an argument: -f \(from (\$GOFLAGS|%GOFLAGS%)\)$' + +env GOFLAGS=-e=asdf +! go list runtime +stderr '^go: invalid boolean value \"asdf\" for flag -e \(from (\$GOFLAGS|%GOFLAGS%)\)' + +# except in go bug (untested) and go env +go env +stdout GOFLAGS + diff --git a/src/make.bash b/src/make.bash index a28b82a058..78882d9834 100755 --- a/src/make.bash +++ b/src/make.bash @@ -63,6 +63,7 @@ set -e unset GOBIN # Issue 14340 +unset GOFLAGS if [ ! -f run.bash ]; then echo 'make.bash must be run from $GOROOT/src' 1>&2 diff --git a/src/make.bat b/src/make.bat index 9df49cd50f..2e718334a2 100644 --- a/src/make.bat +++ b/src/make.bat @@ -47,6 +47,7 @@ setlocal :nolocal set GOBUILDFAIL=0 +set GOFLAGS= if exist make.bat goto ok echo Must run make.bat from Go src directory. diff --git a/src/make.rc b/src/make.rc index 7ae6221b38..a97dfc8a01 100755 --- a/src/make.rc +++ b/src/make.rc @@ -47,7 +47,7 @@ if(~ $1 -v) { shift } - +GOFLAGS=() GOROOT = `{cd .. && pwd} if(! ~ $#GOROOT_BOOTSTRAP 1) GOROOT_BOOTSTRAP = $home/go1.4 diff --git a/src/run.bash b/src/run.bash index 5679f99557..c14f4a206d 100755 --- a/src/run.bash +++ b/src/run.bash @@ -20,6 +20,7 @@ export GOPATH unset CDPATH # in case user has it set unset GOBIN # Issue 14340 +unset GOFLAGS export GOHOSTOS export CC diff --git a/src/run.bat b/src/run.bat index 6e42922a86..0e0c413617 100644 --- a/src/run.bat +++ b/src/run.bat @@ -17,6 +17,7 @@ set GOBUILDFAIL=0 set GOPATH= :: Issue 14340: ignore GOBIN during all.bat. set GOBIN= +set GOFLAGS= rem TODO avoid rebuild if possible diff --git a/src/run.rc b/src/run.rc index 88d77912e3..49d6fd9a4d 100755 --- a/src/run.rc +++ b/src/run.rc @@ -10,5 +10,6 @@ eval `{go env} GOPATH = () # we disallow local import for non-local packages, if $GOROOT happens # to be under $GOPATH, then some tests below will fail GOBIN = () # Issue 14340 +GOFLAGS = () exec go tool dist test -rebuild $*