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