| // 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 ( |
| "cmd/go/internal/base" |
| "context" |
| "errors" |
| "fmt" |
| "strings" |
| ) |
| |
| // FromToolchain returns the Go version for the named toolchain, |
| // derived from the name itself (not by running the toolchain). |
| // A toolchain is named "goVERSION". |
| // A suffix after the VERSION introduced by a -, space, or tab is removed. |
| // Examples: |
| // |
| // FromToolchain("go1.2.3") == "1.2.3" |
| // FromToolchain("go1.2.3-bigcorp") == "1.2.3" |
| // FromToolchain("invalid") == "" |
| func FromToolchain(name string) string { |
| if strings.ContainsAny(name, "\\/") { |
| // The suffix must not include a path separator, since that would cause |
| // exec.LookPath to resolve it from a relative directory instead of from |
| // $PATH. |
| return "" |
| } |
| |
| var v string |
| if strings.HasPrefix(name, "go") { |
| v = name[2:] |
| } else { |
| return "" |
| } |
| // Some builds use custom suffixes; strip them. |
| if i := strings.IndexAny(v, " \t-"); i >= 0 { |
| v = v[:i] |
| } |
| if !IsValid(v) { |
| return "" |
| } |
| return v |
| } |
| |
| func maybeToolchainVersion(name string) string { |
| if IsValid(name) { |
| return name |
| } |
| return FromToolchain(name) |
| } |
| |
| // ToolchainMax returns the maximum of x and y interpreted as toolchain names, |
| // compared using Compare(FromToolchain(x), FromToolchain(y)). |
| // If x and y compare equal, Max returns x. |
| func ToolchainMax(x, y string) string { |
| if Compare(FromToolchain(x), FromToolchain(y)) < 0 { |
| return y |
| } |
| return x |
| } |
| |
| // 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 |
| Toolchain string // for callers if they want to use it, but not printed |
| } |
| |
| 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.AutoToolchain != "" { |
| explain += "toolchain " + Startup.AutoToolchain |
| } else { |
| explain += "go " + Startup.AutoGoVersion |
| } |
| } |
| 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 |
| } |
| |
| // 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) |
| } |