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