cmd/go: inject State parameter into toolchain.Select

This command modifies the call tree starting at `toolchain.Select` to
inject a `State` parameter to every function that is currently using
the global `modload.LoaderState` variable.  By explicilty passing a
`State` parameter, we can begin to eliminate the usage of the global
`modload.LoaderState`.

This commit is part of the overall effort to eliminate global
modloader state.

[git-generate]
cd src/cmd/go/internal/toolchain
rf '
  inject modload.LoaderState Select
  add select.go var moduleLoaderState *modload.State
  ex {
    import "cmd/go/internal/modload";
    modload.LoaderState -> moduleLoaderState
  }
  add Select://+0 moduleLoaderState := modload.NewState()
  rm select.go:/var moduleLoaderState \*modload.State/
'
cd ..
./rf-cleanup.zsh

Change-Id: I759439a47e2b1aaa01a0a800bc18596dd7ce4983
Reviewed-on: https://go-review.googlesource.com/c/go/+/709988
Reviewed-by: Michael Matloob <matloob@google.com>
Reviewed-by: Michael Matloob <matloob@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Ian Alexander 2025-10-02 21:45:53 -04:00
parent 4dc3dd9a86
commit e7c66a58d5
9 changed files with 30 additions and 29 deletions

View file

@ -158,7 +158,7 @@ func runDownload(ctx context.Context, cmd *base.Command, args []string) {
// TODO(#64008): call base.Fatalf instead of toolchain.SwitchOrFatal // TODO(#64008): call base.Fatalf instead of toolchain.SwitchOrFatal
// here, since we can only reach this point with an outdated toolchain // here, since we can only reach this point with an outdated toolchain
// if the go.mod file is inconsistent. // if the go.mod file is inconsistent.
toolchain.SwitchOrFatal(ctx, err) toolchain.SwitchOrFatal(modload.LoaderState, ctx, err)
} }
for _, m := range modFile.Require { for _, m := range modFile.Require {

View file

@ -62,7 +62,7 @@ func runGraph(ctx context.Context, cmd *base.Command, args []string) {
goVersion := graphGo.String() goVersion := graphGo.String()
if goVersion != "" && gover.Compare(gover.Local(), goVersion) < 0 { if goVersion != "" && gover.Compare(gover.Local(), goVersion) < 0 {
toolchain.SwitchOrFatal(ctx, &gover.TooNewError{ toolchain.SwitchOrFatal(modload.LoaderState, ctx, &gover.TooNewError{
What: "-go flag", What: "-go flag",
GoVersion: goVersion, GoVersion: goVersion,
}) })

View file

@ -124,7 +124,7 @@ func runTidy(ctx context.Context, cmd *base.Command, args []string) {
goVersion := tidyGo.String() goVersion := tidyGo.String()
if goVersion != "" && gover.Compare(gover.Local(), goVersion) < 0 { if goVersion != "" && gover.Compare(gover.Local(), goVersion) < 0 {
toolchain.SwitchOrFatal(ctx, &gover.TooNewError{ toolchain.SwitchOrFatal(modload.LoaderState, ctx, &gover.TooNewError{
What: "-go flag", What: "-go flag",
GoVersion: goVersion, GoVersion: goVersion,
}) })

View file

@ -418,7 +418,7 @@ func runGet(ctx context.Context, cmd *base.Command, args []string) {
// but the command line is not. // but the command line is not.
// TODO(bcmills): modload.EditBuildList should catch this instead, // TODO(bcmills): modload.EditBuildList should catch this instead,
// and then this can be changed to base.Fatal(err). // and then this can be changed to base.Fatal(err).
toolchain.SwitchOrFatal(ctx, err) toolchain.SwitchOrFatal(modload.LoaderState, ctx, err)
} }
newReqs := reqsFromGoMod(modload.ModFile()) newReqs := reqsFromGoMod(modload.ModFile())
@ -558,7 +558,7 @@ func newResolver(ctx context.Context, queries []*query) *resolver {
// methods. // methods.
mg, err := modload.LoadModGraph(ctx, "") mg, err := modload.LoadModGraph(ctx, "")
if err != nil { if err != nil {
toolchain.SwitchOrFatal(ctx, err) toolchain.SwitchOrFatal(modload.LoaderState, ctx, err)
} }
buildList := mg.BuildList() buildList := mg.BuildList()
@ -1619,7 +1619,7 @@ func (r *resolver) checkPackageProblems(ctx context.Context, pkgPatterns []strin
// are old enough but the go command itself is not new // are old enough but the go command itself is not new
// enough. See the related comment on the SwitchOrFatal // enough. See the related comment on the SwitchOrFatal
// in runGet when WriteGoMod returns an error. // in runGet when WriteGoMod returns an error.
toolchain.SwitchOrFatal(ctx, err) toolchain.SwitchOrFatal(modload.LoaderState, ctx, err)
} }
} }
@ -1989,7 +1989,7 @@ func (r *resolver) updateBuildList(ctx context.Context, additions []module.Versi
changed, err := modload.EditBuildList(modload.LoaderState, ctx, additions, resolved) changed, err := modload.EditBuildList(modload.LoaderState, ctx, additions, resolved)
if err != nil { if err != nil {
if errors.Is(err, gover.ErrTooNew) { if errors.Is(err, gover.ErrTooNew) {
toolchain.SwitchOrFatal(ctx, err) toolchain.SwitchOrFatal(modload.LoaderState, ctx, err)
} }
constraint, ok := errors.AsType[*modload.ConstraintError](err) constraint, ok := errors.AsType[*modload.ConstraintError](err)
@ -2035,7 +2035,7 @@ func (r *resolver) updateBuildList(ctx context.Context, additions []module.Versi
mg, err := modload.LoadModGraph(ctx, "") mg, err := modload.LoadModGraph(ctx, "")
if err != nil { if err != nil {
toolchain.SwitchOrFatal(ctx, err) toolchain.SwitchOrFatal(modload.LoaderState, ctx, err)
} }
r.buildList = mg.BuildList() r.buildList = mg.BuildList()

View file

@ -570,12 +570,12 @@ func Init(loaderstate *State) {
// of 'go get', but Init reads the -modfile flag in 'go get', so it shouldn't // of 'go get', but Init reads the -modfile flag in 'go get', so it shouldn't
// be called until the command is installed and flags are parsed. Instead of // be called until the command is installed and flags are parsed. Instead of
// calling Init and Enabled, the main package can call this function. // calling Init and Enabled, the main package can call this function.
func WillBeEnabled() bool { func WillBeEnabled(loaderstate *State) bool {
if LoaderState.modRoots != nil || cfg.ModulesEnabled { if loaderstate.modRoots != nil || cfg.ModulesEnabled {
// Already enabled. // Already enabled.
return true return true
} }
if LoaderState.initialized { if loaderstate.initialized {
// Initialized, not enabled. // Initialized, not enabled.
return false return false
} }

View file

@ -22,7 +22,7 @@ func Increment() {
// incrementConfig increments counters for the configuration // incrementConfig increments counters for the configuration
// the command is running in. // the command is running in.
func incrementConfig() { func incrementConfig() {
if !modload.WillBeEnabled() { if !modload.WillBeEnabled(modload.LoaderState) {
counter.Inc("go/mode:gopath") counter.Inc("go/mode:gopath")
} else if workfile := modload.FindGoWork(modload.LoaderState, base.Cwd()); workfile != "" { } else if workfile := modload.FindGoWork(modload.LoaderState, base.Cwd()); workfile != "" {
counter.Inc("go/mode:workspace") counter.Inc("go/mode:workspace")

View file

@ -95,10 +95,11 @@ var toolchainTrace = godebug.New("#toolchaintrace").Value() == "1"
// It must be called early in startup. // It must be called early in startup.
// See https://go.dev/doc/toolchain#select. // See https://go.dev/doc/toolchain#select.
func Select() { func Select() {
moduleLoaderState := modload.NewState()
log.SetPrefix("go: ") log.SetPrefix("go: ")
defer log.SetPrefix("") defer log.SetPrefix("")
if !modload.WillBeEnabled() { if !modload.WillBeEnabled(moduleLoaderState) {
return return
} }
@ -171,7 +172,7 @@ func Select() {
gotoolchain = minToolchain gotoolchain = minToolchain
if mode == "auto" || mode == "path" { if mode == "auto" || mode == "path" {
// Read go.mod to find new minimum and suggested toolchain. // Read go.mod to find new minimum and suggested toolchain.
file, goVers, toolchain := modGoToolchain() file, goVers, toolchain := modGoToolchain(moduleLoaderState)
gover.Startup.AutoFile = file gover.Startup.AutoFile = file
if toolchain == "default" { if toolchain == "default" {
// "default" means always use the default toolchain, // "default" means always use the default toolchain,
@ -231,7 +232,7 @@ func Select() {
} }
} }
} }
maybeSwitchForGoInstallVersion(minVers) maybeSwitchForGoInstallVersion(moduleLoaderState, minVers)
} }
// If we are invoked as a target toolchain, confirm that // If we are invoked as a target toolchain, confirm that
@ -283,7 +284,7 @@ func Select() {
} }
counterSelectExec.Inc() counterSelectExec.Inc()
Exec(modload.LoaderState, gotoolchain) Exec(moduleLoaderState, gotoolchain)
} }
var counterSelectExec = counter.New("go/toolchain/select-exec") var counterSelectExec = counter.New("go/toolchain/select-exec")
@ -527,9 +528,9 @@ func raceSafeCopy(old, new string) error {
// modGoToolchain finds the enclosing go.work or go.mod file // modGoToolchain finds the enclosing go.work or go.mod file
// and returns the go version and toolchain lines from the file. // and returns the go version and toolchain lines from the file.
// The toolchain line overrides the version line // The toolchain line overrides the version line
func modGoToolchain() (file, goVers, toolchain string) { func modGoToolchain(loaderstate *modload.State) (file, goVers, toolchain string) {
wd := base.UncachedCwd() wd := base.UncachedCwd()
file = modload.FindGoWork(modload.LoaderState, wd) file = modload.FindGoWork(loaderstate, wd)
// $GOWORK can be set to a file that does not yet exist, if we are running 'go work init'. // $GOWORK can be set to a file that does not yet exist, if we are running 'go work init'.
// Do not try to load the file in that case // Do not try to load the file in that case
if _, err := os.Stat(file); err != nil { if _, err := os.Stat(file); err != nil {
@ -551,7 +552,7 @@ func modGoToolchain() (file, goVers, toolchain string) {
// maybeSwitchForGoInstallVersion reports whether the command line is go install m@v or go run m@v. // maybeSwitchForGoInstallVersion reports whether the command line is go install m@v or go run m@v.
// If so, switch to the go version required to build m@v if it's higher than minVers. // If so, switch to the go version required to build m@v if it's higher than minVers.
func maybeSwitchForGoInstallVersion(minVers string) { func maybeSwitchForGoInstallVersion(loaderstate *modload.State, minVers string) {
// Note: We assume there are no flags between 'go' and 'install' or 'run'. // Note: We assume there are no flags between 'go' and 'install' or 'run'.
// During testing there are some debugging flags that are accepted // During testing there are some debugging flags that are accepted
// in that position, but in production go binaries there are not. // in that position, but in production go binaries there are not.
@ -692,10 +693,10 @@ func maybeSwitchForGoInstallVersion(minVers string) {
// command lines if we add new flags in the future. // command lines if we add new flags in the future.
// Set up modules without an explicit go.mod, to download go.mod. // Set up modules without an explicit go.mod, to download go.mod.
modload.LoaderState.ForceUseModules = true loaderstate.ForceUseModules = true
modload.LoaderState.RootMode = modload.NoRoot loaderstate.RootMode = modload.NoRoot
modload.Init(modload.LoaderState) modload.Init(loaderstate)
defer modload.LoaderState.Reset() defer loaderstate.Reset()
// See internal/load.PackagesAndErrorsOutsideModule // See internal/load.PackagesAndErrorsOutsideModule
ctx := context.Background() ctx := context.Background()
@ -705,14 +706,14 @@ func maybeSwitchForGoInstallVersion(minVers string) {
allowed = nil allowed = nil
} }
noneSelected := func(path string) (version string) { return "none" } noneSelected := func(path string) (version string) { return "none" }
_, err := modload.QueryPackages(modload.LoaderState, ctx, path, version, noneSelected, allowed) _, err := modload.QueryPackages(loaderstate, ctx, path, version, noneSelected, allowed)
if errors.Is(err, gover.ErrTooNew) { if errors.Is(err, gover.ErrTooNew) {
// Run early switch, same one go install or go run would eventually do, // Run early switch, same one go install or go run would eventually do,
// if it understood all the command-line flags. // if it understood all the command-line flags.
s := NewSwitcher(modload.LoaderState) s := NewSwitcher(loaderstate)
s.Error(err) s.Error(err)
if s.TooNew != nil && gover.Compare(s.TooNew.GoVersion, minVers) > 0 { if s.TooNew != nil && gover.Compare(s.TooNew.GoVersion, minVers) > 0 {
SwitchOrFatal(ctx, err) SwitchOrFatal(loaderstate, ctx, err)
} }
} }
} }

View file

@ -116,8 +116,8 @@ var counterSwitchExec = counter.New("go/toolchain/switch-exec")
// SwitchOrFatal attempts a toolchain switch based on the information in err // SwitchOrFatal attempts a toolchain switch based on the information in err
// and otherwise falls back to base.Fatal(err). // and otherwise falls back to base.Fatal(err).
func SwitchOrFatal(ctx context.Context, err error) { func SwitchOrFatal(loaderstate *modload.State, ctx context.Context, err error) {
s := NewSwitcher(modload.LoaderState) s := NewSwitcher(loaderstate)
s.Error(err) s.Error(err)
s.Switch(ctx) s.Switch(ctx)
base.Exit() base.Exit()

View file

@ -56,7 +56,7 @@ func runSync(ctx context.Context, cmd *base.Command, args []string) {
_, err := modload.LoadModGraph(ctx, "") _, err := modload.LoadModGraph(ctx, "")
if err != nil { if err != nil {
toolchain.SwitchOrFatal(ctx, err) toolchain.SwitchOrFatal(modload.LoaderState, ctx, err)
} }
mustSelectFor := map[module.Version][]module.Version{} mustSelectFor := map[module.Version][]module.Version{}