1// Copyright 2023 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package gover
6
7import (
8	"cmd/go/internal/base"
9	"context"
10	"errors"
11	"fmt"
12	"strings"
13)
14
15// FromToolchain returns the Go version for the named toolchain,
16// derived from the name itself (not by running the toolchain).
17// A toolchain is named "goVERSION".
18// A suffix after the VERSION introduced by a -, space, or tab is removed.
19// Examples:
20//
21//	FromToolchain("go1.2.3") == "1.2.3"
22//	FromToolchain("go1.2.3-bigcorp") == "1.2.3"
23//	FromToolchain("invalid") == ""
24func FromToolchain(name string) string {
25	if strings.ContainsAny(name, "\\/") {
26		// The suffix must not include a path separator, since that would cause
27		// exec.LookPath to resolve it from a relative directory instead of from
28		// $PATH.
29		return ""
30	}
31
32	var v string
33	if strings.HasPrefix(name, "go") {
34		v = name[2:]
35	} else {
36		return ""
37	}
38	// Some builds use custom suffixes; strip them.
39	if i := strings.IndexAny(v, " \t-"); i >= 0 {
40		v = v[:i]
41	}
42	if !IsValid(v) {
43		return ""
44	}
45	return v
46}
47
48func maybeToolchainVersion(name string) string {
49	if IsValid(name) {
50		return name
51	}
52	return FromToolchain(name)
53}
54
55// ToolchainMax returns the maximum of x and y interpreted as toolchain names,
56// compared using Compare(FromToolchain(x), FromToolchain(y)).
57// If x and y compare equal, Max returns x.
58func ToolchainMax(x, y string) string {
59	if Compare(FromToolchain(x), FromToolchain(y)) < 0 {
60		return y
61	}
62	return x
63}
64
65// Startup records the information that went into the startup-time version switch.
66// It is initialized by switchGoToolchain.
67var Startup struct {
68	GOTOOLCHAIN   string // $GOTOOLCHAIN setting
69	AutoFile      string // go.mod or go.work file consulted
70	AutoGoVersion string // go line found in file
71	AutoToolchain string // toolchain line found in file
72}
73
74// A TooNewError explains that a module is too new for this version of Go.
75type TooNewError struct {
76	What      string
77	GoVersion string
78	Toolchain string // for callers if they want to use it, but not printed
79}
80
81func (e *TooNewError) Error() string {
82	var explain string
83	if Startup.GOTOOLCHAIN != "" && Startup.GOTOOLCHAIN != "auto" {
84		explain = "; GOTOOLCHAIN=" + Startup.GOTOOLCHAIN
85	}
86	if Startup.AutoFile != "" && (Startup.AutoGoVersion != "" || Startup.AutoToolchain != "") {
87		explain += fmt.Sprintf("; %s sets ", base.ShortPath(Startup.AutoFile))
88		if Startup.AutoToolchain != "" {
89			explain += "toolchain " + Startup.AutoToolchain
90		} else {
91			explain += "go " + Startup.AutoGoVersion
92		}
93	}
94	return fmt.Sprintf("%v requires go >= %v (running go %v%v)", e.What, e.GoVersion, Local(), explain)
95}
96
97var ErrTooNew = errors.New("module too new")
98
99func (e *TooNewError) Is(err error) bool {
100	return err == ErrTooNew
101}
102
103// A Switcher provides the ability to switch to a new toolchain in response to TooNewErrors.
104// See [cmd/go/internal/toolchain.Switcher] for documentation.
105type Switcher interface {
106	Error(err error)
107	Switch(ctx context.Context)
108}
109