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. 4 5 // Package toolchain implements dynamic switching of Go toolchains. 6 package toolchain 7 8 import ( 9 "context" 10 "errors" 11 "flag" 12 "fmt" 13 "go/build" 14 "io/fs" 15 "log" 16 "os" 17 "path/filepath" 18 "runtime" 19 "strconv" 20 "strings" 21 22 "cmd/go/internal/base" 23 "cmd/go/internal/cfg" 24 "cmd/go/internal/gover" 25 "cmd/go/internal/modfetch" 26 "cmd/go/internal/modload" 27 "cmd/go/internal/run" 28 "cmd/go/internal/work" 29 30 "golang.org/x/mod/module" 31 ) 32 33 const ( 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" 43 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" 49 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" 56 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 62 // by a little.) 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. 67 maxSwitch = 100 68 ) 69 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. 74 var out []string 75 for _, e := range env { 76 if strings.HasPrefix(e, countEnv+"=") { 77 continue 78 } 79 out = append(out, e) 80 } 81 return out 82 } 83 84 // Select invokes a different Go toolchain if directed by 85 // the GOTOOLCHAIN environment variable or the user's configuration 86 // or go.mod file. 87 // It must be called early in startup. 88 // See https://go.dev/doc/toolchain#select. 89 func Select() { 90 log.SetPrefix("go: ") 91 defer log.SetPrefix("") 92 93 if !modload.WillBeEnabled() { 94 return 95 } 96 97 // As a special case, let "go env GOTOOLCHAIN" and "go env -w GOTOOLCHAIN=..." 98 // be handled by the local toolchain, since an older toolchain may not understand it. 99 // This provides an easy way out of "go env -w GOTOOLCHAIN=go1.19" and makes 100 // sure that "go env GOTOOLCHAIN" always prints the local go command's interpretation of it. 101 // We look for these specific command lines in order to avoid mishandling 102 // 103 // GOTOOLCHAIN=go1.999 go env -newflag GOTOOLCHAIN 104 // 105 // where -newflag is a flag known to Go 1.999 but not known to us. 106 if (len(os.Args) == 3 && os.Args[1] == "env" && os.Args[2] == "GOTOOLCHAIN") || 107 (len(os.Args) == 4 && os.Args[1] == "env" && os.Args[2] == "-w" && strings.HasPrefix(os.Args[3], "GOTOOLCHAIN=")) { 108 return 109 } 110 111 // Interpret GOTOOLCHAIN to select the Go toolchain to run. 112 gotoolchain := cfg.Getenv("GOTOOLCHAIN") 113 gover.Startup.GOTOOLCHAIN = gotoolchain 114 if gotoolchain == "" { 115 // cfg.Getenv should fall back to $GOROOT/go.env, 116 // so this should not happen, unless a packager 117 // has deleted the GOTOOLCHAIN line from go.env. 118 // It can also happen if GOROOT is missing or broken, 119 // in which case best to let the go command keep running 120 // and diagnose the problem. 121 return 122 } 123 124 // Note: minToolchain is what https://go.dev/doc/toolchain#select calls the default toolchain. 125 minToolchain := gover.LocalToolchain() 126 minVers := gover.Local() 127 var mode string 128 if gotoolchain == "auto" { 129 mode = "auto" 130 } else if gotoolchain == "path" { 131 mode = "path" 132 } else { 133 min, suffix, plus := strings.Cut(gotoolchain, "+") // go1.2.3+auto 134 if min != "local" { 135 v := gover.FromToolchain(min) 136 if v == "" { 137 if plus { 138 base.Fatalf("invalid GOTOOLCHAIN %q: invalid minimum toolchain %q", gotoolchain, min) 139 } 140 base.Fatalf("invalid GOTOOLCHAIN %q", gotoolchain) 141 } 142 minToolchain = min 143 minVers = v 144 } 145 if plus && suffix != "auto" && suffix != "path" { 146 base.Fatalf("invalid GOTOOLCHAIN %q: only version suffixes are +auto and +path", gotoolchain) 147 } 148 mode = suffix 149 } 150 151 gotoolchain = minToolchain 152 if (mode == "auto" || mode == "path") && !goInstallVersion() { 153 // Read go.mod to find new minimum and suggested toolchain. 154 file, goVers, toolchain := modGoToolchain() 155 gover.Startup.AutoFile = file 156 if toolchain == "default" { 157 // "default" means always use the default toolchain, 158 // which is already set, so nothing to do here. 159 // Note that if we have Go 1.21 installed originally, 160 // GOTOOLCHAIN=go1.30.0+auto or GOTOOLCHAIN=go1.30.0, 161 // and the go.mod says "toolchain default", we use Go 1.30, not Go 1.21. 162 // That is, default overrides the "auto" part of the calculation 163 // but not the minimum that the user has set. 164 // Of course, if the go.mod also says "go 1.35", using Go 1.30 165 // will provoke an error about the toolchain being too old. 166 // That's what people who use toolchain default want: 167 // only ever use the toolchain configured by the user 168 // (including its environment and go env -w file). 169 gover.Startup.AutoToolchain = toolchain 170 } else { 171 if toolchain != "" { 172 // Accept toolchain only if it is > our min. 173 // (If it is equal, then min satisfies it anyway: that can matter if min 174 // has a suffix like "go1.21.1-foo" and toolchain is "go1.21.1".) 175 toolVers := gover.FromToolchain(toolchain) 176 if toolVers == "" || (!strings.HasPrefix(toolchain, "go") && !strings.Contains(toolchain, "-go")) { 177 base.Fatalf("invalid toolchain %q in %s", toolchain, base.ShortPath(file)) 178 } 179 if gover.Compare(toolVers, minVers) > 0 { 180 gotoolchain = toolchain 181 minVers = toolVers 182 gover.Startup.AutoToolchain = toolchain 183 } 184 } 185 if gover.Compare(goVers, minVers) > 0 { 186 gotoolchain = "go" + goVers 187 // Starting with Go 1.21, the first released version has a .0 patch version suffix. 188 // Don't try to download a language version (sans patch component), such as go1.22. 189 // Instead, use the first toolchain of that language version, such as 1.22.0. 190 // See golang.org/issue/62278. 191 if gover.IsLang(goVers) && gover.Compare(goVers, "1.21") >= 0 { 192 gotoolchain += ".0" 193 } 194 gover.Startup.AutoGoVersion = goVers 195 gover.Startup.AutoToolchain = "" // in case we are overriding it for being too old 196 } 197 } 198 } 199 200 // If we are invoked as a target toolchain, confirm that 201 // we provide the expected version and then run. 202 // This check is delayed until after the handling of auto and path 203 // so that we have initialized gover.Startup for use in error messages. 204 if target := os.Getenv(targetEnv); target != "" && TestVersionSwitch != "loop" { 205 if gover.LocalToolchain() != target { 206 base.Fatalf("toolchain %v invoked to provide %v", gover.LocalToolchain(), target) 207 } 208 os.Unsetenv(targetEnv) 209 210 // Note: It is tempting to check that if gotoolchain != "local" 211 // then target == gotoolchain here, as a sanity check that 212 // the child has made the same version determination as the parent. 213 // This turns out not always to be the case. Specifically, if we are 214 // running Go 1.21 with GOTOOLCHAIN=go1.22+auto, which invokes 215 // Go 1.22, then 'go get go@1.23.0' or 'go get needs_go_1_23' 216 // will invoke Go 1.23, but as the Go 1.23 child the reason for that 217 // will not be apparent here: it will look like we should be using Go 1.22. 218 // We rely on the targetEnv being set to know not to downgrade. 219 // A longer term problem with the sanity check is that the exact details 220 // may change over time: there may be other reasons that a future Go 221 // version might invoke an older one, and the older one won't know why. 222 // Best to just accept that we were invoked to provide a specific toolchain 223 // (which we just checked) and leave it at that. 224 return 225 } 226 227 if gotoolchain == "local" || gotoolchain == gover.LocalToolchain() { 228 // Let the current binary handle the command. 229 return 230 } 231 232 // Minimal sanity check of GOTOOLCHAIN setting before search. 233 // We want to allow things like go1.20.3 but also gccgo-go1.20.3. 234 // We want to disallow mistakes / bad ideas like GOTOOLCHAIN=bash, 235 // since we will find that in the path lookup. 236 if !strings.HasPrefix(gotoolchain, "go1") && !strings.Contains(gotoolchain, "-go1") { 237 base.Fatalf("invalid GOTOOLCHAIN %q", gotoolchain) 238 } 239 240 Exec(gotoolchain) 241 } 242 243 // TestVersionSwitch is set in the test go binary to the value in $TESTGO_VERSION_SWITCH. 244 // Valid settings are: 245 // 246 // "switch" - simulate version switches by reinvoking the test go binary with a different TESTGO_VERSION. 247 // "mismatch" - like "switch" but forget to set TESTGO_VERSION, so it looks like we invoked a mismatched toolchain 248 // "loop" - like "mismatch" but forget the target check, causing a toolchain switching loop 249 var TestVersionSwitch string 250 251 // Exec invokes the specified Go toolchain or else prints an error and exits the process. 252 // If $GOTOOLCHAIN is set to path or min+path, Exec only considers the PATH 253 // as a source of Go toolchains. Otherwise Exec tries the PATH but then downloads 254 // a toolchain if necessary. 255 func Exec(gotoolchain string) { 256 log.SetPrefix("go: ") 257 258 writeBits = sysWriteBits() 259 260 count, _ := strconv.Atoi(os.Getenv(countEnv)) 261 if count >= maxSwitch-10 { 262 fmt.Fprintf(os.Stderr, "go: switching from go%v to %v [depth %d]\n", gover.Local(), gotoolchain, count) 263 } 264 if count >= maxSwitch { 265 base.Fatalf("too many toolchain switches") 266 } 267 os.Setenv(countEnv, fmt.Sprint(count+1)) 268 269 env := cfg.Getenv("GOTOOLCHAIN") 270 pathOnly := env == "path" || strings.HasSuffix(env, "+path") 271 272 // For testing, if TESTGO_VERSION is already in use 273 // (only happens in the cmd/go test binary) 274 // and TESTGO_VERSION_SWITCH=switch is set, 275 // "switch" toolchains by changing TESTGO_VERSION 276 // and reinvoking the current binary. 277 // The special cases =loop and =mismatch skip the 278 // setting of TESTGO_VERSION so that it looks like we 279 // accidentally invoked the wrong toolchain, 280 // to test detection of that failure mode. 281 switch TestVersionSwitch { 282 case "switch": 283 os.Setenv("TESTGO_VERSION", gotoolchain) 284 fallthrough 285 case "loop", "mismatch": 286 exe, err := os.Executable() 287 if err != nil { 288 base.Fatalf("%v", err) 289 } 290 execGoToolchain(gotoolchain, os.Getenv("GOROOT"), exe) 291 } 292 293 // Look in PATH for the toolchain before we download one. 294 // This allows custom toolchains as well as reuse of toolchains 295 // already installed using go install golang.org/dl/go1.2.3@latest. 296 if exe, err := cfg.LookPath(gotoolchain); err == nil { 297 execGoToolchain(gotoolchain, "", exe) 298 } 299 300 // GOTOOLCHAIN=auto looks in PATH and then falls back to download. 301 // GOTOOLCHAIN=path only looks in PATH. 302 if pathOnly { 303 base.Fatalf("cannot find %q in PATH", gotoolchain) 304 } 305 306 // Set up modules without an explicit go.mod, to download distribution. 307 modload.Reset() 308 modload.ForceUseModules = true 309 modload.RootMode = modload.NoRoot 310 modload.Init() 311 312 // Download and unpack toolchain module into module cache. 313 // Note that multiple go commands might be doing this at the same time, 314 // and that's OK: the module cache handles that case correctly. 315 m := module.Version{ 316 Path: gotoolchainModule, 317 Version: gotoolchainVersion + "-" + gotoolchain + "." + runtime.GOOS + "-" + runtime.GOARCH, 318 } 319 dir, err := modfetch.Download(context.Background(), m) 320 if err != nil { 321 if errors.Is(err, fs.ErrNotExist) { 322 toolVers := gover.FromToolchain(gotoolchain) 323 if gover.IsLang(toolVers) && gover.Compare(toolVers, "1.21") >= 0 { 324 base.Fatalf("invalid toolchain: %s is a language version but not a toolchain version (%s.x)", gotoolchain, gotoolchain) 325 } 326 base.Fatalf("download %s for %s/%s: toolchain not available", gotoolchain, runtime.GOOS, runtime.GOARCH) 327 } 328 base.Fatalf("download %s: %v", gotoolchain, err) 329 } 330 331 // On first use after download, set the execute bits on the commands 332 // so that we can run them. Note that multiple go commands might be 333 // doing this at the same time, but if so no harm done. 334 if runtime.GOOS != "windows" { 335 info, err := os.Stat(filepath.Join(dir, "bin/go")) 336 if err != nil { 337 base.Fatalf("download %s: %v", gotoolchain, err) 338 } 339 if info.Mode()&0111 == 0 { 340 // allowExec sets the exec permission bits on all files found in dir. 341 allowExec := func(dir string) { 342 err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { 343 if err != nil { 344 return err 345 } 346 if !d.IsDir() { 347 info, err := os.Stat(path) 348 if err != nil { 349 return err 350 } 351 if err := os.Chmod(path, info.Mode()&0777|0111); err != nil { 352 return err 353 } 354 } 355 return nil 356 }) 357 if err != nil { 358 base.Fatalf("download %s: %v", gotoolchain, err) 359 } 360 } 361 362 // Set the bits in pkg/tool before bin/go. 363 // If we are racing with another go command and do bin/go first, 364 // then the check of bin/go above might succeed, the other go command 365 // would skip its own mode-setting, and then the go command might 366 // try to run a tool before we get to setting the bits on pkg/tool. 367 // Setting pkg/tool before bin/go avoids that ordering problem. 368 // The only other tool the go command invokes is gofmt, 369 // so we set that one explicitly before handling bin (which will include bin/go). 370 allowExec(filepath.Join(dir, "pkg/tool")) 371 allowExec(filepath.Join(dir, "bin/gofmt")) 372 allowExec(filepath.Join(dir, "bin")) 373 } 374 } 375 376 srcUGoMod := filepath.Join(dir, "src/_go.mod") 377 srcGoMod := filepath.Join(dir, "src/go.mod") 378 if size(srcGoMod) != size(srcUGoMod) { 379 err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { 380 if err != nil { 381 return err 382 } 383 if path == srcUGoMod { 384 // Leave for last, in case we are racing with another go command. 385 return nil 386 } 387 if pdir, name := filepath.Split(path); name == "_go.mod" { 388 if err := raceSafeCopy(path, pdir+"go.mod"); err != nil { 389 return err 390 } 391 } 392 return nil 393 }) 394 // Handle src/go.mod; this is the signal to other racing go commands 395 // that everything is okay and they can skip this step. 396 if err == nil { 397 err = raceSafeCopy(srcUGoMod, srcGoMod) 398 } 399 if err != nil { 400 base.Fatalf("download %s: %v", gotoolchain, err) 401 } 402 } 403 404 // Reinvoke the go command. 405 execGoToolchain(gotoolchain, dir, filepath.Join(dir, "bin/go")) 406 } 407 408 func size(path string) int64 { 409 info, err := os.Stat(path) 410 if err != nil { 411 return -1 412 } 413 return info.Size() 414 } 415 416 var writeBits fs.FileMode 417 418 // raceSafeCopy copies the file old to the file new, being careful to ensure 419 // that if multiple go commands call raceSafeCopy(old, new) at the same time, 420 // they don't interfere with each other: both will succeed and return and 421 // later observe the correct content in new. Like in the build cache, we arrange 422 // this by opening new without truncation and then writing the content. 423 // Both go commands can do this simultaneously and will write the same thing 424 // (old never changes content). 425 func raceSafeCopy(old, new string) error { 426 oldInfo, err := os.Stat(old) 427 if err != nil { 428 return err 429 } 430 newInfo, err := os.Stat(new) 431 if err == nil && newInfo.Size() == oldInfo.Size() { 432 return nil 433 } 434 data, err := os.ReadFile(old) 435 if err != nil { 436 return err 437 } 438 // The module cache has unwritable directories by default. 439 // Restore the user write bit in the directory so we can create 440 // the new go.mod file. We clear it again at the end on a 441 // best-effort basis (ignoring failures). 442 dir := filepath.Dir(old) 443 info, err := os.Stat(dir) 444 if err != nil { 445 return err 446 } 447 if err := os.Chmod(dir, info.Mode()|writeBits); err != nil { 448 return err 449 } 450 defer os.Chmod(dir, info.Mode()) 451 // Note: create the file writable, so that a racing go command 452 // doesn't get an error before we store the actual data. 453 f, err := os.OpenFile(new, os.O_CREATE|os.O_WRONLY, writeBits&^0o111) 454 if err != nil { 455 // If OpenFile failed because a racing go command completed our work 456 // (and then OpenFile failed because the directory or file is now read-only), 457 // count that as a success. 458 if size(old) == size(new) { 459 return nil 460 } 461 return err 462 } 463 defer os.Chmod(new, oldInfo.Mode()) 464 if _, err := f.Write(data); err != nil { 465 f.Close() 466 return err 467 } 468 return f.Close() 469 } 470 471 // modGoToolchain finds the enclosing go.work or go.mod file 472 // and returns the go version and toolchain lines from the file. 473 // The toolchain line overrides the version line 474 func modGoToolchain() (file, goVers, toolchain string) { 475 wd := base.UncachedCwd() 476 file = modload.FindGoWork(wd) 477 // $GOWORK can be set to a file that does not yet exist, if we are running 'go work init'. 478 // Do not try to load the file in that case 479 if _, err := os.Stat(file); err != nil { 480 file = "" 481 } 482 if file == "" { 483 file = modload.FindGoMod(wd) 484 } 485 if file == "" { 486 return "", "", "" 487 } 488 489 data, err := os.ReadFile(file) 490 if err != nil { 491 base.Fatalf("%v", err) 492 } 493 return file, gover.GoModLookup(data, "go"), gover.GoModLookup(data, "toolchain") 494 } 495 496 // goInstallVersion reports whether the command line is go install m@v or go run m@v. 497 // If so, Select must not read the go.mod or go.work file in "auto" or "path" mode. 498 func goInstallVersion() bool { 499 // Note: We assume there are no flags between 'go' and 'install' or 'run'. 500 // During testing there are some debugging flags that are accepted 501 // in that position, but in production go binaries there are not. 502 if len(os.Args) < 3 { 503 return false 504 } 505 506 var cmdFlags *flag.FlagSet 507 switch os.Args[1] { 508 default: 509 // Command doesn't support a pkg@version as the main module. 510 return false 511 case "install": 512 cmdFlags = &work.CmdInstall.Flag 513 case "run": 514 cmdFlags = &run.CmdRun.Flag 515 } 516 517 // The modcachrw flag is unique, in that it affects how we fetch the 518 // requested module to even figure out what toolchain it needs. 519 // We need to actually set it before we check the toolchain version. 520 // (See https://go.dev/issue/64282.) 521 modcacherwFlag := cmdFlags.Lookup("modcacherw") 522 if modcacherwFlag == nil { 523 base.Fatalf("internal error: modcacherw flag not registered for command") 524 } 525 modcacherwVal, ok := modcacherwFlag.Value.(interface { 526 IsBoolFlag() bool 527 flag.Value 528 }) 529 if !ok || !modcacherwVal.IsBoolFlag() { 530 base.Fatalf("internal error: modcacherw is not a boolean flag") 531 } 532 533 // Make a best effort to parse the command's args to find the pkg@version 534 // argument and the -modcacherw flag. 535 var ( 536 pkgArg string 537 modcacherwSeen bool 538 ) 539 for args := os.Args[2:]; len(args) > 0; { 540 a := args[0] 541 args = args[1:] 542 if a == "--" { 543 if len(args) == 0 { 544 return false 545 } 546 pkgArg = args[0] 547 break 548 } 549 550 a, ok := strings.CutPrefix(a, "-") 551 if !ok { 552 // Not a flag argument. Must be a package. 553 pkgArg = a 554 break 555 } 556 a = strings.TrimPrefix(a, "-") // Treat --flag as -flag. 557 558 name, val, hasEq := strings.Cut(a, "=") 559 560 if name == "modcacherw" { 561 if !hasEq { 562 val = "true" 563 } 564 if err := modcacherwVal.Set(val); err != nil { 565 return false 566 } 567 modcacherwSeen = true 568 continue 569 } 570 571 if hasEq { 572 // Already has a value; don't bother parsing it. 573 continue 574 } 575 576 f := run.CmdRun.Flag.Lookup(a) 577 if f == nil { 578 // We don't know whether this flag is a boolean. 579 if os.Args[1] == "run" { 580 // We don't know where to find the pkg@version argument. 581 // For run, the pkg@version can be anywhere on the command line, 582 // because it is preceded by run flags and followed by arguments to the 583 // program being run. Since we don't know whether this flag takes 584 // an argument, we can't reliably identify the end of the run flags. 585 // Just give up and let the user clarify using the "=" form.. 586 return false 587 } 588 589 // We would like to let 'go install -newflag pkg@version' work even 590 // across a toolchain switch. To make that work, assume by default that 591 // the pkg@version is the last argument and skip the remaining args unless 592 // we spot a plausible "-modcacherw" flag. 593 for len(args) > 0 { 594 a := args[0] 595 name, _, _ := strings.Cut(a, "=") 596 if name == "-modcacherw" || name == "--modcacherw" { 597 break 598 } 599 if len(args) == 1 && !strings.HasPrefix(a, "-") { 600 pkgArg = a 601 } 602 args = args[1:] 603 } 604 continue 605 } 606 607 if bf, ok := f.Value.(interface{ IsBoolFlag() bool }); !ok || !bf.IsBoolFlag() { 608 // The next arg is the value for this flag. Skip it. 609 args = args[1:] 610 continue 611 } 612 } 613 614 if !strings.Contains(pkgArg, "@") || build.IsLocalImport(pkgArg) || filepath.IsAbs(pkgArg) { 615 return false 616 } 617 path, version, _ := strings.Cut(pkgArg, "@") 618 if path == "" || version == "" || gover.IsToolchain(path) { 619 return false 620 } 621 622 if !modcacherwSeen && base.InGOFLAGS("-modcacherw") { 623 fs := flag.NewFlagSet("goInstallVersion", flag.ExitOnError) 624 fs.Var(modcacherwVal, "modcacherw", modcacherwFlag.Usage) 625 base.SetFromGOFLAGS(fs) 626 } 627 628 // It would be correct to simply return true here, bypassing use 629 // of the current go.mod or go.work, and let "go run" or "go install" 630 // do the rest, including a toolchain switch. 631 // Our goal instead is, since we have gone to the trouble of handling 632 // unknown flags to some degree, to run the switch now, so that 633 // these commands can switch to a newer toolchain directed by the 634 // go.mod which may actually understand the flag. 635 // This was brought up during the go.dev/issue/57001 proposal discussion 636 // and may end up being common in self-contained "go install" or "go run" 637 // command lines if we add new flags in the future. 638 639 // Set up modules without an explicit go.mod, to download go.mod. 640 modload.ForceUseModules = true 641 modload.RootMode = modload.NoRoot 642 modload.Init() 643 defer modload.Reset() 644 645 // See internal/load.PackagesAndErrorsOutsideModule 646 ctx := context.Background() 647 allowed := modload.CheckAllowed 648 if modload.IsRevisionQuery(path, version) { 649 // Don't check for retractions if a specific revision is requested. 650 allowed = nil 651 } 652 noneSelected := func(path string) (version string) { return "none" } 653 _, err := modload.QueryPackages(ctx, path, version, noneSelected, allowed) 654 if errors.Is(err, gover.ErrTooNew) { 655 // Run early switch, same one go install or go run would eventually do, 656 // if it understood all the command-line flags. 657 SwitchOrFatal(ctx, err) 658 } 659 660 return true // pkg@version found 661 } 662