1
2
3
4
5
6 package envcmd
7
8 import (
9 "bytes"
10 "context"
11 "encoding/json"
12 "fmt"
13 "go/build"
14 "internal/buildcfg"
15 "io"
16 "os"
17 "path/filepath"
18 "runtime"
19 "slices"
20 "sort"
21 "strings"
22 "unicode"
23 "unicode/utf8"
24
25 "cmd/go/internal/base"
26 "cmd/go/internal/cache"
27 "cmd/go/internal/cfg"
28 "cmd/go/internal/fsys"
29 "cmd/go/internal/load"
30 "cmd/go/internal/modload"
31 "cmd/go/internal/work"
32 "cmd/internal/quoted"
33 "cmd/internal/telemetry"
34 )
35
36 var CmdEnv = &base.Command{
37 UsageLine: "go env [-json] [-changed] [-u] [-w] [var ...]",
38 Short: "print Go environment information",
39 Long: `
40 Env prints Go environment information.
41
42 By default env prints information as a shell script
43 (on Windows, a batch file). If one or more variable
44 names is given as arguments, env prints the value of
45 each named variable on its own line.
46
47 The -json flag prints the environment in JSON format
48 instead of as a shell script.
49
50 The -u flag requires one or more arguments and unsets
51 the default setting for the named environment variables,
52 if one has been set with 'go env -w'.
53
54 The -w flag requires one or more arguments of the
55 form NAME=VALUE and changes the default settings
56 of the named environment variables to the given values.
57
58 The -changed flag prints only those settings whose effective
59 value differs from the default value that would be obtained in
60 an empty environment with no prior uses of the -w flag.
61
62 For more about environment variables, see 'go help environment'.
63 `,
64 }
65
66 func init() {
67 CmdEnv.Run = runEnv
68 base.AddChdirFlag(&CmdEnv.Flag)
69 base.AddBuildFlagsNX(&CmdEnv.Flag)
70 }
71
72 var (
73 envJson = CmdEnv.Flag.Bool("json", false, "")
74 envU = CmdEnv.Flag.Bool("u", false, "")
75 envW = CmdEnv.Flag.Bool("w", false, "")
76 envChanged = CmdEnv.Flag.Bool("changed", false, "")
77 )
78
79 func MkEnv() []cfg.EnvVar {
80 envFile, envFileChanged, _ := cfg.EnvFile()
81 env := []cfg.EnvVar{
82
83 {Name: "GO111MODULE", Value: cfg.Getenv("GO111MODULE")},
84 {Name: "GOARCH", Value: cfg.Goarch, Changed: cfg.Goarch != runtime.GOARCH},
85 {Name: "GOAUTH", Value: cfg.GOAUTH, Changed: cfg.GOAUTHChanged},
86 {Name: "GOCACHE"},
87 {Name: "GOCACHEPROG", Value: cfg.GOCACHEPROG, Changed: cfg.GOCACHEPROGChanged},
88 {Name: "GODEBUG", Value: os.Getenv("GODEBUG")},
89 {Name: "GOENV", Value: envFile, Changed: envFileChanged},
90 {Name: "GOEXE", Value: cfg.ExeSuffix},
91
92
93
94
95
96
97 {Name: "GOEXPERIMENT", Value: cfg.RawGOEXPERIMENT},
98
99 {Name: "GOFIPS140", Value: cfg.GOFIPS140, Changed: cfg.GOFIPS140Changed},
100 {Name: "GOFLAGS", Value: cfg.Getenv("GOFLAGS")},
101 {Name: "GOHOSTARCH", Value: runtime.GOARCH},
102 {Name: "GOHOSTOS", Value: runtime.GOOS},
103 {Name: "GOINSECURE", Value: cfg.GOINSECURE},
104 {Name: "GOMODCACHE", Value: cfg.GOMODCACHE, Changed: cfg.GOMODCACHEChanged},
105 {Name: "GONOPROXY", Value: cfg.GONOPROXY, Changed: cfg.GONOPROXYChanged},
106 {Name: "GONOSUMDB", Value: cfg.GONOSUMDB, Changed: cfg.GONOSUMDBChanged},
107 {Name: "GOOS", Value: cfg.Goos, Changed: cfg.Goos != runtime.GOOS},
108
109
110
111
112
113 {Name: "GOPACKAGESDRIVER", Value: os.Getenv("GOPACKAGESDRIVER")},
114
115 {Name: "GOPATH", Value: cfg.BuildContext.GOPATH, Changed: cfg.GOPATHChanged},
116 {Name: "GOPRIVATE", Value: cfg.GOPRIVATE},
117 {Name: "GOPROXY", Value: cfg.GOPROXY, Changed: cfg.GOPROXYChanged},
118 {Name: "GOROOT", Value: cfg.GOROOT},
119 {Name: "GOSUMDB", Value: cfg.GOSUMDB, Changed: cfg.GOSUMDBChanged},
120 {Name: "GOTELEMETRY", Value: telemetry.Mode()},
121 {Name: "GOTELEMETRYDIR", Value: telemetry.Dir()},
122 {Name: "GOTMPDIR", Value: cfg.Getenv("GOTMPDIR")},
123 {Name: "GOTOOLCHAIN"},
124 {Name: "GOTOOLDIR", Value: build.ToolDir},
125 {Name: "GOVCS", Value: cfg.GOVCS},
126 {Name: "GOVERSION", Value: runtime.Version()},
127 }
128
129 for i := range env {
130 switch env[i].Name {
131 case "GO111MODULE":
132 if env[i].Value != "on" && env[i].Value != "" {
133 env[i].Changed = true
134 }
135 case "GOEXPERIMENT", "GOFLAGS", "GOINSECURE", "GOPACKAGESDRIVER", "GOPRIVATE", "GOTMPDIR", "GOVCS":
136 if env[i].Value != "" {
137 env[i].Changed = true
138 }
139 case "GOCACHE":
140 env[i].Value, env[i].Changed, _ = cache.DefaultDir()
141 case "GOTOOLCHAIN":
142 env[i].Value, env[i].Changed = cfg.EnvOrAndChanged("GOTOOLCHAIN", "")
143 case "GODEBUG":
144 env[i].Changed = env[i].Value != ""
145 }
146 }
147
148 if work.GccgoBin != "" {
149 env = append(env, cfg.EnvVar{Name: "GCCGO", Value: work.GccgoBin, Changed: true})
150 } else {
151 env = append(env, cfg.EnvVar{Name: "GCCGO", Value: work.GccgoName, Changed: work.GccgoChanged})
152 }
153
154 goarch, val, changed := cfg.GetArchEnv()
155 if goarch != "" {
156 env = append(env, cfg.EnvVar{Name: goarch, Value: val, Changed: changed})
157 }
158
159 cc := cfg.Getenv("CC")
160 ccChanged := true
161 if cc == "" {
162 ccChanged = false
163 cc = cfg.DefaultCC(cfg.Goos, cfg.Goarch)
164 }
165 cxx := cfg.Getenv("CXX")
166 cxxChanged := true
167 if cxx == "" {
168 cxxChanged = false
169 cxx = cfg.DefaultCXX(cfg.Goos, cfg.Goarch)
170 }
171 ar, arChanged := cfg.EnvOrAndChanged("AR", "ar")
172 env = append(env, cfg.EnvVar{Name: "AR", Value: ar, Changed: arChanged})
173 env = append(env, cfg.EnvVar{Name: "CC", Value: cc, Changed: ccChanged})
174 env = append(env, cfg.EnvVar{Name: "CXX", Value: cxx, Changed: cxxChanged})
175
176 if cfg.BuildContext.CgoEnabled {
177 env = append(env, cfg.EnvVar{Name: "CGO_ENABLED", Value: "1", Changed: cfg.CGOChanged})
178 } else {
179 env = append(env, cfg.EnvVar{Name: "CGO_ENABLED", Value: "0", Changed: cfg.CGOChanged})
180 }
181
182 return env
183 }
184
185 func findEnv(env []cfg.EnvVar, name string) string {
186 for _, e := range env {
187 if e.Name == name {
188 return e.Value
189 }
190 }
191 if cfg.CanGetenv(name) {
192 return cfg.Getenv(name)
193 }
194 return ""
195 }
196
197
198 func ExtraEnvVars(ld *modload.Loader) []cfg.EnvVar {
199 gomod := ""
200 modload.Init(ld)
201 if ld.HasModRoot() {
202 gomod = ld.ModFilePath()
203 } else if ld.Enabled() {
204 gomod = os.DevNull
205 }
206 ld.InitWorkfile()
207 gowork := modload.WorkFilePath(ld)
208
209 if cfg.Getenv("GOWORK") == "off" {
210 gowork = "off"
211 }
212 gobin := cfg.GOBIN
213 if gobin == "" && cfg.ModulesEnabled {
214 gobin = modload.BinDir(ld)
215 } else if gobin == "" {
216
217
218 gopaths := filepath.SplitList(cfg.BuildContext.GOPATH)
219 wd, err := os.Getwd()
220 if err == nil && len(gopaths) > 0 {
221 gopath := gopaths[0]
222 for _, p := range gopaths {
223 if strings.HasPrefix(wd, p) {
224 gopath = p
225 break
226 }
227 }
228 gobin = filepath.Join(gopath, "bin")
229 }
230 }
231
232 return []cfg.EnvVar{
233 {Name: "GOMOD", Value: gomod},
234 {Name: "GOWORK", Value: gowork},
235 {Name: "GOBIN", Value: gobin, Changed: cfg.GOBINChanged},
236 }
237 }
238
239
240
241 func ExtraEnvVarsCostly(ld *modload.Loader) []cfg.EnvVar {
242 b := work.NewBuilder("", ld.VendorDirOrEmpty)
243 defer func() {
244 if err := b.Close(); err != nil {
245 base.Fatal(err)
246 }
247 }()
248
249 cppflags, cflags, cxxflags, fflags, ldflags, err := b.CFlags(&load.Package{})
250 if err != nil {
251
252 fmt.Fprintf(os.Stderr, "go: invalid cflags: %v\n", err)
253 return nil
254 }
255 cmd := b.GccCmd(".", "")
256
257 join := func(s []string) string {
258 q, err := quoted.Join(s)
259 if err != nil {
260 return strings.Join(s, " ")
261 }
262 return q
263 }
264
265 ret := []cfg.EnvVar{
266
267 {Name: "CGO_CFLAGS", Value: join(cflags)},
268 {Name: "CGO_CPPFLAGS", Value: join(cppflags)},
269 {Name: "CGO_CXXFLAGS", Value: join(cxxflags)},
270 {Name: "CGO_FFLAGS", Value: join(fflags)},
271 {Name: "CGO_LDFLAGS", Value: join(ldflags)},
272 {Name: "PKG_CONFIG", Value: b.PkgconfigCmd()},
273 {Name: "GOGCCFLAGS", Value: join(cmd[3:])},
274 }
275
276 for i := range ret {
277 ev := &ret[i]
278 switch ev.Name {
279 case "GOGCCFLAGS":
280 case "CGO_CPPFLAGS":
281 ev.Changed = ev.Value != ""
282 case "PKG_CONFIG":
283 ev.Changed = ev.Value != cfg.DefaultPkgConfig
284 case "CGO_CXXFLAGS", "CGO_CFLAGS", "CGO_FFLAGS", "CGO_LDFLAGS":
285 ev.Changed = ev.Value != work.DefaultCFlags
286 }
287 }
288
289 return ret
290 }
291
292
293 func argKey(arg string) string {
294 i := strings.Index(arg, "=")
295 if i < 0 {
296 return arg
297 }
298 return arg[:i]
299 }
300
301 func runEnv(ctx context.Context, cmd *base.Command, args []string) {
302 moduleLoader := modload.NewLoader()
303 if *envJson && *envU {
304 base.Fatalf("go: cannot use -json with -u")
305 }
306 if *envJson && *envW {
307 base.Fatalf("go: cannot use -json with -w")
308 }
309 if *envU && *envW {
310 base.Fatalf("go: cannot use -u with -w")
311 }
312
313
314
315 if *envW {
316 runEnvW(args)
317 return
318 }
319
320 if *envU {
321 runEnvU(args)
322 return
323 }
324
325 buildcfg.Check()
326 if cfg.ExperimentErr != nil {
327 base.Fatal(cfg.ExperimentErr)
328 }
329
330 for _, arg := range args {
331 if strings.Contains(arg, "=") {
332 base.Fatalf("go: invalid variable name %q (use -w to set variable)", arg)
333 }
334 }
335
336 env := cfg.CmdEnv
337 env = append(env, ExtraEnvVars(moduleLoader)...)
338
339 if err := fsys.Init(); err != nil {
340 base.Fatal(err)
341 }
342
343
344 needCostly := false
345 if len(args) == 0 {
346
347
348 needCostly = true
349 } else {
350 needCostly = false
351 checkCostly:
352 for _, arg := range args {
353 switch argKey(arg) {
354 case "CGO_CFLAGS",
355 "CGO_CPPFLAGS",
356 "CGO_CXXFLAGS",
357 "CGO_FFLAGS",
358 "CGO_LDFLAGS",
359 "PKG_CONFIG",
360 "GOGCCFLAGS":
361 needCostly = true
362 break checkCostly
363 }
364 }
365 }
366 if needCostly {
367 work.BuildInit(moduleLoader)
368 env = append(env, ExtraEnvVarsCostly(moduleLoader)...)
369 }
370
371 if len(args) > 0 {
372
373 if !*envChanged {
374 if *envJson {
375 es := make([]cfg.EnvVar, 0, len(args))
376 for _, name := range args {
377 e := cfg.EnvVar{Name: name, Value: findEnv(env, name)}
378 es = append(es, e)
379 }
380 env = es
381 } else {
382
383 for _, name := range args {
384 fmt.Printf("%s\n", findEnv(env, name))
385 }
386 return
387 }
388 } else {
389
390 var es []cfg.EnvVar
391 for _, name := range args {
392 for _, e := range env {
393 if e.Name == name {
394 es = append(es, e)
395 break
396 }
397 }
398 }
399 env = es
400 }
401 }
402
403
404 if *envJson {
405 printEnvAsJSON(env, *envChanged)
406 } else {
407 PrintEnv(os.Stdout, env, *envChanged)
408 }
409 }
410
411 func runEnvW(args []string) {
412
413 if len(args) == 0 {
414 base.Fatalf("go: no KEY=VALUE arguments given")
415 }
416 osEnv := make(map[string]string)
417 for _, e := range cfg.OrigEnv {
418 if i := strings.Index(e, "="); i >= 0 {
419 osEnv[e[:i]] = e[i+1:]
420 }
421 }
422 add := make(map[string]string)
423 for _, arg := range args {
424 key, val, found := strings.Cut(arg, "=")
425 if !found {
426 base.Fatalf("go: arguments must be KEY=VALUE: invalid argument: %s", arg)
427 }
428 if err := checkEnvWrite(key, val); err != nil {
429 base.Fatal(err)
430 }
431 if _, ok := add[key]; ok {
432 base.Fatalf("go: multiple values for key: %s", key)
433 }
434 add[key] = val
435 if osVal := osEnv[key]; osVal != "" && osVal != val {
436 fmt.Fprintf(os.Stderr, "warning: go env -w %s=... does not override conflicting OS environment variable\n", key)
437 }
438 }
439
440 if err := checkBuildConfig(add, nil); err != nil {
441 base.Fatal(err)
442 }
443
444 gotmp, okGOTMP := add["GOTMPDIR"]
445 if okGOTMP {
446 if !filepath.IsAbs(gotmp) && gotmp != "" {
447 base.Fatalf("go: GOTMPDIR must be an absolute path")
448 }
449 }
450
451 updateEnvFile(add, nil)
452 }
453
454 func runEnvU(args []string) {
455
456 if len(args) == 0 {
457 base.Fatalf("go: 'go env -u' requires an argument")
458 }
459 del := make(map[string]bool)
460 for _, arg := range args {
461 if err := checkEnvWrite(arg, ""); err != nil {
462 base.Fatal(err)
463 }
464 del[arg] = true
465 }
466
467 if err := checkBuildConfig(nil, del); err != nil {
468 base.Fatal(err)
469 }
470
471 updateEnvFile(nil, del)
472 }
473
474
475
476 func checkBuildConfig(add map[string]string, del map[string]bool) error {
477
478
479
480
481 get := func(key, cur, def string) (string, bool) {
482 if val, ok := add[key]; ok {
483 return val, true
484 }
485 if del[key] {
486 val := getOrigEnv(key)
487 if val == "" {
488 val = def
489 }
490 return val, true
491 }
492 return cur, false
493 }
494
495 goos, okGOOS := get("GOOS", cfg.Goos, build.Default.GOOS)
496 goarch, okGOARCH := get("GOARCH", cfg.Goarch, build.Default.GOARCH)
497 if okGOOS || okGOARCH {
498 if err := work.CheckGOOSARCHPair(goos, goarch); err != nil {
499 return err
500 }
501 }
502
503 goexperiment, okGOEXPERIMENT := get("GOEXPERIMENT", cfg.RawGOEXPERIMENT, buildcfg.DefaultGOEXPERIMENT)
504 if okGOEXPERIMENT {
505 if _, err := buildcfg.ParseGOEXPERIMENT(goos, goarch, goexperiment); err != nil {
506 return err
507 }
508 }
509
510 return nil
511 }
512
513
514 func PrintEnv(w io.Writer, env []cfg.EnvVar, onlyChanged bool) {
515 env = slices.Clone(env)
516 slices.SortFunc(env, func(x, y cfg.EnvVar) int { return strings.Compare(x.Name, y.Name) })
517
518 for _, e := range env {
519 if e.Name != "TERM" {
520 if runtime.GOOS != "plan9" && bytes.Contains([]byte(e.Value), []byte{0}) {
521 base.Fatalf("go: internal error: encountered null byte in environment variable %s on non-plan9 platform", e.Name)
522 }
523 if onlyChanged && !e.Changed {
524 continue
525 }
526 switch runtime.GOOS {
527 default:
528 fmt.Fprintf(w, "%s=%s\n", e.Name, shellQuote(e.Value))
529 case "plan9":
530 if strings.IndexByte(e.Value, '\x00') < 0 {
531 fmt.Fprintf(w, "%s='%s'\n", e.Name, strings.ReplaceAll(e.Value, "'", "''"))
532 } else {
533 v := strings.Split(e.Value, "\x00")
534 fmt.Fprintf(w, "%s=(", e.Name)
535 for x, s := range v {
536 if x > 0 {
537 fmt.Fprintf(w, " ")
538 }
539 fmt.Fprintf(w, "'%s'", strings.ReplaceAll(s, "'", "''"))
540 }
541 fmt.Fprintf(w, ")\n")
542 }
543 case "windows":
544 if hasNonGraphic(e.Value) {
545 base.Errorf("go: stripping unprintable or unescapable characters from %%%q%%", e.Name)
546 }
547 fmt.Fprintf(w, "set %s=%s\n", e.Name, batchEscape(e.Value))
548 }
549 }
550 }
551 }
552
553
554
555
556 func isWindowsUnquotableRune(r rune) bool {
557 if r == '\r' || r == '\n' {
558 return true
559 }
560 return !unicode.IsGraphic(r) && !unicode.IsSpace(r)
561 }
562
563 func hasNonGraphic(s string) bool {
564 return strings.ContainsFunc(s, isWindowsUnquotableRune)
565 }
566
567 func shellQuote(s string) string {
568 var sb strings.Builder
569 sb.WriteByte('\'')
570 for _, r := range s {
571 if r == '\'' {
572
573
574 sb.WriteString(`'\''`)
575 } else {
576 sb.WriteRune(r)
577 }
578 }
579 sb.WriteByte('\'')
580 return sb.String()
581 }
582
583 func batchEscape(s string) string {
584 var sb strings.Builder
585 for _, r := range s {
586 if isWindowsUnquotableRune(r) {
587 sb.WriteRune(unicode.ReplacementChar)
588 continue
589 }
590 switch r {
591 case '%':
592 sb.WriteString("%%")
593 case '<', '>', '|', '&', '^':
594
595
596 sb.WriteByte('^')
597 sb.WriteRune(r)
598 default:
599 sb.WriteRune(r)
600 }
601 }
602 return sb.String()
603 }
604
605 func printEnvAsJSON(env []cfg.EnvVar, onlyChanged bool) {
606 m := make(map[string]string)
607 for _, e := range env {
608 if e.Name == "TERM" {
609 continue
610 }
611 if onlyChanged && !e.Changed {
612 continue
613 }
614 m[e.Name] = e.Value
615 }
616 enc := json.NewEncoder(os.Stdout)
617 enc.SetIndent("", "\t")
618 if err := enc.Encode(m); err != nil {
619 base.Fatalf("go: %s", err)
620 }
621 }
622
623 func getOrigEnv(key string) string {
624 for _, v := range cfg.OrigEnv {
625 if v, found := strings.CutPrefix(v, key+"="); found {
626 return v
627 }
628 }
629 return ""
630 }
631
632 func checkEnvWrite(key, val string) error {
633 switch key {
634 case "GOEXE",
635 "GOGCCFLAGS",
636 "GOHOSTARCH",
637 "GOHOSTOS",
638 "GOMOD",
639 "GOROOT",
640 "GOTELEMETRY",
641 "GOTELEMETRYDIR",
642 "GOTOOLDIR",
643 "GOVERSION",
644 "GOWORK":
645 return fmt.Errorf("%s cannot be modified", key)
646 case "GOENV", "GODEBUG":
647 return fmt.Errorf("%s can only be set using the OS environment", key)
648 }
649
650
651
652 if !cfg.CanGetenv(key) {
653 return fmt.Errorf("unknown go command variable %s", key)
654 }
655
656
657
658
659 switch key {
660 case "GO111MODULE":
661 switch val {
662 case "", "auto", "on", "off":
663 default:
664 return fmt.Errorf("invalid %s value %q", key, val)
665 }
666 case "GOPATH":
667 if strings.HasPrefix(val, "~") {
668 return fmt.Errorf("GOPATH entry cannot start with shell metacharacter '~': %q", val)
669 }
670 if !filepath.IsAbs(val) && val != "" {
671 return fmt.Errorf("GOPATH entry is relative; must be absolute path: %q", val)
672 }
673 case "GOMODCACHE":
674 if !filepath.IsAbs(val) && val != "" {
675 return fmt.Errorf("GOMODCACHE entry is relative; must be absolute path: %q", val)
676 }
677 case "CC", "CXX":
678 if val == "" {
679 break
680 }
681 args, err := quoted.Split(val)
682 if err != nil {
683 return fmt.Errorf("invalid %s: %v", key, err)
684 }
685 if len(args) == 0 {
686 return fmt.Errorf("%s entry cannot contain only space", key)
687 }
688 if !filepath.IsAbs(args[0]) && args[0] != filepath.Base(args[0]) {
689 return fmt.Errorf("%s entry is relative; must be absolute path: %q", key, args[0])
690 }
691 }
692
693 if !utf8.ValidString(val) {
694 return fmt.Errorf("invalid UTF-8 in %s=... value", key)
695 }
696 if strings.Contains(val, "\x00") {
697 return fmt.Errorf("invalid NUL in %s=... value", key)
698 }
699 if strings.ContainsAny(val, "\v\r\n") {
700 return fmt.Errorf("invalid newline in %s=... value", key)
701 }
702 return nil
703 }
704
705 func readEnvFileLines(mustExist bool) []string {
706 file, _, err := cfg.EnvFile()
707 if file == "" {
708 if mustExist {
709 base.Fatalf("go: cannot find go env config: %v", err)
710 }
711 return nil
712 }
713 data, err := os.ReadFile(file)
714 if err != nil && (!os.IsNotExist(err) || mustExist) {
715 base.Fatalf("go: reading go env config: %v", err)
716 }
717 lines := strings.SplitAfter(string(data), "\n")
718 if lines[len(lines)-1] == "" {
719 lines = lines[:len(lines)-1]
720 } else {
721 lines[len(lines)-1] += "\n"
722 }
723 return lines
724 }
725
726 func updateEnvFile(add map[string]string, del map[string]bool) {
727 lines := readEnvFileLines(len(add) == 0)
728
729
730
731 prev := make(map[string]int)
732 for l, line := range lines {
733 if key := lineToKey(line); key != "" {
734 if p, ok := prev[key]; ok {
735 lines[p] = ""
736 }
737 prev[key] = l
738 }
739 }
740
741
742 for key, val := range add {
743 if p, ok := prev[key]; ok {
744 lines[p] = key + "=" + val + "\n"
745 delete(add, key)
746 }
747 }
748 for key, val := range add {
749 lines = append(lines, key+"="+val+"\n")
750 }
751
752
753 for key := range del {
754 if p, ok := prev[key]; ok {
755 lines[p] = ""
756 }
757 }
758
759
760
761
762 start := 0
763 for i := 0; i <= len(lines); i++ {
764 if i == len(lines) || lineToKey(lines[i]) == "" {
765 sortKeyValues(lines[start:i])
766 start = i + 1
767 }
768 }
769
770 file, _, err := cfg.EnvFile()
771 if file == "" {
772 base.Fatalf("go: cannot find go env config: %v", err)
773 }
774 data := []byte(strings.Join(lines, ""))
775 err = os.WriteFile(file, data, 0666)
776 if err != nil {
777
778 os.MkdirAll(filepath.Dir(file), 0777)
779 err = os.WriteFile(file, data, 0666)
780 if err != nil {
781 base.Fatalf("go: writing go env config: %v", err)
782 }
783 }
784 }
785
786
787 func lineToKey(line string) string {
788 i := strings.Index(line, "=")
789 if i < 0 || strings.Contains(line[:i], "#") {
790 return ""
791 }
792 return line[:i]
793 }
794
795
796
797 func sortKeyValues(lines []string) {
798 sort.Slice(lines, func(i, j int) bool {
799 return lineToKey(lines[i]) < lineToKey(lines[j])
800 })
801 }
802
View as plain text