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