or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

dirhash.mdgosumcheck.mdindex.mdmodfile.mdmodule.mdnote.mdsemver.mdstorage.mdsumdb.mdtlog.mdzip.md
tile.json

semver.mddocs/

semver - Semantic Version Comparison

Package semver implements comparison of semantic version strings following Semantic Versioning 2.0.0.

Import

import "golang.org/x/mod/semver"

Overview

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:

  • Square brackets indicate optional parts
  • MAJOR, MINOR, and PATCH are decimal integers without extra leading zeros
  • PRERELEASE and BUILD are each a series of non-empty dot-separated identifiers using only alphanumeric characters and hyphens
  • All-numeric PRERELEASE identifiers must not have leading zeros

This package follows Semantic Versioning 2.0.0 with two exceptions:

  1. It requires the "v" prefix
  2. It recognizes vMAJOR and vMAJOR.MINOR (with no prerelease or build suffixes) as shorthands for vMAJOR.0.0 and vMAJOR.MINOR.0

Functions

Version Validation

func IsValid(v string) bool

Reports 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)

Version Comparison

func Compare(v, w string) int

Returns an integer comparing two versions according to semantic version precedence:

  • Returns 0 if v == w
  • Returns -1 if v < w
  • Returns +1 if v > w

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) string

Canonicalizes 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)

Version Components

func Major(v string) string

Returns 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) string

Returns 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.0
func Prerelease(v string) string

Returns 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) string

Returns 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)

Version Canonicalization

func Canonical(v string) string

Returns 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)

Sorting

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.0

Types

ByVersion

type ByVersion []string

ByVersion implements sort.Interface for sorting semantic version strings.

Methods

func (vs ByVersion) Len() int
func (vs ByVersion) Swap(i, j int)
func (vs ByVersion) Less(i, j int) bool

Example:

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.0

Usage Examples

Basic Version Comparison

package 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)
    }
}

Validating and Parsing Versions

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()
    }
}

Sorting Versions

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)
    }
}

Finding the Latest Version

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)
}

Filtering Versions by Major Version

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)
    }
}

Comparing Version Ranges

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)
    }
}

Working with Prerelease Versions

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))
        }
    }
}

Canonicalizing Versions

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)
    }
}

Extracting Version Components

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)
    }
}

Semantic Versioning Rules

Version Precedence

Version precedence is determined as follows:

  1. Compare major, minor, and patch versions numerically (1.0.0 < 2.0.0 < 2.1.0 < 2.1.1)
  2. When major, minor, and patch are equal, a pre-release version has lower precedence than a normal version (1.0.0-alpha < 1.0.0)
  3. Pre-release versions are compared by identifiers from left to right:
    • Numeric identifiers are compared numerically
    • Alphanumeric identifiers are compared lexically in ASCII sort order
    • Numeric identifiers always have lower precedence than non-numeric identifiers
  4. Build metadata is ignored for comparison purposes

Examples

// 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

Common Pitfalls

Missing 'v' Prefix

// Wrong
semver.IsValid("1.2.3") // false

// Correct
semver.IsValid("v1.2.3") // true

Leading Zeros

// Wrong
semver.IsValid("v01.2.3") // false
semver.IsValid("v1.02.3") // false

// Correct
semver.IsValid("v1.2.3") // true

Too Many Version Parts

// Wrong
semver.IsValid("v1.2.3.4") // false

// Correct
semver.IsValid("v1.2.3") // true

See Also