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") && !goInstallVersion() {
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
216
217
218
219 if gover.IsLang(goVers) && gover.Compare(goVers, "1.21") >= 0 {
220 gotoolchain += ".0"
221 }
222 gover.Startup.AutoGoVersion = goVers
223 gover.Startup.AutoToolchain = ""
224 if toolchainTrace {
225 modeFormat := mode
226 if strings.Contains(cfg.Getenv("GOTOOLCHAIN"), "+") {
227 modeFormat = fmt.Sprintf("<name>+%s", mode)
228 }
229 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)
230 }
231 }
232 }
233 }
234
235
236
237
238
239 if target := os.Getenv(targetEnv); target != "" && TestVersionSwitch != "loop" {
240 if gover.LocalToolchain() != target {
241 base.Fatalf("toolchain %v invoked to provide %v", gover.LocalToolchain(), target)
242 }
243 os.Unsetenv(targetEnv)
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259 return
260 }
261
262 if toolchainTrace {
263
264 io.Copy(os.Stderr, &toolchainTraceBuffer)
265 }
266
267 if gotoolchain == "local" || gotoolchain == gover.LocalToolchain() {
268
269 if toolchainTrace {
270 fmt.Fprintf(os.Stderr, "go: using local toolchain %s\n", gover.LocalToolchain())
271 }
272 return
273 }
274
275
276
277
278
279 if !strings.HasPrefix(gotoolchain, "go1") && !strings.Contains(gotoolchain, "-go1") {
280 base.Fatalf("invalid GOTOOLCHAIN %q", gotoolchain)
281 }
282
283 counterSelectExec.Inc()
284 Exec(gotoolchain)
285 }
286
287 var counterSelectExec = counter.New("go/toolchain/select-exec")
288
289
290
291
292
293
294
295 var TestVersionSwitch string
296
297
298
299
300
301 func Exec(gotoolchain string) {
302 log.SetPrefix("go: ")
303
304 writeBits = sysWriteBits()
305
306 count, _ := strconv.Atoi(os.Getenv(countEnv))
307 if count >= maxSwitch-10 {
308 fmt.Fprintf(os.Stderr, "go: switching from go%v to %v [depth %d]\n", gover.Local(), gotoolchain, count)
309 }
310 if count >= maxSwitch {
311 base.Fatalf("too many toolchain switches")
312 }
313 os.Setenv(countEnv, fmt.Sprint(count+1))
314
315 env := cfg.Getenv("GOTOOLCHAIN")
316 pathOnly := env == "path" || strings.HasSuffix(env, "+path")
317
318
319
320
321
322
323
324
325
326
327 switch TestVersionSwitch {
328 case "switch":
329 os.Setenv("TESTGO_VERSION", gotoolchain)
330 fallthrough
331 case "loop", "mismatch":
332 exe, err := os.Executable()
333 if err != nil {
334 base.Fatalf("%v", err)
335 }
336 execGoToolchain(gotoolchain, os.Getenv("GOROOT"), exe)
337 }
338
339
340
341
342 if exe, err := pathcache.LookPath(gotoolchain); err == nil {
343 execGoToolchain(gotoolchain, "", exe)
344 }
345
346
347
348 if pathOnly {
349 base.Fatalf("cannot find %q in PATH", gotoolchain)
350 }
351
352
353 modload.Reset()
354 modload.ForceUseModules = true
355 modload.RootMode = modload.NoRoot
356 modload.Init()
357
358
359
360
361 m := module.Version{
362 Path: gotoolchainModule,
363 Version: gotoolchainVersion + "-" + gotoolchain + "." + runtime.GOOS + "-" + runtime.GOARCH,
364 }
365 dir, err := modfetch.Download(context.Background(), m)
366 if err != nil {
367 if errors.Is(err, fs.ErrNotExist) {
368 toolVers := gover.FromToolchain(gotoolchain)
369 if gover.IsLang(toolVers) && gover.Compare(toolVers, "1.21") >= 0 {
370 base.Fatalf("invalid toolchain: %s is a language version but not a toolchain version (%s.x)", gotoolchain, gotoolchain)
371 }
372 base.Fatalf("download %s for %s/%s: toolchain not available", gotoolchain, runtime.GOOS, runtime.GOARCH)
373 }
374 base.Fatalf("download %s: %v", gotoolchain, err)
375 }
376
377
378
379
380 if runtime.GOOS != "windows" {
381 info, err := os.Stat(filepath.Join(dir, "bin/go"))
382 if err != nil {
383 base.Fatalf("download %s: %v", gotoolchain, err)
384 }
385 if info.Mode()&0111 == 0 {
386
387
388 allowExec := func(dir, pattern string) {
389 err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
390 if err != nil {
391 return err
392 }
393 if !d.IsDir() {
394 if pattern != "" {
395 if matched, _ := filepath.Match(pattern, d.Name()); !matched {
396
397 return nil
398 }
399 }
400 info, err := os.Stat(path)
401 if err != nil {
402 return err
403 }
404 if err := os.Chmod(path, info.Mode()&0777|0111); err != nil {
405 return err
406 }
407 }
408 return nil
409 })
410 if err != nil {
411 base.Fatalf("download %s: %v", gotoolchain, err)
412 }
413 }
414
415
416
417
418
419
420
421
422
423 allowExec(filepath.Join(dir, "pkg/tool"), "")
424 allowExec(filepath.Join(dir, "lib"), "go_?*_?*_exec")
425 allowExec(filepath.Join(dir, "bin/gofmt"), "")
426 allowExec(filepath.Join(dir, "bin"), "")
427 }
428 }
429
430 srcUGoMod := filepath.Join(dir, "src/_go.mod")
431 srcGoMod := filepath.Join(dir, "src/go.mod")
432 if size(srcGoMod) != size(srcUGoMod) {
433 err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
434 if err != nil {
435 return err
436 }
437 if path == srcUGoMod {
438
439 return nil
440 }
441 if pdir, name := filepath.Split(path); name == "_go.mod" {
442 if err := raceSafeCopy(path, pdir+"go.mod"); err != nil {
443 return err
444 }
445 }
446 return nil
447 })
448
449
450 if err == nil {
451 err = raceSafeCopy(srcUGoMod, srcGoMod)
452 }
453 if err != nil {
454 base.Fatalf("download %s: %v", gotoolchain, err)
455 }
456 }
457
458
459 execGoToolchain(gotoolchain, dir, filepath.Join(dir, "bin/go"))
460 }
461
462 func size(path string) int64 {
463 info, err := os.Stat(path)
464 if err != nil {
465 return -1
466 }
467 return info.Size()
468 }
469
470 var writeBits fs.FileMode
471
472
473
474
475
476
477
478
479 func raceSafeCopy(old, new string) error {
480 oldInfo, err := os.Stat(old)
481 if err != nil {
482 return err
483 }
484 newInfo, err := os.Stat(new)
485 if err == nil && newInfo.Size() == oldInfo.Size() {
486 return nil
487 }
488 data, err := os.ReadFile(old)
489 if err != nil {
490 return err
491 }
492
493
494
495
496 dir := filepath.Dir(old)
497 info, err := os.Stat(dir)
498 if err != nil {
499 return err
500 }
501 if err := os.Chmod(dir, info.Mode()|writeBits); err != nil {
502 return err
503 }
504 defer os.Chmod(dir, info.Mode())
505
506
507 f, err := os.OpenFile(new, os.O_CREATE|os.O_WRONLY, writeBits&^0o111)
508 if err != nil {
509
510
511
512 if size(old) == size(new) {
513 return nil
514 }
515 return err
516 }
517 defer os.Chmod(new, oldInfo.Mode())
518 if _, err := f.Write(data); err != nil {
519 f.Close()
520 return err
521 }
522 return f.Close()
523 }
524
525
526
527
528 func modGoToolchain() (file, goVers, toolchain string) {
529 wd := base.UncachedCwd()
530 file = modload.FindGoWork(wd)
531
532
533 if _, err := os.Stat(file); err != nil {
534 file = ""
535 }
536 if file == "" {
537 file = modload.FindGoMod(wd)
538 }
539 if file == "" {
540 return "", "", ""
541 }
542
543 data, err := os.ReadFile(file)
544 if err != nil {
545 base.Fatalf("%v", err)
546 }
547 return file, gover.GoModLookup(data, "go"), gover.GoModLookup(data, "toolchain")
548 }
549
550
551
552 func goInstallVersion() bool {
553
554
555
556 if len(os.Args) < 3 {
557 return false
558 }
559
560 var cmdFlags *flag.FlagSet
561 switch os.Args[1] {
562 default:
563
564 return false
565 case "install":
566 cmdFlags = &work.CmdInstall.Flag
567 case "run":
568 cmdFlags = &run.CmdRun.Flag
569 }
570
571
572
573
574
575 modcacherwFlag := cmdFlags.Lookup("modcacherw")
576 if modcacherwFlag == nil {
577 base.Fatalf("internal error: modcacherw flag not registered for command")
578 }
579 modcacherwVal, ok := modcacherwFlag.Value.(interface {
580 IsBoolFlag() bool
581 flag.Value
582 })
583 if !ok || !modcacherwVal.IsBoolFlag() {
584 base.Fatalf("internal error: modcacherw is not a boolean flag")
585 }
586
587
588
589 var (
590 pkgArg string
591 modcacherwSeen bool
592 )
593 for args := os.Args[2:]; len(args) > 0; {
594 a := args[0]
595 args = args[1:]
596 if a == "--" {
597 if len(args) == 0 {
598 return false
599 }
600 pkgArg = args[0]
601 break
602 }
603
604 a, ok := strings.CutPrefix(a, "-")
605 if !ok {
606
607 pkgArg = a
608 break
609 }
610 a = strings.TrimPrefix(a, "-")
611
612 name, val, hasEq := strings.Cut(a, "=")
613
614 if name == "modcacherw" {
615 if !hasEq {
616 val = "true"
617 }
618 if err := modcacherwVal.Set(val); err != nil {
619 return false
620 }
621 modcacherwSeen = true
622 continue
623 }
624
625 if hasEq {
626
627 continue
628 }
629
630 f := run.CmdRun.Flag.Lookup(a)
631 if f == nil {
632
633 if os.Args[1] == "run" {
634
635
636
637
638
639
640 return false
641 }
642
643
644
645
646
647 for len(args) > 0 {
648 a := args[0]
649 name, _, _ := strings.Cut(a, "=")
650 if name == "-modcacherw" || name == "--modcacherw" {
651 break
652 }
653 if len(args) == 1 && !strings.HasPrefix(a, "-") {
654 pkgArg = a
655 }
656 args = args[1:]
657 }
658 continue
659 }
660
661 if bf, ok := f.Value.(interface{ IsBoolFlag() bool }); !ok || !bf.IsBoolFlag() {
662
663 args = args[1:]
664 continue
665 }
666 }
667
668 if !strings.Contains(pkgArg, "@") || build.IsLocalImport(pkgArg) || filepath.IsAbs(pkgArg) {
669 return false
670 }
671 path, version, _ := strings.Cut(pkgArg, "@")
672 if path == "" || version == "" || gover.IsToolchain(path) {
673 return false
674 }
675
676 if !modcacherwSeen && base.InGOFLAGS("-modcacherw") {
677 fs := flag.NewFlagSet("goInstallVersion", flag.ExitOnError)
678 fs.Var(modcacherwVal, "modcacherw", modcacherwFlag.Usage)
679 base.SetFromGOFLAGS(fs)
680 }
681
682
683
684
685
686
687
688
689
690
691
692
693
694 modload.ForceUseModules = true
695 modload.RootMode = modload.NoRoot
696 modload.Init()
697 defer modload.Reset()
698
699
700 ctx := context.Background()
701 allowed := modload.CheckAllowed
702 if modload.IsRevisionQuery(path, version) {
703
704 allowed = nil
705 }
706 noneSelected := func(path string) (version string) { return "none" }
707 _, err := modload.QueryPackages(ctx, path, version, noneSelected, allowed)
708 if errors.Is(err, gover.ErrTooNew) {
709
710
711 SwitchOrFatal(ctx, err)
712 }
713
714 return true
715 }
716
View as plain text