// and diagnose the problem.
return
}
+ gover.Startup.GOTOOLCHAIN = gotoolchain
var minToolchain, minVers string
if x, y, ok := strings.Cut(gotoolchain, "+"); ok { // go1.2.3+auto
gotoolchain = "go" + goVers
}
} else {
- goVers, toolchain := modGoToolchain()
+ file, goVers, toolchain := modGoToolchain()
if toolchain == "local" {
// Local means always use the default local toolchain,
// which is already set, so nothing to do here.
gotoolchain = "go" + goVers
}
}
+ gover.Startup.AutoFile = file
+ gover.Startup.AutoGoVersion = goVers
+ gover.Startup.AutoToolchain = toolchain
}
}
// modGoToolchain finds the enclosing go.work or go.mod file
// and returns the go version and toolchain lines from the file.
// The toolchain line overrides the version line
-func modGoToolchain() (goVers, toolchain string) {
+func modGoToolchain() (file, goVers, toolchain string) {
wd := base.UncachedCwd()
- file := modload.FindGoWork(wd)
+ file = modload.FindGoWork(wd)
// $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
if _, err := os.Stat(file); err != nil {
file = modload.FindGoMod(wd)
}
if file == "" {
- return "", ""
+ return "", "", ""
}
data, err := os.ReadFile(file)
if err != nil {
base.Fatalf("%v", err)
}
- return gover.GoModLookup(data, "go"), gover.GoModLookup(data, "toolchain")
+ return file, gover.GoModLookup(data, "go"), gover.GoModLookup(data, "toolchain")
}
// goInstallVersion looks at the command line to see if it is go install m@v or go run m@v.
package gover
-import "strings"
+import (
+ "cmd/go/internal/base"
+ "errors"
+ "fmt"
+ "strings"
+)
// ToolchainVersion returns the Go version for the named toolchain,
// derived from the name itself (not by running the toolchain).
}
return v
}
+
+// Startup records the information that went into the startup-time version switch.
+// It is initialized by switchGoToolchain.
+var Startup struct {
+ GOTOOLCHAIN string // $GOTOOLCHAIN setting
+ AutoFile string // go.mod or go.work file consulted
+ AutoGoVersion string // go line found in file
+ AutoToolchain string // toolchain line found in file
+}
+
+// A TooNewError explains that a module is too new for this version of Go.
+type TooNewError struct {
+ What string
+ GoVersion string
+}
+
+func (e *TooNewError) Error() string {
+ var explain string
+ if Startup.GOTOOLCHAIN != "" && Startup.GOTOOLCHAIN != "auto" {
+ explain = "; GOTOOLCHAIN=" + Startup.GOTOOLCHAIN
+ }
+ if Startup.AutoFile != "" && (Startup.AutoGoVersion != "" || Startup.AutoToolchain != "") {
+ explain += fmt.Sprintf("; %s sets ", base.ShortPath(Startup.AutoFile))
+ if Startup.AutoGoVersion != "" {
+ explain += "go " + Startup.AutoGoVersion
+ if Startup.AutoToolchain != "" {
+ explain += ", "
+ }
+ }
+ if Startup.AutoToolchain != "" {
+ explain += "toolchain " + Startup.AutoToolchain
+ }
+ }
+ return fmt.Sprintf("%v requires go %v (running go %v%v)", e.What, e.GoVersion, Local(), explain)
+}
+
+var ErrTooNew = errors.New("module too new")
+
+func (e *TooNewError) Is(err error) bool {
+ return err == ErrTooNew
+}
"io/fs"
"os"
"path"
+ "path/filepath"
"sort"
"strings"
"time"
+ "cmd/go/internal/gover"
"cmd/go/internal/modfetch/codehost"
"golang.org/x/mod/modfile"
if err != nil {
return err
}
+
+ if gomod, err := r.code.ReadFile(ctx, rev, filepath.Join(subdir, "go.mod"), codehost.MaxGoMod); err == nil {
+ goVers := gover.GoModLookup(gomod, "go")
+ if gover.Compare(goVers, gover.Local()) > 0 {
+ return &gover.TooNewError{What: r.ModulePath() + "@" + version, GoVersion: goVers}
+ }
+ } else if !errors.Is(err, fs.ErrNotExist) {
+ return err
+ }
+
dl, err := r.code.ReadZip(ctx, rev, subdir, codehost.MaxZipFile)
if err != nil {
return err
return "", err
}
checkMod(ctx, mod)
+
+ // If go.mod exists (not an old legacy module), check version is not too new.
+ if data, err := os.ReadFile(filepath.Join(dir, "go.mod")); err == nil {
+ goVersion := gover.GoModLookup(data, "go")
+ if gover.Compare(goVersion, gover.Local()) > 0 {
+ return "", &gover.TooNewError{What: mod.String(), GoVersion: goVersion}
+ }
+ } else if !errors.Is(err, fs.ErrNotExist) {
+ return "", err
+ }
+
return dir, nil
})
}
return false
}
-// checkMod checks the given module's checksum.
+// checkMod checks the given module's checksum and Go version.
func checkMod(ctx context.Context, mod module.Version) {
// Do the file I/O before acquiring the go.sum lock.
ziphash, err := CachePath(ctx, mod, "ziphash")
return nil, err
}
if f.Go != nil && gover.Compare(f.Go.Version, gover.Local()) > 0 {
- base.Fatalf("go: %s requires go %v (running go %v)", base.ShortPath(path), f.Go.Version, gover.Local())
+ base.Fatalf("go: %v", &gover.TooNewError{What: base.ShortPath(path), GoVersion: f.Go.Version})
}
return f, nil
}
if modFile.Go == nil || modFile.Go.Version == "" {
modFile.AddGoStmt(modFileGoVersion(modFile))
}
+
+ if gover.Compare(modFile.Go.Version, gover.Local()) > 0 {
+ // TODO: Reinvoke the newer toolchain if GOTOOLCHAIN=auto.
+ base.Fatalf("go: %v", &gover.TooNewError{What: "updating go.mod", GoVersion: modFile.Go.Version})
+ }
+
if gover.Compare(modFileGoVersion(modFile), separateIndirectVersion) < 0 {
modFile.SetRequire(list)
} else {
return nil, nil, fmt.Errorf("errors parsing go.mod:\n%s\n", err)
}
if f.Go != nil && gover.Compare(f.Go.Version, gover.Local()) > 0 {
- base.Fatalf("go: %s requires go %v (running go %v)", base.ShortPath(gomod), f.Go.Version, gover.Local())
+ base.Fatalf("go: %v", &gover.TooNewError{What: base.ShortPath(gomod), GoVersion: f.Go.Version})
}
if f.Module == nil {
// No module declaration. Must add module path.
summary.require = append(summary.require, req.Mod)
}
}
+ if summary.goVersion != "" && gover.Compare(summary.goVersion, "1.21") >= 0 {
+ if gover.Compare(summary.goVersion, gover.Local()) > 0 {
+ return nil, &gover.TooNewError{What: summary.module.String(), GoVersion: summary.goVersion}
+ }
+ }
if len(f.Retract) > 0 {
summary.retract = make([]retraction, 0, len(f.Retract))
for _, ret := range f.Retract {
meta.GoVersion = goVersion
rawGoVersion.Store(mod, meta.GoVersion)
if gover.Compare(goVersion, gover.Local()) > 0 {
- base.Fatalf("go: %s in %s requires go %v (running go %v)", mod.Path, base.ShortPath(vendorFile), goVersion, gover.Local())
+ base.Fatalf("go: %v", &gover.TooNewError{What: mod.Path + " in " + base.ShortPath(vendorFile), GoVersion: goVersion})
}
}
// All other tokens are reserved for future use.
--- /dev/null
+rsc.io/future v1.0.0
+written by hand
+
+-- .mod --
+module rsc.io/future
+go 1.999
+-- .info --
+{"Version":"v1.0.0"}
+-- main.go --
+package main
+
+func main() {
+}
+-- go.mod --
+module rsc.io/future
+go 1.999
# toolchain local in go.mod
cp go1999toolchainlocal go.mod
! go build
-stderr '^go: go.mod requires go 1.999 \(running go 1\.100\)$'
+stderr '^go: go.mod requires go 1.999 \(running go 1.100; go.mod sets go 1.999, toolchain local\)$'
# toolchain local in go.work
cp empty go.mod
cp go1999toolchainlocal go.work
! go build
-stderr '^go: go.work requires go 1.999 \(running go 1\.100\)$'
+stderr '^go: go.work requires go 1.999 \(running go 1.100; go.work sets go 1.999, toolchain local\)$'
rm go.work
# toolchain line in go.work
--- /dev/null
+env TESTGO_VERSION=go1.21
+! go mod download rsc.io/future@v1.0.0
+stderr '^go: rsc.io/future@v1.0.0 requires go 1.999 \(running go 1.21; go.mod sets go 1.21\)$'
+
+-- go.mod --
+module m
+go 1.21
+
+-- x.go --
+package p
+
+import "rsc.io/future/foo"
# Test support for declaring needed Go version in module.
env GO111MODULE=on
+env TESTGO_VERSION=go1.21
-go list
+# TODO(rsc): go list prints the error twice. Why?
+! go list
+stderr '^go: sub@v1.0.0: sub requires go 1.999 \(running go 1.21; go.mod sets go 1.1\)$'
! go build sub
-stderr '^sub: module requires Go 1.999 or later$'
+stderr '^go: sub@v1.0.0: sub requires go 1.999 \(running go 1.21; go.mod sets go 1.1\)$'
-- go.mod --
module m
package x
-- sub/go.mod --
-module m
+module sub
go 1.999
-- sub/x.go --