Package semver implements comparison of semantic version strings following Semantic Versioning 2.0.0.
import "golang.org/x/mod/semver"The semver package provides functions for parsing, comparing, and manipulating semantic version strings. In this package, semantic version strings must begin with a leading "v", as in "v1.0.0".
The general form of a semantic version string accepted by this package is:
vMAJOR[.MINOR[.PATCH[-PRERELEASE][+BUILD]]]where:
This package follows Semantic Versioning 2.0.0 with two exceptions:
vMAJOR and vMAJOR.MINOR (with no prerelease or build suffixes) as shorthands for vMAJOR.0.0 and vMAJOR.MINOR.0func IsValid(v string) boolReports whether v is a valid semantic version string.
Example:
fmt.Println(semver.IsValid("v1.2.3")) // true
fmt.Println(semver.IsValid("v1.2")) // true
fmt.Println(semver.IsValid("v1")) // true
fmt.Println(semver.IsValid("1.2.3")) // false (missing 'v')
fmt.Println(semver.IsValid("v1.2.3.4")) // false (too many parts)
fmt.Println(semver.IsValid("v01.2.3")) // false (leading zero)func Compare(v, w string) intReturns an integer comparing two versions according to semantic version precedence:
An invalid semantic version string is considered less than a valid one. All invalid semantic version strings compare equal to each other.
Example:
fmt.Println(semver.Compare("v1.0.0", "v1.0.0")) // 0
fmt.Println(semver.Compare("v1.0.0", "v1.0.1")) // -1
fmt.Println(semver.Compare("v1.0.1", "v1.0.0")) // 1
fmt.Println(semver.Compare("v1.2.3", "v1.2.3-pre")) // 1 (release > prerelease)func Max(v, w string) stringCanonicalizes its arguments and then returns the version string that compares greater.
Deprecated: Use Compare instead. In most cases, returning a canonicalized version is not expected or desired.
Example:
max := semver.Max("v1.2", "v1.3")
fmt.Println(max) // v1.3.0 (canonicalized)func Major(v string) stringReturns the major version prefix of the semantic version v. For example, Major("v2.1.0") == "v2". If v is an invalid semantic version string, Major returns the empty string.
Example:
fmt.Println(semver.Major("v2.1.0")) // v2
fmt.Println(semver.Major("v1.0.0-beta")) // v1
fmt.Println(semver.Major("invalid")) // (empty string)func MajorMinor(v string) stringReturns the major.minor version prefix of the semantic version v. For example, MajorMinor("v2.1.0") == "v2.1". If v is an invalid semantic version string, MajorMinor returns the empty string.
Example:
fmt.Println(semver.MajorMinor("v2.1.0")) // v2.1
fmt.Println(semver.MajorMinor("v1.0.0-beta")) // v1.0
fmt.Println(semver.MajorMinor("v2")) // v2.0func Prerelease(v string) stringReturns the prerelease suffix of the semantic version v. For example, Prerelease("v2.1.0-pre+meta") == "-pre". If v is an invalid semantic version string, Prerelease returns the empty string.
Example:
fmt.Println(semver.Prerelease("v2.1.0-pre+meta")) // -pre
fmt.Println(semver.Prerelease("v2.1.0-alpha.1")) // -alpha.1
fmt.Println(semver.Prerelease("v2.1.0")) // (empty string)func Build(v string) stringReturns the build suffix of the semantic version v. For example, Build("v2.1.0+meta") == "+meta". If v is an invalid semantic version string, Build returns the empty string.
Example:
fmt.Println(semver.Build("v2.1.0+meta")) // +meta
fmt.Println(semver.Build("v2.1.0-pre+build123")) // +build123
fmt.Println(semver.Build("v2.1.0")) // (empty string)func Canonical(v string) stringReturns the canonical formatting of the semantic version v. It fills in any missing .MINOR or .PATCH and discards build metadata. Two semantic versions compare equal only if their canonical formatting is identical strings. The canonical invalid semantic version is the empty string.
Example:
fmt.Println(semver.Canonical("v1")) // v1.0.0
fmt.Println(semver.Canonical("v1.2")) // v1.2.0
fmt.Println(semver.Canonical("v1.2.3")) // v1.2.3
fmt.Println(semver.Canonical("v1.2.3-pre")) // v1.2.3-pre
fmt.Println(semver.Canonical("v1.2.3+build")) // v1.2.3 (build metadata removed)
fmt.Println(semver.Canonical("invalid")) // (empty string)func Sort(list []string)Sorts a list of semantic version strings using Compare and falls back to strings.Compare if both versions are considered equal.
Example:
versions := []string{
"v1.10.0",
"v1.2.0",
"v1.2.3",
"v2.0.0",
"v1.2.0-beta",
}
semver.Sort(versions)
for _, v := range versions {
fmt.Println(v)
}
// Output:
// v1.2.0-beta
// v1.2.0
// v1.2.3
// v1.10.0
// v2.0.0type ByVersion []stringByVersion implements sort.Interface for sorting semantic version strings.
func (vs ByVersion) Len() int
func (vs ByVersion) Swap(i, j int)
func (vs ByVersion) Less(i, j int) boolExample:
import "sort"
versions := semver.ByVersion{
"v2.0.0",
"v1.2.0",
"v1.10.0",
}
sort.Sort(versions)
for _, v := range versions {
fmt.Println(v)
}
// Output:
// v1.2.0
// v1.10.0
// v2.0.0package main
import (
"fmt"
"golang.org/x/mod/semver"
)
func main() {
v1 := "v1.2.3"
v2 := "v1.2.4"
switch semver.Compare(v1, v2) {
case -1:
fmt.Printf("%s is less than %s\n", v1, v2)
case 0:
fmt.Printf("%s equals %s\n", v1, v2)
case 1:
fmt.Printf("%s is greater than %s\n", v1, v2)
}
}package main
import (
"fmt"
"golang.org/x/mod/semver"
)
func main() {
versions := []string{
"v1.2.3",
"v2.0.0-rc.1",
"invalid",
"v1.2.3+build.123",
}
for _, v := range versions {
if !semver.IsValid(v) {
fmt.Printf("Invalid: %s\n", v)
continue
}
fmt.Printf("Version: %s\n", v)
fmt.Printf(" Major: %s\n", semver.Major(v))
fmt.Printf(" MajorMinor: %s\n", semver.MajorMinor(v))
fmt.Printf(" Canonical: %s\n", semver.Canonical(v))
if pre := semver.Prerelease(v); pre != "" {
fmt.Printf(" Prerelease: %s\n", pre)
}
if build := semver.Build(v); build != "" {
fmt.Printf(" Build: %s\n", build)
}
fmt.Println()
}
}package main
import (
"fmt"
"golang.org/x/mod/semver"
)
func main() {
versions := []string{
"v2.0.0",
"v1.0.0",
"v1.10.0",
"v1.2.0",
"v1.2.0-beta",
"v1.2.0-alpha",
"v1.2.3",
}
fmt.Println("Before sorting:")
for _, v := range versions {
fmt.Printf(" %s\n", v)
}
semver.Sort(versions)
fmt.Println("\nAfter sorting:")
for _, v := range versions {
fmt.Printf(" %s\n", v)
}
}package main
import (
"fmt"
"golang.org/x/mod/semver"
)
func findLatest(versions []string) string {
var latest string
for _, v := range versions {
if !semver.IsValid(v) {
continue
}
if latest == "" || semver.Compare(v, latest) > 0 {
latest = v
}
}
return latest
}
func main() {
versions := []string{
"v1.0.0",
"v1.2.3",
"v2.0.0-beta",
"v1.10.5",
"invalid",
}
latest := findLatest(versions)
fmt.Printf("Latest version: %s\n", latest)
}package main
import (
"fmt"
"golang.org/x/mod/semver"
)
func filterByMajor(versions []string, major string) []string {
var filtered []string
for _, v := range versions {
if semver.Major(v) == major {
filtered = append(filtered, v)
}
}
return filtered
}
func main() {
versions := []string{
"v1.0.0",
"v1.2.3",
"v2.0.0",
"v2.1.0",
"v3.0.0",
}
v2Versions := filterByMajor(versions, "v2")
fmt.Println("v2 versions:")
for _, v := range v2Versions {
fmt.Printf(" %s\n", v)
}
}package main
import (
"fmt"
"golang.org/x/mod/semver"
)
func isInRange(version, min, max string) bool {
return semver.Compare(version, min) >= 0 &&
semver.Compare(version, max) <= 0
}
func main() {
version := "v1.5.0"
min := "v1.2.0"
max := "v2.0.0"
if isInRange(version, min, max) {
fmt.Printf("%s is in range [%s, %s]\n", version, min, max)
} else {
fmt.Printf("%s is NOT in range [%s, %s]\n", version, min, max)
}
}package main
import (
"fmt"
"golang.org/x/mod/semver"
)
func isPrerelease(v string) bool {
return semver.Prerelease(v) != ""
}
func isStable(v string) bool {
return semver.IsValid(v) && !isPrerelease(v)
}
func main() {
versions := []string{
"v1.0.0",
"v1.0.0-alpha",
"v1.0.0-beta.1",
"v1.0.0-rc.1",
"v2.0.0",
}
fmt.Println("Stable versions:")
for _, v := range versions {
if isStable(v) {
fmt.Printf(" %s\n", v)
}
}
fmt.Println("\nPrerelease versions:")
for _, v := range versions {
if isPrerelease(v) {
fmt.Printf(" %s (prerelease: %s)\n", v, semver.Prerelease(v))
}
}
}package main
import (
"fmt"
"golang.org/x/mod/semver"
)
func main() {
versions := []string{
"v1",
"v1.2",
"v1.2.3",
"v1.2.3-pre",
"v1.2.3+build",
"v1.2.3-pre+build",
}
fmt.Println("Version -> Canonical")
for _, v := range versions {
canonical := semver.Canonical(v)
fmt.Printf("%20s -> %s\n", v, canonical)
}
}package main
import (
"fmt"
"strings"
"golang.org/x/mod/semver"
)
func parseVersion(v string) {
if !semver.IsValid(v) {
fmt.Printf("Invalid version: %s\n\n", v)
return
}
fmt.Printf("Version: %s\n", v)
fmt.Printf(" Canonical: %s\n", semver.Canonical(v))
fmt.Printf(" Major: %s\n", semver.Major(v))
fmt.Printf(" MajorMinor: %s\n", semver.MajorMinor(v))
if pre := semver.Prerelease(v); pre != "" {
fmt.Printf(" Prerelease: %s\n", pre)
// Remove leading '-'
prereleaseParts := strings.Split(strings.TrimPrefix(pre, "-"), ".")
fmt.Printf(" Prerelease parts: %v\n", prereleaseParts)
}
if build := semver.Build(v); build != "" {
fmt.Printf(" Build metadata: %s\n", build)
}
fmt.Println()
}
func main() {
versions := []string{
"v1.2.3",
"v2.0.0-rc.1",
"v3.1.4-beta.2+exp.sha.5114f85",
}
for _, v := range versions {
parseVersion(v)
}
}Version precedence is determined as follows:
// Patch version comparison
semver.Compare("v1.0.0", "v1.0.1") // -1
// Minor version comparison
semver.Compare("v1.0.0", "v1.1.0") // -1
// Major version comparison
semver.Compare("v1.0.0", "v2.0.0") // -1
// Prerelease vs release
semver.Compare("v1.0.0-alpha", "v1.0.0") // -1
// Prerelease identifier comparison
semver.Compare("v1.0.0-alpha", "v1.0.0-alpha.1") // -1
semver.Compare("v1.0.0-alpha.1", "v1.0.0-alpha.beta") // -1
semver.Compare("v1.0.0-alpha.beta", "v1.0.0-beta") // -1
semver.Compare("v1.0.0-beta", "v1.0.0-beta.2") // -1
semver.Compare("v1.0.0-beta.2", "v1.0.0-beta.11") // -1
semver.Compare("v1.0.0-beta.11", "v1.0.0-rc.1") // -1
// Build metadata is ignored
semver.Compare("v1.0.0+build1", "v1.0.0+build2") // 0// Wrong
semver.IsValid("1.2.3") // false
// Correct
semver.IsValid("v1.2.3") // true// Wrong
semver.IsValid("v01.2.3") // false
semver.IsValid("v1.02.3") // false
// Correct
semver.IsValid("v1.2.3") // true// Wrong
semver.IsValid("v1.2.3.4") // false
// Correct
semver.IsValid("v1.2.3") // true