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
5package base
6
7import (
8	"flag"
9	"fmt"
10	"runtime"
11	"strings"
12
13	"cmd/go/internal/cfg"
14	"cmd/internal/quoted"
15)
16
17var goflags []string // cached $GOFLAGS list; can be -x or --x form
18
19// GOFLAGS returns the flags from $GOFLAGS.
20// The list can be assumed to contain one string per flag,
21// with each string either beginning with -name or --name.
22func GOFLAGS() []string {
23	InitGOFLAGS()
24	return goflags
25}
26
27// InitGOFLAGS initializes the goflags list from $GOFLAGS.
28// If goflags is already initialized, it does nothing.
29func InitGOFLAGS() {
30	if goflags != nil { // already initialized
31		return
32	}
33
34	// Ignore bad flag in go env and go bug, because
35	// they are what people reach for when debugging
36	// a problem, and maybe they're debugging GOFLAGS.
37	// (Both will show the GOFLAGS setting if let succeed.)
38	hideErrors := cfg.CmdName == "env" || cfg.CmdName == "bug"
39
40	var err error
41	goflags, err = quoted.Split(cfg.Getenv("GOFLAGS"))
42	if err != nil {
43		if hideErrors {
44			return
45		}
46		Fatalf("go: parsing $GOFLAGS: %v", err)
47	}
48
49	if len(goflags) == 0 {
50		// nothing to do; avoid work on later InitGOFLAGS call
51		goflags = []string{}
52		return
53	}
54
55	// Each of the words returned by strings.Fields must be its own flag.
56	// To set flag arguments use -x=value instead of -x value.
57	// For boolean flags, -x is fine instead of -x=true.
58	for _, f := range goflags {
59		// Check that every flag looks like -x --x -x=value or --x=value.
60		if !strings.HasPrefix(f, "-") || f == "-" || f == "--" || strings.HasPrefix(f, "---") || strings.HasPrefix(f, "-=") || strings.HasPrefix(f, "--=") {
61			if hideErrors {
62				continue
63			}
64			Fatalf("go: parsing $GOFLAGS: non-flag %q", f)
65		}
66
67		name := f[1:]
68		if name[0] == '-' {
69			name = name[1:]
70		}
71		if i := strings.Index(name, "="); i >= 0 {
72			name = name[:i]
73		}
74		if !hasFlag(Go, name) {
75			if hideErrors {
76				continue
77			}
78			Fatalf("go: parsing $GOFLAGS: unknown flag -%s", name)
79		}
80	}
81}
82
83// boolFlag is the optional interface for flag.Value known to the flag package.
84// (It is not clear why package flag does not export this interface.)
85type boolFlag interface {
86	flag.Value
87	IsBoolFlag() bool
88}
89
90// SetFromGOFLAGS sets the flags in the given flag set using settings in $GOFLAGS.
91func SetFromGOFLAGS(flags *flag.FlagSet) {
92	InitGOFLAGS()
93
94	// This loop is similar to flag.Parse except that it ignores
95	// unknown flags found in goflags, so that setting, say, GOFLAGS=-ldflags=-w
96	// does not break commands that don't have a -ldflags.
97	// It also adjusts the output to be clear that the reported problem is from $GOFLAGS.
98	where := "$GOFLAGS"
99	if runtime.GOOS == "windows" {
100		where = "%GOFLAGS%"
101	}
102	for _, goflag := range goflags {
103		name, value, hasValue := goflag, "", false
104		// Ignore invalid flags like '=' or '=value'.
105		// If it is not reported in InitGOFlags it means we don't want to report it.
106		if i := strings.Index(goflag, "="); i == 0 {
107			continue
108		} else if i > 0 {
109			name, value, hasValue = goflag[:i], goflag[i+1:], true
110		}
111		if strings.HasPrefix(name, "--") {
112			name = name[1:]
113		}
114		f := flags.Lookup(name[1:])
115		if f == nil {
116			continue
117		}
118
119		// Use flags.Set consistently (instead of f.Value.Set) so that a subsequent
120		// call to flags.Visit will correctly visit the flags that have been set.
121
122		if fb, ok := f.Value.(boolFlag); ok && fb.IsBoolFlag() {
123			if hasValue {
124				if err := flags.Set(f.Name, value); err != nil {
125					fmt.Fprintf(flags.Output(), "go: invalid boolean value %q for flag %s (from %s): %v\n", value, name, where, err)
126					flags.Usage()
127				}
128			} else {
129				if err := flags.Set(f.Name, "true"); err != nil {
130					fmt.Fprintf(flags.Output(), "go: invalid boolean flag %s (from %s): %v\n", name, where, err)
131					flags.Usage()
132				}
133			}
134		} else {
135			if !hasValue {
136				fmt.Fprintf(flags.Output(), "go: flag needs an argument: %s (from %s)\n", name, where)
137				flags.Usage()
138			}
139			if err := flags.Set(f.Name, value); err != nil {
140				fmt.Fprintf(flags.Output(), "go: invalid value %q for flag %s (from %s): %v\n", value, name, where, err)
141				flags.Usage()
142			}
143		}
144	}
145}
146
147// InGOFLAGS returns whether GOFLAGS contains the given flag, such as "-mod".
148func InGOFLAGS(flag string) bool {
149	for _, goflag := range GOFLAGS() {
150		name := goflag
151		if strings.HasPrefix(name, "--") {
152			name = name[1:]
153		}
154		if i := strings.Index(name, "="); i >= 0 {
155			name = name[:i]
156		}
157		if name == flag {
158			return true
159		}
160	}
161	return false
162}
163