Effective versioning is crucial for managing dependencies, ensuring stability, and communicating changes in your Go projects. With the introduction of Go Modules, Go has a canonical way to handle versions. This guide outlines the best practices to follow.
Go Modules are the official dependency management system for Go and are central to versioning. If you’re starting a new project or maintaining an existing one, ensure it’s using Go Modules.
go.mod file: This file, located at the root of your module, defines the module’s path (its unique identifier), the Go version it’s built with, and its dependencies with their specific versions.go mod init <module_path>, for example, go mod init github.com/yourusername/yourproject.Go modules use Semantic Versioning (SemVer) 2.0.0. Version numbers are typically in the format vMAJOR.MINOR.PATCH.
MAJOR version (e.g., v1.0.0 -> v2.0.0): Incremented when you make incompatible API changes.MINOR version (e.g., v1.0.0 -> v1.1.0): Incremented when you add functionality in a backward-compatible manner.PATCH version (e.g., v1.0.0 -> v1.0.1): Incremented when you make backward-compatible bug fixes.Key SemVer Points in Go:
v0.y.z (Initial Development): Major version zero is for initial development. Anything may change at any time. The public API should not be considered stable.v1.0.0 (First Stable Release): This version defines the first stable public API. From this point on, changes are governed by SemVer rules regarding backward compatibility.v1.0.0-alpha, v2.1.0-beta.1). Pre-releases have lower precedence than their normal version (e.g., v1.0.0-alpha comes before v1.0.0).v1.0.0+build.123). Build metadata is ignored when determining version precedence.When you release a new version of your module, you should create a Git tag with the semantic version number.
git tag v1.0.1
git push origin v1.0.1
This is a critical aspect of Go versioning due to the “import compatibility rule.”
When you release a v2 or higher major version (which implies breaking changes):
go.mod file must be updated to include the major version suffix.
For example, if your module was github.com/yourusername/yourproject for v0 and v1 versions, for v2.0.0 and subsequent v2.x.x versions, the module path becomes github.com/yourusername/yourproject/v2.
// go.mod for v1.x.x
module [github.com/yourusername/yourproject](https://github.com/yourusername/yourproject)
go 1.21
// go.mod for v2.x.x
module [github.com/yourusername/yourproject/v2](https://github.com/yourusername/yourproject/v2)
go 1.21
// Importing v1
import "[github.com/yourusername/yourproject/somepackage](https://github.com/yourusername/yourproject/somepackage)"
// Importing v2
import "[github.com/yourusername/yourproject/v2/somepackage](https://github.com/yourusername/yourproject/v2/somepackage)"
v2, v3, etc.) is treated as a new, separate module by the Go toolchain.Strategies for Structuring Major Versions in Your Repository:
v2/, v3/).v1 codebase into this new directory as a starting point.go.mod file within this subdirectory will declare the /v2 module path (e.g., module github.com/yourusername/yourproject/v2).v0/v1 code can remain in the root directory or its own v1/ directory.v2 releases would be like v2.0.0, v2.0.1, etc. (If the v2 code is in a v2/ subdirectory, the Go toolchain understands this. Some projects tag like v2/v2.0.0 but this is less common now).v2-branch).go.mod file to include the major version suffix in the module path.v2.0.0).go get.Considerations for Major Version Updates:
When you go get a specific commit hash or a branch name that hasn’t been tagged with a semantic version, Go tools will generate a “pseudo-version” in your go.mod file.
vX.Y.Z-yyyymmddhhmmss-commitabbrev (e.g., v0.0.0-20231027143000-abc123def456).
vX.Y.Z-0: Base version from a preceding tag, or vX.0.0 if no tag.yyyymmddhhmmss: UTC timestamp of the commit.commitabbrev: 12-character prefix of the commit hash.Go modules are typically published by pushing tags to a version control repository (e.g., GitHub, GitLab).
git tag vX.Y.Zgit push origin vX.Y.ZThe Go proxy (and other users) will then be able to discover and download this version.
While the common practice is one module per repository, Go does support multiple modules within a single repository.
repo/modA/go.mod and repo/modB/go.mod), you need to prefix your version tags with the directory name:
modA: git tag modA/v1.0.0modB: git tag modB/v1.2.0While not specific to Go versioning, good Git hygiene supports a clean versioning strategy:
feature/my-new-feature), bug fixes (fix/that-annoying-bug), etc.By following these best practices, particularly adhering to Semantic Versioning and understanding how Go Modules handle major versions, you can create a robust and predictable versioning scheme for your Go projects. This benefits both you as the maintainer and the users of your module.