#!/bin/bash # Copyright 1999-2024 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 # @ECLASS: eapi7-ver.eclass # @MAINTAINER: # PMS team # @AUTHOR: # Ulrich Müller # Michał Górny # @BLURB: Testing implementation of EAPI 7 version manipulators # @DESCRIPTION: # A stand-alone implementation of the version manipulation functions # aimed for EAPI 7. Intended to be used for wider testing of # the proposed functions and to allow ebuilds to switch to the new # model early, with minimal change needed for actual EAPI 7. # # https://bugs.gentoo.org/482170 # # @SUBSECTION Version strings # # The functions support arbitrary version strings consisting of version # components interspersed with (possibly empty) version separators. # # A version component can either consist purely of digits ([0-9]+) # or purely of uppercase and lowercase letters ([A-Za-z]+). A version # separator is either a string of any other characters ([^A-Za-z0-9]+), # or it occurs at the transition between a sequence of letters # and a sequence of digits, or vice versa. In the latter case, # the version separator is an empty string. # # The version is processed left-to-right, and each successive component # is assigned numbers starting with 1. The components are either split # on version separators or on boundaries between digits and letters # (in which case the separator between the components is empty). # Version separators are assigned numbers starting with 1 for # the separator between 1st and 2nd components. As a special case, # if the version string starts with a separator, it is assigned index 0. # # Examples: # # @CODE # 1.2b-alpha4 -> 1 . 2 '' b - alpha '' 4 # c s c s c s c s c # 1 1 2 2 3 3 4 4 5 # # .11. -> . 11 . # s c s # 0 1 1 # @CODE # # @SUBSECTION Ranges # # A range can be specified as 'm' for m-th version component, 'm-' # for all components starting with m-th or 'm-n' for components starting # at m-th and ending at n-th (inclusive). If the range spans outside # the version string, it is truncated silently. # @FUNCTION: _ver_parse_range # @USAGE: # @INTERNAL # @DESCRIPTION: # Parse the range string , setting 'start' and 'end' variables # to the appropriate bounds. specifies the appropriate upper # bound for the range; the user-specified value is truncated to this. _ver_parse_range() { local range=${1} local max=${2} [[ ${range} == [0-9]* ]] \ || die "${FUNCNAME}: range must start with a number" start=${range%-*} [[ ${range} == *-* ]] && end=${range#*-} || end=${start} if [[ ${end} ]]; then [[ ${start} -le ${end} ]] \ || die "${FUNCNAME}: end of range must be >= start" [[ ${end} -le ${max} ]] || end=${max} else end=${max} fi } # @FUNCTION: _ver_split # @USAGE: # @INTERNAL # @DESCRIPTION: # Split the version string into separator-component array. # Sets 'comp' to an array of the form: ( s_0 c_1 s_1 c_2 s_2 c_3... ) # where s_i are separators and c_i are components. _ver_split() { local v=${1} LC_ALL=C comp=() # get separators and components local s c while [[ ${v} ]]; do # cut the separator s=${v%%[a-zA-Z0-9]*} v=${v:${#s}} # cut the next component; it can be either digits or letters [[ ${v} == [0-9]* ]] && c=${v%%[^0-9]*} || c=${v%%[^a-zA-Z]*} v=${v:${#c}} comp+=( "${s}" "${c}" ) done } # @FUNCTION: ver_cut # @USAGE: [] # @DESCRIPTION: # Print the substring of the version string containing components # defined by the and the version separators between them. # Processes if specified, ${PV} otherwise. # # For the syntax of versions and ranges, please see the eclass # description. ver_cut() { local range=${1} local v=${2:-${PV}} local start end local -a comp _ver_split "${v}" local max=$((${#comp[@]}/2)) _ver_parse_range "${range}" "${max}" if [[ ${start} -gt 0 ]]; then start=$(( start*2 - 1 )) fi # Work around a bug in bash-3.2, where "${comp[*]:start:end*2-start}" # inserts stray 0x7f characters for empty array elements printf "%s" "${comp[@]:start:end*2-start}" $'\n' } # @FUNCTION: ver_rs # @USAGE: [ ...] [] # @DESCRIPTION: # Print the version string after substituting the specified version # separators at with (string). Multiple ' ' # pairs can be specified. Processes if specified, # ${PV} otherwise. # # For the syntax of versions and ranges, please see the eclass # description. ver_rs() { local v (( ${#} & 1 )) && v=${@: -1} || v=${PV} local start end i local -a comp _ver_split "${v}" local max=$((${#comp[@]}/2 - 1)) while [[ ${#} -ge 2 ]]; do _ver_parse_range "${1}" "${max}" for (( i = start*2; i <= end*2; i+=2 )); do [[ ${i} -eq 0 && -z ${comp[i]} ]] && continue comp[i]=${2} done shift 2 done local IFS= echo "${comp[*]}" } # @FUNCTION: _ver_compare_int # @USAGE: # @RETURN: 0 if -eq , 1 if -lt , 3 if -gt # @INTERNAL # @DESCRIPTION: # Compare two non-negative integers and , of arbitrary length. # If is equal to, less than, or greater than , return 0, 1, or 3 # as exit status, respectively. _ver_compare_int() { local a=$1 b=$2 d=$(( ${#1}-${#2} )) # Zero-pad to equal length if necessary. if [[ ${d} -gt 0 ]]; then printf -v b "%0${d}d%s" 0 "${b}" elif [[ ${d} -lt 0 ]]; then printf -v a "%0$(( -d ))d%s" 0 "${a}" fi [[ ${a} > ${b} ]] && return 3 [[ ${a} == "${b}" ]] } # @FUNCTION: _ver_compare # @USAGE: # @RETURN: 1 if < , 2 if = , 3 if > # @INTERNAL # @DESCRIPTION: # Compare two versions and . If is less than, equal to, # or greater than , return 1, 2, or 3 as exit status, respectively. _ver_compare() { local va=${1} vb=${2} a an al as ar b bn bl bs br re LC_ALL=C re="^([0-9]+(\.[0-9]+)*)([a-z]?)((_(alpha|beta|pre|rc|p)[0-9]*)*)(-r[0-9]+)?$" [[ ${va} =~ ${re} ]] || die "${FUNCNAME}: invalid version: ${va}" an=${BASH_REMATCH[1]} al=${BASH_REMATCH[3]} as=${BASH_REMATCH[4]} ar=${BASH_REMATCH[7]} [[ ${vb} =~ ${re} ]] || die "${FUNCNAME}: invalid version: ${vb}" bn=${BASH_REMATCH[1]} bl=${BASH_REMATCH[3]} bs=${BASH_REMATCH[4]} br=${BASH_REMATCH[7]} # Compare numeric components (PMS algorithm 3.2) # First component _ver_compare_int "${an%%.*}" "${bn%%.*}" || return while [[ ${an} == *.* && ${bn} == *.* ]]; do # Other components (PMS algorithm 3.3) an=${an#*.} bn=${bn#*.} a=${an%%.*} b=${bn%%.*} if [[ ${a} == 0* || ${b} == 0* ]]; then # Remove any trailing zeros [[ ${a} =~ 0+$ ]] && a=${a%"${BASH_REMATCH[0]}"} [[ ${b} =~ 0+$ ]] && b=${b%"${BASH_REMATCH[0]}"} [[ ${a} > ${b} ]] && return 3 [[ ${a} < ${b} ]] && return 1 else _ver_compare_int "${a}" "${b}" || return fi done [[ ${an} == *.* ]] && return 3 [[ ${bn} == *.* ]] && return 1 # Compare letter components (PMS algorithm 3.4) [[ ${al} > ${bl} ]] && return 3 [[ ${al} < ${bl} ]] && return 1 # Compare suffixes (PMS algorithm 3.5) as=${as#_}${as:+_} bs=${bs#_}${bs:+_} while [[ -n ${as} && -n ${bs} ]]; do # Compare each suffix (PMS algorithm 3.6) a=${as%%_*} b=${bs%%_*} if [[ ${a%%[0-9]*} == "${b%%[0-9]*}" ]]; then _ver_compare_int "${a##*[a-z]}" "${b##*[a-z]}" || return else # Check for p first [[ ${a%%[0-9]*} == p ]] && return 3 [[ ${b%%[0-9]*} == p ]] && return 1 # Hack: Use that alpha < beta < pre < rc alphabetically [[ ${a} > ${b} ]] && return 3 || return 1 fi as=${as#*_} bs=${bs#*_} done if [[ -n ${as} ]]; then [[ ${as} == p[_0-9]* ]] && return 3 || return 1 elif [[ -n ${bs} ]]; then [[ ${bs} == p[_0-9]* ]] && return 1 || return 3 fi # Compare revision components (PMS algorithm 3.7) _ver_compare_int "${ar#-r}" "${br#-r}" || return return 2 } # @FUNCTION: ver_test # @USAGE: [] # @DESCRIPTION: # Check if the relation is true. If is not specified, # default to ${PVR}. can be -gt, -ge, -eq, -ne, -le, -lt. # Both versions must conform to the PMS version syntax (with optional # revision parts), and the comparison is performed according to # the algorithm specified in the PMS. ver_test() { local va op vb if [[ $# -eq 3 ]]; then va=${1} shift else va=${PVR} fi [[ $# -eq 2 ]] || die "${FUNCNAME}: bad number of arguments" op=${1} vb=${2} case ${op} in -eq|-ne|-lt|-le|-gt|-ge) ;; *) die "${FUNCNAME}: invalid operator: ${op}" ;; esac _ver_compare "${va}" "${vb}" test $? "${op}" 2 } # Return if we are being sourced return 2>/dev/null source tests-common.sh || exit teq() { local expected=${1}; shift tbegin "${*} -> ${expected}" local got=$("${@}") [[ ${got} == ${expected} ]] tend ${?} "returned: ${got}" } teqr() { local expected=$1; shift tbegin "$* -> ${expected}" "$@" local ret=$? [[ ${ret} -eq ${expected} ]] tend $? "returned: ${ret}" } txf() { tbegin "XFAIL: ${*}" local got=$("${@}" 2>&1) [[ ${got} == die:* ]] tend ${?} "function did not die" } teq 1 ver_cut 1 1.2.3 teq 1 ver_cut 1-1 1.2.3 teq 1.2 ver_cut 1-2 1.2.3 teq 2.3 ver_cut 2- 1.2.3 teq 1.2.3 ver_cut 1- 1.2.3 teq 3b ver_cut 3-4 1.2.3b_alpha4 teq alpha ver_cut 5 1.2.3b_alpha4 teq 1.2 ver_cut 1-2 .1.2.3 teq .1.2 ver_cut 0-2 .1.2.3 teq 2.3 ver_cut 2-3 1.2.3. teq 2.3. ver_cut 2- 1.2.3. teq 2.3. ver_cut 2-4 1.2.3. teq 1-2.3 ver_rs 1 - 1.2.3 teq 1.2-3 ver_rs 2 - 1.2.3 teq 1-2-3.4 ver_rs 1-2 - 1.2.3.4 teq 1.2-3-4 ver_rs 2- - 1.2.3.4 teq 1.2.3 ver_rs 2 . 1.2-3 teq 1.2.3.a ver_rs 3 . 1.2.3a teq 1.2-alpha-4 ver_rs 2-3 - 1.2_alpha4 teq 1.23-b_alpha4 ver_rs 3 - 2 "" 1.2.3b_alpha4 teq a1b_2-c-3-d4e5 ver_rs 3-5 _ 4-6 - a1b2c3d4e5 teq .1-2.3 ver_rs 1 - .1.2.3 teq -1.2.3 ver_rs 0 - .1.2.3 # truncating range teq 1.2 ver_cut 0-2 1.2.3 teq 2.3 ver_cut 2-5 1.2.3 teq "" ver_cut 4 1.2.3 teq "" ver_cut 0 1.2.3 teq "" ver_cut 4- 1.2.3 teq 1.2.3 ver_rs 0 - 1.2.3 teq 1.2.3 ver_rs 3 . 1.2.3 teq 1.2.3 ver_rs 3- . 1.2.3 teq 1.2.3 ver_rs 3-5 . 1.2.3 txf ver_cut foo 1.2.3 txf ver_rs -3 _ a1b2c3d4e5 txf ver_rs 5-3 _ a1b2c3d4e5 # Tests from Portage's test_vercmp.py teqr 0 ver_test 6.0 -gt 5.0 teqr 0 ver_test 5.0 -gt 5 teqr 0 ver_test 1.0-r1 -gt 1.0-r0 teqr 0 ver_test 999999999999999999 -gt 999999999999999998 # 18 digits teqr 0 ver_test 1.0.0 -gt 1.0 teqr 0 ver_test 1.0.0 -gt 1.0b teqr 0 ver_test 1b -gt 1 teqr 0 ver_test 1b_p1 -gt 1_p1 teqr 0 ver_test 1.1b -gt 1.1 teqr 0 ver_test 12.2.5 -gt 12.2b teqr 0 ver_test 4.0 -lt 5.0 teqr 0 ver_test 5 -lt 5.0 teqr 0 ver_test 1.0_pre2 -lt 1.0_p2 teqr 0 ver_test 1.0_alpha2 -lt 1.0_p2 teqr 0 ver_test 1.0_alpha1 -lt 1.0_beta1 teqr 0 ver_test 1.0_beta3 -lt 1.0_rc3 teqr 0 ver_test 1.001000000000000001 -lt 1.001000000000000002 teqr 0 ver_test 1.00100000000 -lt 1.001000000000000001 teqr 0 ver_test 999999999999999998 -lt 999999999999999999 teqr 0 ver_test 1.01 -lt 1.1 teqr 0 ver_test 1.0-r0 -lt 1.0-r1 teqr 0 ver_test 1.0 -lt 1.0-r1 teqr 0 ver_test 1.0 -lt 1.0.0 teqr 0 ver_test 1.0b -lt 1.0.0 teqr 0 ver_test 1_p1 -lt 1b_p1 teqr 0 ver_test 1 -lt 1b teqr 0 ver_test 1.1 -lt 1.1b teqr 0 ver_test 12.2b -lt 12.2.5 teqr 0 ver_test 4.0 -eq 4.0 teqr 0 ver_test 1.0 -eq 1.0 teqr 0 ver_test 1.0-r0 -eq 1.0 teqr 0 ver_test 1.0 -eq 1.0-r0 teqr 0 ver_test 1.0-r0 -eq 1.0-r0 teqr 0 ver_test 1.0-r1 -eq 1.0-r1 teqr 1 ver_test 1 -eq 2 teqr 1 ver_test 1.0_alpha -eq 1.0_pre teqr 1 ver_test 1.0_beta -eq 1.0_alpha teqr 1 ver_test 1 -eq 0.0 teqr 1 ver_test 1.0-r0 -eq 1.0-r1 teqr 1 ver_test 1.0-r1 -eq 1.0-r0 teqr 1 ver_test 1.0 -eq 1.0-r1 teqr 1 ver_test 1.0-r1 -eq 1.0 teqr 1 ver_test 1.0 -eq 1.0.0 teqr 1 ver_test 1_p1 -eq 1b_p1 teqr 1 ver_test 1b -eq 1 teqr 1 ver_test 1.1b -eq 1.1 teqr 1 ver_test 12.2b -eq 12.2 # A subset of tests from Paludis teqr 0 ver_test 1.0_alpha -gt 1_alpha teqr 0 ver_test 1.0_alpha -gt 1 teqr 0 ver_test 1.0_alpha -lt 1.0 teqr 0 ver_test 1.2.0.0_alpha7-r4 -gt 1.2_alpha7-r4 teqr 0 ver_test 0001 -eq 1 teqr 0 ver_test 01 -eq 001 teqr 0 ver_test 0001.1 -eq 1.1 teqr 0 ver_test 01.01 -eq 1.01 teqr 0 ver_test 1.010 -eq 1.01 teqr 0 ver_test 1.00 -eq 1.0 teqr 0 ver_test 1.0100 -eq 1.010 teqr 0 ver_test 1-r00 -eq 1-r0 # Additional tests teqr 0 ver_test 0_rc99 -lt 0 teqr 0 ver_test 011 -eq 11 teqr 0 ver_test 019 -eq 19 teqr 0 ver_test 1.2 -eq 001.2 teqr 0 ver_test 1.2 -gt 1.02 teqr 0 ver_test 1.2a -lt 1.2b teqr 0 ver_test 1.2_pre1 -gt 1.2_pre1_beta2 teqr 0 ver_test 1.2_pre1 -lt 1.2_pre1_p2 teqr 0 ver_test 1.00 -lt 1.0.0 teqr 0 ver_test 1.010 -eq 1.01 teqr 0 ver_test 1.01 -lt 1.1 teqr 0 ver_test 1.2_pre08-r09 -eq 1.2_pre8-r9 teqr 0 ver_test 0 -lt 576460752303423488 # 2**59 teqr 0 ver_test 0 -lt 9223372036854775808 # 2**63 # Bad number or ordering of arguments txf ver_test 1 txf ver_test 1 -lt 2 3 txf ver_test -lt 1 2 # Bad operators txf ver_test 1 "<" 2 txf ver_test 1 lt 2 txf ver_test 1 -foo 2 # Malformed versions txf ver_test "" -ne 1 txf ver_test 1. -ne 1 txf ver_test 1ab -ne 1 txf ver_test b -ne 1 txf ver_test 1-r1_pre -ne 1 txf ver_test 1-pre1 -ne 1 txf ver_test 1_foo -ne 1 txf ver_test 1_pre1.1 -ne 1 txf ver_test 1-r1.0 -ne 1 txf ver_test cvs.9999 -ne 9999 texit