cmd/go: prep for 'go env' refactoring

This CL refactors code a little to make it easier to add GOEXPERIMENT
support in the future.

Change-Id: I87903056f7863049e58be72047b2b8a60a213baf
Reviewed-on: https://go-review.googlesource.com/c/go/+/329654
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
Trust: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Bryan C. Mills <bcmills@google.com>
This commit is contained in:
Matthew Dempsky 2021-06-19 03:35:15 -07:00
parent 901510ed4e
commit a1d27269d6
3 changed files with 170 additions and 127 deletions

View file

@ -10,6 +10,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"go/build" "go/build"
"internal/buildcfg"
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
@ -197,6 +198,21 @@ func runEnv(ctx context.Context, cmd *base.Command, args []string) {
if *envU && *envW { if *envU && *envW {
base.Fatalf("go env: cannot use -u with -w") base.Fatalf("go env: cannot use -u with -w")
} }
// Handle 'go env -w' and 'go env -u' before calling buildcfg.Check,
// so they can be used to recover from an invalid configuration.
if *envW {
runEnvW(args)
return
}
if *envU {
runEnvU(args)
return
}
buildcfg.Check()
env := cfg.CmdEnv env := cfg.CmdEnv
env = append(env, ExtraEnvVars()...) env = append(env, ExtraEnvVars()...)
@ -206,14 +222,7 @@ func runEnv(ctx context.Context, cmd *base.Command, args []string) {
// Do we need to call ExtraEnvVarsCostly, which is a bit expensive? // Do we need to call ExtraEnvVarsCostly, which is a bit expensive?
needCostly := false needCostly := false
if *envU || *envW { if len(args) == 0 {
// We're overwriting or removing default settings,
// so it doesn't really matter what the existing settings are.
//
// Moreover, we haven't validated the new settings yet, so it is
// important that we NOT perform any actions based on them,
// such as initializing the builder to compute other variables.
} else if len(args) == 0 {
// We're listing all environment variables ("go env"), // We're listing all environment variables ("go env"),
// including the expensive ones. // including the expensive ones.
needCostly = true needCostly = true
@ -238,7 +247,31 @@ func runEnv(ctx context.Context, cmd *base.Command, args []string) {
env = append(env, ExtraEnvVarsCostly()...) env = append(env, ExtraEnvVarsCostly()...)
} }
if *envW { if len(args) > 0 {
if *envJson {
var es []cfg.EnvVar
for _, name := range args {
e := cfg.EnvVar{Name: name, Value: findEnv(env, name)}
es = append(es, e)
}
printEnvAsJSON(es)
} else {
for _, name := range args {
fmt.Printf("%s\n", findEnv(env, name))
}
}
return
}
if *envJson {
printEnvAsJSON(env)
return
}
PrintEnv(os.Stdout, env)
}
func runEnvW(args []string) {
// Process and sanity-check command line. // Process and sanity-check command line.
if len(args) == 0 { if len(args) == 0 {
base.Fatalf("go env -w: no KEY=VALUE arguments given") base.Fatalf("go env -w: no KEY=VALUE arguments given")
@ -268,19 +301,9 @@ func runEnv(ctx context.Context, cmd *base.Command, args []string) {
} }
} }
goos, okGOOS := add["GOOS"] if err := checkBuildConfig(add, nil); err != nil {
goarch, okGOARCH := add["GOARCH"]
if okGOOS || okGOARCH {
if !okGOOS {
goos = cfg.Goos
}
if !okGOARCH {
goarch = cfg.Goarch
}
if err := work.CheckGOOSARCHPair(goos, goarch); err != nil {
base.Fatalf("go env -w: %v", err) base.Fatalf("go env -w: %v", err)
} }
}
gotmp, okGOTMP := add["GOTMPDIR"] gotmp, okGOTMP := add["GOTMPDIR"]
if okGOTMP { if okGOTMP {
@ -290,10 +313,9 @@ func runEnv(ctx context.Context, cmd *base.Command, args []string) {
} }
updateEnvFile(add, nil) updateEnvFile(add, nil)
return }
}
if *envU { func runEnvU(args []string) {
// Process and sanity-check command line. // Process and sanity-check command line.
if len(args) == 0 { if len(args) == 0 {
base.Fatalf("go env -u: no arguments given") base.Fatalf("go env -u: no arguments given")
@ -305,50 +327,44 @@ func runEnv(ctx context.Context, cmd *base.Command, args []string) {
} }
del[arg] = true del[arg] = true
} }
if del["GOOS"] || del["GOARCH"] {
goos, goarch := cfg.Goos, cfg.Goarch if err := checkBuildConfig(nil, del); err != nil {
if del["GOOS"] {
goos = getOrigEnv("GOOS")
if goos == "" {
goos = build.Default.GOOS
}
}
if del["GOARCH"] {
goarch = getOrigEnv("GOARCH")
if goarch == "" {
goarch = build.Default.GOARCH
}
}
if err := work.CheckGOOSARCHPair(goos, goarch); err != nil {
base.Fatalf("go env -u: %v", err) base.Fatalf("go env -u: %v", err)
} }
}
updateEnvFile(nil, del) updateEnvFile(nil, del)
return }
// checkBuildConfig checks whether the build configuration is valid
// after the specified configuration environment changes are applied.
func checkBuildConfig(add map[string]string, del map[string]bool) error {
// get returns the value for key after applying add and del and
// reports whether it changed. cur should be the current value
// (i.e., before applying changes) and def should be the default
// value (i.e., when no environment variables are provided at all).
get := func(key, cur, def string) (string, bool) {
if val, ok := add[key]; ok {
return val, true
}
if del[key] {
val := getOrigEnv(key)
if val == "" {
val = def
}
return val, true
}
return cur, false
} }
if len(args) > 0 { goos, okGOOS := get("GOOS", cfg.Goos, build.Default.GOOS)
if *envJson { goarch, okGOARCH := get("GOARCH", cfg.Goarch, build.Default.GOARCH)
var es []cfg.EnvVar if okGOOS || okGOARCH {
for _, name := range args { if err := work.CheckGOOSARCHPair(goos, goarch); err != nil {
e := cfg.EnvVar{Name: name, Value: findEnv(env, name)} return err
es = append(es, e)
} }
printEnvAsJSON(es)
} else {
for _, name := range args {
fmt.Printf("%s\n", findEnv(env, name))
}
}
return
} }
if *envJson { return nil
printEnvAsJSON(env)
return
}
PrintEnv(os.Stdout, env)
} }
// PrintEnv prints the environment variables to w. // PrintEnv prints the environment variables to w.

View file

@ -145,24 +145,6 @@ func main() {
os.Exit(2) os.Exit(2)
} }
if err := buildcfg.Error; err != nil {
fmt.Fprintf(os.Stderr, "go: %v\n", buildcfg.Error)
os.Exit(2)
}
// Set environment (GOOS, GOARCH, etc) explicitly.
// In theory all the commands we invoke should have
// the same default computation of these as we do,
// but in practice there might be skew
// This makes sure we all agree.
cfg.OrigEnv = os.Environ()
cfg.CmdEnv = envcmd.MkEnv()
for _, env := range cfg.CmdEnv {
if os.Getenv(env.Name) != env.Value {
os.Setenv(env.Name, env.Value)
}
}
BigCmdLoop: BigCmdLoop:
for bigCmd := base.Go; ; { for bigCmd := base.Go; ; {
for _, cmd := range bigCmd.Commands { for _, cmd := range bigCmd.Commands {
@ -188,6 +170,39 @@ BigCmdLoop:
if !cmd.Runnable() { if !cmd.Runnable() {
continue continue
} }
invoke(cmd, args)
base.Exit()
return
}
helpArg := ""
if i := strings.LastIndex(cfg.CmdName, " "); i >= 0 {
helpArg = " " + cfg.CmdName[:i]
}
fmt.Fprintf(os.Stderr, "go %s: unknown command\nRun 'go help%s' for usage.\n", cfg.CmdName, helpArg)
base.SetExitStatus(2)
base.Exit()
}
}
func invoke(cmd *base.Command, args []string) {
// 'go env' handles checking the build config
if cmd != envcmd.CmdEnv {
buildcfg.Check()
}
// Set environment (GOOS, GOARCH, etc) explicitly.
// In theory all the commands we invoke should have
// the same default computation of these as we do,
// but in practice there might be skew
// This makes sure we all agree.
cfg.OrigEnv = os.Environ()
cfg.CmdEnv = envcmd.MkEnv()
for _, env := range cfg.CmdEnv {
if os.Getenv(env.Name) != env.Value {
os.Setenv(env.Name, env.Value)
}
}
cmd.Flag.Usage = func() { cmd.Usage() } cmd.Flag.Usage = func() { cmd.Usage() }
if cmd.CustomFlags { if cmd.CustomFlags {
args = args[1:] args = args[1:]
@ -200,17 +215,6 @@ BigCmdLoop:
ctx, span := trace.StartSpan(ctx, fmt.Sprint("Running ", cmd.Name(), " command")) ctx, span := trace.StartSpan(ctx, fmt.Sprint("Running ", cmd.Name(), " command"))
cmd.Run(ctx, cmd, args) cmd.Run(ctx, cmd, args)
span.Done() span.Done()
base.Exit()
return
}
helpArg := ""
if i := strings.LastIndex(cfg.CmdName, " "); i >= 0 {
helpArg = " " + cfg.CmdName[:i]
}
fmt.Fprintf(os.Stderr, "go %s: unknown command\nRun 'go help%s' for usage.\n", cfg.CmdName, helpArg)
base.SetExitStatus(2)
base.Exit()
}
} }
func init() { func init() {

View file

@ -0,0 +1,23 @@
# Test that we can unset variables, even if initially invalid,
# as long as resulting config is valid.
env GOENV=badenv
env GOOS=
env GOARCH=
! go env
stderr '^cmd/go: unsupported GOOS/GOARCH pair bados/badarch$'
! go env -u GOOS
stderr '^go env -u: unsupported GOOS/GOARCH pair \w+/badarch$'
! go env -u GOARCH
stderr '^go env -u: unsupported GOOS/GOARCH pair bados/\w+$'
go env -u GOOS GOARCH
go env
-- badenv --
GOOS=bados
GOARCH=badarch