1 |
#! /usr/bin/env python |
2 |
|
3 |
# rpm-solver.py |
4 |
# Given a pile of RPMs will check dependency closure, will attempt to figure out |
5 |
# their installation order. |
6 |
# |
7 |
# Copyright 2005 Progeny Linux Systems, Inc. |
8 |
# |
9 |
# This program is free software; you can redistribute it and/or modify |
10 |
# it under the terms of the GNU General Public License as published by |
11 |
# the Free Software Foundation; either version 2 of the License, or |
12 |
# (at your option) any later version. |
13 |
# |
14 |
# This program is distributed in the hope that it will be useful, |
15 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
16 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
17 |
# GNU General Public License for more details. |
18 |
# |
19 |
# You should have received a copy of the GNU General Public License |
20 |
# along with this program; if not, write to the Free Software |
21 |
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
22 |
# |
23 |
# Author: Sam Hart |
24 |
|
25 |
import os |
26 |
import fnmatch |
27 |
import getopt |
28 |
import sys |
29 |
import getopt |
30 |
import rpm |
31 |
import traceback |
32 |
import commands |
33 |
import tempfile |
34 |
|
35 |
class progress_bar: |
36 |
def __init__(self, prefix="Progress :", prog_char="-", col=60, outnode=sys.stdout): |
37 |
self.f = outnode |
38 |
self.prog_char = prog_char |
39 |
self.col = col |
40 |
self.spinner = ["|", "/", "-", "\\"] |
41 |
self.spin_count = 0 |
42 |
self.prefix = prefix |
43 |
|
44 |
def set(self, prefix="Progress :"): |
45 |
self.prefix = prefix |
46 |
|
47 |
def clear(self): |
48 |
self.f.write("\r") |
49 |
for i in range(0, self.col): |
50 |
self.f.write(" ") |
51 |
|
52 |
self.f.write("\r") |
53 |
self.f.flush() |
54 |
|
55 |
def progress(self, percentage): |
56 |
"""Count must be out of 100%""" |
57 |
|
58 |
if percentage > 1.0: |
59 |
percentage = 1.0 |
60 |
|
61 |
self.f.write(("\r%s 0 |") % self.prefix) |
62 |
width = self.col - len(("\r%s 0 100 |") % self.prefix) + 1 |
63 |
count = width * percentage |
64 |
|
65 |
i = 1 |
66 |
while i < count: |
67 |
self.f.write(self.prog_char) |
68 |
i = i + 1 |
69 |
|
70 |
if count < width: |
71 |
self.f.write(">") |
72 |
while i < width: |
73 |
self.f.write(" ") |
74 |
i = i + 1 |
75 |
|
76 |
if self.spin_count >= len(self.spinner): |
77 |
self.spin_count = 0 |
78 |
|
79 |
self.f.write(self.spinner[self.spin_count]) |
80 |
self.spin_count = self.spin_count + 1 |
81 |
|
82 |
self.f.write(" 100 ") |
83 |
self.f.flush() |
84 |
|
85 |
class rpm_solver: |
86 |
def __init__(self, progress=0, verbose=0): |
87 |
self.progress = progress |
88 |
self.verbose = verbose |
89 |
self._initdb = 0 |
90 |
col = commands.getoutput("echo \"$COLUMNS\"") |
91 |
try: |
92 |
columns = int(col) |
93 |
except: |
94 |
columns = 60 |
95 |
self.pb = progress_bar("rpm_solver :", "-", columns, sys.stderr) |
96 |
|
97 |
|
98 |
def init_db(self, rpm_dir, avail_dir=None, recursive=0): |
99 |
""" Init the database """ |
100 |
|
101 |
self.solver_db = self.db(rpm_dir, recursive) |
102 |
self.solver_db.populate_db(self.verbose, self.pb, 1, self.progress) |
103 |
|
104 |
self.use_avail = 0 |
105 |
self._initdb = 1 |
106 |
|
107 |
if avail_dir: |
108 |
self.avail_db = self.db(avail_dir, recursive) |
109 |
self.avail_db.populate_db(self.verbose, self.pb, 0, self.progress) |
110 |
self.use_avail = 1 |
111 |
|
112 |
def what_provides(self, solver_db, name, version=None): |
113 |
""" Given a name and a version, see what provides it """ |
114 |
|
115 |
for hdr_key in solver_db.rpmdb.keys(): |
116 |
provides = solver_db.rpmdb[hdr_key][rpm.RPMTAG_PROVIDES] |
117 |
if name in provides: |
118 |
if version: |
119 |
version_check = solver_db.rpmdb[hdr_key][rpm.RPMTAG_VERSION] |
120 |
if version == version_check: |
121 |
return hdr_key |
122 |
else: |
123 |
return hdr_key |
124 |
file_list = solver_db.rpmdb[hdr_key][rpm.RPMTAG_FILENAMES] |
125 |
if name in file_list: |
126 |
return hdr_key |
127 |
|
128 |
return None |
129 |
|
130 |
def dep_closure(self): |
131 |
""" Determine if they have dependency closure """ |
132 |
|
133 |
needed = [] |
134 |
problems = [] |
135 |
|
136 |
if self._initdb: |
137 |
missing_deps = self.solver_db.ts.check() |
138 |
if self.verbose > 1: |
139 |
print "->Result of solver_db.ts.check():" |
140 |
print missing_deps |
141 |
if len(missing_deps): |
142 |
for dep in missing_deps: |
143 |
# XXX FIXME |
144 |
# Okay, we completely ignore the version here, which is |
145 |
# wrong wrong WRONG! We should be smacked. |
146 |
if self.use_avail: |
147 |
package = self.what_provides(self.avail_db, dep[1][0], dep[1][1]) |
148 |
if package: |
149 |
needed.append(package) |
150 |
else: |
151 |
problems.append("%s needs %s" % (dep[0][0], dep[1][0])) |
152 |
else: |
153 |
package = self.what_provides(self.solver_db, dep[1][0]) |
154 |
if package: |
155 |
needed.append(package) |
156 |
else: |
157 |
problems.append("%s needs %s" % (dep[0][0], dep[1][0])) |
158 |
else: |
159 |
problems.append("Database has not been populated") |
160 |
|
161 |
return needed, problems |
162 |
|
163 |
def _get_filename_from_hdr(self, pkg_te): |
164 |
""" Given a package name, find the filename for it """ |
165 |
|
166 |
pkg = pkg_te.N() |
167 |
|
168 |
for name in self.solver_db.rpmdb.keys(): |
169 |
if pkg == self.solver_db.rpmdb[name][rpm.RPMTAG_NAME]: |
170 |
return name |
171 |
|
172 |
return None |
173 |
|
174 |
def order_solver(self): |
175 |
""" Once the database has been populated, try to solve the order """ |
176 |
|
177 |
order_pkg = [] |
178 |
order_filename = [] |
179 |
|
180 |
self.solver_db.ts.order() |
181 |
while 1: |
182 |
try: |
183 |
order_pkg.append(self.solver_db.ts.next()) |
184 |
except: |
185 |
break |
186 |
|
187 |
#order_pkg = self.solver_db.ts() |
188 |
|
189 |
for pkg in order_pkg: |
190 |
order_filename.append(self._get_filename_from_hdr(pkg)) |
191 |
|
192 |
return order_filename |
193 |
|
194 |
class db: |
195 |
def __init__(self, rpm_dir=".", recurse=1, ext="*.rpm"): |
196 |
self.rpm_dir = rpm_dir |
197 |
self.recurse = recurse |
198 |
self.rpmdb = {} |
199 |
self.ext = ext |
200 |
self.tmp_dir = tempfile.mkdtemp() |
201 |
self.ts = rpm.TransactionSet(self.tmp_dir) |
202 |
self.ts.setVSFlags(rpm._RPMVSF_NOSIGNATURES) |
203 |
|
204 |
def close(self): |
205 |
self.ts.closeDB() |
206 |
for root, dirs, files in os.walk(self.tmp_dir, topdown=False): |
207 |
for name in files: |
208 |
os.remove(os.path.join(root, name)) |
209 |
for name in dirs: |
210 |
os.rmdir(os.path.join(root, name)) |
211 |
|
212 |
def get_rpmdb_size(self): |
213 |
return len(self.rpmdb) |
214 |
|
215 |
def populate_db(self, verbose, pb, pop_trans=1, progress=0): |
216 |
""" Populate our own DB :-)""" |
217 |
|
218 |
self.rpm_filenames = self._list_files() |
219 |
|
220 |
i = 0.0 |
221 |
if progress: |
222 |
pb.set("Populate RPMDB :") |
223 |
|
224 |
for filename in self.rpm_filenames: |
225 |
if verbose: print "rpm_solver.db.populate_db : Adding " + str(filename) |
226 |
if progress: |
227 |
i = i + 1.0 |
228 |
percent = i / len(self.rpm_filenames) |
229 |
pb.progress(percent) |
230 |
|
231 |
self.add(filename, pop_trans) |
232 |
|
233 |
if progress: |
234 |
pb.clear() |
235 |
|
236 |
if verbose > 1: |
237 |
print ("->The contents of this transaction set for %s:" % self.rpm_dir) |
238 |
for tx in self.ts: |
239 |
print tx |
240 |
|
241 |
def add(self, filename, pop_trans=1): |
242 |
if filename.startswith("http://"): |
243 |
# XXX FIXME: |
244 |
# Okay, I give up about doing this nicely. Screw it. |
245 |
tmpfile = tempfile.mktemp() |
246 |
problem = 0 |
247 |
while 1: |
248 |
output = commands.getoutput("wget -O %s %s" % (tmpfile, filename)) |
249 |
try: |
250 |
fdno = os.open(tmpfile, os.O_RDONLY) |
251 |
os.close(fdno) |
252 |
break |
253 |
except: |
254 |
if problem > 10: |
255 |
print "FATAL ERROR!" |
256 |
print "Could not download " + filename |
257 |
sys.exit(2) |
258 |
else: |
259 |
problem = problem + 1 |
260 |
localfile = tmpfile |
261 |
else: |
262 |
localfile = filename |
263 |
|
264 |
try: |
265 |
fname = filename.split("/")[-1] |
266 |
except: |
267 |
fname = filename |
268 |
|
269 |
fdno = os.open(localfile, os.O_RDONLY) |
270 |
hdr = self.ts.hdrFromFdno(fdno) |
271 |
os.close(fdno) |
272 |
|
273 |
self.rpmdb[fname] = hdr |
274 |
if pop_trans: |
275 |
self.ts.addInstall(hdr,None) |
276 |
|
277 |
if filename.startswith("http://"): |
278 |
os.unlink(tmpfile) |
279 |
|
280 |
def _list_files(self): |
281 |
"""List all the files in a directory""" |
282 |
|
283 |
root = self.rpm_dir |
284 |
patterns = self.ext |
285 |
recurse = self.recurse |
286 |
return_folders = 0 |
287 |
|
288 |
if root.startswith("http://"): |
289 |
output = commands.getoutput("links -dump %s | grep \"http://\" | grep \".rpm\" | awk '{print $2}'" % root) |
290 |
results = output.split("\n") |
291 |
return results |
292 |
else: |
293 |
# Expand patterns from semicolon-separated string to list |
294 |
pattern_list = patterns.split(';') |
295 |
|
296 |
class Bunch: |
297 |
def __init__(self, **kwds): self.__dict__.update(kwds) |
298 |
arg = Bunch(recurse=recurse, pattern_list=pattern_list, return_folders=return_folders, results=[]) |
299 |
|
300 |
def visit(arg, dirname, files): |
301 |
# Append to arg.results all relevant files |
302 |
for name in files: |
303 |
fullname = os.path.normpath(os.path.join(dirname, name)) |
304 |
fullname = fullname.rstrip() |
305 |
if arg.return_folders or os.path.isfile(fullname): |
306 |
for pattern in arg.pattern_list: |
307 |
if fnmatch.fnmatch(name, pattern): |
308 |
arg.results.append(fullname) |
309 |
break |
310 |
# Block recursion if disallowed |
311 |
if not arg.recurse: files[:]=[] |
312 |
|
313 |
os.path.walk(root, visit, arg) |
314 |
return arg.results |
315 |
|
316 |
def process(rpm_dir, solve_dir, check_only, recursive, progress, verbose): |
317 |
""" Main process if ran from command line """ |
318 |
|
319 |
solver = rpm_solver(progress, verbose) |
320 |
solver.init_db(rpm_dir, solve_dir, recursive) |
321 |
needed, problems = solver.dep_closure() |
322 |
|
323 |
if len(needed): |
324 |
print "Error! The following packages are needed for dependency closure:\n" |
325 |
for pkg in needed: |
326 |
print "\t" + str(pkg) |
327 |
if len(problems): |
328 |
print "Error! The following problems were encountered:\n" |
329 |
for pkg in problems: |
330 |
print "\t" + str(pkg) |
331 |
|
332 |
if len(problems) or len(needed): |
333 |
sys.exit(2) |
334 |
elif check_only: |
335 |
print ("The RPMs in %s have dependency closure" % rpm_dir) |
336 |
else: |
337 |
# Okay we do stuff |
338 |
ordered = solver.order_solver() |
339 |
i = 0 |
340 |
for name in ordered: |
341 |
print ("%d:%s" % (i, name)) |
342 |
i = i + 1 |
343 |
|
344 |
def usage(): |
345 |
print "rpm-solver.py -" |
346 |
print " Given a directory of RPMs, attempt to order their" |
347 |
print "installation or determine if they have dependency closure." |
348 |
print "\nUSAGE:" |
349 |
print " rpm-solver.py [options] <RPM_DIR>" |
350 |
print "\nWhere [options] may be one of the following:" |
351 |
print "\t-c | --check\tCheck for dependency closure only" |
352 |
print "\t-s | --solve\tUse the pool of rpms specified for solving" |
353 |
print "\t-v | --verbose\tBe verbose in processing" |
354 |
print "\t-p | --progress\tUse progress bar" |
355 |
print "\t-r | --recursive\tScan RPM_DIR recursively" |
356 |
print "\n\n" |
357 |
|
358 |
|
359 |
def main(): |
360 |
try: |
361 |
opts, args = getopt.getopt(sys.argv[1:], "vprs:c", ["verbose", "progress", "recursive", "solve=", "check"]) |
362 |
except getopt.GetoptError: |
363 |
# print help information and exit: |
364 |
usage() |
365 |
sys.exit(2) |
366 |
|
367 |
verbose = 0 |
368 |
progress = 0 |
369 |
recursive = 0 |
370 |
solve_dir = None |
371 |
check_only = 0 |
372 |
|
373 |
if len(sys.argv) < 2: |
374 |
usage() |
375 |
sys.exit(2) |
376 |
|
377 |
rpm_dir = sys.argv[-1] |
378 |
|
379 |
for o, a in opts: |
380 |
if o in ("-v", "--verbose"): |
381 |
verbose = verbose + 1 |
382 |
|
383 |
if o in ("-p", "--progress"): |
384 |
progress = 1 |
385 |
|
386 |
if o in ("-r", "--recursive"): |
387 |
recursive = 1 |
388 |
|
389 |
if o in ("-s", "--solve"): |
390 |
solve_dir = a |
391 |
|
392 |
if o in ("-c", "--check"): |
393 |
check_only = 1 |
394 |
|
395 |
if verbose > 1: print "WARNING: Excessive debugging" |
396 |
|
397 |
process(rpm_dir, solve_dir, check_only, recursive, progress, verbose) |
398 |
|
399 |
if __name__ == "__main__": |
400 |
main() |
401 |
|
402 |
# vim:set ai et sts=4 sw=4 tw=80: |