Skip to content

Commit c1a4fc3

Browse files
committed
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 <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Rob Pike <[email protected]>
1 parent 53859e5 commit c1a4fc3

File tree

15 files changed

+259
-1
lines changed

15 files changed

+259
-1
lines changed

src/cmd/go/alldocs.go

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/cmd/go/internal/base/goflags.go

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
// Copyright 2018 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package base
6+
7+
import (
8+
"flag"
9+
"fmt"
10+
"os"
11+
"runtime"
12+
"strings"
13+
14+
"cmd/go/internal/cfg"
15+
)
16+
17+
var (
18+
goflags []string // cached $GOFLAGS list; can be -x or --x form
19+
knownFlag = make(map[string]bool) // flags allowed to appear in $GOFLAGS; no leading dashes
20+
)
21+
22+
// AddKnownFlag adds name to the list of known flags for use in $GOFLAGS.
23+
func AddKnownFlag(name string) {
24+
knownFlag[name] = true
25+
}
26+
27+
// GOFLAGS returns the flags from $GOFLAGS.
28+
// The list can be assumed to contain one string per flag,
29+
// with each string either beginning with -name or --name.
30+
func GOFLAGS() []string {
31+
InitGOFLAGS()
32+
return goflags
33+
}
34+
35+
// InitGOFLAGS initializes the goflags list from $GOFLAGS.
36+
// If goflags is already initialized, it does nothing.
37+
func InitGOFLAGS() {
38+
if goflags != nil { // already initialized
39+
return
40+
}
41+
42+
// Build list of all flags for all commands.
43+
// If no command has that flag, then we report the problem.
44+
// This catches typos while still letting users record flags in GOFLAGS
45+
// that only apply to a subset of go commands.
46+
// Commands using CustomFlags can report their flag names
47+
// by calling AddKnownFlag instead.
48+
var walkFlags func(*Command)
49+
walkFlags = func(cmd *Command) {
50+
for _, sub := range cmd.Commands {
51+
walkFlags(sub)
52+
}
53+
cmd.Flag.VisitAll(func(f *flag.Flag) {
54+
knownFlag[f.Name] = true
55+
})
56+
}
57+
walkFlags(Go)
58+
59+
// Ignore bad flag in go env and go bug, because
60+
// they are what people reach for when debugging
61+
// a problem, and maybe they're debugging GOFLAGS.
62+
// (Both will show the GOFLAGS setting if let succeed.)
63+
hideErrors := cfg.CmdName == "env" || cfg.CmdName == "bug"
64+
65+
goflags = strings.Fields(os.Getenv("GOFLAGS"))
66+
if goflags == nil {
67+
goflags = []string{} // avoid work on later InitGOFLAGS call
68+
}
69+
70+
// Each of the words returned by strings.Fields must be its own flag.
71+
// To set flag arguments use -x=value instead of -x value.
72+
// For boolean flags, -x is fine instead of -x=true.
73+
for _, f := range goflags {
74+
// Check that every flag looks like -x --x -x=value or --x=value.
75+
if !strings.HasPrefix(f, "-") || f == "-" || f == "--" || strings.HasPrefix(f, "---") || strings.HasPrefix(f, "-=") || strings.HasPrefix(f, "--=") {
76+
if hideErrors {
77+
continue
78+
}
79+
Fatalf("go: parsing $GOFLAGS: non-flag %q", f)
80+
}
81+
82+
name := f[1:]
83+
if name[0] == '-' {
84+
name = name[1:]
85+
}
86+
if i := strings.Index(name, "="); i >= 0 {
87+
name = name[:i]
88+
}
89+
if !knownFlag[name] {
90+
if hideErrors {
91+
continue
92+
}
93+
Fatalf("go: parsing $GOFLAGS: unknown flag -%s", name)
94+
}
95+
}
96+
}
97+
98+
// boolFlag is the optional interface for flag.Value known to the flag package.
99+
// (It is not clear why package flag does not export this interface.)
100+
type boolFlag interface {
101+
flag.Value
102+
IsBoolFlag() bool
103+
}
104+
105+
// SetFromGOFLAGS sets the flags in the given flag set using settings in $GOFLAGS.
106+
func SetFromGOFLAGS(flags flag.FlagSet) {
107+
InitGOFLAGS()
108+
109+
// This loop is similar to flag.Parse except that it ignores
110+
// unknown flags found in goflags, so that setting, say, GOFLAGS=-ldflags=-w
111+
// does not break commands that don't have a -ldflags.
112+
// It also adjusts the output to be clear that the reported problem is from $GOFLAGS.
113+
where := "$GOFLAGS"
114+
if runtime.GOOS == "windows" {
115+
where = "%GOFLAGS%"
116+
}
117+
for _, goflag := range goflags {
118+
name, value, hasValue := goflag, "", false
119+
if i := strings.Index(goflag, "="); i >= 0 {
120+
name, value, hasValue = goflag[:i], goflag[i+1:], true
121+
}
122+
if strings.HasPrefix(name, "--") {
123+
name = name[1:]
124+
}
125+
f := flags.Lookup(name[1:])
126+
if f == nil {
127+
continue
128+
}
129+
if fb, ok := f.Value.(boolFlag); ok && fb.IsBoolFlag() {
130+
if hasValue {
131+
if err := fb.Set(value); err != nil {
132+
fmt.Fprintf(flags.Output(), "go: invalid boolean value %q for flag %s (from %s): %v\n", value, name, where, err)
133+
flags.Usage()
134+
}
135+
} else {
136+
if err := fb.Set("true"); err != nil {
137+
fmt.Fprintf(flags.Output(), "go: invalid boolean flag %s (from %s): %v\n", name, where, err)
138+
flags.Usage()
139+
}
140+
}
141+
} else {
142+
if !hasValue {
143+
fmt.Fprintf(flags.Output(), "go: flag needs an argument: %s (from %s)\n", name, where)
144+
flags.Usage()
145+
}
146+
if err := f.Value.Set(value); err != nil {
147+
fmt.Fprintf(flags.Output(), "go: invalid value %q for flag %s (from %s): %v\n", value, name, where, err)
148+
flags.Usage()
149+
}
150+
}
151+
}
152+
}

src/cmd/go/internal/cmdflag/flag.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,14 @@ func SyntaxError(cmd, msg string) {
6969
os.Exit(2)
7070
}
7171

72+
// AddKnownFlags registers the flags in defns with base.AddKnownFlag.
73+
func AddKnownFlags(cmd string, defns []*Defn) {
74+
for _, f := range defns {
75+
base.AddKnownFlag(f.Name)
76+
base.AddKnownFlag(cmd + "." + f.Name)
77+
}
78+
}
79+
7280
// Parse sees if argument i is present in the definitions and if so,
7381
// returns its definition, value, and whether it consumed an extra word.
7482
// 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
121129
f = nil
122130
return
123131
}
132+
133+
// FindGOFLAGS extracts and returns the flags matching defns from GOFLAGS.
134+
// Ideally the caller would mention that the flags were from GOFLAGS
135+
// when reporting errors, but that's too hard for now.
136+
func FindGOFLAGS(defns []*Defn) []string {
137+
var flags []string
138+
for _, flag := range base.GOFLAGS() {
139+
// Flags returned by base.GOFLAGS are well-formed, one of:
140+
// -x
141+
// --x
142+
// -x=value
143+
// --x=value
144+
if strings.HasPrefix(flag, "--") {
145+
flag = flag[1:]
146+
}
147+
name := flag[1:]
148+
if i := strings.Index(name, "="); i >= 0 {
149+
name = name[:i]
150+
}
151+
for _, f := range defns {
152+
if name == f.Name {
153+
flags = append(flags, flag)
154+
break
155+
}
156+
}
157+
}
158+
return flags
159+
}

src/cmd/go/internal/envcmd/env.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ func MkEnv() []cfg.EnvVar {
5454
{Name: "GOBIN", Value: cfg.GOBIN},
5555
{Name: "GOCACHE", Value: cache.DefaultDir()},
5656
{Name: "GOEXE", Value: cfg.ExeSuffix},
57+
{Name: "GOFLAGS", Value: os.Getenv("GOFLAGS")},
5758
{Name: "GOHOSTARCH", Value: runtime.GOARCH},
5859
{Name: "GOHOSTOS", Value: runtime.GOOS},
5960
{Name: "GOOS", Value: cfg.Goos},

src/cmd/go/internal/help/helpdoc.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,11 @@ General-purpose environment variables:
486486
GOCACHE
487487
The directory where the go command will store cached
488488
information for reuse in future builds.
489+
GOFLAGS
490+
A space-separated list of -flag=value settings to apply
491+
to go commands by default, when the given flag is known by
492+
the current command. Flags listed on the command-line
493+
are applied after this list and therefore override it.
489494
GOOS
490495
The operating system for which to compile code.
491496
Examples are linux, darwin, windows, netbsd.

src/cmd/go/internal/test/testflag.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ var testFlagDefn = []*cmdflag.Defn{
6363

6464
// add build flags to testFlagDefn
6565
func init() {
66+
cmdflag.AddKnownFlags("test", testFlagDefn)
6667
var cmd base.Command
6768
work.AddBuildFlags(&cmd)
6869
cmd.Flag.VisitAll(func(f *flag.Flag) {
@@ -87,6 +88,7 @@ func init() {
8788
// go test fmt -custom-flag-for-fmt-test
8889
// go test -x math
8990
func testFlags(args []string) (packageNames, passToTest []string) {
91+
args = str.StringList(cmdflag.FindGOFLAGS(testFlagDefn), args)
9092
inPkg := false
9193
var explicitArgs []string
9294
for i := 0; i < len(args); i++ {

src/cmd/go/internal/vet/vetflag.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212

1313
"cmd/go/internal/base"
1414
"cmd/go/internal/cmdflag"
15+
"cmd/go/internal/str"
1516
"cmd/go/internal/work"
1617
)
1718

@@ -59,6 +60,7 @@ var vetTool string
5960

6061
// add build flags to vetFlagDefn.
6162
func init() {
63+
cmdflag.AddKnownFlags("vet", vetFlagDefn)
6264
var cmd base.Command
6365
work.AddBuildFlags(&cmd)
6466
cmd.Flag.StringVar(&vetTool, "vettool", "", "path to vet tool binary") // for cmd/vet tests; undocumented for now
@@ -73,6 +75,7 @@ func init() {
7375
// vetFlags processes the command line, splitting it at the first non-flag
7476
// into the list of flags and list of packages.
7577
func vetFlags(args []string) (passToVet, packageNames []string) {
78+
args = str.StringList(cmdflag.FindGOFLAGS(vetFlagDefn), args)
7679
for i := 0; i < len(args); i++ {
7780
if !strings.HasPrefix(args[i], "-") {
7881
return args[:i], args[i:]

src/cmd/go/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ BigCmdLoop:
209209
if cmd.CustomFlags {
210210
args = args[1:]
211211
} else {
212+
base.SetFromGOFLAGS(cmd.Flag)
212213
cmd.Flag.Parse(args[1:])
213214
args = cmd.Flag.Args()
214215
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# GOFLAGS sets flags for commands
2+
3+
env GOFLAGS='-e -f={{.Dir}} --test.benchtime=1s -count=10'
4+
go list asdfasdfasdf # succeeds because of -e
5+
go list runtime
6+
stdout '[\\/]runtime$'
7+
8+
env GOFLAGS=-race OLDGOARCH=$GOARCH OLDGOOS=$GOOS GOARCH=386 GOOS=linux
9+
! go list runtime
10+
stderr 'race is only supported on'
11+
12+
env GOARCH=$OLDGOARCH GOOS=$OLDGOOS
13+
14+
# go env succeeds even though -f={{.Dir}} is inappropriate
15+
go env
16+
17+
# bad flags are diagnosed
18+
env GOFLAGS=-typoflag
19+
! go list runtime
20+
stderr 'unknown flag -typoflag'
21+
22+
env GOFLAGS=-
23+
! go list runtime
24+
stderr '^go: parsing \$GOFLAGS: non-flag "-"'
25+
26+
env GOFLAGS=--
27+
! go list runtime
28+
stderr '^go: parsing \$GOFLAGS: non-flag "--"'
29+
30+
env GOFLAGS=---oops
31+
! go list runtime
32+
stderr '^go: parsing \$GOFLAGS: non-flag "---oops"'
33+
34+
env GOFLAGS=-=noname
35+
! go list runtime
36+
stderr '^go: parsing \$GOFLAGS: non-flag "-=noname"'
37+
38+
env GOFLAGS=-f
39+
! go list runtime
40+
stderr '^go: flag needs an argument: -f \(from (\$GOFLAGS|%GOFLAGS%)\)$'
41+
42+
env GOFLAGS=-e=asdf
43+
! go list runtime
44+
stderr '^go: invalid boolean value \"asdf\" for flag -e \(from (\$GOFLAGS|%GOFLAGS%)\)'
45+
46+
# except in go bug (untested) and go env
47+
go env
48+
stdout GOFLAGS
49+

src/make.bash

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
set -e
6464

6565
unset GOBIN # Issue 14340
66+
unset GOFLAGS
6667

6768
if [ ! -f run.bash ]; then
6869
echo 'make.bash must be run from $GOROOT/src' 1>&2

src/make.bat

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ setlocal
4747
:nolocal
4848

4949
set GOBUILDFAIL=0
50+
set GOFLAGS=
5051

5152
if exist make.bat goto ok
5253
echo Must run make.bat from Go src directory.

src/make.rc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ if(~ $1 -v) {
4747
shift
4848
}
4949
50-
50+
GOFLAGS=()
5151
GOROOT = `{cd .. && pwd}
5252
if(! ~ $#GOROOT_BOOTSTRAP 1)
5353
GOROOT_BOOTSTRAP = $home/go1.4

src/run.bash

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export GOPATH
2020

2121
unset CDPATH # in case user has it set
2222
unset GOBIN # Issue 14340
23+
unset GOFLAGS
2324

2425
export GOHOSTOS
2526
export CC

src/run.bat

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ set GOBUILDFAIL=0
1717
set GOPATH=
1818
:: Issue 14340: ignore GOBIN during all.bat.
1919
set GOBIN=
20+
set GOFLAGS=
2021

2122
rem TODO avoid rebuild if possible
2223

src/run.rc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ eval `{go env}
1010
GOPATH = () # we disallow local import for non-local packages, if $GOROOT happens
1111
# to be under $GOPATH, then some tests below will fail
1212
GOBIN = () # Issue 14340
13+
GOFLAGS = ()
1314
1415
exec go tool dist test -rebuild $*

0 commit comments

Comments
 (0)