]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/go: implement 'toolchain local'
authorRuss Cox <rsc@golang.org>
Tue, 23 May 2023 18:23:31 +0000 (14:23 -0400)
committerRuss Cox <rsc@golang.org>
Wed, 24 May 2023 19:27:00 +0000 (19:27 +0000)
The actual selection code already worked
(except for the x/mod parser not reading the file),
so all that is necessary is a test.
For the test, move the version check up before
the module line presence check.

For #57001.

Change-Id: Iaa4f9b92d38fcfd99dc1665ec8d3eb0e52007bb4
Reviewed-on: https://go-review.googlesource.com/c/go/+/497555
TryBot-Bypass: Russ Cox <rsc@golang.org>
Run-TryBot: Russ Cox <rsc@golang.org>
Reviewed-by: Bryan Mills <bcmills@google.com>
src/cmd/go/gotoolchain.go
src/cmd/go/internal/gover/toolchain.go [new file with mode: 0644]
src/cmd/go/internal/modload/init.go
src/cmd/go/internal/modload/modfile.go
src/cmd/go/internal/version/version.go
src/cmd/go/testdata/script/gotoolchain.txt

index 528209b5fe62ff267ef5963d8a0cb207cee4e31c..850777a8547084d11836674587639218ea9cf15c 100644 (file)
@@ -79,44 +79,62 @@ func switchGoToolchain() {
                return
        }
 
-       gotoolchain, min, haveMin := strings.Cut(gotoolchain, "+")
-       if haveMin {
-               if gotoolchain != "auto" && gotoolchain != "path" {
-                       base.Fatalf("invalid GOTOOLCHAIN %q: only auto and path can use +version", gotoolchain)
+       var minToolchain, minVers string
+       if x, y, ok := strings.Cut(gotoolchain, "+"); ok { // go1.2.3+auto
+               orig := gotoolchain
+               minToolchain, gotoolchain = x, y
+               minVers = gover.ToolchainVersion(minToolchain)
+               if minVers == "" {
+                       base.Fatalf("invalid GOTOOLCHAIN %q: invalid minimum toolchain %q", orig, minToolchain)
                }
-               if !strings.HasPrefix(min, "go1") {
-                       base.Fatalf("invalid GOTOOLCHAIN %q: invalid minimum version %q", gotoolchain, min)
+               if gotoolchain != "auto" && gotoolchain != "path" {
+                       base.Fatalf("invalid GOTOOLCHAIN %q: only version suffixes are +auto and +path", orig)
                }
        } else {
-               min = "go" + gover.Local()
+               minVers = gover.Local()
+               minToolchain = "go" + minVers
        }
 
        pathOnly := gotoolchain == "path"
        if gotoolchain == "auto" || gotoolchain == "path" {
+               gotoolchain = minToolchain
+
                // Locate and read go.mod or go.work.
                // For go install m@v, it's the installed module's go.mod.
                if m, goVers, ok := goInstallVersion(); ok {
-                       v := strings.TrimPrefix(min, "go")
-                       if gover.Compare(v, goVers) < 0 {
+                       if gover.Compare(goVers, minVers) > 0 {
                                // Always print, because otherwise there's no way for the user to know
                                // that a non-default toolchain version is being used here.
                                // (Normally you can run "go version", but go install m@v ignores the
                                // context that "go version" works in.)
                                fmt.Fprintf(os.Stderr, "go: using go%s for %v\n", goVers, m)
-                               v = goVers
+                               gotoolchain = "go" + goVers
                        }
-                       gotoolchain = "go" + v
                } else {
                        goVers, toolchain := modGoToolchain()
-                       if toolchain != "" {
-                               // toolchain line wins by itself
-                               gotoolchain = toolchain
+                       if toolchain == "local" {
+                               // Local means always use the default local toolchain,
+                               // which is already set, so nothing to do here.
+                               // Note that if we have Go 1.21 installed originally,
+                               // GOTOOLCHAIN=go1.30.0+auto or GOTOOLCHAIN=go1.30.0,
+                               // and the go.mod  says "toolchain local", we use Go 1.30, not Go 1.21.
+                               // That is, local overrides the "auto" part of the calculation
+                               // but not the minimum that the user has set.
+                               // Of course, if the go.mod also says "go 1.35", using Go 1.30
+                               // will provoke an error about the toolchain being too old.
+                               // That's what people who use toolchain local want:
+                               // only ever use the toolchain configured in the local system
+                               // (including its environment and go env -w file).
+                       } else if toolchain != "" {
+                               // Accept toolchain only if it is >= our min.
+                               toolVers := gover.ToolchainVersion(toolchain)
+                               if gover.Compare(toolVers, minVers) > 0 {
+                                       gotoolchain = toolchain
+                               }
                        } else {
-                               v := strings.TrimPrefix(min, "go")
-                               if gover.Compare(v, goVers) < 0 {
-                                       v = goVers
+                               if gover.Compare(goVers, minVers) > 0 {
+                                       gotoolchain = "go" + goVers
                                }
-                               gotoolchain = "go" + v
                        }
                }
        }
@@ -130,6 +148,8 @@ func switchGoToolchain() {
        // We want to allow things like go1.20.3 but also gccgo-go1.20.3.
        // We want to disallow mistakes / bad ideas like GOTOOLCHAIN=bash,
        // since we will find that in the path lookup.
+       // gover.ToolchainVersion has already done this check (except for the 1)
+       // but doing it again makes sure we don't miss it on unexpected code paths.
        if !strings.HasPrefix(gotoolchain, "go1") && !strings.Contains(gotoolchain, "-go1") {
                base.Fatalf("invalid GOTOOLCHAIN %q", gotoolchain)
        }
diff --git a/src/cmd/go/internal/gover/toolchain.go b/src/cmd/go/internal/gover/toolchain.go
new file mode 100644 (file)
index 0000000..ed1572a
--- /dev/null
@@ -0,0 +1,25 @@
+// 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 gover
+
+import "strings"
+
+// ToolchainVersion returns the Go version for the named toolchain,
+// derived from the name itself (not by running the toolchain).
+// A toolchain is named "goVERSION" or "anything-goVERSION".
+// Examples:
+//
+//     ToolchainVersion("go1.2.3") == "1.2.3"
+//     ToolchainVersion("gccgo-go1.23rc4") == "1.23rc4"
+//     ToolchainVersion("invalid") == ""
+func ToolchainVersion(name string) string {
+       var v string
+       if strings.HasPrefix(name, "go") && IsValid(name[2:]) {
+               v = name[2:]
+       } else if i := strings.Index(name, "-go"); i >= 0 && IsValid(name[i+3:]) {
+               v = name[i+3:]
+       }
+       return v
+}
index 9d8fbd18da89e7ec5cc82f27bbb965da5ca4c989..240a9d29e00154af8992e621cbd921aab4fbe929 100644 (file)
@@ -625,7 +625,14 @@ func ReadWorkFile(path string) (*modfile.WorkFile, error) {
                return nil, err
        }
 
-       return modfile.ParseWork(path, workData, nil)
+       f, err := modfile.ParseWork(path, workData, nil)
+       if err != nil {
+               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())
+       }
+       return f, nil
 }
 
 // WriteWorkFile cleans and writes out the go.work file to the given path.
@@ -697,9 +704,6 @@ func LoadModFile(ctx context.Context) *Requirements {
                if err != nil {
                        base.Fatalf("reading go.work: %v", err)
                }
-               if gover.Compare(workFileGoVersion, gover.Local()) > 0 {
-                       base.Fatalf("go: %s requires go %v (running go %v)", base.ShortPath(workFilePath), workFileGoVersion, gover.Local())
-               }
                for _, modRoot := range modRoots {
                        sumFile := strings.TrimSuffix(modFilePath(modRoot), ".mod") + ".sum"
                        modfetch.WorkspaceGoSumFiles = append(modfetch.WorkspaceGoSumFiles, sumFile)
@@ -762,9 +766,6 @@ func LoadModFile(ctx context.Context) *Requirements {
                                base.Fatalf("go: %v", 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())
-               }
 
                modFiles = append(modFiles, f)
                mainModule := f.Module.Mod
index eef922845434649aac19dc7e204bae578699ff37..02d921201b37c05f5a0f0e6551e2ba74bf050e8d 100644 (file)
@@ -74,6 +74,9 @@ func ReadModFile(gomod string, fix modfile.VersionFixer) (data []byte, f *modfil
                // Errors returned by modfile.Parse begin with file:line.
                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())
+       }
        if f.Module == nil {
                // No module declaration. Must add module path.
                return nil, nil, errors.New("no module declaration in go.mod. To specify the module path:\n\tgo mod edit -module=example.com/mod")
index a0f612314977879d27951a275d633d5aad032796..4a0132a3fe92f40675e6ea76c81a14041742272a 100644 (file)
@@ -17,6 +17,7 @@ import (
        "strings"
 
        "cmd/go/internal/base"
+       "cmd/go/internal/gover"
 )
 
 var CmdVersion = &base.Command{
@@ -73,7 +74,11 @@ func runVersion(ctx context.Context, cmd *base.Command, args []string) {
                        base.SetExitStatus(2)
                        return
                }
-               fmt.Printf("go version %s %s/%s\n", runtime.Version(), runtime.GOOS, runtime.GOARCH)
+               v := runtime.Version()
+               if gover.TestVersion != "" {
+                       v = gover.TestVersion + " (TESTGO_VERSION)"
+               }
+               fmt.Printf("go version %s %s/%s\n", v, runtime.GOOS, runtime.GOARCH)
                return
        }
 
index fdd17b584e1ce8e2a78a109605b6f12857762e26..b29dcc6520ada1f718081371e9924d26bcf57cf8 100644 (file)
@@ -42,13 +42,25 @@ stderr 'go: downloading go1.999testmod \(.*/.*\)'
 
 # GOTOOLCHAIN=auto
 env GOTOOLCHAIN=auto
-env TESTGO_VERSION=go1.100 # set TESTGO_VERSION because devel is newer than everything
+env TESTGO_VERSION=go1.100
 
 # toolchain line in go.mod
 cp go119toolchain1999 go.mod
 go version
 stdout go1.999
 
+# toolchain local in go.mod
+cp go1999toolchainlocal go.mod
+! go build
+stderr '^go: go.mod requires go 1.999 \(running go 1\.100\)$'
+
+# 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\)$'
+rm go.work
+
 # toolchain line in go.work
 cp empty go.mod
 cp go119toolchain1999 go.work
@@ -68,9 +80,36 @@ go version
 stdout go1.999
 rm go.work
 
-# GOTOOLCHAIN=auto falls back to local toolchain if newer than go line
+# GOTOOLCHAIN=auto falls back to local toolchain if newer than go or toolchain line
 env TESTGO_VERSION=go1.1000
 
+# toolchain line in go.mod
+cp go119toolchain1999 go.mod
+go version
+stdout go1.1000
+
+# toolchain line in go.work
+cp empty go.mod
+cp go119toolchain1999 go.work
+go version
+stdout go1.1000
+rm go.work
+
+# go version in go.mod
+cp go1999 go.mod
+go version
+stdout go1.1000
+
+# go version in go.work
+cp empty go.mod
+cp go1999 go.work
+go version
+stdout go1.1000
+rm go.work
+
+# GOTOOLCHAIN=auto uses different toolchain when instructed and newer
+env TESTGO_VERSION=go1.100
+
 # toolchain line in go.mod
 cp go119toolchain1999 go.mod
 go version
@@ -86,13 +125,13 @@ rm go.work
 # go version in go.mod
 cp go1999 go.mod
 go version
-stdout go1.999
+stdout go1.999
 
 # go version in go.work
 cp empty go.mod
 cp go1999 go.work
 go version
-stdout go1.999
+stdout go1.999
 rm go.work
 
 # go1.999 should handle go1.998 without a download
@@ -104,42 +143,42 @@ go version
 # go1.998 should handle go1.998 without a download too
 env TESTGO_VERSION=go1.999
 go version
-! stdout go1.998 # local toolchain instead
+stdout go1.999 # local toolchain instead
 
 # go1.998+foo should handle go1.998 without a download too
 env TESTGO_VERSION=go1.998+foo
 go version
-! stdout go1.998 # local toolchain instead
+stdout 'go1.998\+foo' # local toolchain instead
 
 # go1.998-foo should handle go1.998 without a download too
 env TESTGO_VERSION=go1.998-foo
 go version
-! stdout go1.998 # local toolchain instead
+stdout go1.998-foo # local toolchain instead
 
 # 'go1.998 foo' should handle go1.998 without a download too
 env TESTGO_VERSION='go1.998 foo'
 go version
-! stdout go1.998 # local toolchain instead
+stdout 'go1.998 foo' # local toolchain instead
 
 # go1.997-foo should download go1.998
 env TESTGO_VERSION=go1.997-foo
 ! go version
 stderr go1.998
 
-# GOTOOLCHAIN=auto+go1.1000 falls back to go1.1000 if newer than go line
+# GOTOOLCHAIN=go1.1000+auto falls back to go1.1000 if newer than go line
 env TESTGO_VERSION=go1.1
-env GOTOOLCHAIN=auto+go1.1000
+env GOTOOLCHAIN=go1.1000+auto
 
 # toolchain line in go.mod
 cp go119toolchain1999 go.mod
-go version
-stdout go1.999
+go version
+stderr go1.1000
 
 # toolchain line in go.work
 cp empty go.mod
 cp go119toolchain1999 go.work
-go version
-stdout go1.999
+go version
+stderr go1.1000
 rm go.work
 
 # go version in go.mod
@@ -206,9 +245,9 @@ go 1.19
 go 1.19
 toolchain go1.999testpath
 
--- go1999toolchain119 --
-go 1.999testpath
-toolchain go1.19
+-- go1999toolchainlocal --
+go 1.999
+toolchain local
 
 -- go1.999testpath.go --
 package main