Commit c1a4fc3b authored by Russ Cox's avatar Russ Cox

cmd/go: add $GOFLAGS environment variable

People sometimes want to turn on a particular go command flag by default.
In Go 1.11 we have at least two different cases where users may need this.

1. Linking can be noticeably slower on underpowered systems
due to DWARF, and users may want to set -ldflags=-w by default.

2. For modules, some users or CI systems will want vendoring always,
so they want -getmode=vendor (soon to be -mod=vendor) by default.

This CL generalizes the problem to “set default flags for the go command.”

$GOFLAGS can be a space-separated list of flag settings, but each
space-separated entry in the list must be a standalone flag.
That is, you must do 'GOFLAGS=-ldflags=-w' not 'GOFLAGS=-ldflags -w'.
The latter would mean to pass -w to go commands that understand it
(if any do; if not, it's an error to mention it).

For #26074.
For #26318.
Fixes #26585.

Change-Id: I428f79c1fbfb9e41e54d199c68746405aed2319c
Reviewed-on: https://go-review.googlesource.com/126656
Run-TryBot: Russ Cox <rsc@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: 's avatarRob Pike <r@golang.org>
parent 53859e57
...@@ -1394,6 +1394,10 @@ ...@@ -1394,6 +1394,10 @@
// GOCACHE // GOCACHE
// The directory where the go command will store cached // The directory where the go command will store cached
// information for reuse in future builds. // 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 // GOOS
// The operating system for which to compile code. // The operating system for which to compile code.
// Examples are linux, darwin, windows, netbsd. // Examples are linux, darwin, windows, netbsd.
......
// 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()
}
}
}
}
...@@ -69,6 +69,14 @@ func SyntaxError(cmd, msg string) { ...@@ -69,6 +69,14 @@ func SyntaxError(cmd, msg string) {
os.Exit(2) 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, // Parse sees if argument i is present in the definitions and if so,
// returns its definition, value, and whether it consumed an extra word. // 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. // 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 ...@@ -121,3 +129,31 @@ func Parse(cmd string, defns []*Defn, args []string, i int) (f *Defn, value stri
f = nil f = nil
return 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
}
...@@ -54,6 +54,7 @@ func MkEnv() []cfg.EnvVar { ...@@ -54,6 +54,7 @@ func MkEnv() []cfg.EnvVar {
{Name: "GOBIN", Value: cfg.GOBIN}, {Name: "GOBIN", Value: cfg.GOBIN},
{Name: "GOCACHE", Value: cache.DefaultDir()}, {Name: "GOCACHE", Value: cache.DefaultDir()},
{Name: "GOEXE", Value: cfg.ExeSuffix}, {Name: "GOEXE", Value: cfg.ExeSuffix},
{Name: "GOFLAGS", Value: os.Getenv("GOFLAGS")},
{Name: "GOHOSTARCH", Value: runtime.GOARCH}, {Name: "GOHOSTARCH", Value: runtime.GOARCH},
{Name: "GOHOSTOS", Value: runtime.GOOS}, {Name: "GOHOSTOS", Value: runtime.GOOS},
{Name: "GOOS", Value: cfg.Goos}, {Name: "GOOS", Value: cfg.Goos},
......
...@@ -486,6 +486,11 @@ General-purpose environment variables: ...@@ -486,6 +486,11 @@ General-purpose environment variables:
GOCACHE GOCACHE
The directory where the go command will store cached The directory where the go command will store cached
information for reuse in future builds. 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 GOOS
The operating system for which to compile code. The operating system for which to compile code.
Examples are linux, darwin, windows, netbsd. Examples are linux, darwin, windows, netbsd.
......
...@@ -63,6 +63,7 @@ var testFlagDefn = []*cmdflag.Defn{ ...@@ -63,6 +63,7 @@ var testFlagDefn = []*cmdflag.Defn{
// add build flags to testFlagDefn // add build flags to testFlagDefn
func init() { func init() {
cmdflag.AddKnownFlags("test", testFlagDefn)
var cmd base.Command var cmd base.Command
work.AddBuildFlags(&cmd) work.AddBuildFlags(&cmd)
cmd.Flag.VisitAll(func(f *flag.Flag) { cmd.Flag.VisitAll(func(f *flag.Flag) {
...@@ -87,6 +88,7 @@ func init() { ...@@ -87,6 +88,7 @@ func init() {
// go test fmt -custom-flag-for-fmt-test // go test fmt -custom-flag-for-fmt-test
// go test -x math // go test -x math
func testFlags(args []string) (packageNames, passToTest []string) { func testFlags(args []string) (packageNames, passToTest []string) {
args = str.StringList(cmdflag.FindGOFLAGS(testFlagDefn), args)
inPkg := false inPkg := false
var explicitArgs []string var explicitArgs []string
for i := 0; i < len(args); i++ { for i := 0; i < len(args); i++ {
......
...@@ -12,6 +12,7 @@ import ( ...@@ -12,6 +12,7 @@ import (
"cmd/go/internal/base" "cmd/go/internal/base"
"cmd/go/internal/cmdflag" "cmd/go/internal/cmdflag"
"cmd/go/internal/str"
"cmd/go/internal/work" "cmd/go/internal/work"
) )
...@@ -59,6 +60,7 @@ var vetTool string ...@@ -59,6 +60,7 @@ var vetTool string
// add build flags to vetFlagDefn. // add build flags to vetFlagDefn.
func init() { func init() {
cmdflag.AddKnownFlags("vet", vetFlagDefn)
var cmd base.Command var cmd base.Command
work.AddBuildFlags(&cmd) work.AddBuildFlags(&cmd)
cmd.Flag.StringVar(&vetTool, "vettool", "", "path to vet tool binary") // for cmd/vet tests; undocumented for now cmd.Flag.StringVar(&vetTool, "vettool", "", "path to vet tool binary") // for cmd/vet tests; undocumented for now
...@@ -73,6 +75,7 @@ func init() { ...@@ -73,6 +75,7 @@ func init() {
// vetFlags processes the command line, splitting it at the first non-flag // vetFlags processes the command line, splitting it at the first non-flag
// into the list of flags and list of packages. // into the list of flags and list of packages.
func vetFlags(args []string) (passToVet, packageNames []string) { func vetFlags(args []string) (passToVet, packageNames []string) {
args = str.StringList(cmdflag.FindGOFLAGS(vetFlagDefn), args)
for i := 0; i < len(args); i++ { for i := 0; i < len(args); i++ {
if !strings.HasPrefix(args[i], "-") { if !strings.HasPrefix(args[i], "-") {
return args[:i], args[i:] return args[:i], args[i:]
......
...@@ -209,6 +209,7 @@ BigCmdLoop: ...@@ -209,6 +209,7 @@ BigCmdLoop:
if cmd.CustomFlags { if cmd.CustomFlags {
args = args[1:] args = args[1:]
} else { } else {
base.SetFromGOFLAGS(cmd.Flag)
cmd.Flag.Parse(args[1:]) cmd.Flag.Parse(args[1:])
args = cmd.Flag.Args() args = cmd.Flag.Args()
} }
......
# 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
...@@ -63,6 +63,7 @@ ...@@ -63,6 +63,7 @@
set -e set -e
unset GOBIN # Issue 14340 unset GOBIN # Issue 14340
unset GOFLAGS
if [ ! -f run.bash ]; then if [ ! -f run.bash ]; then
echo 'make.bash must be run from $GOROOT/src' 1>&2 echo 'make.bash must be run from $GOROOT/src' 1>&2
......
...@@ -47,6 +47,7 @@ setlocal ...@@ -47,6 +47,7 @@ setlocal
:nolocal :nolocal
set GOBUILDFAIL=0 set GOBUILDFAIL=0
set GOFLAGS=
if exist make.bat goto ok if exist make.bat goto ok
echo Must run make.bat from Go src directory. echo Must run make.bat from Go src directory.
......
...@@ -47,7 +47,7 @@ if(~ $1 -v) { ...@@ -47,7 +47,7 @@ if(~ $1 -v) {
shift shift
} }
GOFLAGS=()
GOROOT = `{cd .. && pwd} GOROOT = `{cd .. && pwd}
if(! ~ $#GOROOT_BOOTSTRAP 1) if(! ~ $#GOROOT_BOOTSTRAP 1)
GOROOT_BOOTSTRAP = $home/go1.4 GOROOT_BOOTSTRAP = $home/go1.4
......
...@@ -20,6 +20,7 @@ export GOPATH ...@@ -20,6 +20,7 @@ export GOPATH
unset CDPATH # in case user has it set unset CDPATH # in case user has it set
unset GOBIN # Issue 14340 unset GOBIN # Issue 14340
unset GOFLAGS
export GOHOSTOS export GOHOSTOS
export CC export CC
......
...@@ -17,6 +17,7 @@ set GOBUILDFAIL=0 ...@@ -17,6 +17,7 @@ set GOBUILDFAIL=0
set GOPATH= set GOPATH=
:: Issue 14340: ignore GOBIN during all.bat. :: Issue 14340: ignore GOBIN during all.bat.
set GOBIN= set GOBIN=
set GOFLAGS=
rem TODO avoid rebuild if possible rem TODO avoid rebuild if possible
......
...@@ -10,5 +10,6 @@ eval `{go env} ...@@ -10,5 +10,6 @@ eval `{go env}
GOPATH = () # we disallow local import for non-local packages, if $GOROOT happens GOPATH = () # we disallow local import for non-local packages, if $GOROOT happens
# to be under $GOPATH, then some tests below will fail # to be under $GOPATH, then some tests below will fail
GOBIN = () # Issue 14340 GOBIN = () # Issue 14340
GOFLAGS = ()
exec go tool dist test -rebuild $* exec go tool dist test -rebuild $*
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