1 // Copyright 2023 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
5 // Package toolchain implements dynamic switching of Go toolchains.
23 "cmd/go/internal/base"
25 "cmd/go/internal/gover"
26 "cmd/go/internal/modfetch"
27 "cmd/go/internal/modload"
30 "golang.org/x/mod/module"
34 // We download golang.org/toolchain version v0.0.1-<gotoolchain>.<goos>-<goarch>.
35 // If the 0.0.1 indicates anything at all, its the version of the toolchain packaging:
36 // if for some reason we needed to change the way toolchains are packaged into
37 // module zip files in a future version of Go, we could switch to v0.0.2 and then
38 // older versions expecting the old format could use v0.0.1 and newer versions
39 // would use v0.0.2. Of course, then we'd also have to publish two of each
40 // module zip file. It's not likely we'll ever need to change this.
41 gotoolchainModule = "golang.org/toolchain"
42 gotoolchainVersion = "v0.0.1"
44 // targetEnv is a special environment variable set to the expected
45 // toolchain version during the toolchain switch by the parent
46 // process and cleared in the child process. When set, that indicates
47 // to the child to confirm that it provides the expected toolchain version.
48 targetEnv = "GOTOOLCHAIN_INTERNAL_SWITCH_VERSION"
50 // countEnv is a special environment variable
51 // that is incremented during each toolchain switch, to detect loops.
52 // It is cleared before invoking programs in 'go run', 'go test', 'go generate', and 'go tool'
53 // by invoking them in an environment filtered with FilterEnv,
54 // so user programs should not see this in their environment.
55 countEnv = "GOTOOLCHAIN_INTERNAL_SWITCH_COUNT"
57 // maxSwitch is the maximum toolchain switching depth.
58 // Most uses should never see more than three.
59 // (Perhaps one for the initial GOTOOLCHAIN dispatch,
60 // a second for go get doing an upgrade, and a third if
61 // for some reason the chosen upgrade version is too small
63 // When the count reaches maxSwitch - 10, we start logging
64 // the switched versions for debugging before crashing with
65 // a fatal error upon reaching maxSwitch.
66 // That should be enough to see the repetition.
70 // FilterEnv returns a copy of env with internal GOTOOLCHAIN environment
71 // variables filtered out.
72 func FilterEnv(env []string) []string {
73 // Note: Don't need to filter out targetEnv because Switch does that.
75 for _, e := range env {
76 if strings.HasPrefix(e, countEnv+"=") {
84 // Switch invokes a different Go toolchain if directed by
85 // the GOTOOLCHAIN environment variable or the user's configuration
87 // It must be called early in startup.
90 defer log.SetPrefix("")
92 if !modload.WillBeEnabled() {
96 gotoolchain := cfg.Getenv("GOTOOLCHAIN")
97 gover.Startup.GOTOOLCHAIN = gotoolchain
98 if gotoolchain == "" {
99 // cfg.Getenv should fall back to $GOROOT/go.env,
100 // so this should not happen, unless a packager
101 // has deleted the GOTOOLCHAIN line from go.env.
102 // It can also happen if GOROOT is missing or broken,
103 // in which case best to let the go command keep running
104 // and diagnose the problem.
108 minToolchain := gover.LocalToolchain()
109 minVers := gover.Local()
110 if min, mode, ok := strings.Cut(gotoolchain, "+"); ok { // go1.2.3+auto
111 v := gover.FromToolchain(min)
113 base.Fatalf("invalid GOTOOLCHAIN %q: invalid minimum toolchain %q", gotoolchain, min)
117 if mode != "auto" && mode != "path" {
118 base.Fatalf("invalid GOTOOLCHAIN %q: only version suffixes are +auto and +path", gotoolchain)
123 if gotoolchain == "auto" || gotoolchain == "path" {
124 gotoolchain = minToolchain
126 // Locate and read go.mod or go.work.
127 // For go install m@v, it's the installed module's go.mod.
128 if m, goVers, ok := goInstallVersion(); ok {
129 if gover.Compare(goVers, minVers) > 0 {
130 // Always print, because otherwise there's no way for the user to know
131 // that a non-default toolchain version is being used here.
132 // (Normally you can run "go version", but go install m@v ignores the
133 // context that "go version" works in.)
135 gotoolchain, err = NewerToolchain(context.Background(), goVers)
137 fmt.Fprintf(os.Stderr, "go: %v\n", err)
138 gotoolchain = "go" + goVers
140 fmt.Fprintf(os.Stderr, "go: using %s for %v\n", gotoolchain, m)
143 file, goVers, toolchain := modGoToolchain()
144 gover.Startup.AutoFile = file
145 if toolchain == "local" {
146 // Local means always use the default local toolchain,
147 // which is already set, so nothing to do here.
148 // Note that if we have Go 1.21 installed originally,
149 // GOTOOLCHAIN=go1.30.0+auto or GOTOOLCHAIN=go1.30.0,
150 // and the go.mod says "toolchain local", we use Go 1.30, not Go 1.21.
151 // That is, local overrides the "auto" part of the calculation
152 // but not the minimum that the user has set.
153 // Of course, if the go.mod also says "go 1.35", using Go 1.30
154 // will provoke an error about the toolchain being too old.
155 // That's what people who use toolchain local want:
156 // only ever use the toolchain configured in the local system
157 // (including its environment and go env -w file).
158 gover.Startup.AutoToolchain = toolchain
159 gotoolchain = "local"
162 // Accept toolchain only if it is >= our min.
163 toolVers := gover.FromToolchain(toolchain)
164 if toolVers == "" || (!strings.HasPrefix(toolchain, "go") && !strings.Contains(toolchain, "-go")) {
165 base.Fatalf("invalid toolchain %q in %s", toolchain, base.ShortPath(file))
167 if gover.Compare(toolVers, minVers) >= 0 {
168 gotoolchain = toolchain
170 gover.Startup.AutoToolchain = toolchain
173 if gover.Compare(goVers, minVers) > 0 {
174 gotoolchain = "go" + goVers
175 gover.Startup.AutoGoVersion = goVers
176 gover.Startup.AutoToolchain = "" // in case we are overriding it for being too old
182 // If we are invoked as a target toolchain, confirm that
183 // we provide the expected version and then run.
184 // This check is delayed until after the handling of auto and path
185 // so that we have initialized gover.Startup for use in error messages.
186 if target := os.Getenv(targetEnv); target != "" && TestVersionSwitch != "loop" {
187 if gover.LocalToolchain() != target {
188 base.Fatalf("toolchain %v invoked to provide %v", gover.LocalToolchain(), target)
190 os.Unsetenv(targetEnv)
192 // Note: It is tempting to check that if gotoolchain != "local"
193 // then target == gotoolchain here, as a sanity check that
194 // the child has made the same version determination as the parent.
195 // This turns out not always to be the case. Specifically, if we are
196 // running Go 1.21 with GOTOOLCHAIN=go1.22+auto, which invokes
197 // Go 1.22, then 'go get go@1.23.0' or 'go get needs_go_1_23'
198 // will invoke Go 1.23, but as the Go 1.23 child the reason for that
199 // will not be apparent here: it will look like we should be using Go 1.22.
200 // We rely on the targetEnv being set to know not to downgrade.
201 // A longer term problem with the sanity check is that the exact details
202 // may change over time: there may be other reasons that a future Go
203 // version might invoke an older one, and the older one won't know why.
204 // Best to just accept that we were invoked to provide a specific toolchain
205 // (which we just checked) and leave it at that.
209 if gotoolchain == "local" || gotoolchain == gover.LocalToolchain() {
210 // Let the current binary handle the command.
214 // Minimal sanity check of GOTOOLCHAIN setting before search.
215 // We want to allow things like go1.20.3 but also gccgo-go1.20.3.
216 // We want to disallow mistakes / bad ideas like GOTOOLCHAIN=bash,
217 // since we will find that in the path lookup.
218 if !strings.HasPrefix(gotoolchain, "go1") && !strings.Contains(gotoolchain, "-go1") {
219 base.Fatalf("invalid GOTOOLCHAIN %q", gotoolchain)
222 SwitchTo(gotoolchain)
225 // NewerToolchain returns the name of the toolchain to use when we need
226 // to reinvoke a newer toolchain that must support at least the given Go version.
228 // If the latest major release is 1.N.0, we use the latest patch release of 1.(N-1) if that's >= version.
229 // Otherwise we use the latest 1.N if that's allowed.
230 // Otherwise we use the latest release.
231 func NewerToolchain(ctx context.Context, version string) (string, error) {
232 fetch := autoToolchains
234 fetch = pathToolchains
236 list, err := fetch(ctx)
240 return newerToolchain(version, list)
243 // autoToolchains returns the list of toolchain versions available to GOTOOLCHAIN=auto or =min+auto mode.
244 func autoToolchains(ctx context.Context) ([]string, error) {
245 var versions *modfetch.Versions
246 err := modfetch.TryProxies(func(proxy string) error {
247 v, err := modfetch.Lookup(ctx, proxy, "go").Versions(ctx, "")
257 return versions.List, nil
260 // pathToolchains returns the list of toolchain versions available to GOTOOLCHAIN=path or =min+path mode.
261 func pathToolchains(ctx context.Context) ([]string, error) {
262 have := make(map[string]bool)
264 for _, dir := range pathDirs() {
265 if dir == "" || !filepath.IsAbs(dir) {
266 // Refuse to use local directories in $PATH (hard-coding exec.ErrDot).
269 entries, err := os.ReadDir(dir)
273 for _, de := range entries {
274 if de.IsDir() || !strings.HasPrefix(de.Name(), "go1.") {
277 info, err := de.Info()
281 v, ok := pathVersion(dir, de, info)
282 if !ok || !strings.HasPrefix(v, "1.") || have[v] {
286 list = append(list, v)
289 sort.Slice(list, func(i, j int) bool {
290 return gover.Compare(list[i], list[j]) < 0
295 // newerToolchain implements NewerToolchain where the list of choices is known.
296 // It is separated out for easier testing of this logic.
297 func newerToolchain(need string, list []string) (string, error) {
298 // Consider each release in the list, from newest to oldest,
299 // considering only entries >= need and then only entries
300 // that are the latest in their language family
301 // (the latest 1.40, the latest 1.39, and so on).
302 // We prefer the latest patch release before the most recent release family,
303 // so if the latest release is 1.40.1 we'll take the latest 1.39.X.
304 // Failing that, we prefer the latest patch release before the most recent
305 // prerelease family, so if the latest release is 1.40rc1 is out but 1.39 is okay,
306 // we'll still take 1.39.X.
307 // Failing that we'll take the latest release.
309 for i := len(list) - 1; i >= 0; i-- {
311 if gover.Compare(v, need) < 0 {
314 if gover.Lang(latest) == gover.Lang(v) {
319 if newer != "" && !gover.IsPrerelease(newer) {
320 // latest is the last patch release of Go 1.X, and we saw a non-prerelease of Go 1.(X+1),
321 // so latest is the one we want.
326 return "", fmt.Errorf("no releases found for go >= %v", need)
328 return "go" + latest, nil
331 // HasAuto reports whether the GOTOOLCHAIN setting allows "auto" upgrades.
332 func HasAuto() bool {
333 env := cfg.Getenv("GOTOOLCHAIN")
334 return env == "auto" || strings.HasSuffix(env, "+auto")
337 // HasPath reports whether the GOTOOLCHAIN setting allows "path" upgrades.
338 func HasPath() bool {
339 env := cfg.Getenv("GOTOOLCHAIN")
340 return env == "path" || strings.HasSuffix(env, "+path")
343 // TestVersionSwitch is set in the test go binary to the value in $TESTGO_VERSION_SWITCH.
344 // Valid settings are:
346 // "switch" - simulate version switches by reinvoking the test go binary with a different TESTGO_VERSION.
347 // "mismatch" - like "switch" but forget to set TESTGO_VERSION, so it looks like we invoked a mismatched toolchain
348 // "loop" - like "switch" but
349 var TestVersionSwitch string
351 // SwitchTo invokes the specified Go toolchain or else prints an error and exits the process.
352 // If $GOTOOLCHAIN is set to path or min+path, SwitchTo only considers the PATH
353 // as a source of Go toolchains. Otherwise SwitchTo tries the PATH but then downloads
354 // a toolchain if necessary.
355 func SwitchTo(gotoolchain string) {
356 log.SetPrefix("go: ")
358 count, _ := strconv.Atoi(os.Getenv(countEnv))
359 if count >= maxSwitch-10 {
360 fmt.Fprintf(os.Stderr, "go: switching from go%v to %v [depth %d]\n", gover.Local(), gotoolchain, count)
362 if count >= maxSwitch {
363 base.Fatalf("too many toolchain switches")
365 os.Setenv(countEnv, fmt.Sprint(count+1))
367 env := cfg.Getenv("GOTOOLCHAIN")
368 pathOnly := env == "path" || strings.HasSuffix(env, "+path")
370 // For testing, if TESTGO_VERSION is already in use
371 // (only happens in the cmd/go test binary)
372 // and TESTGO_VERSION_SWITCH=switch is set,
373 // "switch" toolchains by changing TESTGO_VERSION
374 // and reinvoking the current binary.
375 // The special cases =loop and =mismatch skip the
376 // setting of TESTGO_VERSION so that it looks like we
377 // accidentally invoked the wrong toolchain,
378 // to test detection of that failure mode.
379 switch TestVersionSwitch {
381 os.Setenv("TESTGO_VERSION", gotoolchain)
383 case "loop", "mismatch":
384 exe, err := os.Executable()
386 base.Fatalf("%v", err)
388 execGoToolchain(gotoolchain, os.Getenv("GOROOT"), exe)
391 // Look in PATH for the toolchain before we download one.
392 // This allows custom toolchains as well as reuse of toolchains
393 // already installed using go install golang.org/dl/go1.2.3@latest.
394 if exe, err := exec.LookPath(gotoolchain); err == nil {
395 execGoToolchain(gotoolchain, "", exe)
398 // GOTOOLCHAIN=auto looks in PATH and then falls back to download.
399 // GOTOOLCHAIN=path only looks in PATH.
401 base.Fatalf("cannot find %q in PATH", gotoolchain)
404 // Set up modules without an explicit go.mod, to download distribution.
406 modload.ForceUseModules = true
407 modload.RootMode = modload.NoRoot
410 // Download and unpack toolchain module into module cache.
411 // Note that multiple go commands might be doing this at the same time,
412 // and that's OK: the module cache handles that case correctly.
414 Path: gotoolchainModule,
415 Version: gotoolchainVersion + "-" + gotoolchain + "." + runtime.GOOS + "-" + runtime.GOARCH,
417 dir, err := modfetch.Download(context.Background(), m)
419 if errors.Is(err, fs.ErrNotExist) {
420 base.Fatalf("download %s for %s/%s: toolchain not available", gotoolchain, runtime.GOOS, runtime.GOARCH)
422 base.Fatalf("download %s: %v", gotoolchain, err)
425 // On first use after download, set the execute bits on the commands
426 // so that we can run them. Note that multiple go commands might be
427 // doing this at the same time, but if so no harm done.
428 if runtime.GOOS != "windows" {
429 info, err := os.Stat(filepath.Join(dir, "bin/go"))
431 base.Fatalf("download %s: %v", gotoolchain, err)
433 if info.Mode()&0111 == 0 {
434 // allowExec sets the exec permission bits on all files found in dir.
435 allowExec := func(dir string) {
436 err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
441 info, err := os.Stat(path)
445 if err := os.Chmod(path, info.Mode()&0777|0111); err != nil {
452 base.Fatalf("download %s: %v", gotoolchain, err)
456 // Set the bits in pkg/tool before bin/go.
457 // If we are racing with another go command and do bin/go first,
458 // then the check of bin/go above might succeed, the other go command
459 // would skip its own mode-setting, and then the go command might
460 // try to run a tool before we get to setting the bits on pkg/tool.
461 // Setting pkg/tool before bin/go avoids that ordering problem.
462 // The only other tool the go command invokes is gofmt,
463 // so we set that one explicitly before handling bin (which will include bin/go).
464 allowExec(filepath.Join(dir, "pkg/tool"))
465 allowExec(filepath.Join(dir, "bin/gofmt"))
466 allowExec(filepath.Join(dir, "bin"))
470 // Reinvoke the go command.
471 execGoToolchain(gotoolchain, dir, filepath.Join(dir, "bin/go"))
474 // modGoToolchain finds the enclosing go.work or go.mod file
475 // and returns the go version and toolchain lines from the file.
476 // The toolchain line overrides the version line
477 func modGoToolchain() (file, goVers, toolchain string) {
478 wd := base.UncachedCwd()
479 file = modload.FindGoWork(wd)
480 // $GOWORK can be set to a file that does not yet exist, if we are running 'go work init'.
481 // Do not try to load the file in that case
482 if _, err := os.Stat(file); err != nil {
486 file = modload.FindGoMod(wd)
492 data, err := os.ReadFile(file)
494 base.Fatalf("%v", err)
496 return file, gover.GoModLookup(data, "go"), gover.GoModLookup(data, "toolchain")
499 // goInstallVersion looks at the command line to see if it is go install m@v or go run m@v.
500 // If so, it returns the m@v and the go version from that module's go.mod.
501 func goInstallVersion() (m module.Version, goVers string, found bool) {
502 // Note: We assume there are no flags between 'go' and 'install' or 'run'.
503 // During testing there are some debugging flags that are accepted
504 // in that position, but in production go binaries there are not.
505 if len(os.Args) < 3 || (os.Args[1] != "install" && os.Args[1] != "run") {
506 return module.Version{}, "", false
512 // Cannot parse 'go install' command line precisely, because there
513 // may be new flags we don't know about. Instead, assume the final
514 // argument is a pkg@version we can use.
515 arg = os.Args[len(os.Args)-1]
517 // For run, the pkg@version can be anywhere on the command line.
518 // We don't know the flags, so we can't strictly speaking do this correctly.
519 // We do the best we can by interrogating the CmdRun flags and assume
520 // that any unknown flag does not take an argument.
522 for i := 0; i < len(args); i++ {
524 if !strings.HasPrefix(a, "-") {
537 a = strings.TrimPrefix(a, "-")
538 a = strings.TrimPrefix(a, "-")
539 if strings.HasPrefix(a, "-") {
540 // non-flag but also non-m@v
543 if strings.Contains(a, "=") {
547 f := run.CmdRun.Flag.Lookup(a)
549 // Unknown flag. Assume it doesn't take a value: best we can do.
552 if bf, ok := f.Value.(interface{ IsBoolFlag() bool }); ok && bf.IsBoolFlag() {
553 // Does not take value.
556 i++ // Does take a value; skip it.
559 if !strings.Contains(arg, "@") || build.IsLocalImport(arg) || filepath.IsAbs(arg) {
560 return module.Version{}, "", false
562 m.Path, m.Version, _ = strings.Cut(arg, "@")
563 if m.Path == "" || m.Version == "" || gover.IsToolchain(m.Path) {
564 return module.Version{}, "", false
567 // Set up modules without an explicit go.mod, to download go.mod.
568 modload.ForceUseModules = true
569 modload.RootMode = modload.NoRoot
571 defer modload.Reset()
573 // See internal/load.PackagesAndErrorsOutsideModule
574 ctx := context.Background()
575 allowed := modload.CheckAllowed
576 if modload.IsRevisionQuery(m.Path, m.Version) {
577 // Don't check for retractions if a specific revision is requested.
580 noneSelected := func(path string) (version string) { return "none" }
581 _, err := modload.QueryPackages(ctx, m.Path, m.Version, noneSelected, allowed)
582 if tooNew := (*gover.TooNewError)(nil); errors.As(err, &tooNew) {
583 m.Path, m.Version, _ = strings.Cut(tooNew.What, "@")
584 return m, tooNew.GoVersion, true
587 // QueryPackages succeeded, or it failed for a reason other than
588 // this Go toolchain being too old for the modules encountered.
589 // Either way, we identified the m@v on the command line,
590 // so return found == true so the caller does not fall back to
591 // consulting go.mod.