]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/go: add $GOFLAGS environment variable
authorRuss Cox <rsc@golang.org>
Sun, 29 Jul 2018 05:10:02 +0000 (01:10 -0400)
committerRuss Cox <rsc@golang.org>
Wed, 1 Aug 2018 00:35:21 +0000 (00:35 +0000)
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: Rob Pike <r@golang.org>
15 files changed:
src/cmd/go/alldocs.go
src/cmd/go/internal/base/goflags.go [new file with mode: 0644]
src/cmd/go/internal/cmdflag/flag.go
src/cmd/go/internal/envcmd/env.go
src/cmd/go/internal/help/helpdoc.go
src/cmd/go/internal/test/testflag.go
src/cmd/go/internal/vet/vetflag.go
src/cmd/go/main.go
src/cmd/go/testdata/script/goflags.txt [new file with mode: 0644]
src/make.bash
src/make.bat
src/make.rc
src/run.bash
src/run.bat
src/run.rc

index 5d7dea81fb22504db8648ef93518cff441da7183..242e00fbe822ae02a1e936207db1abeef9c74d5d 100644 (file)
 //     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 (file)
index 0000000..2f50b50
--- /dev/null
@@ -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()
+                       }
+               }
+       }
+}
index 7ab30221279f2a44474c5884d12ec1b1acb20f2e..b2a67e6f74a829161b4302e2c48797ac37238376 100644 (file)
@@ -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
+}
index 1f458483b1d254aa56f8295a6e562d2641c3b4e2..afadbade38ef789ff2a573e81f94998566083898 100644 (file)
@@ -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},
index b5fab2f21e26cfc90e76d7366ffb984864530eb3..179812083a8ee237e5d19459d1973ad5a243225f 100644 (file)
@@ -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.
index 8a686b71255133f1be47bb4d02285dc3c9209fe1..73f8c69d9e171aafb89062c384d747bc86d94904 100644 (file)
@@ -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++ {
index bdfe0330189096e667894492b5a585879b547aaf..6cf2a8ca67ad8d2ec39fe04815d25fbf37fbfce4 100644 (file)
@@ -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:]
index 0743b996a70c3d792c4987b969a5ab7657a22377..25dfe8ffa4c9574b5092fee8b1c12f5ebaee3006 100644 (file)
@@ -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 (file)
index 0000000..20de325
--- /dev/null
@@ -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
+
index a28b82a05881ffabda6c606fb0a596c7f74ec198..78882d98341f6eede9a9d68200cfe41eceeaf5cf 100755 (executable)
@@ -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
index 9df49cd50fa137e954eb1f91abb6d1e6d7e09c38..2e718334a2e47b1818b62f7f5438a7e59ab15ac4 100644 (file)
@@ -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.
index 7ae6221b38c8d09f4e29df394f94354e9249039c..a97dfc8a0105fc4a21184f0156995cfca2d1283f 100755 (executable)
@@ -47,7 +47,7 @@ if(~ $1 -v) {
        shift
 }
 
-
+GOFLAGS=()
 GOROOT = `{cd .. && pwd}
 if(! ~ $#GOROOT_BOOTSTRAP 1)
        GOROOT_BOOTSTRAP = $home/go1.4
index 5679f9955797036884084fdce0b52499f2425012..c14f4a206d3f3d968cfae4ca9bfd8d5810ed457b 100755 (executable)
@@ -20,6 +20,7 @@ export GOPATH
 
 unset CDPATH   # in case user has it set
 unset GOBIN     # Issue 14340
+unset GOFLAGS
 
 export GOHOSTOS
 export CC
index 6e42922a86e594f372f077846435a539c4333322..0e0c413617c7653bdad4e8333cf253705a89d575 100644 (file)
@@ -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
 
index 88d77912e31efc74088c885abdd47d275c3b27a4..49d6fd9a4df1e6fa28c51fe08f8268a4362cafb1 100755 (executable)
@@ -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 $*