From ccd992355df7192993c666236047820244914598 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Tue, 16 Apr 2024 21:19:13 +0200 Subject: Adding upstream version 1.21.8. Signed-off-by: Daniel Baumann --- src/cmd/go/internal/gover/gomod.go | 43 +++++ src/cmd/go/internal/gover/gover.go | 254 ++++++++++++++++++++++++++++ src/cmd/go/internal/gover/gover_test.go | 160 ++++++++++++++++++ src/cmd/go/internal/gover/local.go | 42 +++++ src/cmd/go/internal/gover/mod.go | 127 ++++++++++++++ src/cmd/go/internal/gover/mod_test.go | 72 ++++++++ src/cmd/go/internal/gover/toolchain.go | 98 +++++++++++ src/cmd/go/internal/gover/toolchain_test.go | 19 +++ src/cmd/go/internal/gover/version.go | 74 ++++++++ 9 files changed, 889 insertions(+) create mode 100644 src/cmd/go/internal/gover/gomod.go create mode 100644 src/cmd/go/internal/gover/gover.go create mode 100644 src/cmd/go/internal/gover/gover_test.go create mode 100644 src/cmd/go/internal/gover/local.go create mode 100644 src/cmd/go/internal/gover/mod.go create mode 100644 src/cmd/go/internal/gover/mod_test.go create mode 100644 src/cmd/go/internal/gover/toolchain.go create mode 100644 src/cmd/go/internal/gover/toolchain_test.go create mode 100644 src/cmd/go/internal/gover/version.go (limited to 'src/cmd/go/internal/gover') diff --git a/src/cmd/go/internal/gover/gomod.go b/src/cmd/go/internal/gover/gomod.go new file mode 100644 index 0000000..4a4ae53 --- /dev/null +++ b/src/cmd/go/internal/gover/gomod.go @@ -0,0 +1,43 @@ +// 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 ( + "bytes" + "strings" +) + +var nl = []byte("\n") + +// GoModLookup takes go.mod or go.work content, +// finds the first line in the file starting with the given key, +// and returns the value associated with that key. +// +// Lookup should only be used with non-factored verbs +// such as "go" and "toolchain", usually to find versions +// or version-like strings. +func GoModLookup(gomod []byte, key string) string { + for len(gomod) > 0 { + var line []byte + line, gomod, _ = bytes.Cut(gomod, nl) + line = bytes.TrimSpace(line) + if v, ok := parseKey(line, key); ok { + return v + } + } + return "" +} + +func parseKey(line []byte, key string) (string, bool) { + if !strings.HasPrefix(string(line), key) { + return "", false + } + s := strings.TrimPrefix(string(line), key) + if len(s) == 0 || (s[0] != ' ' && s[0] != '\t') { + return "", false + } + s, _, _ = strings.Cut(s, "//") // strip comments + return strings.TrimSpace(s), true +} diff --git a/src/cmd/go/internal/gover/gover.go b/src/cmd/go/internal/gover/gover.go new file mode 100644 index 0000000..b2a8261 --- /dev/null +++ b/src/cmd/go/internal/gover/gover.go @@ -0,0 +1,254 @@ +// 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 implements support for Go toolchain versions like 1.21.0 and 1.21rc1. +// (For historical reasons, Go does not use semver for its toolchains.) +// This package provides the same basic analysis that golang.org/x/mod/semver does for semver. +// It also provides some helpers for extracting versions from go.mod files +// and for dealing with module.Versions that may use Go versions or semver +// depending on the module path. +package gover + +import ( + "cmp" +) + +// A version is a parsed Go version: major[.minor[.patch]][kind[pre]] +// The numbers are the original decimal strings to avoid integer overflows +// and since there is very little actual math. (Probably overflow doesn't matter in practice, +// but at the time this code was written, there was an existing test that used +// go1.99999999999, which does not fit in an int on 32-bit platforms. +// The "big decimal" representation avoids the problem entirely.) +type version struct { + major string // decimal + minor string // decimal or "" + patch string // decimal or "" + kind string // "", "alpha", "beta", "rc" + pre string // decimal or "" +} + +// Compare returns -1, 0, or +1 depending on whether +// x < y, x == y, or x > y, interpreted as toolchain versions. +// The versions x and y must not begin with a "go" prefix: just "1.21" not "go1.21". +// Malformed versions compare less than well-formed versions and equal to each other. +// The language version "1.21" compares less than the release candidate and eventual releases "1.21rc1" and "1.21.0". +func Compare(x, y string) int { + vx := parse(x) + vy := parse(y) + + if c := cmpInt(vx.major, vy.major); c != 0 { + return c + } + if c := cmpInt(vx.minor, vy.minor); c != 0 { + return c + } + if c := cmpInt(vx.patch, vy.patch); c != 0 { + return c + } + if c := cmp.Compare(vx.kind, vy.kind); c != 0 { // "" < alpha < beta < rc + return c + } + if c := cmpInt(vx.pre, vy.pre); c != 0 { + return c + } + return 0 +} + +// Max returns the maximum of x and y interpreted as toolchain versions, +// compared using Compare. +// If x and y compare equal, Max returns x. +func Max(x, y string) string { + if Compare(x, y) < 0 { + return y + } + return x +} + +// Toolchain 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 +} + +// IsLang reports whether v denotes the overall Go language version +// and not a specific release. Starting with the Go 1.21 release, "1.x" denotes +// the overall language version; the first release is "1.x.0". +// The distinction is important because the relative ordering is +// +// 1.21 < 1.21rc1 < 1.21.0 +// +// meaning that Go 1.21rc1 and Go 1.21.0 will both handle go.mod files that +// say "go 1.21", but Go 1.21rc1 will not handle files that say "go 1.21.0". +func IsLang(x string) bool { + v := parse(x) + return v != version{} && v.patch == "" && v.kind == "" && v.pre == "" +} + +// Lang returns the Go language version. For example, Lang("1.2.3") == "1.2". +func Lang(x string) string { + v := parse(x) + if v.minor == "" { + return v.major + } + return v.major + "." + v.minor +} + +// IsPrerelease reports whether v denotes a Go prerelease version. +func IsPrerelease(x string) bool { + return parse(x).kind != "" +} + +// Prev returns the Go major release immediately preceding v, +// or v itself if v is the first Go major release (1.0) or not a supported +// Go version. +// +// Examples: +// +// Prev("1.2") = "1.1" +// Prev("1.3rc4") = "1.2" +func Prev(x string) string { + v := parse(x) + if cmpInt(v.minor, "1") <= 0 { + return v.major + } + return v.major + "." + decInt(v.minor) +} + +// IsValid reports whether the version x is valid. +func IsValid(x string) bool { + return parse(x) != version{} +} + +// parse parses the Go version string x into a version. +// It returns the zero version if x is malformed. +func parse(x string) version { + var v version + + // Parse major version. + var ok bool + v.major, x, ok = cutInt(x) + if !ok { + return version{} + } + if x == "" { + // Interpret "1" as "1.0.0". + v.minor = "0" + v.patch = "0" + return v + } + + // Parse . before minor version. + if x[0] != '.' { + return version{} + } + + // Parse minor version. + v.minor, x, ok = cutInt(x[1:]) + if !ok { + return version{} + } + if x == "" { + // Patch missing is same as "0" for older versions. + // Starting in Go 1.21, patch missing is different from explicit .0. + if cmpInt(v.minor, "21") < 0 { + v.patch = "0" + } + return v + } + + // Parse patch if present. + if x[0] == '.' { + v.patch, x, ok = cutInt(x[1:]) + if !ok || x != "" { + // Note that we are disallowing prereleases (alpha, beta, rc) for patch releases here (x != ""). + // Allowing them would be a bit confusing because we already have: + // 1.21 < 1.21rc1 + // But a prerelease of a patch would have the opposite effect: + // 1.21.3rc1 < 1.21.3 + // We've never needed them before, so let's not start now. + return version{} + } + return v + } + + // Parse prerelease. + i := 0 + for i < len(x) && (x[i] < '0' || '9' < x[i]) { + if x[i] < 'a' || 'z' < x[i] { + return version{} + } + i++ + } + if i == 0 { + return version{} + } + v.kind, x = x[:i], x[i:] + if x == "" { + return v + } + v.pre, x, ok = cutInt(x) + if !ok || x != "" { + return version{} + } + + return v +} + +// cutInt scans the leading decimal number at the start of x to an integer +// and returns that value and the rest of the string. +func cutInt(x string) (n, rest string, ok bool) { + i := 0 + for i < len(x) && '0' <= x[i] && x[i] <= '9' { + i++ + } + if i == 0 || x[0] == '0' && i != 1 { + return "", "", false + } + return x[:i], x[i:], true +} + +// cmpInt returns cmp.Compare(x, y) interpreting x and y as decimal numbers. +// (Copied from golang.org/x/mod/semver's compareInt.) +func cmpInt(x, y string) int { + if x == y { + return 0 + } + if len(x) < len(y) { + return -1 + } + if len(x) > len(y) { + return +1 + } + if x < y { + return -1 + } else { + return +1 + } +} + +// decInt returns the decimal string decremented by 1, or the empty string +// if the decimal is all zeroes. +// (Copied from golang.org/x/mod/module's decDecimal.) +func decInt(decimal string) string { + // Scan right to left turning 0s to 9s until you find a digit to decrement. + digits := []byte(decimal) + i := len(digits) - 1 + for ; i >= 0 && digits[i] == '0'; i-- { + digits[i] = '9' + } + if i < 0 { + // decimal is all zeros + return "" + } + if i == 0 && digits[i] == '1' && len(digits) > 1 { + digits = digits[1:] + } else { + digits[i]-- + } + return string(digits) +} diff --git a/src/cmd/go/internal/gover/gover_test.go b/src/cmd/go/internal/gover/gover_test.go new file mode 100644 index 0000000..3a0bf10 --- /dev/null +++ b/src/cmd/go/internal/gover/gover_test.go @@ -0,0 +1,160 @@ +// 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 ( + "reflect" + "testing" +) + +func TestCompare(t *testing.T) { test2(t, compareTests, "Compare", Compare) } + +var compareTests = []testCase2[string, string, int]{ + {"", "", 0}, + {"x", "x", 0}, + {"", "x", 0}, + {"1", "1.1", -1}, + {"1.5", "1.6", -1}, + {"1.5", "1.10", -1}, + {"1.6", "1.6.1", -1}, + {"1.19", "1.19.0", 0}, + {"1.19rc1", "1.19", -1}, + {"1.20", "1.20.0", 0}, + {"1.20rc1", "1.20", -1}, + {"1.21", "1.21.0", -1}, + {"1.21", "1.21rc1", -1}, + {"1.21rc1", "1.21.0", -1}, + {"1.6", "1.19", -1}, + {"1.19", "1.19.1", -1}, + {"1.19rc1", "1.19", -1}, + {"1.19rc1", "1.19.1", -1}, + {"1.19rc1", "1.19rc2", -1}, + {"1.19.0", "1.19.1", -1}, + {"1.19rc1", "1.19.0", -1}, + {"1.19alpha3", "1.19beta2", -1}, + {"1.19beta2", "1.19rc1", -1}, + {"1.1", "1.99999999999999998", -1}, + {"1.99999999999999998", "1.99999999999999999", -1}, +} + +func TestParse(t *testing.T) { test1(t, parseTests, "parse", parse) } + +var parseTests = []testCase1[string, version]{ + {"1", version{"1", "0", "0", "", ""}}, + {"1.2", version{"1", "2", "0", "", ""}}, + {"1.2.3", version{"1", "2", "3", "", ""}}, + {"1.2rc3", version{"1", "2", "", "rc", "3"}}, + {"1.20", version{"1", "20", "0", "", ""}}, + {"1.21", version{"1", "21", "", "", ""}}, + {"1.21rc3", version{"1", "21", "", "rc", "3"}}, + {"1.21.0", version{"1", "21", "0", "", ""}}, + {"1.24", version{"1", "24", "", "", ""}}, + {"1.24rc3", version{"1", "24", "", "rc", "3"}}, + {"1.24.0", version{"1", "24", "0", "", ""}}, + {"1.999testmod", version{"1", "999", "", "testmod", ""}}, + {"1.99999999999999999", version{"1", "99999999999999999", "", "", ""}}, +} + +func TestLang(t *testing.T) { test1(t, langTests, "Lang", Lang) } + +var langTests = []testCase1[string, string]{ + {"1.2rc3", "1.2"}, + {"1.2.3", "1.2"}, + {"1.2", "1.2"}, + {"1", "1.0"}, + {"1.999testmod", "1.999"}, +} + +func TestIsLang(t *testing.T) { test1(t, isLangTests, "IsLang", IsLang) } + +var isLangTests = []testCase1[string, bool]{ + {"1.2rc3", false}, + {"1.2.3", false}, + {"1.999testmod", false}, + {"1.22", true}, + {"1.21", true}, + {"1.20", false}, // == 1.20.0 + {"1.19", false}, // == 1.20.0 + {"1.3", false}, // == 1.3.0 + {"1.2", false}, // == 1.2.0 + {"1", false}, // == 1.0.0 +} + +func TestPrev(t *testing.T) { test1(t, prevTests, "Prev", Prev) } + +var prevTests = []testCase1[string, string]{ + {"", ""}, + {"0", "0"}, + {"1.3rc4", "1.2"}, + {"1.3.5", "1.2"}, + {"1.3", "1.2"}, + {"1", "1"}, + {"1.99999999999999999", "1.99999999999999998"}, + {"1.40000000000000000", "1.39999999999999999"}, +} + +func TestIsValid(t *testing.T) { test1(t, isValidTests, "IsValid", IsValid) } + +var isValidTests = []testCase1[string, bool]{ + {"1.2rc3", true}, + {"1.2.3", true}, + {"1.999testmod", true}, + {"1.600+auto", false}, + {"1.22", true}, + {"1.21.0", true}, + {"1.21rc2", true}, + {"1.21", true}, + {"1.20.0", true}, + {"1.20", true}, + {"1.19", true}, + {"1.3", true}, + {"1.2", true}, + {"1", true}, +} + +type testCase1[In, Out any] struct { + in In + out Out +} + +type testCase2[In1, In2, Out any] struct { + in1 In1 + in2 In2 + out Out +} + +type testCase3[In1, In2, In3, Out any] struct { + in1 In1 + in2 In2 + in3 In3 + out Out +} + +func test1[In, Out any](t *testing.T, tests []testCase1[In, Out], name string, f func(In) Out) { + t.Helper() + for _, tt := range tests { + if out := f(tt.in); !reflect.DeepEqual(out, tt.out) { + t.Errorf("%s(%v) = %v, want %v", name, tt.in, out, tt.out) + } + } +} + +func test2[In1, In2, Out any](t *testing.T, tests []testCase2[In1, In2, Out], name string, f func(In1, In2) Out) { + t.Helper() + for _, tt := range tests { + if out := f(tt.in1, tt.in2); !reflect.DeepEqual(out, tt.out) { + t.Errorf("%s(%+v, %+v) = %+v, want %+v", name, tt.in1, tt.in2, out, tt.out) + } + } +} + +func test3[In1, In2, In3, Out any](t *testing.T, tests []testCase3[In1, In2, In3, Out], name string, f func(In1, In2, In3) Out) { + t.Helper() + for _, tt := range tests { + if out := f(tt.in1, tt.in2, tt.in3); !reflect.DeepEqual(out, tt.out) { + t.Errorf("%s(%+v, %+v, %+v) = %+v, want %+v", name, tt.in1, tt.in2, tt.in3, out, tt.out) + } + } +} diff --git a/src/cmd/go/internal/gover/local.go b/src/cmd/go/internal/gover/local.go new file mode 100644 index 0000000..8183a5c --- /dev/null +++ b/src/cmd/go/internal/gover/local.go @@ -0,0 +1,42 @@ +// 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 ( + "internal/goversion" + "runtime" + "strconv" +) + +// TestVersion is initialized in the go command test binary +// to be $TESTGO_VERSION, to allow tests to override the +// go command's idea of its own version as returned by Local. +var TestVersion string + +// Local returns the local Go version, the one implemented by this go command. +func Local() string { + v, _ := local() + return v +} + +// LocalToolchain returns the local toolchain name, the one implemented by this go command. +func LocalToolchain() string { + _, t := local() + return t +} + +func local() (goVers, toolVers string) { + toolVers = runtime.Version() + if TestVersion != "" { + toolVers = TestVersion + } + goVers = FromToolchain(toolVers) + if goVers == "" { + // Development branch. Use "Dev" version with just 1.N, no rc1 or .0 suffix. + goVers = "1." + strconv.Itoa(goversion.Version) + toolVers = "go" + goVers + } + return goVers, toolVers +} diff --git a/src/cmd/go/internal/gover/mod.go b/src/cmd/go/internal/gover/mod.go new file mode 100644 index 0000000..d3cc170 --- /dev/null +++ b/src/cmd/go/internal/gover/mod.go @@ -0,0 +1,127 @@ +// 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 ( + "sort" + "strings" + + "golang.org/x/mod/module" + "golang.org/x/mod/semver" +) + +// IsToolchain reports whether the module path corresponds to the +// virtual, non-downloadable module tracking go or toolchain directives in the go.mod file. +// +// Note that IsToolchain only matches "go" and "toolchain", not the +// real, downloadable module "golang.org/toolchain" containing toolchain files. +// +// IsToolchain("go") = true +// IsToolchain("toolchain") = true +// IsToolchain("golang.org/x/tools") = false +// IsToolchain("golang.org/toolchain") = false +func IsToolchain(path string) bool { + return path == "go" || path == "toolchain" +} + +// ModCompare returns the result of comparing the versions x and y +// for the module with the given path. +// The path is necessary because the "go" and "toolchain" modules +// use a different version syntax and semantics (gover, this package) +// than most modules (semver). +func ModCompare(path string, x, y string) int { + if path == "go" { + return Compare(x, y) + } + if path == "toolchain" { + return Compare(maybeToolchainVersion(x), maybeToolchainVersion(y)) + } + return semver.Compare(x, y) +} + +// ModSort is like module.Sort but understands the "go" and "toolchain" +// modules and their version ordering. +func ModSort(list []module.Version) { + sort.Slice(list, func(i, j int) bool { + mi := list[i] + mj := list[j] + if mi.Path != mj.Path { + return mi.Path < mj.Path + } + // To help go.sum formatting, allow version/file. + // Compare semver prefix by semver rules, + // file by string order. + vi := mi.Version + vj := mj.Version + var fi, fj string + if k := strings.Index(vi, "/"); k >= 0 { + vi, fi = vi[:k], vi[k:] + } + if k := strings.Index(vj, "/"); k >= 0 { + vj, fj = vj[:k], vj[k:] + } + if vi != vj { + return ModCompare(mi.Path, vi, vj) < 0 + } + return fi < fj + }) +} + +// ModIsValid reports whether vers is a valid version syntax for the module with the given path. +func ModIsValid(path, vers string) bool { + if IsToolchain(path) { + if path == "toolchain" { + return IsValid(FromToolchain(vers)) + } + return IsValid(vers) + } + return semver.IsValid(vers) +} + +// ModIsPrefix reports whether v is a valid version syntax prefix for the module with the given path. +// The caller is assumed to have checked that ModIsValid(path, vers) is true. +func ModIsPrefix(path, vers string) bool { + if IsToolchain(path) { + if path == "toolchain" { + return IsLang(FromToolchain(vers)) + } + return IsLang(vers) + } + // Semver + dots := 0 + for i := 0; i < len(vers); i++ { + switch vers[i] { + case '-', '+': + return false + case '.': + dots++ + if dots >= 2 { + return false + } + } + } + return true +} + +// ModIsPrerelease reports whether v is a prerelease version for the module with the given path. +// The caller is assumed to have checked that ModIsValid(path, vers) is true. +func ModIsPrerelease(path, vers string) bool { + if IsToolchain(path) { + return IsPrerelease(vers) + } + return semver.Prerelease(vers) != "" +} + +// ModMajorMinor returns the "major.minor" truncation of the version v, +// for use as a prefix in "@patch" queries. +func ModMajorMinor(path, vers string) string { + if IsToolchain(path) { + if path == "toolchain" { + return "go" + Lang(FromToolchain(vers)) + } + return Lang(vers) + } + return semver.MajorMinor(vers) +} diff --git a/src/cmd/go/internal/gover/mod_test.go b/src/cmd/go/internal/gover/mod_test.go new file mode 100644 index 0000000..c92169c --- /dev/null +++ b/src/cmd/go/internal/gover/mod_test.go @@ -0,0 +1,72 @@ +// 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 ( + "slices" + "strings" + "testing" + + "golang.org/x/mod/module" +) + +func TestIsToolchain(t *testing.T) { test1(t, isToolchainTests, "IsToolchain", IsToolchain) } + +var isToolchainTests = []testCase1[string, bool]{ + {"go", true}, + {"toolchain", true}, + {"anything", false}, + {"golang.org/toolchain", false}, +} + +func TestModCompare(t *testing.T) { test3(t, modCompareTests, "ModCompare", ModCompare) } + +var modCompareTests = []testCase3[string, string, string, int]{ + {"go", "1.2", "1.3", -1}, + {"go", "v1.2", "v1.3", 0}, // equal because invalid + {"go", "1.2", "1.2", 0}, + {"toolchain", "go1.2", "go1.3", -1}, + {"toolchain", "go1.2", "go1.2", 0}, + {"toolchain", "1.2", "1.3", -1}, // accepted but non-standard + {"toolchain", "v1.2", "v1.3", 0}, // equal because invalid + {"rsc.io/quote", "v1.2", "v1.3", -1}, + {"rsc.io/quote", "1.2", "1.3", 0}, // equal because invalid +} + +func TestModIsValid(t *testing.T) { test2(t, modIsValidTests, "ModIsValid", ModIsValid) } + +var modIsValidTests = []testCase2[string, string, bool]{ + {"go", "1.2", true}, + {"go", "v1.2", false}, + {"toolchain", "go1.2", true}, + {"toolchain", "v1.2", false}, + {"rsc.io/quote", "v1.2", true}, + {"rsc.io/quote", "1.2", false}, +} + +func TestModSort(t *testing.T) { + test1(t, modSortTests, "ModSort", func(list []module.Version) []module.Version { + out := slices.Clone(list) + ModSort(out) + return out + }) +} + +var modSortTests = []testCase1[[]module.Version, []module.Version]{ + { + mvl(`z v1.1; a v1.2; a v1.1; go 1.3; toolchain 1.3; toolchain 1.2; go 1.2`), + mvl(`a v1.1; a v1.2; go 1.2; go 1.3; toolchain 1.2; toolchain 1.3; z v1.1`), + }, +} + +func mvl(s string) []module.Version { + var list []module.Version + for _, f := range strings.Split(s, ";") { + f = strings.TrimSpace(f) + path, vers, _ := strings.Cut(f, " ") + list = append(list, module.Version{Path: path, Version: vers}) + } + return list +} diff --git a/src/cmd/go/internal/gover/toolchain.go b/src/cmd/go/internal/gover/toolchain.go new file mode 100644 index 0000000..a24df98 --- /dev/null +++ b/src/cmd/go/internal/gover/toolchain.go @@ -0,0 +1,98 @@ +// 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) +} + +// 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) +} diff --git a/src/cmd/go/internal/gover/toolchain_test.go b/src/cmd/go/internal/gover/toolchain_test.go new file mode 100644 index 0000000..d1c22fb --- /dev/null +++ b/src/cmd/go/internal/gover/toolchain_test.go @@ -0,0 +1,19 @@ +// 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 "testing" + +func TestFromToolchain(t *testing.T) { test1(t, fromToolchainTests, "FromToolchain", FromToolchain) } + +var fromToolchainTests = []testCase1[string, string]{ + {"go1.2.3", "1.2.3"}, + {"1.2.3", ""}, + {"go1.2.3+bigcorp", ""}, + {"go1.2.3-bigcorp", "1.2.3"}, + {"go1.2.3-bigcorp more text", "1.2.3"}, + {"gccgo-go1.23rc4", ""}, + {"gccgo-go1.23rc4-bigdwarf", ""}, +} diff --git a/src/cmd/go/internal/gover/version.go b/src/cmd/go/internal/gover/version.go new file mode 100644 index 0000000..2681013 --- /dev/null +++ b/src/cmd/go/internal/gover/version.go @@ -0,0 +1,74 @@ +// 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 "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 + // tests outside of the main module. + NarrowAllVersion = "1.16" + + // DefaultGoModVersion is the Go version to assume for go.mod files + // that do not declare a Go version. The go command has been + // writing go versions to modules since Go 1.12, so a go.mod + // without a version is either very old or recently hand-written. + // Since we can't tell which, we have to assume it's very old. + // The semantics of the go.mod changed at Go 1.17 to support + // graph pruning. If see a go.mod without a go line, we have to + // assume Go 1.16 so that we interpret the requirements correctly. + // Note that this default must stay at Go 1.16; it cannot be moved forward. + DefaultGoModVersion = "1.16" + + // DefaultGoWorkVersion is the Go version to assume for go.work files + // that do not declare a Go version. Workspaces were added in Go 1.18, + // so use that. + DefaultGoWorkVersion = "1.18" + + // ExplicitIndirectVersion is the Go version at which a + // module's go.mod file is expected to list explicit requirements on every + // module that provides any package transitively imported by that module. + // + // Other indirect dependencies of such a module can be safely pruned out of + // the module graph; see https://golang.org/ref/mod#graph-pruning. + ExplicitIndirectVersion = "1.17" + + // separateIndirectVersion is the Go version at which + // "// indirect" dependencies are added in a block separate from the direct + // ones. See https://golang.org/issue/45965. + SeparateIndirectVersion = "1.17" + + // tidyGoModSumVersion is the Go version at which + // 'go mod tidy' preserves go.mod checksums needed to build test dependencies + // of packages in "all", so that 'go test all' can be run without checksum + // errors. + // See https://go.dev/issue/56222. + TidyGoModSumVersion = "1.21" + + // goStrictVersion is the Go version at which the Go versions + // became "strict" in the sense that, restricted to modules at this version + // or later, every module must have a go version line ≥ all its dependencies. + // 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 +} -- cgit v1.2.3