1
2
3
4
5
6 package toolchain
7
8 import (
9 "bytes"
10 "context"
11 "errors"
12 "flag"
13 "fmt"
14 "go/build"
15 "internal/godebug"
16 "io"
17 "io/fs"
18 "log"
19 "os"
20 "path/filepath"
21 "runtime"
22 "strconv"
23 "strings"
24
25 "cmd/go/internal/base"
26 "cmd/go/internal/cfg"
27 "cmd/go/internal/gover"
28 "cmd/go/internal/modfetch"
29 "cmd/go/internal/modload"
30 "cmd/go/internal/run"
31 "cmd/go/internal/work"
32 "cmd/internal/pathcache"
33 "cmd/internal/telemetry/counter"
34
35 "golang.org/x/mod/module"
36 )
37
38 const (
39
40
41
42
43
44
45
46 gotoolchainModule = "golang.org/toolchain"
47 gotoolchainVersion = "v0.0.1"
48
49
50
51
52
53 targetEnv = "GOTOOLCHAIN_INTERNAL_SWITCH_VERSION"
54
55
56
57
58
59
60 countEnv = "GOTOOLCHAIN_INTERNAL_SWITCH_COUNT"
61
62
63
64
65
66
67
68
69
70
71
72 maxSwitch = 100
73 )
74
75
76
77 func FilterEnv(env []string) []string {
78
79 var out []string
80 for _, e := range env {
81 if strings.HasPrefix(e, countEnv+"=") {
82 continue
83 }
84 out = append(out, e)
85 }
86 return out
87 }
88
89 var counterErrorsInvalidToolchainInFile = counter.New("go/errors:invalid-toolchain-in-file")
90 var toolchainTrace = godebug.New("#toolchaintrace").Value() == "1"
91
92
93
94
95
96
97 func Select() {
98 log.SetPrefix("go: ")
99 defer log.SetPrefix("")
100
101 if !modload.WillBeEnabled() {
102 return
103 }
104
105
106
107
108
109
110
111
112
113
114 if (len(os.Args) == 3 && os.Args[1] == "env" && os.Args[2] == "GOTOOLCHAIN") ||
115 (len(os.Args) == 4 && os.Args[1] == "env" && os.Args[2] == "-w" && strings.HasPrefix(os.Args[3], "GOTOOLCHAIN=")) {
116 return
117 }
118
119
120
121
122
123 if len(os.Args) == 3 && os.Args[1] == "env" && (os.Args[2] == "GOMOD" || os.Args[2] == "GOWORK") {
124 return
125 }
126
127
128 gotoolchain := cfg.Getenv("GOTOOLCHAIN")
129 gover.Startup.GOTOOLCHAIN = gotoolchain
130 if gotoolchain == "" {
131
132
133
134
135
136
137 return
138 }
139
140
141 minToolchain := gover.LocalToolchain()
142 minVers := gover.Local()
143 var mode string
144 var toolchainTraceBuffer bytes.Buffer
145 if gotoolchain == "auto" {
146 mode = "auto"
147 } else if gotoolchain == "path" {
148 mode = "path"
149 } else {
150 min, suffix, plus := strings.Cut(gotoolchain, "+")
151 if min != "local" {
152 v := gover.FromToolchain(min)
153 if v == "" {
154 if plus {
155 base.Fatalf("invalid GOTOOLCHAIN %q: invalid minimum toolchain %q", gotoolchain, min)
156 }
157 base.Fatalf("invalid GOTOOLCHAIN %q", gotoolchain)
158 }
159 minToolchain = min
160 minVers = v
161 }
162 if plus && suffix != "auto" && suffix != "path" {
163 base.Fatalf("invalid GOTOOLCHAIN %q: only version suffixes are +auto and +path", gotoolchain)
164 }
165 mode = suffix
166 if toolchainTrace {
167 fmt.Fprintf(&toolchainTraceBuffer, "go: default toolchain set to %s from GOTOOLCHAIN=%s\n", minToolchain, gotoolchain)
168 }
169 }
170
171 gotoolchain = minToolchain
172 if mode == "auto" || mode == "path" {
173
174 file, goVers, toolchain := modGoToolchain()
175 gover.Startup.AutoFile = file
176 if toolchain == "default" {
177
178
179
180
181
182
183
184
185
186
187
188
189 gover.Startup.AutoToolchain = toolchain
190 } else {
191 if toolchain != "" {
192
193
194
195 toolVers := gover.FromToolchain(toolchain)
196 if toolVers == "" || (!strings.HasPrefix(toolchain, "go") && !strings.Contains(toolchain, "-go")) {
197 counterErrorsInvalidToolchainInFile.Inc()
198 base.Fatalf("invalid toolchain %q in %s", toolchain, base.ShortPath(file))
199 }
200 if gover.Compare(toolVers, minVers) > 0 {
201 if toolchainTrace {
202 modeFormat := mode
203 if strings.Contains(cfg.Getenv("GOTOOLCHAIN"), "+") {
204 modeFormat = fmt.Sprintf("<name>+%s", mode)
205 }
206 fmt.Fprintf(&toolchainTraceBuffer, "go: upgrading toolchain to %s (required by toolchain line in %s; upgrade allowed by GOTOOLCHAIN=%s)\n", toolchain, base.ShortPath(file), modeFormat)
207 }
208 gotoolchain = toolchain
209 minVers = toolVers
210 gover.Startup.AutoToolchain = toolchain
211 }
212 }
213 if gover.Compare(goVers, minVers) > 0 {
214 gotoolchain = "go" + goVers
215 minVers = goVers
216
217
218
219
220 if gover.IsLang(goVers) && gover.Compare(goVers, "1.21") >= 0 {
221 gotoolchain += ".0"
222 }
223 gover.Startup.AutoGoVersion = goVers
224 gover.Startup.AutoToolchain = ""
225 if toolchainTrace {
226 modeFormat := mode
227 if strings.Contains(cfg.Getenv("GOTOOLCHAIN"), "+") {
228 modeFormat = fmt.Sprintf("<name>+%s", mode)
229 }
230 fmt.Fprintf(&toolchainTraceBuffer, "go: upgrading toolchain to %s (required by go line in %s; upgrade allowed by GOTOOLCHAIN=%s)\n", gotoolchain, base.ShortPath(file), modeFormat)
231 }
232 }
233 }
234 maybeSwitchForGoInstallVersion(minVers)
235 }
236
237
238
239
240
241 if target := os.Getenv(targetEnv); target != "" && TestVersionSwitch != "loop" {
242 if gover.LocalToolchain() != target {
243 base.Fatalf("toolchain %v invoked to provide %v", gover.LocalToolchain(), target)
244 }
245 os.Unsetenv(targetEnv)
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261 return
262 }
263
264 if toolchainTrace {
265
266 io.Copy(os.Stderr, &toolchainTraceBuffer)
267 }
268
269 if gotoolchain == "local" || gotoolchain == gover.LocalToolchain() {
270
271 if toolchainTrace {
272 fmt.Fprintf(os.Stderr, "go: using local toolchain %s\n", gover.LocalToolchain())
273 }
274 return
275 }
276
277
278
279
280
281 if !strings.HasPrefix(gotoolchain, "go1") && !strings.Contains(gotoolchain, "-go1") {
282 base.Fatalf("invalid GOTOOLCHAIN %q", gotoolchain)
283 }
284
285 counterSelectExec.Inc()
286 Exec(gotoolchain)
287 }
288
289 var counterSelectExec = counter.New("go/toolchain/select-exec")
290
291
292
293
294
295
296
297 var TestVersionSwitch string
298
299
300
301
302
303 func Exec(gotoolchain string) {
304 log.SetPrefix("go: ")
305
306 writeBits = sysWriteBits()
307
308 count, _ := strconv.Atoi(os.Getenv(countEnv))
309 if count >= maxSwitch-10 {
310 fmt.Fprintf(os.Stderr, "go: switching from go%v to %v [depth %d]\n", gover.Local(), gotoolchain, count)
311 }
312 if count >= maxSwitch {
313 base.Fatalf("too many toolchain switches")
314 }
315 os.Setenv(countEnv, fmt.Sprint(count+1))
316
317 env := cfg.Getenv("GOTOOLCHAIN")
318 pathOnly := env == "path" || strings.HasSuffix(env, "+path")
319
320
321
322
323
324
325
326
327
328
329 switch TestVersionSwitch {
330 case "switch":
331 os.Setenv("TESTGO_VERSION", gotoolchain)
332 fallthrough
333 case "loop", "mismatch":
334 exe, err := os.Executable()
335 if err != nil {
336 base.Fatalf("%v", err)
337 }
338 execGoToolchain(gotoolchain, os.Getenv("GOROOT"), exe)
339 }
340
341
342
343
344 if exe, err := pathcache.LookPath(gotoolchain); err == nil {
345 execGoToolchain(gotoolchain, "", exe)
346 }
347
348
349
350 if pathOnly {
351 base.Fatalf("cannot find %q in PATH", gotoolchain)
352 }
353
354
355 modload.Reset()
356 modload.ForceUseModules = true
357 modload.RootMode = modload.NoRoot
358 modload.Init()
359
360
361
362
363 m := module.Version{
364 Path: gotoolchainModule,
365 Version: gotoolchainVersion + "-" + gotoolchain + "." + runtime.GOOS + "-" + runtime.GOARCH,
366 }
367 dir, err := modfetch.Download(context.Background(), m)
368 if err != nil {
369 if errors.Is(err, fs.ErrNotExist) {
370 toolVers := gover.FromToolchain(gotoolchain)
371 if gover.IsLang(toolVers) && gover.Compare(toolVers, "1.21") >= 0 {
372 base.Fatalf("invalid toolchain: %s is a language version but not a toolchain version (%s.x)", gotoolchain, gotoolchain)
373 }
374 base.Fatalf("download %s for %s/%s: toolchain not available", gotoolchain, runtime.GOOS, runtime.GOARCH)
375 }
376 base.Fatalf("download %s: %v", gotoolchain, err)
377 }
378
379
380
381
382 if runtime.GOOS != "windows" {
383 info, err := os.Stat(filepath.Join(dir, "bin/go"))
384 if err != nil {
385 base.Fatalf("download %s: %v", gotoolchain, err)
386 }
387 if info.Mode()&0111 == 0 {
388
389
390 allowExec := func(dir, pattern string) {
391 err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
392 if err != nil {
393 return err
394 }
395 if !d.IsDir() {
396 if pattern != "" {
397 if matched, _ := filepath.Match(pattern, d.Name()); !matched {
398
399 return nil
400 }
401 }
402 info, err := os.Stat(path)
403 if err != nil {
404 return err
405 }
406 if err := os.Chmod(path, info.Mode()&0777|0111); err != nil {
407 return err
408 }
409 }
410 return nil
411 })
412 if err != nil {
413 base.Fatalf("download %s: %v", gotoolchain, err)
414 }
415 }
416
417
418
419
420
421
422
423
424
425 allowExec(filepath.Join(dir, "pkg/tool"), "")
426 allowExec(filepath.Join(dir, "lib"), "go_?*_?*_exec")
427 allowExec(filepath.Join(dir, "bin/gofmt"), "")
428 allowExec(filepath.Join(dir, "bin"), "")
429 }
430 }
431
432 srcUGoMod := filepath.Join(dir, "src/_go.mod")
433 srcGoMod := filepath.Join(dir, "src/go.mod")
434 if size(srcGoMod) != size(srcUGoMod) {
435 err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
436 if err != nil {
437 return err
438 }
439 if path == srcUGoMod {
440
441 return nil
442 }
443 if pdir, name := filepath.Split(path); name == "_go.mod" {
444 if err := raceSafeCopy(path, pdir+"go.mod"); err != nil {
445 return err
446 }
447 }
448 return nil
449 })
450
451
452 if err == nil {
453 err = raceSafeCopy(srcUGoMod, srcGoMod)
454 }
455 if err != nil {
456 base.Fatalf("download %s: %v", gotoolchain, err)
457 }
458 }
459
460
461 execGoToolchain(gotoolchain, dir, filepath.Join(dir, "bin/go"))
462 }
463
464 func size(path string) int64 {
465 info, err := os.Stat(path)
466 if err != nil {
467 return -1
468 }
469 return info.Size()
470 }
471
472 var writeBits fs.FileMode
473
474
475
476
477
478
479
480
481 func raceSafeCopy(old, new string) error {
482 oldInfo, err := os.Stat(old)
483 if err != nil {
484 return err
485 }
486 newInfo, err := os.Stat(new)
487 if err == nil && newInfo.Size() == oldInfo.Size() {
488 return nil
489 }
490 data, err := os.ReadFile(old)
491 if err != nil {
492 return err
493 }
494
495
496
497
498 dir := filepath.Dir(old)
499 info, err := os.Stat(dir)
500 if err != nil {
501 return err
502 }
503 if err := os.Chmod(dir, info.Mode()|writeBits); err != nil {
504 return err
505 }
506 defer os.Chmod(dir, info.Mode())
507
508
509 f, err := os.OpenFile(new, os.O_CREATE|os.O_WRONLY, writeBits&^0o111)
510 if err != nil {
511
512
513
514 if size(old) == size(new) {
515 return nil
516 }
517 return err
518 }
519 defer os.Chmod(new, oldInfo.Mode())
520 if _, err := f.Write(data); err != nil {
521 f.Close()
522 return err
523 }
524 return f.Close()
525 }
526
527
528
529
530 func modGoToolchain() (file, goVers, toolchain string) {
531 wd := base.UncachedCwd()
532 file = modload.FindGoWork(wd)
533
534
535 if _, err := os.Stat(file); err != nil {
536 file = ""
537 }
538 if file == "" {
539 file = modload.FindGoMod(wd)
540 }
541 if file == "" {
542 return "", "", ""
543 }
544
545 data, err := os.ReadFile(file)
546 if err != nil {
547 base.Fatalf("%v", err)
548 }
549 return file, gover.GoModLookup(data, "go"), gover.GoModLookup(data, "toolchain")
550 }
551
552
553
554 func maybeSwitchForGoInstallVersion(minVers string) {
555
556
557
558 if len(os.Args) < 3 {
559 return
560 }
561
562 var cmdFlags *flag.FlagSet
563 switch os.Args[1] {
564 default:
565
566 return
567 case "install":
568 cmdFlags = &work.CmdInstall.Flag
569 case "run":
570 cmdFlags = &run.CmdRun.Flag
571 }
572
573
574
575
576
577 modcacherwFlag := cmdFlags.Lookup("modcacherw")
578 if modcacherwFlag == nil {
579 base.Fatalf("internal error: modcacherw flag not registered for command")
580 }
581 modcacherwVal, ok := modcacherwFlag.Value.(interface {
582 IsBoolFlag() bool
583 flag.Value
584 })
585 if !ok || !modcacherwVal.IsBoolFlag() {
586 base.Fatalf("internal error: modcacherw is not a boolean flag")
587 }
588
589
590
591 var (
592 pkgArg string
593 modcacherwSeen bool
594 )
595 for args := os.Args[2:]; len(args) > 0; {
596 a := args[0]
597 args = args[1:]
598 if a == "--" {
599 if len(args) == 0 {
600 return
601 }
602 pkgArg = args[0]
603 break
604 }
605
606 a, ok := strings.CutPrefix(a, "-")
607 if !ok {
608
609 pkgArg = a
610 break
611 }
612 a = strings.TrimPrefix(a, "-")
613
614 name, val, hasEq := strings.Cut(a, "=")
615
616 if name == "modcacherw" {
617 if !hasEq {
618 val = "true"
619 }
620 if err := modcacherwVal.Set(val); err != nil {
621 return
622 }
623 modcacherwSeen = true
624 continue
625 }
626
627 if hasEq {
628
629 continue
630 }
631
632 f := run.CmdRun.Flag.Lookup(a)
633 if f == nil {
634
635 if os.Args[1] == "run" {
636
637
638
639
640
641
642 return
643 }
644
645
646
647
648
649 for len(args) > 0 {
650 a := args[0]
651 name, _, _ := strings.Cut(a, "=")
652 if name == "-modcacherw" || name == "--modcacherw" {
653 break
654 }
655 if len(args) == 1 && !strings.HasPrefix(a, "-") {
656 pkgArg = a
657 }
658 args = args[1:]
659 }
660 continue
661 }
662
663 if bf, ok := f.Value.(interface{ IsBoolFlag() bool }); !ok || !bf.IsBoolFlag() {
664
665 args = args[1:]
666 continue
667 }
668 }
669
670 if !strings.Contains(pkgArg, "@") || build.IsLocalImport(pkgArg) || filepath.IsAbs(pkgArg) {
671 return
672 }
673 path, version, _ := strings.Cut(pkgArg, "@")
674 if path == "" || version == "" || gover.IsToolchain(path) {
675 return
676 }
677
678 if !modcacherwSeen && base.InGOFLAGS("-modcacherw") {
679 fs := flag.NewFlagSet("goInstallVersion", flag.ExitOnError)
680 fs.Var(modcacherwVal, "modcacherw", modcacherwFlag.Usage)
681 base.SetFromGOFLAGS(fs)
682 }
683
684
685
686
687
688
689
690
691
692
693
694
695 modload.ForceUseModules = true
696 modload.RootMode = modload.NoRoot
697 modload.Init()
698 defer modload.Reset()
699
700
701 ctx := context.Background()
702 allowed := modload.CheckAllowed
703 if modload.IsRevisionQuery(path, version) {
704
705 allowed = nil
706 }
707 noneSelected := func(path string) (version string) { return "none" }
708 _, err := modload.QueryPackages(ctx, path, version, noneSelected, allowed)
709 if errors.Is(err, gover.ErrTooNew) {
710
711
712 var s Switcher
713 s.Error(err)
714 if s.TooNew != nil && gover.Compare(s.TooNew.GoVersion, minVers) > 0 {
715 SwitchOrFatal(ctx, err)
716 }
717 }
718 }
719
View as plain text