Version numbering

STATUS Stable: Someone other than the author has read or used this - proceed with confidence

Managing version numbers in software projects requires balancing several competing needs: maintaining a clear history of releases, ensuring build artifacts are properly labeled, and keeping version information accessible to running applications.

While it's tempting to store version numbers directly in source files and commit them to the repository, this approach creates unnecessary commits, complicates merges, and makes it harder to audit changes later.

This document describes a CICD-based approach where version numbers are generated and managed by the pipeline itself, using Git tags as the source of truth and performing any necessary version substitutions during the build process.

Doing so keeps the version numbers out of committed code while ensuring all build artifacts and deployments are properly versioned and correlated to Git tags.

Principles

  • Semantic versioning (major.minor.patch) and its variants apply to almost any type of Git repo - definitely to code libraries, but also to web apps, mobile apps, infrastructure-as-code, and documentation.
  • Git tags are the standard way to store canonical version information and correlate the version number to a specific state of the code base.
  • GitLab Releases are the standard way to store metadata about a version, including release notes.
  • Apps sometimes need to know their version number, at build time, at runtime (such as to show the version number on a help page), or both.
  • Sometimes certain configuration files must contain a reference to the project's version number, for example pyproject.toml, terraform.tfvars, or pom.xml.
  • It's best if version numbering happens downstream of source code, and the only record of a version number lives in the Git tags themselves. In other words, resist the temptation to add new commits just to bump a version - see Avoid Git pushes in pipelines.
  • For configuration files that require version numbers, it's better to replace the version number in the files at build-time (based on the Git tag) than to store the version number itself in the repo.

Library

The ProCICD base library (and many private CICD libraries) uses the VerNum tool for semantic version number management and processing. Refer to the VerNum README for details on the tool itself.

Capabilities include:

  • Flexible technology support: Works with any technology or framework
  • Embeds common practices: Uses Git tags, GitLab release objects, and CICD
  • Manual and automated releases: Trigger version increments either manually through pipeline variables or automatically using commit messages
  • Rich release documentation: Automatically generates changelogs from commit messages and maintains activity logs
  • Safe version management: Checks for existing releases, protected branch requirements, and different handling for push events versus manual triggers
  • Protected release process: Releases only occur on protected default branches, preventing accidental version increments

Configuration

To use version numbering (assuming a CICD library derived from the base library) add the following to a pipeline:

include:
  - local: 'util/all-global.gitlab-ci.yml'
  - local: 'release/release-global.gitlab-ci.yml'

If version numbering is the only functionality required from ProCICD, then include the release pipe in your project:

include:
    - project: your/library
      file: release/release-pipe.gitlab-ci.yml

The above approaches will add a set of jobs that:

  • Output the latest version (based on Git tags that match the pattern v1.2.3)
  • Increment the version according to an increment provided as a variable
  • Place the new version number in a job artifact (using setenv) for use by later jobs
  • Add a GitLab release object and Git tag for the new version (using release-cli)

Jobs that need to access the version being released can extend the .release base job and reference the RELEASE_VERSION variable in their script blocks.

Performing a release

To trigger a release, you can use one of two variables:

  • VERSION_INCREMENT: Increment the version number. Provide the value patch, minor, or major
  • VERSION_RERELEASE: Perform release/deployment operations on an already created/tagged version

Treat these variables as momentary and keep them out of the settings. Provide the value in the "Run Pipeline" form or use the glab CLI:

glab ci run --variables VERSION_INCREMENT:minor

Or attach the variable to a push operation:

git push -o ci.variable="VERSION_INCREMENT=alpha"

Auto-release

Most teams like to control when release number increments happen at pipeline runtime using variables. Alternatively, the framework supports automatic version number increments based on commit messages.

To enable Auto-release, set the auto-release input to a space-separated list of the increments that are allowed. For example:

include:
  - project: 'procicd/lib'
    file: 'python/pypi-library-pipe.gitlab-ci.yml'
    inputs:
      auto-release: patch minor

(Note that the -pipe element must support the auto-release input.)

When enabled, include [patch], [minor], or [major] in commit messages to perform a release with that increment.

Changing files

For those files within the repo that require the version number in order to operate correctly, use the following methodology:

  • Use a placeholder (typically 0.0.0) in configuration files in the code base to represent the version number
  • Make sure that local build/test operations on developer workstations work as expected for version 0.0.0
  • Perform a replace operation (typically using sed) in the pipeline after determining the version number but before build/deploy operations that require it

Examples of such files include pyproject.toml, terraform.tfvars, and pom.xml.

Example

The reference example of version number usage appears in the Steampunk Wizard Busy project and the PyPI library pipeline.

About VerNum

  • The PyPI page for VerNum describes it in detail.
  • VerNum provides several schemes for version numbering; the most common and default is the simple major/minor/patch scheme (called just patch).
  • If performing version increments frequently from the Run Pipeline page, consider including one of the scheme addons that will create a dropdown list for easy use.
  • For even more sophisticated variants, include a .vernum.yml file in the project as per the documentation.
  • VerNum doesn't always bootstrap. Add a tag with the name v0.0.0 before starting.