blob: 2f6ec22e30bcb8dfdc7f01f338f50553db84737e [file] [log] [blame]
Jack Jansen6a600ab2003-02-10 15:55:51 +00001"""Package Install Manager for Python.
2
3This is currently a MacOSX-only strawman implementation.
4Motto: "He may be shabby, but he gets you what you need" :-)
5
6Tools to allow easy installation of packages. The idea is that there is
7an online XML database per (platform, python-version) containing packages
8known to work with that combination. This module contains tools for getting
9and parsing the database, testing whether packages are installed, computing
10dependencies and installing packages.
11
12There is a minimal main program that works as a command line tool, but the
13intention is that the end user will use this through a GUI.
14"""
Jack Jansen95839b82003-02-09 23:10:20 +000015import sys
16import os
17import urllib
18import urlparse
19import plistlib
20import distutils.util
Jack Jansenc4b217d2003-02-10 13:38:44 +000021import md5
Jack Jansen95839b82003-02-09 23:10:20 +000022
Jack Jansen6a600ab2003-02-10 15:55:51 +000023__all__ = ["PimpPreferences", "PimpDatabase", "PimpPackage", "main"]
24
Jack Jansen95839b82003-02-09 23:10:20 +000025_scriptExc_NotInstalled = "pimp._scriptExc_NotInstalled"
26_scriptExc_OldInstalled = "pimp._scriptExc_OldInstalled"
27_scriptExc_BadInstalled = "pimp._scriptExc_BadInstalled"
28
29NO_EXECUTE=0
30
31DEFAULT_FLAVORORDER=['source', 'binary']
32DEFAULT_DOWNLOADDIR='/tmp'
33DEFAULT_BUILDDIR='/tmp'
34DEFAULT_INSTALLDIR=os.path.join(sys.prefix, "Lib", "site-packages")
35DEFAULT_PIMPDATABASE="http://www.cwi.nl/~jack/pimp/pimp-%s.plist" % distutils.util.get_platform()
36
37ARCHIVE_FORMATS = [
38 (".tar.Z", "zcat \"%s\" | tar xf -"),
39 (".taz", "zcat \"%s\" | tar xf -"),
40 (".tar.gz", "zcat \"%s\" | tar xf -"),
41 (".tgz", "zcat \"%s\" | tar xf -"),
42 (".tar.bz", "bzcat \"%s\" | tar xf -"),
43]
44
Jack Jansenc4b217d2003-02-10 13:38:44 +000045class MyURLopener(urllib.FancyURLopener):
46 """Like FancyURLOpener, but we do want to get errors as exceptions"""
47 def http_error_default(self, url, fp, errcode, errmsg, headers):
48 urllib.URLopener.http_error_default(self, url, fp, errcode, errmsg, headers)
49
Jack Jansen95839b82003-02-09 23:10:20 +000050class PimpPreferences:
Jack Jansen6a600ab2003-02-10 15:55:51 +000051 """Container for per-user preferences, such as the database to use
52 and where to install packages"""
53
Jack Jansen95839b82003-02-09 23:10:20 +000054 def __init__(self,
55 flavorOrder=None,
56 downloadDir=None,
57 buildDir=None,
58 installDir=None,
59 pimpDatabase=None):
60 if not flavorOrder:
61 flavorOrder = DEFAULT_FLAVORORDER
62 if not downloadDir:
63 downloadDir = DEFAULT_DOWNLOADDIR
64 if not buildDir:
65 buildDir = DEFAULT_BUILDDIR
66 if not installDir:
67 installDir = DEFAULT_INSTALLDIR
68 if not pimpDatabase:
69 pimpDatabase = DEFAULT_PIMPDATABASE
70 self.flavorOrder = flavorOrder
71 self.downloadDir = downloadDir
72 self.buildDir = buildDir
73 self.installDir = installDir
74 self.pimpDatabase = pimpDatabase
75
76 def check(self):
Jack Jansen6a600ab2003-02-10 15:55:51 +000077 """Check that the preferences make sense: directories exist and are
78 writable, the install directory is on sys.path, etc."""
79
Jack Jansen95839b82003-02-09 23:10:20 +000080 rv = ""
81 RWX_OK = os.R_OK|os.W_OK|os.X_OK
82 if not os.path.exists(self.downloadDir):
83 rv += "Warning: Download directory \"%s\" does not exist\n" % self.downloadDir
84 elif not os.access(self.downloadDir, RWX_OK):
85 rv += "Warning: Download directory \"%s\" is not writable or not readable\n" % self.downloadDir
86 if not os.path.exists(self.buildDir):
87 rv += "Warning: Build directory \"%s\" does not exist\n" % self.buildDir
88 elif not os.access(self.buildDir, RWX_OK):
89 rv += "Warning: Build directory \"%s\" is not writable or not readable\n" % self.buildDir
90 if not os.path.exists(self.installDir):
91 rv += "Warning: Install directory \"%s\" does not exist\n" % self.installDir
92 elif not os.access(self.installDir, RWX_OK):
93 rv += "Warning: Install directory \"%s\" is not writable or not readable\n" % self.installDir
94 else:
95 installDir = os.path.realpath(self.installDir)
96 for p in sys.path:
97 try:
98 realpath = os.path.realpath(p)
99 except:
100 pass
101 if installDir == realpath:
102 break
103 else:
104 rv += "Warning: Install directory \"%s\" is not on sys.path\n" % self.installDir
105 return rv
106
107 def compareFlavors(self, left, right):
Jack Jansen6a600ab2003-02-10 15:55:51 +0000108 """Compare two flavor strings. This is part of your preferences
109 because whether the user prefers installing from source or binary is."""
Jack Jansen95839b82003-02-09 23:10:20 +0000110 if left in self.flavorOrder:
111 if right in self.flavorOrder:
112 return cmp(self.flavorOrder.index(left), self.flavorOrder.index(right))
113 return -1
114 if right in self.flavorOrder:
115 return 1
116 return cmp(left, right)
117
118class PimpDatabase:
Jack Jansen6a600ab2003-02-10 15:55:51 +0000119 """Class representing a pimp database. It can actually contain
120 information from multiple databases through inclusion, but the
121 toplevel database is considered the master, as its maintainer is
122 "responsible" for the contents"""
123
Jack Jansen95839b82003-02-09 23:10:20 +0000124 def __init__(self, prefs):
125 self._packages = []
126 self.preferences = prefs
127 self._urllist = []
128 self._version = ""
129 self._maintainer = ""
130 self._description = ""
131
132 def appendURL(self, url, included=0):
Jack Jansen6a600ab2003-02-10 15:55:51 +0000133 """Append packages from the database with the given URL.
134 Only the first database should specify included=0, so the
135 global information (maintainer, description) get stored."""
136
Jack Jansen95839b82003-02-09 23:10:20 +0000137 if url in self._urllist:
138 return
139 self._urllist.append(url)
Jack Jansen26bf3ac2003-02-10 14:19:14 +0000140 fp = MyURLopener().open(url).fp
Jack Jansen95839b82003-02-09 23:10:20 +0000141 dict = plistlib.Plist.fromFile(fp)
142 # Test here for Pimp version, etc
143 if not included:
144 self._version = dict.get('version', '0.1')
145 self._maintainer = dict.get('maintainer', '')
146 self._description = dict.get('description', '')
Jack Jansen6a600ab2003-02-10 15:55:51 +0000147 self._appendPackages(dict['packages'])
Jack Jansen95839b82003-02-09 23:10:20 +0000148 others = dict.get('include', [])
149 for url in others:
150 self.appendURL(url, included=1)
151
Jack Jansen6a600ab2003-02-10 15:55:51 +0000152 def _appendPackages(self, packages):
153 """Given a list of dictionaries containing package
154 descriptions create the PimpPackage objects and append them
155 to our internal storage."""
156
Jack Jansen95839b82003-02-09 23:10:20 +0000157 for p in packages:
158 pkg = PimpPackage(self, **dict(p))
159 self._packages.append(pkg)
160
161 def list(self):
Jack Jansen6a600ab2003-02-10 15:55:51 +0000162 """Return a list of all PimpPackage objects in the database."""
163
Jack Jansen95839b82003-02-09 23:10:20 +0000164 return self._packages
165
166 def listnames(self):
Jack Jansen6a600ab2003-02-10 15:55:51 +0000167 """Return a list of names of all packages in the database."""
168
Jack Jansen95839b82003-02-09 23:10:20 +0000169 rv = []
170 for pkg in self._packages:
171 rv.append(_fmtpackagename(pkg))
172 return rv
173
174 def dump(self, pathOrFile):
Jack Jansen6a600ab2003-02-10 15:55:51 +0000175 """Dump the contents of the database to an XML .plist file.
176
177 The file can be passed as either a file object or a pathname.
178 All data, including included databases, is dumped."""
179
Jack Jansen95839b82003-02-09 23:10:20 +0000180 packages = []
181 for pkg in self._packages:
182 packages.append(pkg.dump())
183 dict = {
184 'version': self._version,
185 'maintainer': self._maintainer,
186 'description': self._description,
187 'packages': packages
188 }
189 plist = plistlib.Plist(**dict)
190 plist.write(pathOrFile)
191
192 def find(self, ident):
Jack Jansen6a600ab2003-02-10 15:55:51 +0000193 """Find a package. The package can be specified by name
194 or as a dictionary with name, version and flavor entries.
195
196 Only name is obligatory. If there are multiple matches the
197 best one (higher version number, flavors ordered according to
198 users' preference) is returned."""
199
Jack Jansen95839b82003-02-09 23:10:20 +0000200 if type(ident) == str:
201 # Remove ( and ) for pseudo-packages
202 if ident[0] == '(' and ident[-1] == ')':
203 ident = ident[1:-1]
204 # Split into name-version-flavor
205 fields = ident.split('-')
206 if len(fields) < 1 or len(fields) > 3:
207 return None
208 name = fields[0]
209 if len(fields) > 1:
210 version = fields[1]
211 else:
212 version = None
213 if len(fields) > 2:
214 flavor = fields[2]
215 else:
216 flavor = None
217 else:
218 name = ident['name']
219 version = ident.get('version')
220 flavor = ident.get('flavor')
221 found = None
222 for p in self._packages:
223 if name == p.name and \
224 (not version or version == p.version) and \
225 (not flavor or flavor == p.flavor):
226 if not found or found < p:
227 found = p
228 return found
229
230class PimpPackage:
Jack Jansen6a600ab2003-02-10 15:55:51 +0000231 """Class representing a single package."""
232
Jack Jansen95839b82003-02-09 23:10:20 +0000233 def __init__(self, db, name,
234 version=None,
235 flavor=None,
236 description=None,
237 longdesc=None,
238 downloadURL=None,
239 installTest=None,
Jack Jansenb4bb64e2003-02-10 13:08:04 +0000240 prerequisites=None,
241 preInstall=None,
Jack Jansenc4b217d2003-02-10 13:38:44 +0000242 postInstall=None,
243 MD5Sum=None):
Jack Jansen95839b82003-02-09 23:10:20 +0000244 self._db = db
245 self.name = name
246 self.version = version
247 self.flavor = flavor
248 self.description = description
249 self.longdesc = longdesc
250 self.downloadURL = downloadURL
251 self._installTest = installTest
252 self._prerequisites = prerequisites
Jack Jansenb4bb64e2003-02-10 13:08:04 +0000253 self._preInstall = preInstall
254 self._postInstall = postInstall
Jack Jansenc4b217d2003-02-10 13:38:44 +0000255 self._MD5Sum = MD5Sum
Jack Jansen95839b82003-02-09 23:10:20 +0000256
257 def dump(self):
Jack Jansen6a600ab2003-02-10 15:55:51 +0000258 """Return a dict object containing the information on the package."""
Jack Jansen95839b82003-02-09 23:10:20 +0000259 dict = {
260 'name': self.name,
261 }
262 if self.version:
263 dict['version'] = self.version
264 if self.flavor:
265 dict['flavor'] = self.flavor
266 if self.description:
267 dict['description'] = self.description
268 if self.longdesc:
269 dict['longdesc'] = self.longdesc
270 if self.downloadURL:
271 dict['downloadURL'] = self.downloadURL
272 if self._installTest:
273 dict['installTest'] = self._installTest
274 if self._prerequisites:
275 dict['prerequisites'] = self._prerequisites
Jack Jansenb4bb64e2003-02-10 13:08:04 +0000276 if self._preInstall:
277 dict['preInstall'] = self._preInstall
278 if self._postInstall:
279 dict['postInstall'] = self._postInstall
Jack Jansenc4b217d2003-02-10 13:38:44 +0000280 if self._MD5Sum:
281 dict['MD5Sum'] = self._MD5Sum
Jack Jansen95839b82003-02-09 23:10:20 +0000282 return dict
283
284 def __cmp__(self, other):
Jack Jansen6a600ab2003-02-10 15:55:51 +0000285 """Compare two packages, where the "better" package sorts lower."""
286
Jack Jansen95839b82003-02-09 23:10:20 +0000287 if not isinstance(other, PimpPackage):
288 return cmp(id(self), id(other))
289 if self.name != other.name:
290 return cmp(self.name, other.name)
291 if self.version != other.version:
Jack Jansen6a600ab2003-02-10 15:55:51 +0000292 return -cmp(self.version, other.version)
Jack Jansen95839b82003-02-09 23:10:20 +0000293 return self._db.preferences.compareFlavors(self.flavor, other.flavor)
294
295 def installed(self):
Jack Jansen6a600ab2003-02-10 15:55:51 +0000296 """Test wheter the package is installed.
297
298 Returns two values: a status indicator which is one of
299 "yes", "no", "old" (an older version is installed) or "bad"
300 (something went wrong during the install test) and a human
301 readable string which may contain more details."""
302
Jack Jansen95839b82003-02-09 23:10:20 +0000303 namespace = {
304 "NotInstalled": _scriptExc_NotInstalled,
305 "OldInstalled": _scriptExc_OldInstalled,
306 "BadInstalled": _scriptExc_BadInstalled,
307 "os": os,
308 "sys": sys,
309 }
310 installTest = self._installTest.strip() + '\n'
311 try:
312 exec installTest in namespace
313 except ImportError, arg:
314 return "no", str(arg)
315 except _scriptExc_NotInstalled, arg:
316 return "no", str(arg)
317 except _scriptExc_OldInstalled, arg:
318 return "old", str(arg)
319 except _scriptExc_BadInstalled, arg:
320 return "bad", str(arg)
321 except:
322 print 'TEST:', repr(self._installTest)
323 return "bad", "Package install test got exception"
324 return "yes", ""
325
326 def prerequisites(self):
Jack Jansen6a600ab2003-02-10 15:55:51 +0000327 """Return a list of prerequisites for this package.
328
329 The list contains 2-tuples, of which the first item is either
330 a PimpPackage object or None, and the second is a descriptive
331 string. The first item can be None if this package depends on
332 something that isn't pimp-installable, in which case the descriptive
333 string should tell the user what to do."""
334
Jack Jansen95839b82003-02-09 23:10:20 +0000335 rv = []
336 if not self.downloadURL:
337 return [(None, "This package needs to be installed manually")]
338 if not self._prerequisites:
339 return []
340 for item in self._prerequisites:
341 if type(item) == str:
342 pkg = None
343 descr = str(item)
344 else:
345 pkg = self._db.find(item)
346 if not pkg:
347 descr = "Requires unknown %s"%_fmtpackagename(item)
348 else:
349 descr = pkg.description
350 rv.append((pkg, descr))
351 return rv
352
353 def _cmd(self, output, dir, *cmditems):
Jack Jansen6a600ab2003-02-10 15:55:51 +0000354 """Internal routine to run a shell command in a given directory."""
355
Jack Jansen95839b82003-02-09 23:10:20 +0000356 cmd = ("cd \"%s\"; " % dir) + " ".join(cmditems)
357 if output:
358 output.write("+ %s\n" % cmd)
359 if NO_EXECUTE:
360 return 0
361 fp = os.popen(cmd, "r")
362 while 1:
363 line = fp.readline()
364 if not line:
365 break
366 if output:
367 output.write(line)
368 rv = fp.close()
369 return rv
370
Jack Jansen6a600ab2003-02-10 15:55:51 +0000371 def downloadSinglePackage(self, output=None):
372 """Download a single package, if needed.
373
374 An MD5 signature is used to determine whether download is needed,
375 and to test that we actually downloaded what we expected.
376 If output is given it is a file-like object that will receive a log
377 of what happens.
378
379 If anything unforeseen happened the method returns an error message
380 string.
381 """
382
Jack Jansen95839b82003-02-09 23:10:20 +0000383 scheme, loc, path, query, frag = urlparse.urlsplit(self.downloadURL)
384 path = urllib.url2pathname(path)
385 filename = os.path.split(path)[1]
Jack Jansenc4b217d2003-02-10 13:38:44 +0000386 self.archiveFilename = os.path.join(self._db.preferences.downloadDir, filename)
387 if not self._archiveOK():
Jack Jansen26bf3ac2003-02-10 14:19:14 +0000388 if scheme == 'manual':
389 return "Please download package manually and save as %s" % self.archiveFilename
Jack Jansenc4b217d2003-02-10 13:38:44 +0000390 if self._cmd(output, self._db.preferences.downloadDir,
391 "curl",
392 "--output", self.archiveFilename,
393 self.downloadURL):
394 return "download command failed"
Jack Jansen95839b82003-02-09 23:10:20 +0000395 if not os.path.exists(self.archiveFilename) and not NO_EXECUTE:
396 return "archive not found after download"
Jack Jansenc4b217d2003-02-10 13:38:44 +0000397 if not self._archiveOK():
398 return "archive does not have correct MD5 checksum"
399
400 def _archiveOK(self):
Jack Jansen6a600ab2003-02-10 15:55:51 +0000401 """Test an archive. It should exist and the MD5 checksum should be correct."""
402
Jack Jansenc4b217d2003-02-10 13:38:44 +0000403 if not os.path.exists(self.archiveFilename):
404 return 0
405 if not self._MD5Sum:
406 sys.stderr.write("Warning: no MD5Sum for %s\n" % _fmtpackagename(self))
407 return 1
408 data = open(self.archiveFilename, 'rb').read()
409 checksum = md5.new(data).hexdigest()
410 return checksum == self._MD5Sum
Jack Jansen95839b82003-02-09 23:10:20 +0000411
Jack Jansen6a600ab2003-02-10 15:55:51 +0000412 def unpackSinglePackage(self, output=None):
413 """Unpack a downloaded package archive."""
414
Jack Jansen95839b82003-02-09 23:10:20 +0000415 filename = os.path.split(self.archiveFilename)[1]
416 for ext, cmd in ARCHIVE_FORMATS:
417 if filename[-len(ext):] == ext:
418 break
419 else:
420 return "unknown extension for archive file: %s" % filename
421 basename = filename[:-len(ext)]
422 cmd = cmd % self.archiveFilename
423 self._buildDirname = os.path.join(self._db.preferences.buildDir, basename)
424 if self._cmd(output, self._db.preferences.buildDir, cmd):
425 return "unpack command failed"
426 setupname = os.path.join(self._buildDirname, "setup.py")
427 if not os.path.exists(setupname) and not NO_EXECUTE:
428 return "no setup.py found after unpack of archive"
429
Jack Jansen6a600ab2003-02-10 15:55:51 +0000430 def installSinglePackage(self, output=None):
431 """Download, unpack and install a single package.
432
433 If output is given it should be a file-like object and it
434 will receive a log of what happened."""
435
Jack Jansen95839b82003-02-09 23:10:20 +0000436 if not self.downloadURL:
437 return "%s: This package needs to be installed manually" % _fmtpackagename(self)
438 msg = self.downloadSinglePackage(output)
439 if msg:
440 return "download %s: %s" % (_fmtpackagename(self), msg)
441 msg = self.unpackSinglePackage(output)
442 if msg:
443 return "unpack %s: %s" % (_fmtpackagename(self), msg)
Jack Jansenb4bb64e2003-02-10 13:08:04 +0000444 if self._preInstall:
445 if self._cmd(output, self._buildDirname, self._preInstall):
446 return "pre-install %s: running \"%s\" failed" % \
447 (_fmtpackagename(self), self._preInstall)
Jack Jansen95839b82003-02-09 23:10:20 +0000448 if self._cmd(output, self._buildDirname, sys.executable, "setup.py install"):
449 return "install %s: running \"setup.py install\" failed" % _fmtpackagename(self)
Jack Jansenb4bb64e2003-02-10 13:08:04 +0000450 if self._postInstall:
451 if self._cmd(output, self._buildDirname, self._postInstall):
452 return "post-install %s: running \"%s\" failed" % \
453 (_fmtpackagename(self), self._postInstall)
Jack Jansen95839b82003-02-09 23:10:20 +0000454 return None
455
456class PimpInstaller:
Jack Jansen6a600ab2003-02-10 15:55:51 +0000457 """Installer engine: computes dependencies and installs
458 packages in the right order."""
459
Jack Jansen95839b82003-02-09 23:10:20 +0000460 def __init__(self, db):
461 self._todo = []
462 self._db = db
463 self._curtodo = []
464 self._curmessages = []
465
466 def __contains__(self, package):
467 return package in self._todo
468
469 def _addPackages(self, packages):
470 for package in packages:
471 if not package in self._todo:
472 self._todo.insert(0, package)
473
474 def _prepareInstall(self, package, force=0, recursive=1):
Jack Jansen6a600ab2003-02-10 15:55:51 +0000475 """Internal routine, recursive engine for prepareInstall.
476
477 Test whether the package is installed and (if not installed
478 or if force==1) prepend it to the temporary todo list and
479 call ourselves recursively on all prerequisites."""
480
Jack Jansen95839b82003-02-09 23:10:20 +0000481 if not force:
482 status, message = package.installed()
483 if status == "yes":
484 return
485 if package in self._todo or package in self._curtodo:
486 return
487 self._curtodo.insert(0, package)
488 if not recursive:
489 return
490 prereqs = package.prerequisites()
491 for pkg, descr in prereqs:
492 if pkg:
493 self._prepareInstall(pkg, force, recursive)
494 else:
495 self._curmessages.append("Requires: %s" % descr)
496
497 def prepareInstall(self, package, force=0, recursive=1):
Jack Jansen6a600ab2003-02-10 15:55:51 +0000498 """Prepare installation of a package.
499
500 If the package is already installed and force is false nothing
501 is done. If recursive is true prerequisites are installed first.
502
503 Returns a list of packages (to be passed to install) and a list
504 of messages of any problems encountered.
505 """
506
Jack Jansen95839b82003-02-09 23:10:20 +0000507 self._curtodo = []
508 self._curmessages = []
509 self._prepareInstall(package, force, recursive)
510 rv = self._curtodo, self._curmessages
511 self._curtodo = []
512 self._curmessages = []
513 return rv
514
515 def install(self, packages, output):
Jack Jansen6a600ab2003-02-10 15:55:51 +0000516 """Install a list of packages."""
517
Jack Jansen95839b82003-02-09 23:10:20 +0000518 self._addPackages(packages)
519 status = []
520 for pkg in self._todo:
521 msg = pkg.installSinglePackage(output)
522 if msg:
523 status.append(msg)
524 return status
525
526
527def _fmtpackagename(dict):
Jack Jansen6a600ab2003-02-10 15:55:51 +0000528 """Return the full name "name-version-flavor" of a package.
529
530 If the package is a pseudo-package, something that cannot be
531 installed through pimp, return the name in (parentheses)."""
532
Jack Jansen95839b82003-02-09 23:10:20 +0000533 if isinstance(dict, PimpPackage):
534 dict = dict.dump()
535 rv = dict['name']
536 if dict.has_key('version'):
537 rv = rv + '-%s' % dict['version']
538 if dict.has_key('flavor'):
539 rv = rv + '-%s' % dict['flavor']
540 if not dict.get('downloadURL'):
541 # Pseudo-package, show in parentheses
542 rv = '(%s)' % rv
543 return rv
544
545def _run(mode, verbose, force, args):
Jack Jansen6a600ab2003-02-10 15:55:51 +0000546 """Engine for the main program"""
547
Jack Jansen95839b82003-02-09 23:10:20 +0000548 prefs = PimpPreferences()
549 prefs.check()
550 db = PimpDatabase(prefs)
551 db.appendURL(prefs.pimpDatabase)
552
Jack Jansenc4b217d2003-02-10 13:38:44 +0000553 if mode == 'dump':
554 db.dump(sys.stdout)
555 elif mode =='list':
Jack Jansen95839b82003-02-09 23:10:20 +0000556 if not args:
557 args = db.listnames()
558 print "%-20.20s\t%s" % ("Package", "Description")
559 print
560 for pkgname in args:
561 pkg = db.find(pkgname)
562 if pkg:
563 description = pkg.description
564 pkgname = _fmtpackagename(pkg)
565 else:
566 description = 'Error: no such package'
567 print "%-20.20s\t%s" % (pkgname, description)
568 if verbose:
569 print "\tHome page:\t", pkg.longdesc
570 print "\tDownload URL:\t", pkg.downloadURL
Jack Jansenc4b217d2003-02-10 13:38:44 +0000571 elif mode =='status':
Jack Jansen95839b82003-02-09 23:10:20 +0000572 if not args:
573 args = db.listnames()
574 print "%-20.20s\t%s\t%s" % ("Package", "Installed", "Message")
575 print
576 for pkgname in args:
577 pkg = db.find(pkgname)
578 if pkg:
579 status, msg = pkg.installed()
580 pkgname = _fmtpackagename(pkg)
581 else:
582 status = 'error'
583 msg = 'No such package'
584 print "%-20.20s\t%-9.9s\t%s" % (pkgname, status, msg)
585 if verbose and status == "no":
586 prereq = pkg.prerequisites()
587 for pkg, msg in prereq:
588 if not pkg:
589 pkg = ''
590 else:
591 pkg = _fmtpackagename(pkg)
592 print "%-20.20s\tRequirement: %s %s" % ("", pkg, msg)
593 elif mode == 'install':
594 if not args:
595 print 'Please specify packages to install'
596 sys.exit(1)
597 inst = PimpInstaller(db)
598 for pkgname in args:
599 pkg = db.find(pkgname)
600 if not pkg:
601 print '%s: No such package' % pkgname
602 continue
603 list, messages = inst.prepareInstall(pkg, force)
604 if messages and not force:
605 print "%s: Not installed:" % pkgname
606 for m in messages:
607 print "\t", m
608 else:
609 if verbose:
610 output = sys.stdout
611 else:
612 output = None
613 messages = inst.install(list, output)
614 if messages:
615 print "%s: Not installed:" % pkgname
616 for m in messages:
617 print "\t", m
618
619def main():
Jack Jansen6a600ab2003-02-10 15:55:51 +0000620 """Minimal commandline tool to drive pimp."""
621
Jack Jansen95839b82003-02-09 23:10:20 +0000622 import getopt
623 def _help():
624 print "Usage: pimp [-v] -s [package ...] List installed status"
625 print " pimp [-v] -l [package ...] Show package information"
626 print " pimp [-vf] -i package ... Install packages"
Jack Jansenc4b217d2003-02-10 13:38:44 +0000627 print " pimp -d Dump database to stdout"
Jack Jansen95839b82003-02-09 23:10:20 +0000628 print "Options:"
629 print " -v Verbose"
630 print " -f Force installation"
631 sys.exit(1)
632
633 try:
Jack Jansenc4b217d2003-02-10 13:38:44 +0000634 opts, args = getopt.getopt(sys.argv[1:], "slifvd")
Jack Jansen95839b82003-02-09 23:10:20 +0000635 except getopt.Error:
636 _help()
637 if not opts and not args:
638 _help()
639 mode = None
640 force = 0
641 verbose = 0
642 for o, a in opts:
643 if o == '-s':
644 if mode:
645 _help()
646 mode = 'status'
647 if o == '-l':
648 if mode:
649 _help()
650 mode = 'list'
Jack Jansenc4b217d2003-02-10 13:38:44 +0000651 if o == '-d':
Jack Jansen95839b82003-02-09 23:10:20 +0000652 if mode:
653 _help()
Jack Jansenc4b217d2003-02-10 13:38:44 +0000654 mode = 'dump'
Jack Jansen95839b82003-02-09 23:10:20 +0000655 if o == '-i':
656 mode = 'install'
657 if o == '-f':
658 force = 1
659 if o == '-v':
660 verbose = 1
661 if not mode:
662 _help()
663 _run(mode, verbose, force, args)
664
665if __name__ == '__main__':
666 main()
667
668