Optimal RPM Version Numbering Scheme for Pre-release Packages (alpha/beta/rc)


2 views

When dealing with pre-release packages in RPM, the version comparison logic often leads to unexpected ordering. The fundamental issue stems from how RPM's version comparison algorithm works:


# Problematic example:
rpm -q --qf '%{VERSION}-%{RELEASE}\n' package-2.4.0 package-2.4.0.alpha1
# Output shows 2.4.0 is considered OLDER than 2.4.0.alpha1

RPM compares versions using these key rules:

  • Components are split at non-alphanumeric characters
  • Numeric components are compared as numbers
  • String components are compared alphabetically
  • Missing components are considered newer than existing ones

Here's a workable solution that maintains proper ordering:


Version: 2.4.0
Release: 0.alpha1%{?dist}

Version: 2.4.0
Release: 0.beta1%{?dist}

Version: 2.4.0
Release: 0.rc1%{?dist}

Version: 2.4.0
Release: 1%{?dist}  # Final release

For your spec file, you can use conditional macros:


%define prereltype alpha
%define prerelnum 1

Version: 2.4.0
Release: 0.%{prereltype}%{prerelnum}%{?dist}

# For final releases:
Version: 2.4.0
Release: 1%{?dist}

Here's how to implement this in a CI/CD pipeline:


#!/bin/bash

# Detect build type
if [[ $BUILD_TYPE == "alpha" ]]; then
    PREREL="0.alpha$BUILD_NUM"
elif [[ $BUILD_TYPE == "beta" ]]; then
    PREREL="0.beta$BUILD_NUM"
elif [[ $BUILD_TYPE == "rc" ]]; then
    PREREL="0.rc$BUILD_NUM"
else
    PREREL="1" # Final release
fi

rpmbuild -bb \
    --define "_version 2.4.0" \
    --define "_release $PREREL%{?dist}" \
    package.spec

For projects with parallel development streams:


# Stable branch
Version: 2.4.0
Release: 0.rc2%{?dist}

# Development branch
Version: 2.5.0
Release: 0.alpha3%{?dist}

This approach ensures proper ordering while maintaining semantic meaning in your version numbers.


RPM's version comparison algorithm follows a lexicographical order that doesn't naturally accommodate pre-release versions. The core issue manifests when comparing:

# Problematic comparison example
rpmdev-vercmp 2.4.0 2.4.0.alpha1
# Returns 1 (2.4.0 is considered NEWER than 2.4.0.alpha1)

The fundamental problem occurs because RPM compares each component left-to-right:

2.4.0 > 2.4.0.alpha1  # Dot adds another version component
2.4.0 < 2.4.0a1       # Letter 'a' makes this version newer

The most reliable approach is to encode pre-release status numerically within the version string:

2.4.0~0.alpha1   # Tilde indicates pre-release (sorts before empty)
2.4.0~1.beta2
2.4.0~2.rc1
2.4.0           # Final release

Implementation in spec file:

# Sample .spec file snippet
Version: 2.4.0
Release: 0.alpha1%{?dist}

While epoch can enforce ordering, it breaks cross-branch upgrades:

# Bad practice example (causes upgrade deadlocks)
Epoch: 20240301
Version: 2.3.2

Combine numeric pre-release indicators with release tags:

# Version progression example
1: 2.4.0~0.1.alpha1
2: 2.4.0~0.2.alpha2
3: 2.4.0~0.3.beta1
4: 2.4.0~1.rc1
5: 2.4.0          # Final release
6: 2.4.1~0.1.beta1

Create a versioning script to manage transitions:

#!/bin/bash
# version-bump.sh
case $1 in
  alpha) sed -i 's/$~[0-9]\+$\.alpha[0-9]\+/\1.alpha$(($(echo &|cut -d. -f3)+1))/' package.spec ;;
  beta)  sed -i 's/alpha/beta/' package.spec ;;
  rc)    sed -i 's/beta/rc/' package.spec ;;
  final) sed -i 's/~[0-9]\+$\.[a-z]\+[0-9]\+$//' package.spec ;;
esac

Test version ordering with rpmdev-vercmp:

# Test sequence
rpmdev-vercmp 2.4.0~0.1.alpha1 2.4.0~0.2.alpha2  # Should return -1
rpmdev-vercmp 2.4.0~1.rc1 2.4.0                  # Should return -1
rpmdev-vercmp 2.4.0 2.4.1~0.1.beta1              # Should return -1

Examine how major projects handle this:

# mysql-community-server.spec excerpt
Version: 8.0.32
Release: 1%{?dist}      # Final release
...
Version: 8.0.33
Release: 0.1.rc1%{?dist} # Release candidate

Exception case for urgent security fixes:

# Emergency version bump example
Version: 2.4.0
Release: 1.1%{?dist}    # Post-release fix
Epoch: 1                # Only if absolutely necessary

YUM/DNF specific behaviors:

# yum versionlock example to prevent accidental upgrades
yum versionlock add 'mypackage-2.4.0~*'  # Lock all pre-releases