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
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
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
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
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
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
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
 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