//
// Usage:
//
-// go work use [-r] moddirs
+// go work use [-r] [moddirs]
//
// Use provides a command-line interface for adding
// directories, optionally recursively, to a go.work file.
//
// A use directive will be added to the go.work file for each argument
-// directory listed on the command line go.work file, if it exists on disk,
-// or removed from the go.work file if it does not exist on disk.
+// directory listed on the command line go.work file, if it exists,
+// or removed from the go.work file if it does not exist.
+// Use fails if any remaining use directives refer to modules that
+// do not exist.
+//
+// Use updates the go line in go.work to specify a version at least as
+// new as all the go lines in the used modules, both preexisting ones
+// and newly added ones. With no arguments, this update is the only
+// thing that go work use does.
//
// The -r flag searches recursively for modules in the argument
// directories, and the use command operates as if each of the directories
import (
"cmd/go/internal/base"
+ "context"
"errors"
"fmt"
"strings"
func (e *TooNewError) Is(err error) bool {
return err == ErrTooNew
}
+
+// A Switcher provides the ability to switch to a new toolchain in response to TooNewErrors.
+// See [cmd/go/internal/toolchain.Switcher] for documentation.
+type Switcher interface {
+ Error(err error)
+ Switch(ctx context.Context)
+}
package gover
+import "golang.org/x/mod/modfile"
+
const (
// narrowAllVersion is the Go version at which the
// module-module "all" pattern no longer closes over the dependencies of
// It is also the version after which "too new" a version is considered a fatal error.
GoStrictVersion = "1.21"
)
+
+// FromGoMod returns the go version from the go.mod file.
+// It returns DefaultGoModVersion if the go.mod file does not contain a go line or if mf is nil.
+func FromGoMod(mf *modfile.File) string {
+ if mf == nil || mf.Go == nil {
+ return DefaultGoModVersion
+ }
+ return mf.Go.Version
+}
+
+// FromGoWork returns the go version from the go.mod file.
+// It returns DefaultGoWorkVersion if the go.mod file does not contain a go line or if wf is nil.
+func FromGoWork(wf *modfile.WorkFile) string {
+ if wf == nil || wf.Go == nil {
+ return DefaultGoWorkVersion
+ }
+ return wf.Go.Version
+}
goVersion := graphGo.String()
if goVersion != "" && gover.Compare(gover.Local(), goVersion) < 0 {
- toolchain.TryVersion(ctx, goVersion)
- base.Fatal(&gover.TooNewError{
+ toolchain.SwitchOrFatal(ctx, &gover.TooNewError{
What: "-go flag",
GoVersion: goVersion,
})
goVersion := tidyGo.String()
if goVersion != "" && gover.Compare(gover.Local(), goVersion) < 0 {
- toolchain.TryVersion(ctx, goVersion)
- base.Fatal(&gover.TooNewError{
+ toolchain.SwitchOrFatal(ctx, &gover.TooNewError{
What: "-go flag",
GoVersion: goVersion,
})
LoadTests: true,
AllowErrors: tidyE,
SilenceMissingStdImports: true,
- TrySwitchToolchain: toolchain.TryVersion,
+ Switcher: new(toolchain.Switcher),
}, "all")
}
oldReqs := reqsFromGoMod(modload.ModFile())
if err := modload.WriteGoMod(ctx, opts); err != nil {
- if tooNew := (*gover.TooNewError)(nil); errors.As(err, &tooNew) {
- // This can happen for 'go get go@newversion'
- // when all the required modules are old enough
- // but the command line is not.
- // TODO(bcmills): modload.EditBuildList should catch this instead.
- toolchain.TryVersion(ctx, tooNew.GoVersion)
- }
- base.Fatal(err)
+ // A TooNewError can happen for 'go get go@newversion'
+ // when all the required modules are old enough
+ // but the command line is not.
+ // TODO(bcmills): modload.EditBuildList should catch this instead,
+ // and then this can be changed to base.Fatal(err).
+ toolchain.SwitchOrFatal(ctx, err)
}
newReqs := reqsFromGoMod(modload.ModFile())
r.reportChanges(oldReqs, newReqs)
+
+ if gowork := modload.FindGoWork(base.Cwd()); gowork != "" {
+ wf, err := modload.ReadWorkFile(gowork)
+ if err == nil && modload.UpdateWorkGoVersion(wf, modload.MainModules.GoVersion()) {
+ modload.WriteWorkFile(gowork, wf)
+ }
+ }
}
// parseArgs parses command-line arguments and reports errors.
// methods.
mg, err := modload.LoadModGraph(ctx, "")
if err != nil {
- if tooNew := (*gover.TooNewError)(nil); errors.As(err, &tooNew) {
- toolchain.TryVersion(ctx, tooNew.GoVersion)
- }
- base.Fatal(err)
+ toolchain.SwitchOrFatal(ctx, err)
}
buildList := mg.BuildList()
LoadTests: *getT,
AssumeRootsImported: true, // After 'go get foo', imports of foo should build.
SilencePackageErrors: true, // May be fixed by subsequent upgrades or downgrades.
- TrySwitchToolchain: toolchain.TryVersion,
+ Switcher: new(toolchain.Switcher),
}
opts.AllowPackage = func(ctx context.Context, path string, m module.Version) error {
// If we found modules that were too new, find the max of the required versions
// and then try to switch to a newer toolchain.
- goVers := ""
+ var sw toolchain.Switcher
for _, q := range queries {
for _, cs := range q.candidates {
- if e := (*gover.TooNewError)(nil); errors.As(cs.err, &e) {
- goVers = gover.Max(goVers, e.GoVersion)
- }
+ sw.Error(cs.err)
}
}
- if goVers != "" {
- toolchain.TryVersion(ctx, goVers)
+ // Only switch if we need a newer toolchain.
+ // Otherwise leave the cs.err for reporting later.
+ if sw.NeedSwitch() {
+ sw.Switch(ctx)
+ // If NeedSwitch is true and Switch returns, Switch has failed to locate a newer toolchain.
+ // It printed the errors along with one more about not finding a good toolchain.
+ base.Exit()
}
for _, q := range queries {
changed, err := modload.EditBuildList(ctx, additions, resolved)
if err != nil {
- if tooNew := (*gover.TooNewError)(nil); errors.As(err, &tooNew) {
- toolchain.TryVersion(ctx, tooNew.GoVersion)
- base.Fatal(err)
+ if errors.Is(err, gover.ErrTooNew) {
+ toolchain.SwitchOrFatal(ctx, err)
}
var constraint *modload.ConstraintError
mg, err := modload.LoadModGraph(ctx, "")
if err != nil {
- if tooNew := (*gover.TooNewError)(nil); errors.As(err, &tooNew) {
- toolchain.TryVersion(ctx, tooNew.GoVersion)
- }
- base.Fatal(err)
+ toolchain.SwitchOrFatal(ctx, err)
}
r.buildList = mg.BuildList()
// or the go.work file in workspace mode.
func (mms *MainModuleSet) GoVersion() string {
if inWorkspaceMode() {
- if mms.workFile != nil && mms.workFile.Go != nil {
- return mms.workFile.Go.Version
- }
- return gover.DefaultGoWorkVersion
+ return gover.FromGoWork(mms.workFile)
}
if mms != nil && len(mms.versions) == 1 {
f := mms.ModFile(mms.mustGetSingleMainModule())
// TODO(#49228): Clean this up; see loadModFile.
return gover.Local()
}
- if f.Go != nil {
- return f.Go.Version
- }
+ return gover.FromGoMod(f)
}
return gover.DefaultGoModVersion
}
return os.WriteFile(path, out, 0666)
}
+// UpdateWorkGoVersion updates the go line in wf to be at least goVers,
+// reporting whether it changed the file.
+func UpdateWorkGoVersion(wf *modfile.WorkFile, goVers string) (changed bool) {
+ old := gover.FromGoWork(wf)
+ if gover.Compare(old, goVers) >= 0 {
+ return false
+ }
+
+ wf.AddGoStmt(goVers)
+
+ // We wrote a new go line. For reproducibility,
+ // if the toolchain running right now is newer than the new toolchain line,
+ // update the toolchain line to record the newer toolchain.
+ // The user never sets the toolchain explicitly in a 'go work' command,
+ // so this is only happening as a result of a go or toolchain line found
+ // in a module.
+ // If the toolchain running right now is a dev toolchain (like "go1.21")
+ // writing 'toolchain go1.21' will not be useful, since that's not an actual
+ // toolchain you can download and run. In that case fall back to at least
+ // checking that the toolchain is new enough for the Go version.
+ toolchain := "go" + old
+ if wf.Toolchain != nil {
+ toolchain = wf.Toolchain.Name
+ }
+ if gover.IsLang(gover.Local()) {
+ toolchain = gover.ToolchainMax(toolchain, "go"+goVers)
+ } else {
+ toolchain = gover.ToolchainMax(toolchain, "go"+gover.Local())
+ }
+
+ // Drop the toolchain line if it is implied by the go line
+ // or if it is asking for a toolchain older than Go 1.21,
+ // which will not understand the toolchain line.
+ if toolchain == "go"+goVers || gover.Compare(gover.FromToolchain(toolchain), gover.GoStrictVersion) < 0 {
+ wf.DropToolchainStmt()
+ } else {
+ wf.AddToolchainStmt(toolchain)
+ }
+ return true
+}
+
// UpdateWorkFile updates comments on directory directives in the go.work
// file to include the associated module path.
func UpdateWorkFile(wf *modfile.WorkFile) {
data, f, err := ReadModFile(gomod, fixVersion(ctx, &fixed))
if err != nil {
if inWorkspaceMode() {
- err = fmt.Errorf("cannot load module %s listed in go.work file: %w", base.ShortPath(gomod), err)
+ if tooNew, ok := err.(*gover.TooNewError); ok && !strings.HasPrefix(cfg.CmdName, "work ") {
+ // Switching to a newer toolchain won't help - the go.work has the wrong version.
+ // Report this more specific error, unless we are a command like 'go work use'
+ // or 'go work sync', which will fix the problem after the caller sees the TooNewError
+ // and switches to a newer toolchain.
+ err = errWorkTooOld(gomod, workFile, tooNew.GoVersion)
+ } else {
+ err = fmt.Errorf("cannot load module %s listed in go.work file: %w",
+ base.ShortPath(filepath.Dir(gomod)), err)
+ }
}
errs = append(errs, err)
continue
}
+ if inWorkspaceMode() && !strings.HasPrefix(cfg.CmdName, "work ") {
+ // Refuse to use workspace if its go version is too old.
+ // Disable this check if we are a workspace command like work use or work sync,
+ // which will fix the problem.
+ mv := gover.FromGoMod(f)
+ wv := gover.FromGoWork(workFile)
+ if gover.Compare(mv, wv) > 0 && gover.Compare(mv, gover.GoStrictVersion) >= 0 {
+ errs = append(errs, errWorkTooOld(gomod, workFile, mv))
+ continue
+ }
+ }
modFiles = append(modFiles, f)
mainModule := f.Module.Mod
return requirements, nil
}
+func errWorkTooOld(gomod string, wf *modfile.WorkFile, goVers string) error {
+ return fmt.Errorf("module %s listed in go.work file requires go >= %s, but go.work lists go %s; to update it:\n\tgo work use",
+ base.ShortPath(filepath.Dir(gomod)), goVers, gover.FromGoWork(wf))
+}
+
// CreateModFile initializes a new module by creating a go.mod file.
//
// If modPath is empty, CreateModFile will attempt to infer the path from the
pruning = workspace
roots = make([]module.Version, len(MainModules.Versions()), 2+len(MainModules.Versions()))
copy(roots, MainModules.Versions())
- if workFile.Go != nil {
- goVersion = workFile.Go.Version
- }
+ goVersion = gover.FromGoWork(workFile)
if workFile.Toolchain != nil {
toolchain = workFile.Toolchain.Name
}
direct[r.Mod.Path] = true
}
}
- if modFile.Go != nil {
- goVersion = modFile.Go.Version
- }
+ goVersion = gover.FromGoMod(modFile)
if modFile.Toolchain != nil {
toolchain = modFile.Toolchain.Name
}
}
// Add explicit go and toolchain versions, inferring as needed.
- if goVersion == "" {
- goVersion = gover.DefaultGoModVersion
- }
roots = append(roots, module.Version{Path: "go", Version: goVersion})
direct["go"] = true // Every module directly uses the language and runtime.
// Resolve the query against this module.
MainModule module.Version
- // TrySwitchToolchain, if non-nil, attempts to reinvoke a toolchain capable of
- // handling the given Go version.
- //
- // TrySwitchToolchain only returns if the attempt toswitch was unsuccessful.
- TrySwitchToolchain func(ctx context.Context, version string)
+ // If Switcher is non-nil, then LoadPackages passes all encountered errors
+ // to Switcher.Error and tries Switcher.Switch before base.ExitIfErrors.
+ Switcher gover.Switcher
}
// LoadPackages identifies the set of packages matching the given patterns and
if !ld.SilencePackageErrors {
for _, match := range matches {
for _, err := range match.Errs {
- ld.errorf("%v\n", err)
+ ld.error(err)
}
}
}
- base.ExitIfErrors()
+ ld.exitIfErrors(ctx)
if !opts.SilenceUnmatchedWarnings {
search.WarnUnmatched(matches)
ld.pkgs = nil
}
-// errorf reports an error via either os.Stderr or base.Errorf,
+// error reports an error via either os.Stderr or base.Error,
// according to whether ld.AllowErrors is set.
-func (ld *loader) errorf(format string, args ...any) {
+func (ld *loader) error(err error) {
if ld.AllowErrors {
- fmt.Fprintf(os.Stderr, format, args...)
+ fmt.Fprintf(os.Stderr, "go: %v\n", err)
+ } else if ld.Switcher != nil {
+ ld.Switcher.Error(err)
} else {
- base.Errorf(format, args...)
+ base.Error(err)
+ }
+}
+
+// switchIfErrors switches toolchains if a switch is needed.
+func (ld *loader) switchIfErrors(ctx context.Context) {
+ if ld.Switcher != nil {
+ ld.Switcher.Switch(ctx)
}
}
+// exitIfErrors switches toolchains if a switch is needed
+// or else exits if any errors have been reported.
+func (ld *loader) exitIfErrors(ctx context.Context) {
+ ld.switchIfErrors(ctx)
+ base.ExitIfErrors()
+}
+
// goVersion reports the Go version that should be used for the loader's
// requirements: ld.TidyGoVersion if set, or ld.requirements.GoVersion()
// otherwise.
return ld.requirements.GoVersion()
}
-func (ld *loader) maybeTryToolchain(ctx context.Context, err error) {
- if ld.TrySwitchToolchain == nil {
- return
- }
- var tooNew *gover.TooNewError
- if !errors.As(err, &tooNew) {
- return
- }
- ld.TrySwitchToolchain(ctx, tooNew.GoVersion)
-}
-
// A loadPkg records information about a single loaded package.
type loadPkg struct {
// Populated at construction time:
var err error
ld.requirements, _, err = expandGraph(ctx, ld.requirements)
if err != nil {
- ld.maybeTryToolchain(ctx, err)
- ld.errorf("go: %v\n", err)
+ ld.error(err)
}
}
- base.ExitIfErrors() // or we will report them again
+ ld.exitIfErrors(ctx)
updateGoVersion := func() {
goVersion := ld.goVersion()
var err error
ld.requirements, err = convertPruning(ctx, ld.requirements, pruningForGoVersion(goVersion))
if err != nil {
- ld.maybeTryToolchain(ctx, err)
- ld.errorf("go: %v\n", err)
- base.ExitIfErrors()
+ ld.error(err)
+ ld.exitIfErrors(ctx)
}
}
changed, err := ld.updateRequirements(ctx)
if err != nil {
- ld.maybeTryToolchain(ctx, err)
- ld.errorf("go: %v\n", err)
+ ld.error(err)
break
}
if changed {
modAddedBy, err := ld.resolveMissingImports(ctx)
if err != nil {
- ld.maybeTryToolchain(ctx, err)
- ld.errorf("go: %v\n", err)
+ ld.error(err)
break
}
if len(modAddedBy) == 0 {
direct := ld.requirements.direct
rs, err := updateRoots(ctx, direct, ld.requirements, noPkgs, toAdd, ld.AssumeRootsImported)
if err != nil {
- ld.maybeTryToolchain(ctx, err)
// If an error was found in a newly added module, report the package
// import stack instead of the module requirement stack. Packages
// are more descriptive.
if err, ok := err.(*mvs.BuildListError); ok {
if pkg := modAddedBy[err.Module()]; pkg != nil {
- ld.errorf("go: %s: %v\n", pkg.stackText(), err.Err)
+ ld.error(fmt.Errorf("%s: %w", pkg.stackText(), err.Err))
break
}
}
- ld.errorf("go: %v\n", err)
+ ld.error(err)
break
}
if reflect.DeepEqual(rs.rootModules, ld.requirements.rootModules) {
}
ld.requirements = rs
}
- base.ExitIfErrors()
+ ld.exitIfErrors(ctx)
// Tidy the build list, if applicable, before we report errors.
// (The process of tidying may remove errors from irrelevant dependencies.)
if ld.Tidy {
rs, err := tidyRoots(ctx, ld.requirements, ld.pkgs)
if err != nil {
- ld.errorf("go: %v\n", err)
+ ld.error(err)
} else {
if ld.TidyGoVersion != "" {
// Attempt to switch to the requested Go version. We have been using its
tidy := overrideRoots(ctx, rs, []module.Version{{Path: "go", Version: ld.TidyGoVersion}})
mg, err := tidy.Graph(ctx)
if err != nil {
- ld.errorf("go: %v\n", err)
+ ld.error(err)
}
if v := mg.Selected("go"); v == ld.TidyGoVersion {
rs = tidy
if cfg.BuildV {
msg = conflict.String()
}
- ld.errorf("go: %v\n", msg)
+ ld.error(errors.New(msg))
}
}
continue
}
if v, ok := ld.requirements.rootSelected(m.Path); !ok || v != m.Version {
- ld.errorf("go: internal error: a requirement on %v is needed but was not added during package loading (selected %s)\n", m, v)
+ ld.error(fmt.Errorf("internal error: a requirement on %v is needed but was not added during package loading (selected %s)", m, v))
}
}
}
ld.requirements = rs
}
- base.ExitIfErrors()
+ ld.exitIfErrors(ctx)
}
// Report errors, if any.
continue
}
- ld.errorf("%s: %v\n", pkg.stackText(), pkg.err)
+ ld.error(fmt.Errorf("%s: %w", pkg.stackText(), pkg.err))
}
ld.checkMultiplePaths()
rs, err := updateRoots(ctx, ld.requirements.direct, ld.requirements, nil, toAdd, ld.AssumeRootsImported)
if err != nil {
- ld.maybeTryToolchain(ctx, err)
// We are missing some root dependency, and for some reason we can't load
// enough of the module dependency graph to add the missing root. Package
// loading is doomed to fail, so fail quickly.
- ld.errorf("go: %v\n", err)
- base.ExitIfErrors()
+ ld.error(err)
+ ld.exitIfErrors(ctx)
return false
}
if reflect.DeepEqual(rs.rootModules, ld.requirements.rootModules) {
if prev, ok := firstPath[src]; !ok {
firstPath[src] = mod.Path
} else if prev != mod.Path {
- ld.errorf("go: %s@%s used for two different module paths (%s and %s)\n", src.Path, src.Version, prev, mod.Path)
+ ld.error(fmt.Errorf("%s@%s used for two different module paths (%s and %s)", src.Path, src.Version, prev, mod.Path))
}
}
}
mg, err := rs.Graph(ctx)
if err != nil {
- ld.maybeTryToolchain(ctx, err)
- ld.errorf("go: error loading go %s module graph: %v\n", compatVersion, err)
+ ld.error(fmt.Errorf("error loading go %s module graph: %w", compatVersion, err))
+ ld.switchIfErrors(ctx)
suggestFixes()
+ ld.exitIfErrors(ctx)
return
}
Path: pkg.mod.Path,
Version: mg.Selected(pkg.mod.Path),
}
- ld.errorf("%s loaded from %v,\n\tbut go %s would fail to locate it in %s\n", pkg.stackText(), pkg.mod, compatVersion, selected)
+ ld.error(fmt.Errorf("%s loaded from %v,\n\tbut go %s would fail to locate it in %s", pkg.stackText(), pkg.mod, compatVersion, selected))
} else {
if ambiguous := (*AmbiguousImportError)(nil); errors.As(mismatch.err, &ambiguous) {
// TODO: Is this check needed?
}
- ld.errorf("%s loaded from %v,\n\tbut go %s would fail to locate it:\n\t%v\n", pkg.stackText(), pkg.mod, compatVersion, mismatch.err)
+ ld.error(fmt.Errorf("%s loaded from %v,\n\tbut go %s would fail to locate it:\n\t%v", pkg.stackText(), pkg.mod, compatVersion, mismatch.err))
}
suggestEFlag = true
// pkg.err should have already been logged elsewhere — along with a
// stack trace — so log only the import path and non-error info here.
suggestUpgrade = true
- ld.errorf("%s failed to load from any module,\n\tbut go %s would load it from %v\n", pkg.path, compatVersion, mismatch.mod)
+ ld.error(fmt.Errorf("%s failed to load from any module,\n\tbut go %s would load it from %v", pkg.path, compatVersion, mismatch.mod))
case pkg.mod != mismatch.mod:
// The package is loaded successfully by both Go versions, but from a
// unnoticed!) variations in behavior between builds with different
// toolchains.
suggestUpgrade = true
- ld.errorf("%s loaded from %v,\n\tbut go %s would select %v\n", pkg.stackText(), pkg.mod, compatVersion, mismatch.mod.Version)
+ ld.error(fmt.Errorf("%s loaded from %v,\n\tbut go %s would select %v\n", pkg.stackText(), pkg.mod, compatVersion, mismatch.mod.Version))
default:
base.Fatalf("go: internal error: mismatch recorded for package %s, but no differences found", pkg.path)
}
}
+ ld.switchIfErrors(ctx)
suggestFixes()
- base.ExitIfErrors()
+ ld.exitIfErrors(ctx)
}
// scanDir is like imports.ScanDir but elides known magic imports from the list,
// are the roots of the module graph and we expect them to be kept consistent.
panic("internal error: rawGoModSummary called on a main module")
}
+ if m.Version == "" && inWorkspaceMode() && m.Path == "command-line-arguments" {
+ // "go work sync" calls LoadModGraph to make sure the module graph is valid.
+ // If there are no modules in the workspace, we synthesize an empty
+ // command-line-arguments module, which rawGoModData cannot read a go.mod for.
+ return &modFileSummary{module: m}, nil
+ }
return rawGoModSummaryCache.Do(m, func() (*modFileSummary, error) {
summary := new(modFileSummary)
name, data, err := rawGoModData(m)
summary.require = append(summary.require, req.Mod)
}
}
- if summary.goVersion != "" && gover.Compare(summary.goVersion, "1.21") >= 0 {
+ if summary.goVersion != "" && gover.Compare(summary.goVersion, gover.GoStrictVersion) >= 0 {
if gover.Compare(summary.goVersion, gover.Local()) > 0 {
- return nil, &gover.TooNewError{What: summary.module.String(), GoVersion: summary.goVersion}
+ return nil, &gover.TooNewError{What: "module " + m.String(), GoVersion: summary.goVersion}
}
summary.require = append(summary.require, module.Version{Path: "go", Version: summary.goVersion})
}
--- /dev/null
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package toolchain
+
+import (
+ "context"
+ "fmt"
+ "os"
+
+ "cmd/go/internal/base"
+ "cmd/go/internal/gover"
+)
+
+// A Switcher collects errors to be reported and then decides
+// between reporting the errors or switching to a new toolchain
+// to resolve them.
+//
+// The client calls [Switcher.Error] repeatedly with errors encountered
+// and then calls [Switcher.Switch]. If the errors included any
+// *gover.TooNewErrors (potentially wrapped) and switching is
+// permitted by GOTOOLCHAIN, Switch switches to a new toolchain.
+// Otherwise Switch prints all the errors using base.Error.
+type Switcher struct {
+ TooNew *gover.TooNewError // max go requirement observed
+ Errors []error // errors collected so far
+}
+
+// Error reports the error to the Switcher,
+// which saves it for processing during Switch.
+func (s *Switcher) Error(err error) {
+ s.Errors = append(s.Errors, err)
+ s.addTooNew(err)
+}
+
+// addTooNew adds any TooNew errors that can be found in err.
+func (s *Switcher) addTooNew(err error) {
+ switch err := err.(type) {
+ case interface{ Unwrap() []error }:
+ for _, e := range err.Unwrap() {
+ s.addTooNew(e)
+ }
+
+ case interface{ Unwrap() error }:
+ s.addTooNew(err.Unwrap())
+
+ case *gover.TooNewError:
+ if s.TooNew == nil ||
+ gover.Compare(err.GoVersion, s.TooNew.GoVersion) > 0 ||
+ gover.Compare(err.GoVersion, s.TooNew.GoVersion) == 0 && err.What < s.TooNew.What {
+ s.TooNew = err
+ }
+ }
+}
+
+// NeedSwitch reports whether Switch would attempt to switch toolchains.
+func (s *Switcher) NeedSwitch() bool {
+ return s.TooNew != nil && (HasAuto() || HasPath())
+}
+
+// Switch decides whether to switch to a newer toolchain
+// to resolve any of the saved errors.
+// It switches if toolchain switches are permitted and there is at least one TooNewError.
+//
+// If Switch decides not to switch toolchains, it prints the errors using base.Error and returns.
+//
+// If Switch decides to switch toolchains but cannot identify a toolchain to use.
+// it prints the errors along with one more about not being able to find the toolchain
+// and returns.
+//
+// Otherwise, Switch prints an informational message giving a reason for the
+// switch and the toolchain being invoked and then switches toolchains.
+// This operation never returns.
+func (s *Switcher) Switch(ctx context.Context) {
+ if !s.NeedSwitch() {
+ for _, err := range s.Errors {
+ base.Error(err)
+ }
+ return
+ }
+
+ // Switch to newer Go toolchain if necessary and possible.
+ tv, err := NewerToolchain(ctx, s.TooNew.GoVersion)
+ if err != nil {
+ for _, err := range s.Errors {
+ base.Error(err)
+ }
+ base.Error(fmt.Errorf("switching to go >= %v: %w", s.TooNew.GoVersion, err))
+ return
+ }
+
+ fmt.Fprintf(os.Stderr, "go: %v requires go >= %v; switching to %v\n", s.TooNew.What, s.TooNew.GoVersion, tv)
+ SwitchTo(tv)
+ panic("unreachable")
+}
+
+// SwitchOrFatal attempts a toolchain switch based on the information in err
+// and otherwise falls back to base.Fatal(err).
+func SwitchOrFatal(ctx context.Context, err error) {
+ var s Switcher
+ s.Error(err)
+ s.Switch(ctx)
+ base.Exit()
+}
// consulting go.mod.
return m, "", true
}
-
-// TryVersion tries to switch to a Go toolchain appropriate for version,
-// which was either found in a go.mod file of a dependency or resolved
-// on the command line from go@v.
-func TryVersion(ctx context.Context, version string) {
- if !gover.IsValid(version) {
- fmt.Fprintf(os.Stderr, "go: misuse of tryVersion: invalid version %q\n", version)
- return
- }
- if (!HasAuto() && !HasPath()) || gover.Compare(version, gover.Local()) <= 0 {
- return
- }
- tv, err := NewerToolchain(ctx, version)
- if err != nil {
- base.Errorf("go: %v\n", err)
- }
- fmt.Fprintf(os.Stderr, "go: switching to %v\n", tv)
- SwitchTo(tv)
-}
}
}
- modload.UpdateWorkFile(workFile)
-
workFile.SortBlocks()
workFile.Cleanup() // clean file after edits
+ // Note: No call to modload.UpdateWorkFile here.
+ // Edit's job is only to make the edits on the command line,
+ // not to apply the kinds of semantic changes that
+ // UpdateWorkFile does (or would eventually do, if we
+ // decide to add the module comments in go.work).
+
if *editJSON {
editPrintJSON(workFile)
return
package workcmd
import (
+ "context"
+ "path/filepath"
+
"cmd/go/internal/base"
"cmd/go/internal/fsys"
"cmd/go/internal/gover"
"cmd/go/internal/modload"
- "context"
- "os"
- "path/filepath"
"golang.org/x/mod/modfile"
)
modload.ForceUseModules = true
- workFile := modload.WorkFilePath()
- if workFile == "" {
- workFile = filepath.Join(base.Cwd(), "go.work")
+ gowork := modload.WorkFilePath()
+ if gowork == "" {
+ gowork = filepath.Join(base.Cwd(), "go.work")
}
- CreateWorkFile(ctx, workFile, args)
-}
-
-// CreateWorkFile initializes a new workspace by creating a go.work file.
-func CreateWorkFile(ctx context.Context, workFile string, modDirs []string) {
- if _, err := fsys.Stat(workFile); err == nil {
- base.Fatalf("go: %s already exists", workFile)
+ if _, err := fsys.Stat(gowork); err == nil {
+ base.Fatalf("go: %s already exists", gowork)
}
goV := gover.Local() // Use current Go version by default
wf := new(modfile.WorkFile)
wf.Syntax = new(modfile.FileSyntax)
wf.AddGoStmt(goV)
-
- for _, dir := range modDirs {
- _, f, err := modload.ReadModFile(filepath.Join(dir, "go.mod"), nil)
- if err != nil {
- if os.IsNotExist(err) {
- base.Fatalf("go: creating workspace file: no go.mod file exists in directory %v", dir)
- }
- base.Fatalf("go: error parsing go.mod in directory %s: %v", dir, err)
- }
- wf.AddUse(modload.ToDirectoryPath(dir), f.Module.Mod.Path)
- }
-
- modload.UpdateWorkFile(wf)
- modload.WriteWorkFile(workFile, wf)
+ workUse(ctx, gowork, wf, args)
+ modload.WriteWorkFile(gowork, wf)
}
"cmd/go/internal/modload"
"cmd/go/internal/toolchain"
"context"
- "errors"
"golang.org/x/mod/module"
)
base.Fatalf("go: no go.work file found\n\t(run 'go work init' first or specify path using GOWORK environment variable)")
}
- workGraph, err := modload.LoadModGraph(ctx, "")
- if tooNew := (*gover.TooNewError)(nil); errors.As(err, &tooNew) {
- toolchain.TryVersion(ctx, tooNew.GoVersion)
- base.Fatal(err)
+ _, err := modload.LoadModGraph(ctx, "")
+ if err != nil {
+ toolchain.SwitchOrFatal(ctx, err)
}
- _ = workGraph
mustSelectFor := map[module.Version][]module.Version{}
mms := modload.MainModules
workFilePath := modload.WorkFilePath() // save go.work path because EnterModule clobbers it.
+ var goV string
for _, m := range mms.Versions() {
if mms.ModRoot(m) == "" && m.Path == "command-line-arguments" {
// This is not a real module.
// Edit the build list in the same way that 'go get' would if we
// requested the relevant module versions explicitly.
+ // TODO(#57001): Do we need a toolchain.SwitchOrFatal here,
+ // and do we need to pass a toolchain.Switcher in LoadPackages?
+ // If so, think about saving the WriteGoMods for after the loop,
+ // so we don't write some go.mods with the "before" toolchain
+ // and others with the "after" toolchain. If nothing else, that
+ // discrepancy could show up in auto-recorded toolchain lines.
changed, err := modload.EditBuildList(ctx, nil, mustSelectFor[m])
if err != nil {
- base.Errorf("go: %v", err)
- }
- if !changed {
continue
}
-
- modload.LoadPackages(ctx, modload.PackageOpts{
- Tags: imports.AnyTags(),
- Tidy: true,
- VendorModulesInGOROOTSrc: true,
- ResolveMissingImports: false,
- LoadTests: true,
- AllowErrors: true,
- SilenceMissingStdImports: true,
- SilencePackageErrors: true,
- }, "all")
- modload.WriteGoMod(ctx, modload.WriteOpts{})
+ if changed {
+ modload.LoadPackages(ctx, modload.PackageOpts{
+ Tags: imports.AnyTags(),
+ Tidy: true,
+ VendorModulesInGOROOTSrc: true,
+ ResolveMissingImports: false,
+ LoadTests: true,
+ AllowErrors: true,
+ SilenceMissingStdImports: true,
+ SilencePackageErrors: true,
+ }, "all")
+ modload.WriteGoMod(ctx, modload.WriteOpts{})
+ }
+ goV = gover.Max(goV, modload.MainModules.GoVersion())
}
wf, err := modload.ReadWorkFile(workFilePath)
if err != nil {
base.Fatal(err)
}
+ modload.UpdateWorkGoVersion(wf, goV)
modload.UpdateWorkFile(wf)
if err := modload.WriteWorkFile(workFilePath, wf); err != nil {
base.Fatal(err)
package workcmd
import (
- "cmd/go/internal/base"
- "cmd/go/internal/fsys"
- "cmd/go/internal/modload"
- "cmd/go/internal/str"
"context"
"fmt"
"io/fs"
"os"
"path/filepath"
+
+ "cmd/go/internal/base"
+ "cmd/go/internal/fsys"
+ "cmd/go/internal/gover"
+ "cmd/go/internal/modload"
+ "cmd/go/internal/str"
+ "cmd/go/internal/toolchain"
+
+ "golang.org/x/mod/modfile"
)
var cmdUse = &base.Command{
- UsageLine: "go work use [-r] moddirs",
+ UsageLine: "go work use [-r] [moddirs]",
Short: "add modules to workspace file",
Long: `Use provides a command-line interface for adding
directories, optionally recursively, to a go.work file.
A use directive will be added to the go.work file for each argument
-directory listed on the command line go.work file, if it exists on disk,
-or removed from the go.work file if it does not exist on disk.
+directory listed on the command line go.work file, if it exists,
+or removed from the go.work file if it does not exist.
+Use fails if any remaining use directives refer to modules that
+do not exist.
+
+Use updates the go line in go.work to specify a version at least as
+new as all the go lines in the used modules, both preexisting ones
+and newly added ones. With no arguments, this update is the only
+thing that go work use does.
The -r flag searches recursively for modules in the argument
directories, and the use command operates as if each of the directories
were specified as arguments: namely, use directives will be added for
directories that exist, and removed for directories that do not exist.
+
+
See the workspaces reference at https://go.dev/ref/mod#workspaces
for more information.
`,
func runUse(ctx context.Context, cmd *base.Command, args []string) {
modload.ForceUseModules = true
-
- var gowork string
modload.InitWorkfile()
- gowork = modload.WorkFilePath()
-
+ gowork := modload.WorkFilePath()
if gowork == "" {
base.Fatalf("go: no go.work file found\n\t(run 'go work init' first or specify path using GOWORK environment variable)")
}
- workFile, err := modload.ReadWorkFile(gowork)
+ wf, err := modload.ReadWorkFile(gowork)
if err != nil {
base.Fatal(err)
}
- workDir := filepath.Dir(gowork) // Absolute, since gowork itself is absolute.
+ workUse(ctx, gowork, wf, args)
+ modload.WriteWorkFile(gowork, wf)
+}
+
+func workUse(ctx context.Context, gowork string, wf *modfile.WorkFile, args []string) {
+ workDir := filepath.Dir(gowork) // absolute, since gowork itself is absolute
haveDirs := make(map[string][]string) // absolute → original(s)
- for _, use := range workFile.Use {
+ for _, use := range wf.Use {
var abs string
if filepath.IsAbs(use.Path) {
abs = filepath.Clean(use.Path)
// all entries for the absolute path should be removed.
keepDirs := make(map[string]string)
+ var sw toolchain.Switcher
+
// lookDir updates the entry in keepDirs for the directory dir,
// which is either absolute or relative to the current working directory
// (not necessarily the directory containing the workfile).
lookDir := func(dir string) {
absDir, dir := pathRel(workDir, dir)
- fi, err := fsys.Stat(filepath.Join(absDir, "go.mod"))
+ file := base.ShortPath(filepath.Join(absDir, "go.mod"))
+ fi, err := fsys.Stat(file)
if err != nil {
if os.IsNotExist(err) {
keepDirs[absDir] = ""
} else {
- base.Error(err)
+ sw.Error(err)
}
return
}
if !fi.Mode().IsRegular() {
- base.Errorf("go: %v is not regular", filepath.Join(dir, "go.mod"))
+ sw.Error(fmt.Errorf("%v is not a regular file", file))
+ return
}
if dup := keepDirs[absDir]; dup != "" && dup != dir {
keepDirs[absDir] = dir
}
- if len(args) == 0 {
- base.Fatalf("go: 'go work use' requires one or more directory arguments")
- }
for _, useDir := range args {
absArg, _ := pathRel(workDir, useDir)
- info, err := fsys.Stat(absArg)
+ info, err := fsys.Stat(base.ShortPath(absArg))
if err != nil {
// Errors raised from os.Stat are formatted to be more user-friendly.
if os.IsNotExist(err) {
- base.Errorf("go: directory %v does not exist", absArg)
- } else {
- base.Error(err)
+ err = fmt.Errorf("directory %v does not exist", base.ShortPath(absArg))
}
+ sw.Error(err)
continue
} else if !info.IsDir() {
- base.Errorf("go: %s is not a directory", absArg)
+ sw.Error(fmt.Errorf("%s is not a directory", base.ShortPath(absArg)))
continue
}
if !info.IsDir() {
if info.Mode()&fs.ModeSymlink != 0 {
if target, err := fsys.Stat(path); err == nil && target.IsDir() {
- fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", path)
+ fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", base.ShortPath(path))
}
}
return nil
}
}
- base.ExitIfErrors()
-
+ // Update the work file.
for absDir, keepDir := range keepDirs {
nKept := 0
for _, dir := range haveDirs[absDir] {
if dir == keepDir { // (note that dir is always non-empty)
nKept++
} else {
- workFile.DropUse(dir)
+ wf.DropUse(dir)
}
}
if keepDir != "" && nKept != 1 {
// If we kept more than one copy, delete them all.
// We'll recreate a unique copy with AddUse.
if nKept > 1 {
- workFile.DropUse(keepDir)
+ wf.DropUse(keepDir)
}
- workFile.AddUse(keepDir, "")
+ wf.AddUse(keepDir, "")
}
}
- modload.UpdateWorkFile(workFile)
- modload.WriteWorkFile(gowork, workFile)
+
+ // Read the Go versions from all the use entries, old and new (but not dropped).
+ goV := gover.FromGoWork(wf)
+ for _, use := range wf.Use {
+ if use.Path == "" { // deleted
+ continue
+ }
+ var abs string
+ if filepath.IsAbs(use.Path) {
+ abs = filepath.Clean(use.Path)
+ } else {
+ abs = filepath.Join(workDir, use.Path)
+ }
+ _, mf, err := modload.ReadModFile(base.ShortPath(filepath.Join(abs, "go.mod")), nil)
+ if err != nil {
+ sw.Error(err)
+ continue
+ }
+ goV = gover.Max(goV, gover.FromGoMod(mf))
+ }
+ sw.Switch(ctx)
+ base.ExitIfErrors()
+
+ modload.UpdateWorkGoVersion(wf, goV)
+ modload.UpdateWorkFile(wf)
}
// pathRel returns the absolute and canonical forms of dir for use in a
stdout randautoseed=0
rm go.work
-# Go 1.20 workspace should set panicnil=1 even in Go 1.21 module.
+# Go 1.20 workspace with Go 1.21 module cannot happen.
cp go.work.20 go.work
cp go.mod.21 go.mod
-go list -f '{{.Module.GoVersion}} {{.DefaultGODEBUG}}'
-stdout panicnil=1
-stdout randautoseed=0
+! go list -f '{{.Module.GoVersion}} {{.DefaultGODEBUG}}'
+stderr 'go: module . listed in go.work file requires go >= 1.21'
rm go.work
[short] skip
--- /dev/null
+# Check that go lines are always >= go lines of dependencies.
+
+# Using too old a release cannot even complete module load.
+env TESTGO_VERSION=go1.21.1
+env TESTGO_VERSION_SWITCH=switch
+cp go.mod go.mod.orig
+
+# If the offending module is not imported, it's not detected.
+go list
+cmp go.mod go.mod.orig
+
+# Adding the import produces the error.
+# Maybe this should auto-switch, but it requires more plumbing to get this error through,
+# and it's a misconfigured system that should not arise in practice, so not switching is fine.
+! go list -deps -tags usem1
+cmp go.mod go.mod.orig
+stderr '^go: module ./m1 requires go >= 1.21.2 \(running go 1.21.1\)$'
+
+# go get go@1.21.2 fixes the error.
+cp go.mod.orig go.mod
+go get go@1.21.2
+go list -deps -tags usem1
+
+# go get -tags usem1 fixes the error.
+cp go.mod.orig go.mod
+go get -tags usem1
+go list -deps -tags usem1
+
+# go get fixes the error.
+cp go.mod.orig go.mod
+go get
+go list -deps -tags usem1
+
+# Using a new enough release reports the error after module load and suggests 'go mod tidy'
+env TESTGO_VERSION=go1.21.2
+cp go.mod.orig go.mod
+! go list -deps -tags usem1
+stderr 'updates to go.mod needed'
+stderr 'go mod tidy'
+go mod tidy
+go list -deps -tags usem1
+
+# go get also works
+cp go.mod.orig go.mod
+! go list -deps -tags usem1
+stderr 'updates to go.mod needed'
+stderr 'go mod tidy'
+go get go@1.21.2
+go list -deps -tags usem1
+
+
+-- go.mod --
+module m
+go 1.21.1
+
+require m1 v0.0.1
+
+replace m1 => ./m1
+
+-- m1/go.mod --
+go 1.21.2
+
+-- p.go --
+//go:build usem1
+
+package p
+
+import _ "m1"
+
+-- p1.go --
+package p
+
+-- m1/p.go --
+package p
stderr '^go: rsc.io/future@v1.0.0 requires go >= 1.999 \(running go 1.21.0\)'
! go mod download
-stderr '^go: rsc.io/future@v1.0.0: rsc.io/future requires go >= 1.999 \(running go 1.21.0\)'
+stderr '^go: rsc.io/future@v1.0.0: module rsc.io/future@v1.0.0 requires go >= 1.999 \(running go 1.21.0\)'
! go mod verify
-stderr '^go: rsc.io/future@v1.0.0: rsc.io/future requires go >= 1.999 \(running go 1.21.0\)'
+stderr '^go: rsc.io/future@v1.0.0: module rsc.io/future@v1.0.0 requires go >= 1.999 \(running go 1.21.0\)'
! go mod graph
-stderr '^go: rsc.io/future@v1.0.0: rsc.io/future requires go >= 1.999 \(running go 1.21.0\)'
+stderr '^go: rsc.io/future@v1.0.0: module rsc.io/future@v1.0.0 requires go >= 1.999 \(running go 1.21.0\)'
# 'go get' should update the main module's go.mod file to a version compatible with the
# go version required for rsc.io/future, not fail.
go get .
-stderr '^go: switching to go1.999testmod$'
+stderr '^go: module rsc.io/future@v1.0.0 requires go >= 1.999; switching to go1.999testmod$'
stderr '^go: upgraded go 1.21 => 1.999$'
stderr '^go: added toolchain go1.999testmod$'
# stderr '^useappengine[/\\]x.go:2:8: cannot find package$'
! go get ./usenonexistent
-stderr '^x/usenonexistent imports\n\tnonexistent.rsc.io: cannot find module providing package nonexistent.rsc.io$'
+stderr '^go: x/usenonexistent imports\n\tnonexistent.rsc.io: cannot find module providing package nonexistent.rsc.io$'
# go mod vendor and go mod tidy should ignore appengine imports.
# TODO(#41688): This should include a file and line, and report the reason for the error..
# (Today it includes only an import stack.)
! go get ./main
-stderr '^m/main imports\n\tm/bad imports\n\t🐧.example.com/string: malformed import path "🐧.example.com/string": invalid char ''🐧''$'
+stderr '^go: m/main imports\n\tm/bad imports\n\t🐧.example.com/string: malformed import path "🐧.example.com/string": invalid char ''🐧''$'
-- go.mod --
! go mod tidy
-stderr '^example.com/untidy imports\n\texample.net/directnotfound: cannot find module providing package example.net/directnotfound: module example.net/directnotfound: reading http://.*: 404 Not Found$'
+stderr '^go: example.com/untidy imports\n\texample.net/directnotfound: cannot find module providing package example.net/directnotfound: module example.net/directnotfound: reading http://.*: 404 Not Found$'
-stderr '^example.com/untidy imports\n\texample.net/m imports\n\texample.net/indirectnotfound: cannot find module providing package example.net/indirectnotfound: module example.net/indirectnotfound: reading http://.*: 404 Not Found$'
+stderr '^go: example.com/untidy imports\n\texample.net/m imports\n\texample.net/indirectnotfound: cannot find module providing package example.net/indirectnotfound: module example.net/indirectnotfound: reading http://.*: 404 Not Found$'
-stderr '^example.com/untidy tested by\n\texample.com/untidy.test imports\n\texample.net/directtestnotfound: cannot find module providing package example.net/directtestnotfound: module example.net/directtestnotfound: reading http://.*: 404 Not Found$'
+stderr '^go: example.com/untidy tested by\n\texample.com/untidy.test imports\n\texample.net/directtestnotfound: cannot find module providing package example.net/directtestnotfound: module example.net/directtestnotfound: reading http://.*: 404 Not Found$'
-stderr '^example.com/untidy imports\n\texample.net/m tested by\n\texample.net/m.test imports\n\texample.net/indirecttestnotfound: cannot find module providing package example.net/indirecttestnotfound: module example.net/indirecttestnotfound: reading http://.*: 404 Not Found$'
+stderr '^go: example.com/untidy imports\n\texample.net/m tested by\n\texample.net/m.test imports\n\texample.net/indirecttestnotfound: cannot find module providing package example.net/indirecttestnotfound: module example.net/indirecttestnotfound: reading http://.*: 404 Not Found$'
cmp go.mod.orig go.mod
! go mod vendor
-stderr '^example.com/untidy imports\n\texample.net/directnotfound: no required module provides package example.net/directnotfound; to add it:\n\tgo get example.net/directnotfound$'
+stderr '^go: example.com/untidy imports\n\texample.net/directnotfound: no required module provides package example.net/directnotfound; to add it:\n\tgo get example.net/directnotfound$'
-stderr '^example.com/untidy imports\n\texample.net/m: module example.net/m provides package example.net/m and is replaced but not required; to add it:\n\tgo get example.net/m@v0.1.0$'
+stderr '^go: example.com/untidy imports\n\texample.net/m: module example.net/m provides package example.net/m and is replaced but not required; to add it:\n\tgo get example.net/m@v0.1.0$'
-stderr '^example.com/untidy tested by\n\texample.com/untidy.test imports\n\texample.net/directtestnotfound: no required module provides package example.net/directtestnotfound; to add it:\n\tgo get example.net/directtestnotfound$'
+stderr '^go: example.com/untidy tested by\n\texample.com/untidy.test imports\n\texample.net/directtestnotfound: no required module provides package example.net/directtestnotfound; to add it:\n\tgo get example.net/directtestnotfound$'
! stderr 'indirecttestnotfound' # Vendor prunes test dependencies.
cp go.mod.orig go.mod
go mod vendor -e
stderr -count=2 'no required module provides package'
-stderr '^example.com/untidy imports\n\texample.net/m: module example.net/m provides package example.net/m and is replaced but not required; to add it:\n\tgo get example.net/m@v0.1.0$'
+stderr '^go: example.com/untidy imports\n\texample.net/m: module example.net/m provides package example.net/m and is replaced but not required; to add it:\n\tgo get example.net/m@v0.1.0$'
exists vendor/modules.txt
! exists vendor/example.net
# TODO(#27899): Should we automatically upgrade example.net/m to v0.2.0
# to resolve the conflict?
! go get example.net/m/p@v1.0.0
-stderr '^example.net/m/p: ambiguous import: found package example.net/m/p in multiple modules:\n\texample.net/m v0.1.0 \(.*[/\\]m1[/\\]p\)\n\texample.net/m/p v1.0.0 \(.*[/\\]p0\)\n\z'
+stderr '^go: example.net/m/p: ambiguous import: found package example.net/m/p in multiple modules:\n\texample.net/m v0.1.0 \(.*[/\\]m1[/\\]p\)\n\texample.net/m/p v1.0.0 \(.*[/\\]p0\)\n\z'
cmp go.mod go.mod.orig
# Upgrading both modules simultaneously resolves the ambiguous upgrade.
# package, then 'go get' should fail with a useful error message.
! go get example.net/pkgadded@v1.0.0 .
-stderr '^example.com/m imports\n\texample.net/pkgadded/subpkg: cannot find module providing package example.net/pkgadded/subpkg$'
+stderr '^go: example.com/m imports\n\texample.net/pkgadded/subpkg: cannot find module providing package example.net/pkgadded/subpkg$'
! stderr 'example.net/pkgadded v1\.2\.0'
cmp go.mod.orig go.mod
# cannot be resolved.
! go get
-stderr '^example.com/m imports\n\texample.com/badimport imports\n\texample.net/oops: cannot find module providing package example.net/oops$'
+stderr '^go: example.com/m imports\n\texample.com/badimport imports\n\texample.net/oops: cannot find module providing package example.net/oops$'
cmp go.mod.orig go.mod
cd importsyntax
env GOTOOLCHAIN=auto
cp go.mod.new go.mod
go get rsc.io/needgo121 rsc.io/needgo122 rsc.io/needgo123 rsc.io/needall
-stderr '^go: switching to go1.23.9$'
+stderr '^go: rsc.io/needall@v0.0.1 requires go >= 1.23; switching to go1.23.9$'
+! stderr '\(running'
stderr '^go: added rsc.io/needall v0.0.1'
-! stderr 'requires go >= 1.23'
grep 'go 1.23' go.mod
grep 'toolchain go1.23.9' go.mod
env GOTOOLCHAIN=go1.21+auto
cp go.mod.new go.mod
go get rsc.io/needgo121 rsc.io/needgo122 rsc.io/needgo123 rsc.io/needall
-stderr '^go: switching to go1.23.9$'
+stderr '^go: rsc.io/needall@v0.0.1 requires go >= 1.23; switching to go1.23.9$'
+! stderr '\(running'
stderr '^go: added rsc.io/needall v0.0.1'
-! stderr 'requires go >= 1.23'
grep 'go 1.23' go.mod
grep 'toolchain go1.23.9' go.mod
env GOTOOLCHAIN=go1.21
cp go.mod.new go.mod
! go get rsc.io/needgo121 rsc.io/needgo122 rsc.io/needgo123 rsc.io/needall
-! stderr 'switching to go'
+! stderr switching
stderr 'rsc.io/needgo122@v0.0.1 requires go >= 1.22'
stderr 'rsc.io/needgo123@v0.0.1 requires go >= 1.23'
stderr 'rsc.io/needall@v0.0.1 requires go >= 1.23'
env GOTOOLCHAIN=local
cp go.mod.new go.mod
! go get rsc.io/needgo121 rsc.io/needgo122 rsc.io/needgo123 rsc.io/needall
-! stderr 'switching to go'
+! stderr switching
stderr 'rsc.io/needgo122@v0.0.1 requires go >= 1.22'
stderr 'rsc.io/needgo123@v0.0.1 requires go >= 1.23'
stderr 'rsc.io/needall@v0.0.1 requires go >= 1.23'
env GOTOOLCHAIN=auto
cp go.mod.new go.mod
go get go@1.22
-stderr '^go: switching to go1.22.9$'
+stderr '^go: updating go.mod requires go >= 1.22.9; switching to go1.22.9$'
# go get go@1.22rc1 should use 1.22rc1 exactly, not a later release.
env GOTOOLCHAIN=local
env GOTOOLCHAIN=auto
cp go.mod.new go.mod
go get go@1.22rc1
-stderr '^go: switching to go1.22.9$'
+stderr '^go: updating go.mod requires go >= 1.22rc1; switching to go1.22.9$'
stderr '^go: upgraded go 1.1 => 1.22rc1$'
stderr '^go: added toolchain go1.22.9$'
env GOTOOLCHAIN=auto
cp go.mod.new go.mod
go get go@1.22.1
-stderr '^go: switching to go1.22.9$'
+stderr '^go: updating go.mod requires go >= 1.22.1; switching to go1.22.9$'
stderr '^go: upgraded go 1.1 => 1.22.1$'
stderr '^go: added toolchain go1.22.9$'
cp go.mod.new go.mod
go get rsc.io/needgo122
stderr '^go: upgraded go 1.1 => 1.22$'
-stderr '^go: switching to go1.22.9$'
+stderr '^go: rsc.io/needgo122@v0.0.1 requires go >= 1.22; switching to go1.22.9$'
stderr '^go: added toolchain go1.22.9$'
# go get needgo1223 (says 'go 1.22.3') should use go 1.22.3
cp go.mod.new go.mod
go get rsc.io/needgo1223
stderr '^go: upgraded go 1.1 => 1.22.3$'
-stderr '^go: switching to go1.22.9$'
+stderr '^go: rsc.io/needgo1223@v0.0.1 requires go >= 1.22.3; switching to go1.22.9$'
stderr '^go: added toolchain go1.22.9$'
# go get needgo124 (says 'go 1.24') should use go 1.24rc1, the only version available
env GOTOOLCHAIN=auto
cp go.mod.new go.mod
go get rsc.io/needgo124
-stderr '^go: switching to go1.24rc1'
+stderr '^go: rsc.io/needgo124@v0.0.1 requires go >= 1.24; switching to go1.24rc1$'
stderr '^go: upgraded go 1.1 => 1.24$'
stderr '^go: added toolchain go1.24rc1$'
# With the -t flag, the test dependencies must resolve successfully.
! go get -t example.net/testonly@v0.1.0
-stderr '^example.net/testonly tested by\n\texample.net/testonly\.test imports\n\texample.net/missing: cannot find module providing package example.net/missing$'
+stderr '^go: example.net/testonly tested by\n\texample.net/testonly\.test imports\n\texample.net/missing: cannot find module providing package example.net/missing$'
# 'go get' should succeed for a module path that does not contain a package,
cp go.mod.orig go.mod
! go get example.net/split/nested@v0.1.0
-stderr '^example.net/split/nested: ambiguous import: found package example.net/split/nested in multiple modules:\n\texample.net/split v0.2.0 \(.*split.2[/\\]nested\)\n\texample.net/split/nested v0.1.0 \(.*nested.1\)$'
+stderr '^go: example.net/split/nested: ambiguous import: found package example.net/split/nested in multiple modules:\n\texample.net/split v0.2.0 \(.*split.2[/\\]nested\)\n\texample.net/split/nested v0.1.0 \(.*nested.1\)$'
# A wildcard that matches packages in some module at its selected version
# but not at the requested version should fail.
env TESTGO_VERSION=go1.21
! go list
-stderr -count=1 '^go: sub@v1.0.0: sub requires go >= 1.999 \(running go 1.21\)$'
+stderr -count=1 '^go: sub@v1.0.0: module ./sub requires go >= 1.999 \(running go 1.21\)$'
! go build sub
-stderr -count=1 '^go: sub@v1.0.0: sub requires go >= 1.999 \(running go 1.21\)$'
+stderr -count=1 '^go: sub@v1.0.0: module ./sub requires go >= 1.999 \(running go 1.21\)$'
-- go.mod --
module m
go list -f '{{.Module.GoVersion}}'
stdout 1.23rc1
- # would be nice but doesn't work yet
- # go mod why -m go
- # stderr xxx
-
# Repeating the update with go@1.24.0 should use that Go version.
cp go.mod1 go.mod
go get example.com/a@v1.0.1 go@1.24.0
# go.mod referenced from go.work too new
cp go.work.old go.work
! go build .
-stderr '^go: cannot load module go.mod listed in go.work file: go.mod requires go >= 1.99999 \(running go 1\..+\)$'
+stderr '^go: module . listed in go.work file requires go >= 1.99999, but go.work lists go 1.10; to update it:\n\tgo work use$'
+
+! go work sync
+stderr '^go: cannot load module . listed in go.work file: go.mod requires go >= 1.99999 \(running go 1\..+\)$'
# go.work too new
cp go.work.new go.work
cp go.mod go.mod.orig
+# tidy reports needing 1.22.0 for b1
+env GOTOOLCHAIN=local
+! go mod tidy
+stderr '^go: example imports\n\texample.net/b: module ./b1 requires go >= 1.22.0 \(running go 1.21.0; GOTOOLCHAIN=local\)$'
+env GOTOOLCHAIN=auto
go mod tidy
- # TODO(bcmills): The "switching to" message should explain which
- # newly-added package caused the switch. I think that will be fixed
- # by resolving the TODO in modload.fetch.
+
cmp stderr tidy-stderr.want
cmp go.mod go.mod.tidy
cp go.mod.orig go.mod
+env GOTOOLCHAIN=local
+! go get -v .
+stderr '^go: example.net/b@v0.1.0: module ./b1 requires go >= 1.22.0 \(running go 1.21.0; GOTOOLCHAIN=local\)$'
+env GOTOOLCHAIN=auto
go get -v .
cmp stderr get-v-stderr.want
cmp go.mod go.mod.tidy
cp go.mod.orig go.mod
+env GOTOOLCHAIN=local
+! go get -u -v .
+stderr '^go: example.net/a@v0.2.0: module ./a2 requires go >= 1.22.0 \(running go 1.21.0; GOTOOLCHAIN=local\)$'
+env GOTOOLCHAIN=auto
go get -u -v .
cmp stderr get-u-v-stderr.want
cmp go.mod go.mod.upgraded
-- tidy-stderr.want --
go: found example.net/b in example.net/b v0.1.0
-go: switching to go1.22.9
+go: module ./b1 requires go >= 1.22.0; switching to go1.22.9
go: found example.net/b in example.net/b v0.1.0
go: found example.net/c in example.net/c v0.1.0
-- get-v-stderr.want --
go: trying upgrade to example.net/b@v0.1.0
-go: switching to go1.22.9
+go: module ./b1 requires go >= 1.22.0; switching to go1.22.9
go: trying upgrade to example.net/b@v0.1.0
go: accepting indirect upgrade from go@1.20 to 1.22.0
go: trying upgrade to example.net/c@v0.1.0
-- get-u-v-stderr.want --
go: trying upgrade to example.net/a@v0.2.0
go: trying upgrade to example.net/b@v0.1.0
-go: switching to go1.22.9
+go: module ./a2 requires go >= 1.22.0; switching to go1.22.9
go: trying upgrade to example.net/a@v0.2.0
go: trying upgrade to example.net/b@v0.1.0
go: accepting indirect upgrade from go@1.20 to 1.22.0
go: trying upgrade to example.net/c@v0.1.0
go: trying upgrade to example.net/d@v0.2.0
-go: switching to go1.23.9
+go: module ./d2 requires go >= 1.23.0; switching to go1.23.9
go: trying upgrade to example.net/a@v0.2.0
go: trying upgrade to example.net/b@v0.1.0
go: accepting indirect upgrade from go@1.20 to 1.22.0
# TODO(#26909): Ideally these errors should include line numbers for the imports within the main module.
cd fail
! go mod tidy
-stderr '^localhost.fail imports\n\tw: module w@latest found \(v0.0.0-00010101000000-000000000000, replaced by ../w\), but does not contain package w$'
-stderr '^localhost.fail imports\n\tnonexist: nonexist@v0.1.0: replacement directory ../nonexist does not exist$'
+stderr '^go: localhost.fail imports\n\tw: module w@latest found \(v0.0.0-00010101000000-000000000000, replaced by ../w\), but does not contain package w$'
+stderr '^go: localhost.fail imports\n\tnonexist: nonexist@v0.1.0: replacement directory ../nonexist does not exist$'
-- go.mod --
module example.com/m
! go mod tidy
-stderr '^example\.com/m imports\n\texample\.net/added: module example\.net/added@latest found \(v0\.3\.0, replaced by \./a1\), but does not contain package example\.net/added$'
+stderr '^go: example\.com/m imports\n\texample\.net/added: module example\.net/added@latest found \(v0\.3\.0, replaced by \./a1\), but does not contain package example\.net/added$'
cmp go.mod go.mod.orig
go mod tidy -e
-stderr '^example\.com/m imports\n\texample\.net/added: module example\.net/added@latest found \(v0\.3\.0, replaced by \./a1\), but does not contain package example\.net/added\nexample\.net/added failed to load from any module,\n\tbut go 1\.16 would load it from example\.net/added@v0\.2\.0$'
+stderr '^go: example\.com/m imports\n\texample\.net/added: module example\.net/added@latest found \(v0\.3\.0, replaced by \./a1\), but does not contain package example\.net/added\ngo: example\.net/added failed to load from any module,\n\tbut go 1\.16 would load it from example\.net/added@v0\.2\.0$'
! stderr '\n\tgo mod tidy'
! go mod tidy
-stderr '^example\.com/m imports\n\texample\.net/indirect imports\n\texample\.net/ambiguous/nested/pkg loaded from example\.net/ambiguous/nested@v0\.1\.0,\n\tbut go 1.16 would fail to locate it:\n\tambiguous import: found package example\.net/ambiguous/nested/pkg in multiple modules:\n\texample\.net/ambiguous v0.1.0 \(.*\)\n\texample\.net/ambiguous/nested v0.1.0 \(.*\)\n\n'
+stderr '^go: example\.com/m imports\n\texample\.net/indirect imports\n\texample\.net/ambiguous/nested/pkg loaded from example\.net/ambiguous/nested@v0\.1\.0,\n\tbut go 1.16 would fail to locate it:\n\tambiguous import: found package example\.net/ambiguous/nested/pkg in multiple modules:\n\texample\.net/ambiguous v0.1.0 \(.*\)\n\texample\.net/ambiguous/nested v0.1.0 \(.*\)\n\n'
stderr '\n\nTo proceed despite packages unresolved in go 1\.16:\n\tgo mod tidy -e\nIf reproducibility with go 1.16 is not needed:\n\tgo mod tidy -compat=1\.17\nFor other options, see:\n\thttps://golang\.org/doc/modules/pruning\n'
! go mod tidy
-stderr '^example\.com/m imports\n\texample\.net/deleted loaded from example\.net/deleted@v0\.1\.0,\n\tbut go 1\.16 would fail to locate it in example\.net/deleted@v0\.2\.0\n\n'
+stderr '^go: example\.com/m imports\n\texample\.net/deleted loaded from example\.net/deleted@v0\.1\.0,\n\tbut go 1\.16 would fail to locate it in example\.net/deleted@v0\.2\.0\n\n'
stderr '\n\nTo upgrade to the versions selected by go 1.16, leaving some packages unresolved:\n\tgo mod tidy -e -go=1\.16 && go mod tidy -e -go=1\.17\nIf reproducibility with go 1.16 is not needed:\n\tgo mod tidy -compat=1\.17\nFor other options, see:\n\thttps://golang\.org/doc/modules/pruning\n'
stderr '^go: updates to go\.mod needed; to update it:\n\tgo mod tidy$'
! go mod tidy
-stderr '^example\.com/m imports\n\texample\.net/deleted: module example\.net/deleted@latest found \(v0\.2\.0, replaced by \./d2\), but does not contain package example\.net/deleted$'
+stderr '^go: example\.com/m imports\n\texample\.net/deleted: module example\.net/deleted@latest found \(v0\.2\.0, replaced by \./d2\), but does not contain package example\.net/deleted$'
-- go.mod --
cp go.mod go.mod.orig
! go mod tidy
-stderr '^example\.com/m imports\n\texample\.net/lazy tested by\n\texample\.net/lazy.test imports\n\texample\.com/retract/incompatible loaded from example\.com/retract/incompatible@v1\.0\.0,\n\tbut go 1\.16 would select v2\.0\.0\+incompatible\n\n'
+stderr '^go: example\.com/m imports\n\texample\.net/lazy tested by\n\texample\.net/lazy.test imports\n\texample\.com/retract/incompatible loaded from example\.com/retract/incompatible@v1\.0\.0,\n\tbut go 1\.16 would select v2\.0\.0\+incompatible\n\n'
stderr '\n\nTo upgrade to the versions selected by go 1\.16:\n\tgo mod tidy -go=1\.16 && go mod tidy -go=1\.17\nIf reproducibility with go 1.16 is not needed:\n\tgo mod tidy -compat=1.17\nFor other options, see:\n\thttps://golang\.org/doc/modules/pruning\n'
cmp go.mod go.mod.orig
cp go.mod go.mod.orig
! go mod tidy
-stderr '^example\.com/m imports\n\texample\.net/lazy imports\n\texample\.com/retract/incompatible loaded from example\.com/retract/incompatible@v1\.0\.0,\n\tbut go 1\.16 would select v2\.0\.0\+incompatible\n\n'
+stderr '^go: example\.com/m imports\n\texample\.net/lazy imports\n\texample\.com/retract/incompatible loaded from example\.com/retract/incompatible@v1\.0\.0,\n\tbut go 1\.16 would select v2\.0\.0\+incompatible\n\n'
stderr '\n\nTo upgrade to the versions selected by go 1\.16:\n\tgo mod tidy -go=1\.16 && go mod tidy -go=1\.17\nIf reproducibility with go 1\.16 is not needed:\n\tgo mod tidy -compat=1.17\nFor other options, see:\n\thttps://golang\.org/doc/modules/pruning\n'
cmp go.mod go.mod.orig
stderr '^go: finding module for package example\.net/x$'
# TODO: This error message should be clearer — it doesn't indicate why v0.2.0-pre is required.
-stderr '^example\.net/m imports\n\texample\.net/x: package example\.net/x provided by example\.net/x at latest version v0\.1\.0 but not at required version v0\.2\.0-pre$'
+stderr '^go: example\.net/m imports\n\texample\.net/x: package example\.net/x provided by example\.net/x at latest version v0\.1\.0 but not at required version v0\.2\.0-pre$'
# 'go mod tidy -e' should follow upgrades to try to resolve the modules that it
stderr '^go: found example\.net/y in example\.net/y v0.2.0$'
# TODO: This error message should be clearer — it doesn't indicate why v0.2.0-pre is required.
-stderr '^example\.net/m imports\n\texample\.net/x: package example\.net/x provided by example\.net/x at latest version v0\.1\.0 but not at required version v0\.2\.0-pre$'
+stderr '^go: example\.net/m imports\n\texample\.net/x: package example\.net/x provided by example\.net/x at latest version v0\.1\.0 but not at required version v0\.2\.0-pre$'
# Since we attempt to resolve the dependencies of package x whenever we add x itself,
go mod tidy -e
cmp go.mod go.mod.tidye
stderr '^go: found example\.net/y in example\.net/y v0.2.0$'
-stderr '^example\.net/m imports\n\texample\.net/x: package example\.net/x provided by example\.net/x at latest version v0\.1\.0 but not at required version v0\.2\.0-pre$'
+stderr '^go: example\.net/m imports\n\texample\.net/x: package example\.net/x provided by example\.net/x at latest version v0\.1\.0 but not at required version v0\.2\.0-pre$'
go get example.net/x@v0.1.0 example.net/y@v0.1.0
go mod tidy
stderr '^go: found example\.net/x in example\.net/x v0.1.0$'
# TODO: These error messages should be clearer — it doesn't indicate why v0.2.0-pre is required.
-stderr '^example\.net/m imports\n\texample\.net/w: package example\.net/w provided by example\.net/w at latest version v0\.1\.0 but not at required version v0\.2\.0-pre$'
-stderr '^example\.net/m imports\n\texample\.net/y: package example\.net/y provided by example\.net/y at latest version v0\.1\.0 but not at required version v0\.2\.0-pre$'
-stderr '^example\.net/m imports\n\texample\.net/z: package example\.net/z provided by example\.net/z at latest version v0\.1\.0 but not at required version v0\.2\.0-pre$'
+stderr '^go: example\.net/m imports\n\texample\.net/w: package example\.net/w provided by example\.net/w at latest version v0\.1\.0 but not at required version v0\.2\.0-pre$'
+stderr '^go: example\.net/m imports\n\texample\.net/y: package example\.net/y provided by example\.net/y at latest version v0\.1\.0 but not at required version v0\.2\.0-pre$'
+stderr '^go: example\.net/m imports\n\texample\.net/z: package example\.net/z provided by example\.net/z at latest version v0\.1\.0 but not at required version v0\.2\.0-pre$'
# 'go mod tidy -e' should preserve all of the upgrades to modules that could
stderr -count=2 '^go: finding module for package example\.net/z$'
stderr '^go: found example\.net/x in example\.net/x v0.1.0$'
-stderr '^example\.net/m imports\n\texample\.net/w: package example\.net/w provided by example\.net/w at latest version v0\.1\.0 but not at required version v0\.2\.0-pre$'
-stderr '^example\.net/m imports\n\texample\.net/y: package example\.net/y provided by example\.net/y at latest version v0\.1\.0 but not at required version v0\.2\.0-pre$'
-stderr '^example\.net/m imports\n\texample\.net/z: package example\.net/z provided by example\.net/z at latest version v0\.1\.0 but not at required version v0\.2\.0-pre$'
+stderr '^go: example\.net/m imports\n\texample\.net/w: package example\.net/w provided by example\.net/w at latest version v0\.1\.0 but not at required version v0\.2\.0-pre$'
+stderr '^go: example\.net/m imports\n\texample\.net/y: package example\.net/y provided by example\.net/y at latest version v0\.1\.0 but not at required version v0\.2\.0-pre$'
+stderr '^go: example\.net/m imports\n\texample\.net/z: package example\.net/z provided by example\.net/z at latest version v0\.1\.0 but not at required version v0\.2\.0-pre$'
go mod tidy -e
! go mod tidy
! stderr 'package nonexist is not in std'
-stderr '^issue27063 imports\n\tnonexist.example.com: cannot find module providing package nonexist.example.com'
-stderr '^issue27063 imports\n\tissue27063/other imports\n\tother.example.com/nonexist: cannot find module providing package other.example.com/nonexist'
+stderr '^go: issue27063 imports\n\tnonexist.example.com: cannot find module providing package nonexist.example.com'
+stderr '^go: issue27063 imports\n\tissue27063/other imports\n\tother.example.com/nonexist: cannot find module providing package other.example.com/nonexist'
! go mod vendor
! stderr 'package nonexist is not in std'
-stderr '^issue27063 imports\n\tnonexist.example.com: no required module provides package nonexist.example.com; to add it:\n\tgo get nonexist.example.com$'
-stderr '^issue27063 imports\n\tissue27063/other imports\n\tother.example.com/nonexist: no required module provides package other.example.com/nonexist; to add it:\n\tgo get other.example.com/nonexist$'
+stderr '^go: issue27063 imports\n\tnonexist.example.com: no required module provides package nonexist.example.com; to add it:\n\tgo get nonexist.example.com$'
+stderr '^go: issue27063 imports\n\tissue27063/other imports\n\tother.example.com/nonexist: no required module provides package other.example.com/nonexist; to add it:\n\tgo get other.example.com/nonexist$'
-- go.mod --
module issue27063
! go mod tidy
! stderr panic
-stderr '^golang\.org/issue46659 imports\n\texample\.com/missingpkg/deprecated: package example\.com/missingpkg/deprecated provided by example\.com/missingpkg at latest version v1\.0\.0 but not at required version v1\.0\.1-beta$'
+stderr '^go: golang\.org/issue46659 imports\n\texample\.com/missingpkg/deprecated: package example\.com/missingpkg/deprecated provided by example\.com/missingpkg at latest version v1\.0\.0 but not at required version v1\.0\.1-beta$'
go mod tidy -e
! go work init doesnotexist
-stderr 'go: creating workspace file: no go.mod file exists in directory doesnotexist'
+stderr 'go: directory doesnotexist does not exist'
go env GOWORK
! stdout .
stderr 'a(\\|/)a.go:4:8: no required module provides package rsc.io/quote; to add it:\n\tcd '$WORK(\\|/)gopath(\\|/)src(\\|/)a'\n\tgo get rsc.io/quote'
cd a
go get rsc.io/quote
+cat go.mod
go env GOMOD # go env GOMOD reports the module in a single module context
stdout $GOPATH(\\|/)src(\\|/)a(\\|/)go.mod
cd ..
cp go.work.backup go.work
cp go.work.d go.work
+go work use # update go version
go run example.com/d
# Test that we don't run into "newRequirements called with unsorted roots"
# panic with unsorted main modules.
cp go.work.backwards go.work
+go work use # update go version
go run example.com/d
# Test that command-line-arguments work inside and outside modules.
--- /dev/null
+# go get should update the go and toolchain lines in go.work
+env TESTGO_VERSION=go1.21
+env TESTGO_VERSION_SWITCH=switch
+env GOTOOLCHAIN=auto
+cp go.mod.new go.mod
+cp go.work.new go.work
+go get rsc.io/needgo121 rsc.io/needgo122 rsc.io/needgo123 rsc.io/needall
+stderr '^go: rsc.io/needall@v0.0.1 requires go >= 1.23; switching to go1.23.9$'
+stderr '^go: added rsc.io/needall v0.0.1'
+grep 'go 1.23$' go.mod
+grep 'go 1.23$' go.work
+grep 'toolchain go1.23.9' go.mod
+grep 'toolchain go1.23.9' go.work
+
+-- go.mod.new --
+module m
+go 1.1
+
+-- p.go --
+package p
+
+-- go.work.new --
+go 1.18
+use .
--- /dev/null
+# Check that go line in go.work is always >= go line of used modules.
+
+# Using an old Go version, fails during module loading, but we rewrite the error to the
+# same one a switching version would use, without the auto-switch.
+# This is a misconfigured system that should not arise in practice.
+env TESTGO_VERSION=go1.21.1
+env TESTGO_VERSION_SWITCH=switch
+cp go.work go.work.orig
+! go list
+stderr '^go: module . listed in go.work file requires go >= 1.21.2, but go.work lists go 1.21.1; to update it:\n\tgo work use$'
+go work use
+go list
+
+# Using a new enough Go version, fails later and can suggest 'go work use'.
+env TESTGO_VERSION=go1.21.2
+env TESTGO_VERSION_SWITCH=switch
+cp go.work.orig go.work
+! go list
+stderr '^go: module . listed in go.work file requires go >= 1.21.2, but go.work lists go 1.21.1; to update it:\n\tgo work use$'
+
+# go work use fixes the problem.
+go work use
+go list
+
+-- go.work --
+go 1.21.1
+use .
+
+-- go.mod --
+module m
+go 1.21.2
+
+-- p.go --
+package p
--- /dev/null
+
+# Create basic modules and work space.
+# Note that toolchain lines in modules should be completely ignored.
+env TESTGO_VERSION=go1.50
+mkdir m1_22_0
+go mod init -C m1_22_0
+go mod edit -C m1_22_0 -go=1.22.0 -toolchain=go1.99.0
+
+# work init writes the current Go version to the go line
+go work init
+grep '^go 1.50$' go.work
+! grep toolchain go.work
+
+# work init with older modules should leave go 1.50 in the go.work.
+rm go.work
+go work init ./m1_22_0
+grep '^go 1.50$' go.work
+! grep toolchain go.work
+
+# work init with newer modules should bump go,
+# including updating to a newer toolchain as needed.
+# Because work init writes the current toolchain as the go version,
+# it writes the bumped go version, not the max of the used modules.
+env TESTGO_VERSION=go1.21
+env TESTGO_VERSION_SWITCH=switch
+rm go.work
+env GOTOOLCHAIN=local
+! go work init ./m1_22_0
+stderr '^go: m1_22_0'${/}'go.mod requires go >= 1.22.0 \(running go 1.21; GOTOOLCHAIN=local\)$'
+env GOTOOLCHAIN=auto
+go work init ./m1_22_0
+stderr '^go: m1_22_0'${/}'go.mod requires go >= 1.22.0; switching to go1.22.9$'
+cat go.work
+grep '^go 1.22.9$' go.work
+! grep toolchain go.work
--- /dev/null
+# Create basic modules and work space.
+env TESTGO_VERSION=go1.50
+mkdir m1_22_0
+go mod init -C m1_22_0
+go mod edit -C m1_22_0 -go=1.22.0 -toolchain=go1.99.0
+mkdir m1_22_1
+go mod init -C m1_22_1
+go mod edit -C m1_22_1 -go=1.22.1 -toolchain=go1.99.1
+mkdir m1_24_rc0
+go mod init -C m1_24_rc0
+go mod edit -C m1_24_rc0 -go=1.24rc0 -toolchain=go1.99.2
+
+go work init ./m1_22_0 ./m1_22_1
+grep '^go 1.50$' go.work
+! grep toolchain go.work
+
+# work sync with older modules should leave go 1.50 in the go.work.
+go work sync
+cat go.work
+grep '^go 1.50$' go.work
+! grep toolchain go.work
+
+# work sync with newer modules should update go 1.21 -> 1.22.1 and toolchain -> go1.22.9 in go.work
+env TESTGO_VERSION=go1.21
+env TESTGO_VERSION_SWITCH=switch
+go work edit -go=1.21
+grep '^go 1.21$' go.work
+! grep toolchain go.work
+env GOTOOLCHAIN=local
+! go work sync
+stderr '^go: cannot load module m1_22_0 listed in go.work file: m1_22_0'${/}'go.mod requires go >= 1.22.0 \(running go 1.21; GOTOOLCHAIN=local\)$'
+stderr '^go: cannot load module m1_22_1 listed in go.work file: m1_22_1'${/}'go.mod requires go >= 1.22.1 \(running go 1.21; GOTOOLCHAIN=local\)$'
+env GOTOOLCHAIN=auto
+go work sync
+stderr '^go: m1_22_1'${/}'go.mod requires go >= 1.22.1; switching to go1.22.9$'
+grep '^go 1.22.1$' go.work
+grep '^toolchain go1.22.9$' go.work
+
+# work sync with newer modules should update go 1.22.1 -> 1.24rc1 and drop toolchain
+go work edit -use=./m1_24_rc0
+go work sync
+stderr '^go: m1_24_rc0'${/}'go.mod requires go >= 1.24rc0; switching to go1.24rc1$'
+cat go.work
+grep '^go 1.24rc0$' go.work
+grep '^toolchain go1.24rc1$' go.work
go work use -r foo
cmp go.work go.want_work_r
+! go work use other
+stderr '^go: error reading other'${/}'go.mod: missing module declaration'
+
+go mod edit -C other -module=other
go work use other
cmp go.work go.want_work_other
-- go.work --
! go list .
-stderr '^go: cannot load module y.go.mod listed in go\.work file: open .+go\.mod:'
+stderr '^go: cannot load module y listed in go\.work file: open y'${/}'go\.mod:'
-- go.work --
use ./y
+++ /dev/null
-# For now, 'go work use' requires arguments.
-# (Eventually, we may may it implicitly behave like 'go work use .'.
-
-! go work use
-stderr '^go: ''go work use'' requires one or more directory arguments'
-
-! go work use -r
-stderr '^go: ''go work use'' requires one or more directory arguments'
-
--- go.work --
-go 1.18
! go work use foo bar baz
-stderr '^go: '$WORK'[/\\]gopath[/\\]src[/\\]foo is not a directory'
-stderr '^go: directory '$WORK'[/\\]gopath[/\\]src[/\\]baz does not exist'
+stderr '^go: foo is not a directory'
+stderr '^go: directory baz does not exist'
cmp go.work go.work_want
! go work use -r qux
-stderr '^go: '$WORK'[/\\]gopath[/\\]src[/\\]qux is not a directory'
+stderr '^go: qux is not a directory'
-- go.work --
go 1.18
-- foo --
-- qux --
-- bar/go.mod --
-module bar
\ No newline at end of file
+module bar
--- /dev/null
+# Create basic modules and work space.
+env TESTGO_VERSION=go1.50
+mkdir m1_22_0
+go mod init -C m1_22_0
+go mod edit -C m1_22_0 -go=1.22.0 -toolchain=go1.99.0
+mkdir m1_22_1
+go mod init -C m1_22_1
+go mod edit -C m1_22_1 -go=1.22.1 -toolchain=go1.99.1
+mkdir m1_24_rc0
+go mod init -C m1_24_rc0
+go mod edit -C m1_24_rc0 -go=1.24rc0 -toolchain=go1.99.2
+
+go work init
+grep '^go 1.50$' go.work
+! grep toolchain go.work
+
+# work use with older modules should leave go 1.50 in the go.work.
+go work use ./m1_22_0
+grep '^go 1.50$' go.work
+! grep toolchain go.work
+
+# work use with newer modules should bump go and toolchain,
+# including updating to a newer toolchain as needed.
+env TESTGO_VERSION=go1.21
+env TESTGO_VERSION_SWITCH=switch
+rm go.work
+go work init
+env GOTOOLCHAIN=local
+! go work use ./m1_22_0
+stderr '^go: m1_22_0'${/}'go.mod requires go >= 1.22.0 \(running go 1.21; GOTOOLCHAIN=local\)$'
+env GOTOOLCHAIN=auto
+go work use ./m1_22_0
+stderr '^go: m1_22_0'${/}'go.mod requires go >= 1.22.0; switching to go1.22.9$'
+grep '^go 1.22.0$' go.work
+grep '^toolchain go1.22.9$' go.work
+
+# work use with an even newer module should bump go again.
+go work use ./m1_22_1
+! stderr switching
+grep '^go 1.22.1$' go.work
+grep '^toolchain go1.22.9$' go.work # unchanged
+
+# work use with an even newer module should bump go and toolchain again.
+env GOTOOLCHAIN=go1.22.9
+! go work use ./m1_24_rc0
+stderr '^go: m1_24_rc0'${/}'go.mod requires go >= 1.24rc0 \(running go 1.22.9; GOTOOLCHAIN=go1.22.9\)$'
+env GOTOOLCHAIN=auto
+go work use ./m1_24_rc0
+stderr '^go: m1_24_rc0'${/}'go.mod requires go >= 1.24rc0; switching to go1.24rc1$'
+grep '^go 1.24rc0$' go.work
+grep '^toolchain go1.24rc1$' go.work