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

module.mddocs/

module - Module Path and Version Types

Package module defines the module.Version type along with support code for working with module paths and versions.

Import

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

Overview

The module package provides the fundamental types and validation functions for working with Go module paths and versions. It enforces the naming constraints and compatibility rules defined in the Go modules specification.

The core Version type is a simple path-version pair with no built-in restrictions. Additional validation functions like Check, CheckPath, and CheckImportPath verify that particular path-version pairs are valid according to Go's module rules.

Escaped Paths

Module paths appear as substrings of file system paths (in the download cache) and of web server URLs in the proxy protocol. Since file systems and web servers cannot be relied upon to be case-sensitive, Go modules use an escaping scheme to ensure unique representation.

The safe escaped form replaces every uppercase letter with an exclamation mark followed by the letter's lowercase equivalent:

  • github.com/Azure/azure-sdk-for-gogithub.com/!azure/azure-sdk-for-go
  • github.com/GoogleCloudPlatform/cloudsql-proxygithub.com/!google!cloud!platform/cloudsql-proxy
  • github.com/Sirupsen/logrusgithub.com/!sirupsen/logrus

Import paths that avoid uppercase letters remain unchanged. Since import paths have never allowed exclamation marks, no escaping of literal ! is needed.

Constants

const PseudoVersionTimestampFormat = "20060102150405"

Format string for timestamps in pseudo-versions. Pseudo-versions use this layout to encode the commit timestamp in a compact, sortable format.

Types

Version

type Version struct {
    // Path is a module path, like "golang.org/x/text" or "rsc.io/quote/v2".
    Path string

    // Version is usually a semantic version in canonical form.
    // There are three exceptions to this general rule.
    // First, the top-level target of a build has no specific version
    // and uses Version = "".
    // Second, during MVS calculations the version "none" is used
    // to represent the decision to take no version of a given module.
    // Third, filesystem paths found in "replace" directives are
    // represented by a path with an empty version.
    Version string `json:",omitempty"`
}

A Version is defined by a module path and version pair. These are stored in their plain (unescaped) form.

Methods

func (m Version) String() string

Returns a representation of the Version suitable for logging (Path@Version, or just Path if Version is empty).

Example:

v := module.Version{Path: "golang.org/x/text", Version: "v0.3.0"}
fmt.Println(v.String()) // Output: golang.org/x/text@v0.3.0

InvalidPathError

type InvalidPathError struct {
    Kind string // "module", "import", or "file"
    Path string
    Err  error
}

An InvalidPathError indicates a module, import, or file path doesn't satisfy all naming constraints. See CheckPath, CheckImportPath, and CheckFilePath for specific restrictions.

Methods

func (e *InvalidPathError) Error() string
func (e *InvalidPathError) Unwrap() error

InvalidVersionError

type InvalidVersionError struct {
    Version string
    Pseudo  bool
    Err     error
}

An InvalidVersionError indicates an error specific to a version, with the module path unknown or specified externally. A ModuleError may wrap an InvalidVersionError, but an InvalidVersionError must not wrap a ModuleError.

Methods

func (e *InvalidVersionError) Error() string
func (e *InvalidVersionError) Unwrap() error

ModuleError

type ModuleError struct {
    Path    string
    Version string
    Err     error
}

A ModuleError indicates an error specific to a module.

Methods

func (e *ModuleError) Error() string
func (e *ModuleError) Unwrap() error

Path Validation Functions

CheckPath

func CheckPath(path string) error

Checks that a module path is valid. A valid module path is a valid import path (as checked by CheckImportPath) with three additional constraints:

  1. The leading path element (up to the first slash, if any), by convention a domain name, must contain only lowercase ASCII letters, ASCII digits, dots (U+002E), and dashes (U+002D); it must contain at least one dot and cannot start with a dash.

  2. For a final path element of the form /vN, where N looks numeric (ASCII digits and dots), N must not begin with a leading zero, must not be /v1, and must not contain any dots.

  3. For paths beginning with "gopkg.in/", this second requirement is replaced by a requirement that the path follow the gopkg.in server's conventions.

  4. No path element may begin with a dot.

Example:

if err := module.CheckPath("github.com/user/repo"); err != nil {
    log.Fatal(err)
}

if err := module.CheckPath("example.com/my/module/v2"); err != nil {
    log.Fatal(err)
}

CheckImportPath

func CheckImportPath(path string) error

Checks that an import path is valid.

A valid import path consists of one or more valid path elements separated by slashes (U+002F). It must not begin with nor end in a slash.

A valid path element is a non-empty string made up of ASCII letters, ASCII digits, and limited ASCII punctuation: -, ., _, and ~. It must not:

  • End with a dot (U+002E)
  • Contain two dots in a row
  • Have a prefix up to the first dot that is a reserved file name on Windows (CON, com1, NuL, etc.)
  • Have a suffix of a tilde followed by one or more ASCII digits (to exclude Windows short-names)

CheckImportPath may be less restrictive in the future, but see the package documentation for additional information about subtleties of Unicode.

Example:

if err := module.CheckImportPath("github.com/user/repo/pkg"); err != nil {
    log.Fatal(err)
}

CheckFilePath

func CheckFilePath(path string) error

Checks that a slash-separated file path is valid. The definition of a valid file path is the same as the definition of a valid import path except that the set of allowed characters is larger: all Unicode letters, ASCII digits, the ASCII space character (U+0020), and the ASCII punctuation characters !#$%&()+,-.=@[]^_{}~.

The excluded punctuation characters (", *, <, >, ?, `, ', |, /, \, and :) have special meanings in certain shells or operating systems.

CheckFilePath may be less restrictive in the future, but see the package documentation for additional information about subtleties of Unicode.

Example:

if err := module.CheckFilePath("internal/some-file.go"); err != nil {
    log.Fatal(err)
}

Version Validation Functions

Check

func Check(path, version string) error

Checks that a given module path and version pair is valid. In addition to the path being a valid module path and the version being a valid semantic version, the two must correspond. For example, the path "yaml/v2" only corresponds to semantic versions beginning with "v2.".

Example:

// Valid
if err := module.Check("github.com/user/repo/v2", "v2.1.0"); err != nil {
    log.Fatal(err)
}

// Invalid - version doesn't match path major version
if err := module.Check("github.com/user/repo/v2", "v1.0.0"); err != nil {
    fmt.Println("Error:", err) // path /v2 requires major version v2
}

CheckPathMajor

func CheckPathMajor(v, pathMajor string) error

Returns a non-nil error if the semantic version v does not match the path major version pathMajor.

The pathMajor parameter should be the major version suffix from the module path (e.g., "/v2" or ".v1" for gopkg.in), or an empty string for modules without a major version suffix.

Example:

// Check if v2.1.0 matches /v2 path suffix
if err := module.CheckPathMajor("v2.1.0", "/v2"); err != nil {
    log.Fatal(err)
}

MatchPathMajor

func MatchPathMajor(v, pathMajor string) bool

Reports whether the semantic version v matches the path major version pathMajor. MatchPathMajor returns true if and only if CheckPathMajor returns nil.

Example:

if module.MatchPathMajor("v2.1.0", "/v2") {
    fmt.Println("Version matches path major version")
}

Version Canonicalization

CanonicalVersion

func CanonicalVersion(v string) string

Returns the canonical form of the version string v. It is the same as semver.Canonical except that it preserves the special build suffix "+incompatible".

Example:

fmt.Println(module.CanonicalVersion("v1.2"))          // Output: v1.2.0
fmt.Println(module.CanonicalVersion("v1.2.3-pre"))    // Output: v1.2.3-pre
fmt.Println(module.CanonicalVersion("v2.0.0+incompatible")) // Output: v2.0.0+incompatible

Path Escaping

EscapePath

func EscapePath(path string) (escaped string, err error)

Returns the escaped form of the given module path. It fails if the module path is invalid.

The escape encoding replaces every uppercase letter with an exclamation mark followed by the corresponding lowercase letter.

Example:

escaped, err := module.EscapePath("github.com/Azure/azure-sdk-for-go")
if err != nil {
    log.Fatal(err)
}
fmt.Println(escaped) // Output: github.com/!azure/azure-sdk-for-go

UnescapePath

func UnescapePath(escaped string) (path string, err error)

Returns the module path for the given escaped path. It fails if the escaped path is invalid or describes an invalid path.

Example:

path, err := module.UnescapePath("github.com/!azure/azure-sdk-for-go")
if err != nil {
    log.Fatal(err)
}
fmt.Println(path) // Output: github.com/Azure/azure-sdk-for-go

EscapeVersion

func EscapeVersion(v string) (escaped string, err error)

Returns the escaped form of the given module version. Versions are allowed to be in non-semver form but must be valid file names and not contain exclamation marks.

Example:

escaped, err := module.EscapeVersion("v1.2.3")
if err != nil {
    log.Fatal(err)
}
fmt.Println(escaped) // Output: v1.2.3

UnescapeVersion

func UnescapeVersion(escaped string) (v string, err error)

Returns the version string for the given escaped version. It fails if the escaped form is invalid or describes an invalid version. Versions are allowed to be in non-semver form but must be valid file names and not contain exclamation marks.

Pseudo-Versions

Pseudo-versions are specially formatted pre-release versions used to represent commits that don't have a corresponding version tag. They take the form vX.Y.Z-yyyymmddhhmmss-abcdefabcdef.

IsPseudoVersion

func IsPseudoVersion(v string) bool

Reports whether v is a pseudo-version.

Example:

if module.IsPseudoVersion("v0.0.0-20191109021931-daa7c04131f5") {
    fmt.Println("This is a pseudo-version")
}

PseudoVersion

func PseudoVersion(major, older string, t time.Time, rev string) string

Returns a pseudo-version for the given:

  • major: Major version ("v0", "v1", "v2", etc.)
  • older: Preexisting older tagged version ("" or "v1.2.3" or "v1.2.3-pre")
  • t: Revision time
  • rev: Revision identifier (usually a 12-byte commit hash prefix)

Example:

import "time"

t := time.Date(2019, 11, 9, 2, 19, 31, 0, time.UTC)
pseudo := module.PseudoVersion("v0", "", t, "daa7c04131f5")
fmt.Println(pseudo) // Output: v0.0.0-20191109021931-daa7c04131f5

PseudoVersionBase

func PseudoVersionBase(v string) (string, error)

Returns the canonical parent version, if any, upon which the pseudo-version v is based. If v has no parent version (that is, if it is "vX.0.0-[…]"), PseudoVersionBase returns the empty string and a nil error.

Example:

base, err := module.PseudoVersionBase("v1.2.4-0.20191109021931-daa7c04131f5")
if err != nil {
    log.Fatal(err)
}
fmt.Println(base) // Output: v1.2.4-0

PseudoVersionRev

func PseudoVersionRev(v string) (rev string, err error)

Returns the revision identifier of the pseudo-version v. It returns an error if v is not a pseudo-version.

Example:

rev, err := module.PseudoVersionRev("v0.0.0-20191109021931-daa7c04131f5")
if err != nil {
    log.Fatal(err)
}
fmt.Println(rev) // Output: daa7c04131f5

PseudoVersionTime

func PseudoVersionTime(v string) (time.Time, error)

Returns the timestamp of the pseudo-version v. It returns an error if v is not a pseudo-version or if the timestamp embedded in the pseudo-version is not a valid time.

Example:

t, err := module.PseudoVersionTime("v0.0.0-20191109021931-daa7c04131f5")
if err != nil {
    log.Fatal(err)
}
fmt.Println(t.Format(time.RFC3339)) // Output: 2019-11-09T02:19:31Z

IsZeroPseudoVersion

func IsZeroPseudoVersion(v string) bool

Returns whether v is a pseudo-version with a zero base, timestamp, and revision, as returned by ZeroPseudoVersion.

ZeroPseudoVersion

func ZeroPseudoVersion(major string) string

Returns a pseudo-version with a zero timestamp and revision, which may be used as a placeholder.

Example:

zero := module.ZeroPseudoVersion("v2")
fmt.Println(zero) // Output: v2.0.0-00010101000000-000000000000

Path Manipulation

SplitPathVersion

func SplitPathVersion(path string) (prefix, pathMajor string, ok bool)

Returns prefix and major version such that prefix+pathMajor == path and version is either empty or "/vN" for N >= 2. As a special case, gopkg.in paths are recognized directly; they require ".vN" instead of "/vN", and for all N, not just N >= 2.

SplitPathVersion returns with ok = false when presented with a path whose last path element does not satisfy the constraints applied by CheckPath, such as "example.com/pkg/v1" or "example.com/pkg/v1.2".

Example:

prefix, pathMajor, ok := module.SplitPathVersion("github.com/user/repo/v2")
if ok {
    fmt.Printf("Prefix: %s, Major: %s\n", prefix, pathMajor)
    // Output: Prefix: github.com/user/repo, Major: /v2
}

prefix, pathMajor, ok = module.SplitPathVersion("gopkg.in/yaml.v2")
if ok {
    fmt.Printf("Prefix: %s, Major: %s\n", prefix, pathMajor)
    // Output: Prefix: gopkg.in/yaml, Major: .v2
}

PathMajorPrefix

func PathMajorPrefix(pathMajor string) string

Returns the major-version tag prefix implied by pathMajor. An empty PathMajorPrefix allows either v0 or v1.

Note that MatchPathMajor may accept some versions that do not actually begin with this prefix: namely, it accepts a 'v0.0.0-' prefix for a '.v1' pathMajor, even though that pathMajor implies 'v1' tagging.

Example:

prefix := module.PathMajorPrefix("/v2")
fmt.Println(prefix) // Output: v2

Pattern Matching

MatchPrefixPatterns

func MatchPrefixPatterns(globs, target string) bool

Reports whether any path prefix of target matches one of the glob patterns (as defined by path.Match) in the comma-separated globs list. This implements the algorithm used when matching a module path to the GOPRIVATE environment variable, as described by 'go help module-private'.

It ignores any empty or malformed patterns in the list. Trailing slashes on patterns are ignored.

Example:

globs := "*.corp.example.com,rsc.io/private"
target := "git.corp.example.com/repo"

if module.MatchPrefixPatterns(globs, target) {
    fmt.Println("Target matches private pattern")
}

Sorting

Sort

func Sort(list []Version)

Sorts the list by Path, breaking ties by comparing Version fields. The Version fields are interpreted as semantic versions (using semver.Compare) optionally followed by a tie-breaking suffix introduced by a slash character, like in "v0.0.1/go.mod".

Example:

versions := []module.Version{
    {Path: "golang.org/x/text", Version: "v0.3.2"},
    {Path: "golang.org/x/text", Version: "v0.3.0"},
    {Path: "golang.org/x/net", Version: "v0.0.0-20200226"},
}

module.Sort(versions)
for _, v := range versions {
    fmt.Println(v)
}

Error Handling

VersionError

func VersionError(v Version, err error) error

Returns a ModuleError derived from a Version and error, or err itself if it is already such an error.

Example:

v := module.Version{Path: "example.com/pkg", Version: "v1.0.0"}
err := errors.New("some error")
modErr := module.VersionError(v, err)
fmt.Println(modErr) // Output: example.com/pkg@v1.0.0: some error

Usage Examples

Validating and Escaping Module Paths

package main

import (
    "fmt"
    "log"

    "golang.org/x/mod/module"
)

func main() {
    path := "github.com/Azure/azure-sdk-for-go"

    // Validate the path
    if err := module.CheckPath(path); err != nil {
        log.Fatalf("Invalid path: %v", err)
    }

    // Escape for filesystem use
    escaped, err := module.EscapePath(path)
    if err != nil {
        log.Fatalf("Failed to escape path: %v", err)
    }

    fmt.Printf("Original: %s\n", path)
    fmt.Printf("Escaped: %s\n", escaped)

    // Unescape back
    unescaped, err := module.UnescapePath(escaped)
    if err != nil {
        log.Fatalf("Failed to unescape path: %v", err)
    }

    fmt.Printf("Unescaped: %s\n", unescaped)
}

Working with Pseudo-Versions

package main

import (
    "fmt"
    "log"
    "time"

    "golang.org/x/mod/module"
)

func main() {
    // Create a pseudo-version
    t := time.Now()
    pseudo := module.PseudoVersion("v0", "", t, "abcdef123456")
    fmt.Printf("Pseudo-version: %s\n", pseudo)

    // Check if it's a pseudo-version
    if module.IsPseudoVersion(pseudo) {
        fmt.Println("Confirmed: this is a pseudo-version")
    }

    // Extract timestamp
    timestamp, err := module.PseudoVersionTime(pseudo)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Timestamp: %s\n", timestamp.Format(time.RFC3339))

    // Extract revision
    rev, err := module.PseudoVersionRev(pseudo)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Revision: %s\n", rev)
}

Path and Version Compatibility

package main

import (
    "fmt"

    "golang.org/x/mod/module"
)

func main() {
    cases := []struct {
        path    string
        version string
    }{
        {"github.com/user/repo", "v1.0.0"},
        {"github.com/user/repo/v2", "v2.1.0"},
        {"github.com/user/repo/v2", "v1.0.0"}, // Invalid
        {"gopkg.in/yaml.v2", "v2.4.0"},
    }

    for _, c := range cases {
        err := module.Check(c.path, c.version)
        if err != nil {
            fmt.Printf("❌ %s @ %s: %v\n", c.path, c.version, err)
        } else {
            fmt.Printf("✓ %s @ %s: valid\n", c.path, c.version)
        }
    }
}

See Also

  • modfile - Parsing go.mod files
  • semver - Semantic version comparison
  • zip - Module zip file handling