]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/go: allow using go env with unknown vars already in go/env file
authorRuss Cox <rsc@golang.org>
Mon, 22 May 2023 18:05:08 +0000 (14:05 -0400)
committerRuss Cox <rsc@golang.org>
Tue, 23 May 2023 02:43:14 +0000 (02:43 +0000)
If you are using a newer toolchain and set go env -w X=Y, then it's
a bit frustrating that you can't update the variable in an older toolchain
with go env -w X=OTHER or go env -u X, or even see it with go env X.
This CL makes all those work.

This is particularly important when playing with go env -w GOTOOLCHAIN=oldversion
because from that point on the old version is handling 'go env' commands,
and the old version doesn't know about GOTOOLCHAIN.
The most complete way to recover from that situation is to use

GOTOOLCHAIN=local go env -w ...

but we will backport this CL to Go 1.19 and Go 1.20 so that they can
recover a bit more easily.

Fixes #59870.

Change-Id: I7a0bb043109e75a0d746069015f6e7992f78287f
Reviewed-on: https://go-review.googlesource.com/c/go/+/496957
Reviewed-by: Bryan Mills <bcmills@google.com>
Run-TryBot: Russ Cox <rsc@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>

src/cmd/go/internal/envcmd/env.go
src/cmd/go/testdata/script/env_write.txt

index c865cb8a991d43c6f0a67f9c9e8ec56f4b02de5e..99dea69a74664b7d9299aec711e8a6bb632553dc 100644 (file)
@@ -145,13 +145,13 @@ func envOr(name, def string) string {
        return def
 }
 
-func findEnv(env []cfg.EnvVar, name string) string {
+func findEnv(env []cfg.EnvVar, envFile map[string]string, name string) string {
        for _, e := range env {
                if e.Name == name {
                        return e.Value
                }
        }
-       return ""
+       return envFile[name]
 }
 
 // ExtraEnvVars returns environment variables that should not leak into child processes.
@@ -252,6 +252,7 @@ func runEnv(ctx context.Context, cmd *base.Command, args []string) {
 
        env := cfg.CmdEnv
        env = append(env, ExtraEnvVars()...)
+       envFile := readEnvFile()
 
        if err := fsys.Init(base.Cwd()); err != nil {
                base.Fatalf("go: %v", err)
@@ -289,13 +290,13 @@ func runEnv(ctx context.Context, cmd *base.Command, args []string) {
                if *envJson {
                        var es []cfg.EnvVar
                        for _, name := range args {
-                               e := cfg.EnvVar{Name: name, Value: findEnv(env, name)}
+                               e := cfg.EnvVar{Name: name, Value: findEnv(env, envFile, name)}
                                es = append(es, e)
                        }
                        printEnvAsJSON(es)
                } else {
                        for _, name := range args {
-                               fmt.Printf("%s\n", findEnv(env, name))
+                               fmt.Printf("%s\n", findEnv(env, envFile, name))
                        }
                }
                return
@@ -321,12 +322,13 @@ func runEnvW(args []string) {
                }
        }
        add := make(map[string]string)
+       envFile := readEnvFile()
        for _, arg := range args {
                key, val, found := strings.Cut(arg, "=")
                if !found {
                        base.Fatalf("go: arguments must be KEY=VALUE: invalid argument: %s", arg)
                }
-               if err := checkEnvWrite(key, val); err != nil {
+               if err := checkEnvWrite(key, val, envFile, 'w'); err != nil {
                        base.Fatalf("go: %v", err)
                }
                if _, ok := add[key]; ok {
@@ -357,9 +359,10 @@ func runEnvU(args []string) {
        if len(args) == 0 {
                base.Fatalf("go: 'go env -u' requires an argument")
        }
+       envFile := readEnvFile()
        del := make(map[string]bool)
        for _, arg := range args {
-               if err := checkEnvWrite(arg, ""); err != nil {
+               if err := checkEnvWrite(arg, "", envFile, 'u'); err != nil {
                        base.Fatalf("go: %v", err)
                }
                del[arg] = true
@@ -517,7 +520,14 @@ func getOrigEnv(key string) string {
        return ""
 }
 
-func checkEnvWrite(key, val string) error {
+func checkEnvWrite(key, val string, envFile map[string]string, op rune) error {
+       _, inEnvFile := envFile[key]
+
+       // Always OK to delete something in the env file; maybe a different toolchain put it there.
+       if op == 'u' && inEnvFile {
+               return nil
+       }
+
        switch key {
        case "GOEXE", "GOGCCFLAGS", "GOHOSTARCH", "GOHOSTOS", "GOMOD", "GOWORK", "GOTOOLDIR", "GOVERSION":
                return fmt.Errorf("%s cannot be modified", key)
@@ -526,8 +536,11 @@ func checkEnvWrite(key, val string) error {
        }
 
        // To catch typos and the like, check that we know the variable.
-       if !cfg.CanGetenv(key) {
-               return fmt.Errorf("unknown go command variable %s", key)
+       // If it's already in the env file, we assume it's known.
+       if !inEnvFile && !cfg.CanGetenv(key) {
+               if _, ok := envFile[key]; !ok {
+                       return fmt.Errorf("unknown go command variable %s", key)
+               }
        }
 
        // Some variables can only have one of a few valid values. If set to an
@@ -579,22 +592,42 @@ func checkEnvWrite(key, val string) error {
        return nil
 }
 
-func updateEnvFile(add map[string]string, del map[string]bool) {
+func readEnvFile() map[string]string {
+       lines := readEnvFileLines(false)
+       m := make(map[string]string)
+       for _, line := range lines {
+               key := lineToKey(line)
+               if key == "" {
+                       continue
+               }
+               m[key] = string(line[len(key)+len("="):])
+       }
+       return m
+}
+
+func readEnvFileLines(mustExist bool) []string {
        file, err := cfg.EnvFile()
        if file == "" {
-               base.Fatalf("go: cannot find go env config: %v", err)
+               if mustExist {
+                       base.Fatalf("go: cannot find go env config: %v", err)
+               }
+               return nil
        }
        data, err := os.ReadFile(file)
-       if err != nil && (!os.IsNotExist(err) || len(add) == 0) {
+       if err != nil && (!os.IsNotExist(err) || mustExist) {
                base.Fatalf("go: reading go env config: %v", err)
        }
-
        lines := strings.SplitAfter(string(data), "\n")
        if lines[len(lines)-1] == "" {
                lines = lines[:len(lines)-1]
        } else {
                lines[len(lines)-1] += "\n"
        }
+       return lines
+}
+
+func updateEnvFile(add map[string]string, del map[string]bool) {
+       lines := readEnvFileLines(len(add) == 0)
 
        // Delete all but last copy of any duplicated variables,
        // since the last copy is the one that takes effect.
@@ -637,7 +670,11 @@ func updateEnvFile(add map[string]string, del map[string]bool) {
                }
        }
 
-       data = []byte(strings.Join(lines, ""))
+       file, err := cfg.EnvFile()
+       if file == "" {
+               base.Fatalf("go: cannot find go env config: %v", err)
+       }
+       data := []byte(strings.Join(lines, ""))
        err = os.WriteFile(file, data, 0666)
        if err != nil {
                // Try creating directory.
index ccd0eb343e292f7d9d7c8059cbfa0a8127a4df68..2e8b4391137b40b6ff2ca4c7753ebd7fdc5f9152 100644 (file)
@@ -185,3 +185,21 @@ env GOEXPERIMENT=
 ! go env -w GOEXPERIMENT=badexp
 stderr 'unknown GOEXPERIMENT badexp'
 go env -w GOEXPERIMENT=fieldtrack
+
+# go env -w and go env -u work on unknown fields already in the go/env file
+cp bad.env $GOENV
+go env GOENV
+cat $GOENV
+go env
+! stdout UNKNOWN
+go env UNKNOWN
+stdout yes
+go env -w UNKNOWN=maybe
+go env UNKNOWN
+stdout maybe
+go env -u UNKNOWN
+go env UNKNOWN
+! stdout . # gone
+
+-- bad.env --
+UNKNOWN=yes