| // Copyright 2022 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 semver provides shared utilities for manipulating |
| // Go semantic versions. |
| package semver |
| |
| import ( |
| "fmt" |
| "regexp" |
| "strings" |
| |
| "golang.org/x/mod/semver" |
| ) |
| |
| // addSemverPrefix adds a 'v' prefix to s if it isn't already prefixed |
| // with 'v' or 'go'. This allows us to easily test go-style SEMVER |
| // strings against normal SEMVER strings. |
| func addSemverPrefix(s string) string { |
| if !strings.HasPrefix(s, "v") && !strings.HasPrefix(s, "go") { |
| return "v" + s |
| } |
| return s |
| } |
| |
| // removeSemverPrefix removes the 'v' or 'go' prefixes from go-style |
| // SEMVER strings, for usage in the public vulnerability format. |
| func removeSemverPrefix(s string) string { |
| s = strings.TrimPrefix(s, "v") |
| s = strings.TrimPrefix(s, "go") |
| return s |
| } |
| |
| // canonicalizeSemverPrefix turns a SEMVER string into the canonical |
| // representation using the 'v' prefix, as used by the OSV format. |
| // Input may be a bare SEMVER ("1.2.3"), Go prefixed SEMVER ("go1.2.3"), |
| // or already canonical SEMVER ("v1.2.3"). |
| func canonicalizeSemverPrefix(s string) string { |
| return addSemverPrefix(removeSemverPrefix(s)) |
| } |
| |
| // Less returns whether v1 < v2, where v1 and v2 are |
| // semver versions with either a "v", "go" or no prefix. |
| func Less(v1, v2 string) bool { |
| return semver.Compare(canonicalizeSemverPrefix(v1), canonicalizeSemverPrefix(v2)) < 0 |
| } |
| |
| // Valid returns whether v is valid semver, allowing |
| // either a "v", "go" or no prefix. |
| func Valid(v string) bool { |
| return semver.IsValid(canonicalizeSemverPrefix(v)) |
| } |
| |
| var ( |
| // Regexp for matching go tags. The groups are: |
| // 1 the major.minor version |
| // 2 the patch version, or empty if none |
| // 3 the entire prerelease, if present |
| // 4 the prerelease type ("beta" or "rc") |
| // 5 the prerelease number |
| tagRegexp = regexp.MustCompile(`^go(\d+\.\d+)(\.\d+|)((beta|rc|-pre)(\d+))?$`) |
| ) |
| |
| // This is a modified copy of pkgsite/internal/stdlib:VersionForTag. |
| func GoTagToSemver(tag string) string { |
| if tag == "" { |
| return "" |
| } |
| |
| tag = strings.Fields(tag)[0] |
| // Special cases for go1. |
| if tag == "go1" { |
| return "v1.0.0" |
| } |
| if tag == "go1.0" { |
| return "" |
| } |
| m := tagRegexp.FindStringSubmatch(tag) |
| if m == nil { |
| return "" |
| } |
| version := "v" + m[1] |
| if m[2] != "" { |
| version += m[2] |
| } else { |
| version += ".0" |
| } |
| if m[3] != "" { |
| if !strings.HasPrefix(m[4], "-") { |
| version += "-" |
| } |
| version += m[4] + "." + m[5] |
| } |
| return version |
| } |
| |
| // This is a modified copy of pkgsite/internal/stlib:TagForVersion |
| func SemverToGoTag(v string) string { |
| // Special case: v1.0.0 => go1. |
| if v == "v1.0.0" { |
| return "go1" |
| } |
| |
| goVersion := semver.Canonical(v) |
| prerelease := semver.Prerelease(goVersion) |
| versionWithoutPrerelease := strings.TrimSuffix(goVersion, prerelease) |
| patch := strings.TrimPrefix(versionWithoutPrerelease, semver.MajorMinor(goVersion)+".") |
| if patch == "0" && (semver.Compare(v, "v1.21.0") < 0 || prerelease != "") { |
| // Starting with go1.21.0, the first patch version includes .0. |
| // Prereleases do not include .0 (we don't do prereleases for other patch releases). |
| versionWithoutPrerelease = strings.TrimSuffix(versionWithoutPrerelease, ".0") |
| } |
| goVersion = fmt.Sprintf("go%s", strings.TrimPrefix(versionWithoutPrerelease, "v")) |
| if prerelease != "" { |
| i := finalDigitsIndex(prerelease) |
| if i >= 1 { |
| // Remove the dot. |
| prerelease = prerelease[:i-1] + prerelease[i:] |
| } |
| goVersion += prerelease |
| } |
| return goVersion |
| } |
| |
| // finalDigitsIndex returns the index of the first digit in the sequence of digits ending s. |
| // If s doesn't end in digits, it returns -1. |
| func finalDigitsIndex(s string) int { |
| // Assume ASCII (since the semver package does anyway). |
| var i int |
| for i = len(s) - 1; i >= 0; i-- { |
| if s[i] < '0' || s[i] > '9' { |
| break |
| } |
| } |
| if i == len(s)-1 { |
| return -1 |
| } |
| return i + 1 |
| } |