Module: Puppet::Util::RpmCompare

Included in:
Package::Version::Rpm, Package::Version::Rpm
Defined in:
lib/puppet/util/rpm_compare.rb

Constant Summary collapse

ARCH_LIST =
%w[
  noarch i386 i686 ppc ppc64 armv3l armv4b armv4l armv4tl armv5tel
  armv5tejl armv6l armv7l m68kmint s390 s390x ia64 x86_64 sh3 sh4
].freeze
ARCH_REGEX =
Regexp.new(ARCH_LIST.map { |arch| "\\.#{arch}" }.join('|'))

Instance Method Summary collapse

Instance Method Details

#compare_values(s1, s2) ⇒ Object

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.



151
152
153
154
155
156
157
# File 'lib/puppet/util/rpm_compare.rb', line 151

def compare_values(s1, s2)
  return 0 if s1.nil? && s2.nil?
  return 1 if !s1.nil? && s2.nil?
  return -1 if s1.nil? && !s2.nil?

  rpmvercmp(s1, s2)
end

#rpm_compare_evr(should, is) ⇒ Object

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


172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/puppet/util/rpm_compare.rb', line 172

def rpm_compare_evr(should, is)
  # pass on to rpm labelCompare
  should_hash = rpm_parse_evr(should)
  is_hash = rpm_parse_evr(is)

  unless should_hash[:epoch].nil?
    rc = compare_values(should_hash[:epoch], is_hash[:epoch])
    return rc unless rc == 0
  end

  rc = compare_values(should_hash[:version], is_hash[:version])
  return rc unless rc == 0

  # here is our special case, PUP-1244.
  # if should_hash[:release] is nil (not specified by the user),
  # and comparisons up to here are equal, return equal. We need to
  # evaluate to whatever level of detail the user specified, so we
  # don't end up upgrading or *downgrading* when not intended.
  #
  # This should NOT be triggered if we're trying to ensure latest.
  return 0 if should_hash[:release].nil?

  compare_values(should_hash[:release], is_hash[:release])
end

#rpm_parse_evr(full_version) ⇒ Object

parse a rpm “version” specification this re-implements rpm’s rpmUtils.miscutils.stringToVersion() in ruby



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/puppet/util/rpm_compare.rb', line 118

def rpm_parse_evr(full_version)
  epoch_index = full_version.index(':')
  if epoch_index
    epoch = full_version[0, epoch_index]
    full_version = full_version[epoch_index + 1, full_version.length]
  else
    epoch = nil
  end
  begin
    epoch = String(Integer(epoch))
  rescue
    # If there are non-digits in the epoch field, default to nil
    epoch = nil
  end
  release_index = full_version.index('-')
  if release_index
    version = full_version[0, release_index]
    release = full_version[release_index + 1, full_version.length]
    arch = release.scan(ARCH_REGEX)[0]
    if arch
      architecture = arch.delete('.')
      release.gsub!(ARCH_REGEX, '')
    end
  else
    version = full_version
    release = nil
  end
  { :epoch => epoch, :version => version, :release => release, :arch => architecture }
end

#rpmvercmp(str1, str2) ⇒ Object

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


27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/puppet/util/rpm_compare.rb', line 27

def rpmvercmp(str1, str2)
  return 0 if str1 == str2

  front_strip_re = /^[^A-Za-z0-9~]+/

  while str1.length > 0 or str2.length > 0
    # trim anything that's in front_strip_re and != '~' off the beginning of each string
    str1 = str1.gsub(front_strip_re, '')
    str2 = str2.gsub(front_strip_re, '')

    # "handle the tilde separator, it sorts before everything else"
    if str1 =~ /^~/ && str2 =~ /^~/
      # if they both have ~, strip it
      str1 = str1[1..]
      str2 = str2[1..]
      next
    elsif str1 =~ /^~/
      return -1
    elsif str2 =~ /^~/
      return 1
    end

    break if str1.length == 0 or str2.length == 0

    # "grab first completely alpha or completely numeric segment"
    isnum = false
    # if the first char of str1 is a digit, grab the chunk of continuous digits from each string
    if str1 =~ /^[0-9]+/
      if str1 =~ /^[0-9]+/
        segment1 = $LAST_MATCH_INFO.to_s
        str1 = $LAST_MATCH_INFO.post_match
      else
        segment1 = ''
      end
      if str2 =~ /^[0-9]+/
        segment2 = $LAST_MATCH_INFO.to_s
        str2 = $LAST_MATCH_INFO.post_match
      else
        segment2 = ''
      end
      isnum = true
    # else grab the chunk of continuous alphas from each string (which may be '')
    else
      if str1 =~ /^[A-Za-z]+/
        segment1 = $LAST_MATCH_INFO.to_s
        str1 = $LAST_MATCH_INFO.post_match
      else
        segment1 = ''
      end
      if str2 =~ /^[A-Za-z]+/
        segment2 = $LAST_MATCH_INFO.to_s
        str2 = $LAST_MATCH_INFO.post_match
      else
        segment2 = ''
      end
    end

    # if the segments we just grabbed from the strings are different types (i.e. one numeric one alpha),
    # where alpha also includes ''; "numeric segments are always newer than alpha segments"
    if segment2.length == 0
      return 1 if isnum

      return -1
    end

    if isnum
      # "throw away any leading zeros - it's a number, right?"
      segment1 = segment1.gsub(/^0+/, '')
      segment2 = segment2.gsub(/^0+/, '')
      # "whichever number has more digits wins"
      return 1 if segment1.length > segment2.length
      return -1 if segment1.length < segment2.length
    end

    # "strcmp will return which one is greater - even if the two segments are alpha
    # or if they are numeric. don't return if they are equal because there might
    # be more segments to compare"
    rc = segment1 <=> segment2
    return rc if rc != 0
  end # end while loop

  # if we haven't returned anything yet, "whichever version still has characters left over wins"
  return 1 if str1.length > str2.length
  return -1 if str1.length < str2.length

  0
end