module Puppet::Util::RpmCompare
Constants
- ARCH_LIST
- ARCH_REGEX
Public Instance Methods
this method is a native implementation of the compare_values
function in rpm's python bindings, found in python/header-py.c, as used by rpm.
# File lib/puppet/util/rpm_compare.rb 147 def compare_values(s1, s2) 148 return 0 if s1.nil? && s2.nil? 149 return 1 if ( not s1.nil? ) && s2.nil? 150 return -1 if s1.nil? && (not s2.nil?) 151 return rpmvercmp(s1, s2) 152 end
how rpm compares two package versions: rpmUtils.miscutils.compareEVR(), which massages data types and then calls rpm.labelCompare(), found in rpm.git/python/header-py.c, which sets epoch to 0 if null, then compares epoch, then ver, then rel using compare_values
() and returns the first non-0 result, else 0. This function combines the logic of compareEVR() and labelCompare().
“version_should” can be v, v-r, or e:v-r. “version_is” will always be at least v-r, can be e:v-r
return 1: a is newer than b
0: a and b are the same version -1: b is newer than a
# File lib/puppet/util/rpm_compare.rb 167 def rpm_compareEVR(should, is) 168 # pass on to rpm labelCompare 169 should_hash = rpm_parse_evr(should) 170 is_hash = rpm_parse_evr(is) 171 172 if !should_hash[:epoch].nil? 173 rc = compare_values(should_hash[:epoch], is_hash[:epoch]) 174 return rc unless rc == 0 175 end 176 177 rc = compare_values(should_hash[:version], is_hash[:version]) 178 return rc unless rc == 0 179 180 # here is our special case, PUP-1244. 181 # if should_hash[:release] is nil (not specified by the user), 182 # and comparisons up to here are equal, return equal. We need to 183 # evaluate to whatever level of detail the user specified, so we 184 # don't end up upgrading or *downgrading* when not intended. 185 # 186 # This should NOT be triggered if we're trying to ensure latest. 187 return 0 if should_hash[:release].nil? 188 189 rc = compare_values(should_hash[:release], is_hash[:release]) 190 191 return rc 192 end
parse a rpm “version” specification this re-implements rpm's rpmUtils.miscutils.stringToVersion() in ruby
# File lib/puppet/util/rpm_compare.rb 114 def rpm_parse_evr(full_version) 115 epoch_index = full_version.index(':') 116 if epoch_index 117 epoch = full_version[0,epoch_index] 118 full_version = full_version[epoch_index+1,full_version.length] 119 else 120 epoch = nil 121 end 122 begin 123 epoch = String(Integer(epoch)) 124 rescue 125 # If there are non-digits in the epoch field, default to nil 126 epoch = nil 127 end 128 release_index = full_version.index('-') 129 if release_index 130 version = full_version[0,release_index] 131 release = full_version[release_index+1,full_version.length] 132 arch = release.scan(ARCH_REGEX)[0] 133 if arch 134 architecture = arch.delete('.') 135 release.gsub!(ARCH_REGEX, '') 136 end 137 else 138 version = full_version 139 release = nil 140 end 141 return { :epoch => epoch, :version => version, :release => release, :arch => architecture } 142 end
This is an attempt at implementing RPM's lib/rpmvercmp.c rpmvercmp(a, b) in Ruby.
Some of the things in here look REALLY UGLY and/or arbitrary. Our goal is to match how RPM compares versions, quirks and all.
I've kept a lot of C-like string processing in an effort to keep this as identical to RPM as possible.
returns 1 if str1 is newer than str2,
0 if they are identical -1 if str1 is older than str2
# File lib/puppet/util/rpm_compare.rb 25 def rpmvercmp(str1, str2) 26 return 0 if str1 == str2 27 28 front_strip_re = /^[^A-Za-z0-9~]+/ 29 30 while str1.length > 0 or str2.length > 0 31 # trim anything that's in front_strip_re and != '~' off the beginning of each string 32 str1 = str1.gsub(front_strip_re, '') 33 str2 = str2.gsub(front_strip_re, '') 34 35 # "handle the tilde separator, it sorts before everything else" 36 if str1 =~ /^~/ && str2 =~ /^~/ 37 # if they both have ~, strip it 38 str1 = str1[1..-1] 39 str2 = str2[1..-1] 40 next 41 elsif str1 =~ /^~/ 42 return -1 43 elsif str2 =~ /^~/ 44 return 1 45 end 46 47 break if str1.length == 0 or str2.length == 0 48 49 # "grab first completely alpha or completely numeric segment" 50 isnum = false 51 # if the first char of str1 is a digit, grab the chunk of continuous digits from each string 52 if str1 =~ /^[0-9]+/ 53 if str1 =~ /^[0-9]+/ 54 segment1 = $~.to_s 55 str1 = $~.post_match 56 else 57 segment1 = '' 58 end 59 if str2 =~ /^[0-9]+/ 60 segment2 = $~.to_s 61 str2 = $~.post_match 62 else 63 segment2 = '' 64 end 65 isnum = true 66 # else grab the chunk of continuous alphas from each string (which may be '') 67 else 68 if str1 =~ /^[A-Za-z]+/ 69 segment1 = $~.to_s 70 str1 = $~.post_match 71 else 72 segment1 = '' 73 end 74 if str2 =~ /^[A-Za-z]+/ 75 segment2 = $~.to_s 76 str2 = $~.post_match 77 else 78 segment2 = '' 79 end 80 end 81 82 # if the segments we just grabbed from the strings are different types (i.e. one numeric one alpha), 83 # where alpha also includes ''; "numeric segments are always newer than alpha segments" 84 if segment2.length == 0 85 return 1 if isnum 86 return -1 87 end 88 89 if isnum 90 # "throw away any leading zeros - it's a number, right?" 91 segment1 = segment1.gsub(/^0+/, '') 92 segment2 = segment2.gsub(/^0+/, '') 93 # "whichever number has more digits wins" 94 return 1 if segment1.length > segment2.length 95 return -1 if segment1.length < segment2.length 96 end 97 98 # "strcmp will return which one is greater - even if the two segments are alpha 99 # or if they are numeric. don't return if they are equal because there might 100 # be more segments to compare" 101 rc = segment1 <=> segment2 102 return rc if rc != 0 103 end #end while loop 104 105 # if we haven't returned anything yet, "whichever version still has characters left over wins" 106 return 1 if str1.length > str2.length 107 return -1 if str1.length < str2.length 108 0 109 end