module Puppet::Util::RpmCompare

Constants

ARCH_LIST
ARCH_REGEX

Public Instance Methods

compare_values(s1, s2) click to toggle source

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
rpm_compareEVR(should, is) click to toggle source

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
rpm_parse_evr(full_version) click to toggle source

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
rpmvercmp(str1, str2) click to toggle source

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