Package module defines the module.Version type along with support code for working with module paths and versions.
import "golang.org/x/mod/module"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.
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-go → github.com/!azure/azure-sdk-for-gogithub.com/GoogleCloudPlatform/cloudsql-proxy → github.com/!google!cloud!platform/cloudsql-proxygithub.com/Sirupsen/logrus → github.com/!sirupsen/logrusImport paths that avoid uppercase letters remain unchanged. Since import paths have never allowed exclamation marks, no escaping of literal ! is needed.
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.
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.
func (m Version) String() stringReturns 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.0type 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.
func (e *InvalidPathError) Error() string
func (e *InvalidPathError) Unwrap() errortype 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.
func (e *InvalidVersionError) Error() string
func (e *InvalidVersionError) Unwrap() errortype ModuleError struct {
Path string
Version string
Err error
}A ModuleError indicates an error specific to a module.
func (e *ModuleError) Error() string
func (e *ModuleError) Unwrap() errorfunc CheckPath(path string) errorChecks that a module path is valid. A valid module path is a valid import path (as checked by CheckImportPath) with three additional constraints:
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.
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.
For paths beginning with "gopkg.in/", this second requirement is replaced by a requirement that the path follow the gopkg.in server's conventions.
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)
}func CheckImportPath(path string) errorChecks 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:
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)
}func CheckFilePath(path string) errorChecks 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)
}func Check(path, version string) errorChecks 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
}func CheckPathMajor(v, pathMajor string) errorReturns 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)
}func MatchPathMajor(v, pathMajor string) boolReports 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")
}func CanonicalVersion(v string) stringReturns 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+incompatiblefunc 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-gofunc 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-gofunc 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.3func 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 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.
func IsPseudoVersion(v string) boolReports whether v is a pseudo-version.
Example:
if module.IsPseudoVersion("v0.0.0-20191109021931-daa7c04131f5") {
fmt.Println("This is a pseudo-version")
}func PseudoVersion(major, older string, t time.Time, rev string) stringReturns 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 timerev: 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-daa7c04131f5func 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-0func 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: daa7c04131f5func 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:31Zfunc IsZeroPseudoVersion(v string) boolReturns whether v is a pseudo-version with a zero base, timestamp, and revision, as returned by ZeroPseudoVersion.
func ZeroPseudoVersion(major string) stringReturns 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-000000000000func 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
}func PathMajorPrefix(pathMajor string) stringReturns 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: v2func MatchPrefixPatterns(globs, target string) boolReports 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")
}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)
}func VersionError(v Version, err error) errorReturns 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 errorpackage 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)
}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)
}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)
}
}
}