2 # -*- coding: utf-8 -*-
4 # Copyright (C) 2006 Mandriva; 2009 Red Hat, Inc.; 2009 Ville Skyttä
5 # Authors: Frederic Lepied, Florian Festi
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU Library General Public License as published by
9 # the Free Software Foundation; version 2 only
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU Library General Public License for more details.
16 # You should have received a copy of the GNU Library General Public License
17 # along with this program; if not, write to the Free Software Foundation, Inc.,
18 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
30 if os.path.isdir("/usr/share/rpmlint"):
31 site.addsitedir("/usr/share/rpmlint")
39 TAGS = ( rpm.RPMTAG_NAME, rpm.RPMTAG_SUMMARY,
40 rpm.RPMTAG_DESCRIPTION, rpm.RPMTAG_GROUP,
41 rpm.RPMTAG_LICENSE, rpm.RPMTAG_URL,
42 rpm.RPMTAG_PREIN, rpm.RPMTAG_POSTIN,
43 rpm.RPMTAG_PREUN, rpm.RPMTAG_POSTUN,
44 rpm.RPMTAG_PRETRANS, rpm.RPMTAG_POSTTRANS)
46 PRCO = ( 'REQUIRES', 'PROVIDES', 'CONFLICTS', 'OBSOLETES')
48 #{fname : (size, mode, mtime, flags, dev, inode,
49 # nlink, state, vflags, user, group, digest)}
50 __FILEIDX = [ ['S', 0],
62 DEPFORMAT = '%-12s%s %s %s %s'
70 def __init__(self, old, new, ignore=None):
73 if self.ignore is None:
76 FILEIDX = self.__FILEIDX
77 for tag in self.ignore:
84 old = self.__load_pkg(old).header
85 new = self.__load_pkg(new).header
94 if old_tag != new_tag:
95 tagname = rpm.tagnames[tag]
97 self.__add(self.FORMAT, (self.ADDED, tagname))
99 self.__add(self.FORMAT, (self.REMOVED, tagname))
101 self.__add(self.FORMAT, ('S.5.....', tagname))
103 # compare Provides, Requires, ...
104 for tag in self.PRCO:
105 self.__comparePRCOs(old, new, tag)
109 old_files_dict = self.__fileIteratorToDict(old.fiFromHeader())
110 new_files_dict = self.__fileIteratorToDict(new.fiFromHeader())
111 files = list(set(itertools.chain(old_files_dict.iterkeys(),
112 new_files_dict.iterkeys())))
118 old_file = old_files_dict.get(f)
119 new_file = new_files_dict.get(f)
122 self.__add(self.FORMAT, (self.ADDED, f))
124 self.__add(self.FORMAT, (self.REMOVED, f))
127 for entry in FILEIDX:
128 if entry[1] != None and \
129 old_file[entry[1]] != new_file[entry[1]]:
130 format = format + entry[0]
133 format = format + '.'
135 self.__add(self.FORMAT, (format, f))
137 # return a report of the differences
139 return '\n'.join((format % data for format, data in self.result))
141 # do the two rpms differ
143 return bool(self.result)
145 # add one differing item
146 def __add(self, format, data):
147 self.result.append((format, data))
149 # load a package from a file or from the installed ones
150 def __load_pkg(self, name, tmpdir = tempfile.gettempdir()):
153 if stat.S_ISREG(st[stat.ST_MODE]):
154 return Pkg.Pkg(name, tmpdir)
155 except (OSError, TypeError):
157 inst = Pkg.getInstalledPkgs(name)
159 raise KeyError("No installed packages by name %s" % name)
161 raise KeyError("More than one installed packages by name %s" % name)
164 # output the right string according to RPMSENSE_* const
165 def sense2str(self, sense):
167 for tag, char in ((rpm.RPMSENSE_LESS, "<"),
168 (rpm.RPMSENSE_GREATER, ">"),
169 (rpm.RPMSENSE_EQUAL, "=")):
174 # output the right requires string according to RPMSENSE_* const
175 def req2str(self, req):
177 # we want to use 64 even with rpm versions that define RPMSENSE_PREREQ
178 # as 0 to get sane results when comparing packages built with an old
179 # (64) version and a new (0) one
180 if req & (rpm.RPMSENSE_PREREQ or 64):
184 if req & rpm.RPMSENSE_SCRIPT_PRE:
186 elif req & rpm.RPMSENSE_SCRIPT_POST:
188 elif req & rpm.RPMSENSE_SCRIPT_PREUN:
190 elif req & rpm.RPMSENSE_SCRIPT_POSTUN:
192 elif req & getattr(rpm, "RPMSENSE_PRETRANS", 1 << 7): # rpm >= 4.9.0
193 ss.append("pretrans")
194 elif req & getattr(rpm, "RPMSENSE_POSTTRANS", 1 << 5): # rpm >= 4.9.0
195 ss.append("posttrans")
197 s += "(%s)" % ",".join(ss)
201 # compare Provides, Requires, Conflicts, Obsoletes
202 def __comparePRCOs(self, old, new, name):
203 oldflags = old[name[:-1]+'FLAGS']
204 newflags = new[name[:-1]+'FLAGS']
205 # fix buggy rpm binding not returning list for single entries
206 if not isinstance(oldflags, list): oldflags = [ oldflags ]
207 if not isinstance(newflags, list): newflags = [ newflags ]
209 o = zip(old[name], oldflags, old[name[:-1]+'VERSION'])
210 n = zip(new[name], newflags, new[name[:-1]+'VERSION'])
212 # filter self provides, TODO: self %name(%_isa) as well
213 if name == 'PROVIDES':
214 oldE = old['epoch'] is not None and str(old['epoch'])+":" or ""
215 oldNV = (old['name'], rpm.RPMSENSE_EQUAL,
216 "%s%s-%s" % (oldE, old['version'], old['release']))
217 newE = new['epoch'] is not None and str(new['epoch'])+":" or ""
218 newNV = (new['name'], rpm.RPMSENSE_EQUAL,
219 "%s%s-%s" % (newE, new['version'], new['release']))
220 o = [entry for entry in o if entry != oldNV]
221 n = [entry for entry in n if entry != newNV]
224 if not oldentry in n:
226 if namestr == 'REQUIRES':
227 namestr = self.req2str(oldentry[1])
228 self.__add(self.DEPFORMAT,
229 (self.REMOVED, namestr, oldentry[0],
230 self.sense2str(oldentry[1]), oldentry[2]))
232 if not newentry in o:
234 if namestr == 'REQUIRES':
235 namestr = self.req2str(newentry[1])
236 self.__add(self.DEPFORMAT,
237 (self.ADDED, namestr, newentry[0],
238 self.sense2str(newentry[1]), newentry[2]))
240 def __fileIteratorToDict(self, fi):
243 result[filedata[0]] = filedata[1:]
247 print ('''Usage: %s [<options>] <old package> <new package>
249 -h, --help Output this message and exit
250 -i, --ignore File property to ignore when calculating differences (may be
251 used multiple times); valid values are: S (size), M (mode),
252 5 (checksum), D (device), N (inode), L (number of links),
253 V (vflags), U (user), G (group), F (digest), T (time)''' \
261 opts, args = getopt.getopt(sys.argv[1:],
262 "hti:", ["help", "ignore-times", "ignore="])
263 except getopt.GetoptError, e:
264 Pkg.warn("Error: %s" % e)
267 for option, argument in opts:
268 if option in ("-h", "--help"):
270 if option in ("-t", "--ignore-times"):
271 # deprecated; --ignore=T should be used instead
272 ignore_tags.append("T")
273 if option in ("-i", "--ignore"):
274 ignore_tags.append(argument)
279 d = Rpmdiff(args[0], args[1], ignore=ignore_tags)
280 textdiff = d.textdiff()
283 sys.exit(int(d.differs()))
285 if __name__ == '__main__':
291 # indent-tabs-mode: nil
292 # py-indent-offset: 4