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