so we can stop talking about versions and start shipping.

20241127214906.0.0
-
-
When it was built
 
What was built
 
How it was built
date -u +%Y%m%d%H%M%S
 
git rev-parse --short HEAD
 
${CI_JOB_ID}

TL;DR

TrunkVer is a SemVer-compatible versioning scheme for continuously-delivered, trunk-based applications and systems that don't follow a release scheme.

It is a drop-in replacement for semantic versions and replaces the version with meaningful meta data, telling you at a glance what the artifact is, when it was built and where you may find the build log.

Usage

GitHub Actions

- name: Generate trunkver
  id: trunkver
  uses: crftd-tech/trunkver@main

- name: Print trunkver
  env:
    TRUNKVER: ${{ steps.trunkver.outputs.trunkver }}
  run: |
    echo "$TRUNKVER"

GitLab

Using our template from https://gitlab.com/crftd-tech/trunkver-gitlab-ci

include:
- remote: 'https://gitlab.com/crftd-tech/trunkver-gitlab-ci/-/raw/main/trunkver.gitlab-ci.yml'

Downloading the CLI directly

build:
  script:
    - curl -sSL https://github.com/crftd-tech/trunkver/releases/latest/download/trunkver_linux_amd64 -o trunkver
    - chmod +x trunkver
    - export TRUNKVER=$(./trunkver generate)

Docker

docker run --rm ghcr.io/crftd-tech/trunkver:latest generate --build-ref "$CI_JOB_ID" --source-ref "g$(git rev-parse --short HEAD)"

Other CIs

curl -sSL https://github.com/crftd-tech/trunkver/releases/latest/download/trunkver_linux_amd64 -o trunkver
chmod +x trunkver
./trunkver generate

Rationale

We have identified a frequent source of avoidable confusion, conflict and cost in the software delivery process caused by versioning software that should not be versioned - or rather, the versioning should be automated.

Historically, countless versioning schemes have been used to signify changes to a piece of software, using a lot of not clearly defined words such as beta, final or release candidate as well as arbitrary numbering schemes that typically involve one or more digits that are incremented according to certain rules, or worse, without clear rules.

Over time, semantic versioning has been proposed and adopted by many developer teams for good reasons. It clearly defines what each part of a version number means, such as incrementing the first digit, or major version, to signify a change that is considered to be a “breaking” one. This can be used by both humans and machines to improve their work, such as a machine refusing to automatically apply an update in this case and notifying a human to adapt to the breaking change. We are fans of semantic versioning.

However, we keep encountering teams and organizations that apply semantic versioning or a custom versioning scheme to software that does not need any of that - and through this, they create an astonishing amount of unnecessary work such as arguing whether or not a certain piece of software should be called “alpha”, “beta”, “rho”, “really final v4” etc, manually creating tickets listing the changes or even specialized gatekeeper roles such as “release engineer” - in the worst case a single person in the whole organization. Because this makes it harder, boring and costly to deploy, it systematically reduces the number of deployments, and through this the delivery performance of the organization.

We acknowledge that these efforts often stem from perfectly valid requirements of various stakeholders, such as the necessity to audit the release process, finding out what version of the software is currently running or adhering to a specific certified process. Ironically, the manual process around it makes this not only costly, but often defeats the intended purpose. We have seen audit trails missing commits, full of copy/paste errors, etc. We therefore argue the only way to get auditing right is by automating it too, including the version numbering.

In an organization that creates software in teams of trusted contributors that deploy software to controlled environments together, this kind of versioning ceremony can become a major hurdle to adopting the XP and DevOps practices of trunk-based development and continuous integration/deployment/delivery - and it can and should be replaced by a tiny amount of code.

Principles

Specification

The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.

  1. A TrunkVer is suitable for versioning of any artifact that may be released without any regard for compatibility with third parties integrating with the artifact.

  2. A TrunkVer may be used for an artifact, while SemVer or other versioning schemes may be used for specific interfaces to that artifact, e.g. REST APIs.

  3. A TrunkVer is syntactically compatible with SemVer, although it does not respect its semantic interpretation of the version number.

  4. A TrunkVer consists of three components: A timestamp, a source reference, and a build reference.

  5. The timestamp is precise to one second and always formatted in UTC i.e. YYYYMMDDHHMMSS, e.g. 20241230142105. It replaces the Major part of a SemVer.

  6. The source reference identifies the exact source code used to build the artifact. Identifiers MUST comprise only ASCII alphanumerics [09-A-Za-z]. Identifiers MUST NOT be empty. If the source reference is a git commit checksum, it may be truncated to 7 characters (--short) and be prefixed with a g.

  7. The build reference identifies the exact build job used to build the artifact. Identifiers MUST comprise only ASCII alphanumerics and hyphens [0-9A-Za-z-].

  8. A TrunkVer is formatted as follows: The timestamp, followed by .0.0-, followed by the source reference, followed by -, followed by the build reference. It may be used to replace any SemVer version.

  9. Alternatively, for the purpose of e.g. prereleases of an otherwise SemVer-versioned artifact, a TrunkVer may be assembled into the prerelease part of a SemVer version as follows: The timestamp, followed by -, followed by the source reference, followed by -, followed by the build reference.

EBNF Definition

TRUNKVER = ( MAJOR_TRUNKVER | PRERELEASE_TRUNKVER );
MAJOR_TRUNKVER = TIMESTAMP, '.0.0-', BUILD_REF, '-', SOURCE_REF;
MINOR_TRUNKVER = TIMESTAMP, '-', BUILD_REF, '-', SOURCE_REF;

TIMESTAMP = NON_ZERO_DIGIT, 11*DIGIT;
SOURCE_REF = GIT_COMMIT_REF | { ALPHANUMERIC };
GIT_COMMIT_REF = 'g', HEXADECIMAL;
BUILD_REF = { ALPHANUMERIC | '-' };


DIGIT = "0" | NON_ZERO_DIGIT;
NON_ZERO_DIGIT = "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9";
HEXADECIMAL = "a" | "b" | "c" | "d" | "e" | "f" | "A" | "B" | "C" | "D" | "E" | "F" | "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9";
ALPHANUMERIC = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" | "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z";

FAQ

Links