Software Versioning

Portable Software Versioning

It appears that you are using AdBlocking software. The cost of running this website is covered by advertisements. If you like it please feel free to a small amount of money to secure the future of this website.

This document illustrates a strategy used for versioning software projects that is portable across different languages and technologies.

Having a universal way of versioning software development projects is a good thing to help us keep track of what's going on.

From Wikipedia (https://en.wikipedia.org/wiki/Software_versioning): "Software versioning is the process of assigning either unique version names or unique version numbers to unique states of computer software. [...] Modern computer software is often tracked using two different software versioning schemes: an internal version number that may be incremented many times in a single day, such as a revision control number, and a released version that typically changes far less often, such as semantic versioning or a project code name."

Semantic Versioning

The most common type of versioning, that is also a de-facto standard in the Linux world, is the Semantic Versioning (http://semver.org), that is used amongst others by RPM and DEB packages. The Semantic Versioning basically prescribes 3 integer numbers separated by a dot (MAJOR.MINOR.PATCH) that are incremented in the following way:

  • MAJOR version when you make incompatible API changes;
  • MINOR version when you add functionality in a backwards-compatible manner;
  • PATCH version when you make backwards-compatible bug fixes.

Single Source of Truth (SSOT)

The version number should be manually written only once in a Single Source of Truth (SSOT) (https://en.wikipedia.org/wiki/Single_source_of_truth).

In our case the version SSOT is a text file named "VERSION" in the root folder of the project.

Once the semantic software version is stored in the VERSION file, it can be parsed and used in multiple contexts (reusability):

  • directly searched and accessed by script languages;
  • injected during compilation time in the source code, so we can return its value by invoking the API (e.g. a "version" argument in a command-line application or a "status" entry point in a RESTful interface);
    • In GO language this can be achieved using the ldflags argument, for example:
      go build -ldflags '-X main.ServiceVersion=$(cat VERSION) -X main.ServiceRelease=$(cat RELEASE)'
    • In CMake we can extract the version parts in this way:
      file(STRINGS VERSION VERSION_FILE_CONTENT)
      string(REPLACE "." ";" VERSION_FILE_PARTS ${VERSION_FILE_CONTENT})
      list(GET VERSION_FILE_PARTS 0 VERSION_MAJOR)
      list(GET VERSION_FILE_PARTS 1 VERSION_MINOR)
      list(GET VERSION_FILE_PARTS 2 VERSION_PATCH)
      set(PROJECT_VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}")
      message(STATUS "PROJECT_VERSION='${PROJECT_VERSION}'")
      
      file(STRINGS RELEASE PROJECT_RELEASE) message(STATUS "PROJECT_RELEASE='${PROJECT_RELEASE}'")
  • used to name or tag the software packages (e.g. RPM, DEB, TAR.GZ, Docker Images, …)
  • used to tag the software repository so we can easily track back the released versions;
  • checked by a git hook before pushing (see the rndpwd example), so we can be sure that the version is correctly updated at any software change.

All the above operations are usually automated via build scripts (e.g. Makefile) and/or CI/CD tasks.

Release Number

During the automatic build and release process performed by a CI/CD system (e.g. Jenkins, GoCD, TravisCI, ...), another unique ever-increasing build (or release) number is automatically generated. This number is usually appended, separated with a dash, to the semantic version to form the full distribution "number". For example, the number "1.2.3-45" indicates the 45th build of the 1.2.3 version of the software. This allows us to track back and read the test, build and deployment logs belonging to a specific build or software package.

Using the same strategy used for the version number, the release number can be stored in a file named "RELEASE" in the root of the project. This file is initialized with the value 1 and updated only on the build agent by the CI/CD system. The presence of the RELEASE file, even if not updated, allows us to develop and test the build and packaging procedures locally.

Project Structure

The resulting project structure should be:

.
├── ...     : other project files
├── VERSION : version file (1.2.3)
└── RELEASE : release file (1)

Having the VERSION and RELEASE files in the root of each project allows us to reuse the same versioning procedures across multiple projects written in different programming languages and ensure traceability of the source code from the built binaries and packages.

See also: Software Structure

 

© 1998-2023 – Nicola Asuni - Tecnick.com - All rights reserved.
about - disclaimer - privacy