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