home *** CD-ROM | disk | FTP | other *** search
/ Maximum CD 2010 May / maximum-cd-2010-05.iso / DiscContents / boxee-0.9.20.10711.exe / system / python / Lib / plat-mac / pimp.pyo (.txt) < prev    next >
Encoding:
Python Compiled Bytecode  |  2009-07-20  |  38.8 KB  |  1,329 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyo (Python 2.4)
  3.  
  4. '''Package Install Manager for Python.
  5.  
  6. This is currently a MacOSX-only strawman implementation.
  7. Despite other rumours the name stands for "Packman IMPlementation".
  8.  
  9. Tools to allow easy installation of packages. The idea is that there is
  10. an online XML database per (platform, python-version) containing packages
  11. known to work with that combination. This module contains tools for getting
  12. and parsing the database, testing whether packages are installed, computing
  13. dependencies and installing packages.
  14.  
  15. There is a minimal main program that works as a command line tool, but the
  16. intention is that the end user will use this through a GUI.
  17. '''
  18. import sys
  19. import os
  20. import popen2
  21. import urllib
  22. import urllib2
  23. import urlparse
  24. import plistlib
  25. import distutils.util as distutils
  26. import distutils.sysconfig as distutils
  27. import md5
  28. import tarfile
  29. import tempfile
  30. import shutil
  31. import time
  32. __all__ = [
  33.     'PimpPreferences',
  34.     'PimpDatabase',
  35.     'PimpPackage',
  36.     'main',
  37.     'getDefaultDatabase',
  38.     'PIMP_VERSION',
  39.     'main']
  40. _scriptExc_NotInstalled = 'pimp._scriptExc_NotInstalled'
  41. _scriptExc_OldInstalled = 'pimp._scriptExc_OldInstalled'
  42. _scriptExc_BadInstalled = 'pimp._scriptExc_BadInstalled'
  43. NO_EXECUTE = 0
  44. PIMP_VERSION = '0.5'
  45. DEFAULT_FLAVORORDER = [
  46.     'source',
  47.     'binary',
  48.     'installer']
  49. DEFAULT_DOWNLOADDIR = '/tmp'
  50. DEFAULT_BUILDDIR = '/tmp'
  51. DEFAULT_INSTALLDIR = distutils.sysconfig.get_python_lib()
  52. DEFAULT_PIMPDATABASE_FMT = 'http://www.python.org/packman/version-%s/%s-%s-%s-%s-%s.plist'
  53.  
  54. def getDefaultDatabase(experimental = False):
  55.     if experimental:
  56.         status = 'exp'
  57.     else:
  58.         status = 'prod'
  59.     (major, minor, micro, state, extra) = sys.version_info
  60.     pyvers = '%d.%d' % (major, minor)
  61.     if micro == 0 and state != 'final':
  62.         pyvers = pyvers + '%s%d' % (state, extra)
  63.     
  64.     longplatform = distutils.util.get_platform()
  65.     (osname, release, machine) = longplatform.split('-')
  66.     if osname == 'darwin':
  67.         if sys.prefix.startswith('/System/Library/Frameworks/Python.framework'):
  68.             osname = 'darwin_apple'
  69.         elif sys.prefix.startswith('/Library/Frameworks/Python.framework'):
  70.             osname = 'darwin_macpython'
  71.         
  72.     
  73.     rel = release
  74.     while True:
  75.         url = DEFAULT_PIMPDATABASE_FMT % (PIMP_VERSION, status, pyvers, osname, rel, machine)
  76.         
  77.         try:
  78.             urllib2.urlopen(url)
  79.         except urllib2.HTTPError:
  80.             arg = None
  81.  
  82.         break
  83.         if not rel:
  84.             url = DEFAULT_PIMPDATABASE_FMT % (PIMP_VERSION, status, pyvers, osname, release, machine)
  85.             break
  86.         
  87.         idx = rel.rfind('.')
  88.         if idx < 0:
  89.             rel = ''
  90.             continue
  91.         rel = rel[:idx]
  92.     return url
  93.  
  94.  
  95. def _cmd(output, dir, *cmditems):
  96.     '''Internal routine to run a shell command in a given directory.'''
  97.     cmd = 'cd "%s"; ' % dir + ' '.join(cmditems)
  98.     if output:
  99.         output.write('+ %s\n' % cmd)
  100.     
  101.     if NO_EXECUTE:
  102.         return 0
  103.     
  104.     child = popen2.Popen4(cmd)
  105.     child.tochild.close()
  106.     while None:
  107.         line = child.fromchild.readline()
  108.         if not line:
  109.             break
  110.         
  111.         if output:
  112.             output.write(line)
  113.             continue
  114.     return child.wait()
  115.  
  116.  
  117. class PimpDownloader:
  118.     '''Abstract base class - Downloader for archives'''
  119.     
  120.     def __init__(self, argument, dir = '', watcher = None):
  121.         self.argument = argument
  122.         self._dir = dir
  123.         self._watcher = watcher
  124.  
  125.     
  126.     def download(self, url, filename, output = None):
  127.         pass
  128.  
  129.     
  130.     def update(self, str):
  131.         if self._watcher:
  132.             return self._watcher.update(str)
  133.         
  134.         return True
  135.  
  136.  
  137.  
  138. class PimpCurlDownloader(PimpDownloader):
  139.     
  140.     def download(self, url, filename, output = None):
  141.         self.update('Downloading %s...' % url)
  142.         exitstatus = _cmd(output, self._dir, 'curl', '--output', filename, url)
  143.         self.update('Downloading %s: finished' % url)
  144.         return not exitstatus
  145.  
  146.  
  147.  
  148. class PimpUrllibDownloader(PimpDownloader):
  149.     
  150.     def download(self, url, filename, output = None):
  151.         output = open(filename, 'wb')
  152.         self.update('Downloading %s: opening connection' % url)
  153.         keepgoing = True
  154.         download = urllib2.urlopen(url)
  155.         if download.headers.has_key('content-length'):
  156.             length = long(download.headers['content-length'])
  157.         else:
  158.             length = -1
  159.         data = download.read(4096)
  160.         dlsize = 0
  161.         lasttime = 0
  162.         while keepgoing:
  163.             dlsize = dlsize + len(data)
  164.             if len(data) == 0:
  165.                 break
  166.             
  167.             output.write(data)
  168.             if int(time.time()) != lasttime:
  169.                 lasttime = int(time.time())
  170.                 if length == -1:
  171.                     keepgoing = self.update('Downloading %s: %d bytes...' % (url, dlsize))
  172.                 else:
  173.                     keepgoing = self.update('Downloading %s: %d%% (%d bytes)...' % (url, int(100.0 * dlsize / length), dlsize))
  174.             
  175.             data = download.read(4096)
  176.         if keepgoing:
  177.             self.update('Downloading %s: finished' % url)
  178.         
  179.         return keepgoing
  180.  
  181.  
  182.  
  183. class PimpUnpacker:
  184.     '''Abstract base class - Unpacker for archives'''
  185.     _can_rename = False
  186.     
  187.     def __init__(self, argument, dir = '', renames = [], watcher = None):
  188.         self.argument = argument
  189.         if renames and not (self._can_rename):
  190.             raise RuntimeError, 'This unpacker cannot rename files'
  191.         
  192.         self._dir = dir
  193.         self._renames = renames
  194.         self._watcher = watcher
  195.  
  196.     
  197.     def unpack(self, archive, output = None, package = None):
  198.         pass
  199.  
  200.     
  201.     def update(self, str):
  202.         if self._watcher:
  203.             return self._watcher.update(str)
  204.         
  205.         return True
  206.  
  207.  
  208.  
  209. class PimpCommandUnpacker(PimpUnpacker):
  210.     '''Unpack archives by calling a Unix utility'''
  211.     _can_rename = False
  212.     
  213.     def unpack(self, archive, output = None, package = None):
  214.         cmd = self.argument % archive
  215.         if _cmd(output, self._dir, cmd):
  216.             return 'unpack command failed'
  217.         
  218.  
  219.  
  220.  
  221. class PimpTarUnpacker(PimpUnpacker):
  222.     '''Unpack tarfiles using the builtin tarfile module'''
  223.     _can_rename = True
  224.     
  225.     def unpack(self, archive, output = None, package = None):
  226.         tf = tarfile.open(archive, 'r')
  227.         members = tf.getmembers()
  228.         skip = []
  229.         if self._renames:
  230.             for member in members:
  231.                 for oldprefix, newprefix in self._renames:
  232.                     if oldprefix[:len(self._dir)] == self._dir:
  233.                         oldprefix2 = oldprefix[len(self._dir):]
  234.                     else:
  235.                         oldprefix2 = None
  236.                     if member.name[:len(oldprefix)] == oldprefix:
  237.                         if newprefix is None:
  238.                             skip.append(member)
  239.                         else:
  240.                             member.name = newprefix + member.name[len(oldprefix):]
  241.                             print '    ', member.name
  242.                         break
  243.                         continue
  244.                     if oldprefix2 and member.name[:len(oldprefix2)] == oldprefix2:
  245.                         if newprefix is None:
  246.                             skip.append(member)
  247.                         else:
  248.                             member.name = newprefix + member.name[len(oldprefix2):]
  249.                         break
  250.                         continue
  251.                 
  252.             
  253.         
  254.         for member in members:
  255.             if member in skip:
  256.                 self.update('Skipping %s' % member.name)
  257.                 continue
  258.             
  259.             self.update('Extracting %s' % member.name)
  260.             tf.extract(member, self._dir)
  261.         
  262.         if skip:
  263.             names = _[1]
  264.             if names:
  265.                 return 'Not all files were unpacked: %s' % ' '.join(names)
  266.             
  267.         
  268.  
  269.  
  270. ARCHIVE_FORMATS = [
  271.     ('.tar.Z', PimpTarUnpacker, None),
  272.     ('.taz', PimpTarUnpacker, None),
  273.     ('.tar.gz', PimpTarUnpacker, None),
  274.     ('.tgz', PimpTarUnpacker, None),
  275.     ('.tar.bz', PimpTarUnpacker, None),
  276.     ('.zip', PimpCommandUnpacker, 'unzip "%s"')]
  277.  
  278. class PimpPreferences:
  279.     '''Container for per-user preferences, such as the database to use
  280.     and where to install packages.'''
  281.     
  282.     def __init__(self, flavorOrder = None, downloadDir = None, buildDir = None, installDir = None, pimpDatabase = None):
  283.         if not flavorOrder:
  284.             flavorOrder = DEFAULT_FLAVORORDER
  285.         
  286.         if not downloadDir:
  287.             downloadDir = DEFAULT_DOWNLOADDIR
  288.         
  289.         if not buildDir:
  290.             buildDir = DEFAULT_BUILDDIR
  291.         
  292.         if not pimpDatabase:
  293.             pimpDatabase = getDefaultDatabase()
  294.         
  295.         self.setInstallDir(installDir)
  296.         self.flavorOrder = flavorOrder
  297.         self.downloadDir = downloadDir
  298.         self.buildDir = buildDir
  299.         self.pimpDatabase = pimpDatabase
  300.         self.watcher = None
  301.  
  302.     
  303.     def setWatcher(self, watcher):
  304.         self.watcher = watcher
  305.  
  306.     
  307.     def setInstallDir(self, installDir = None):
  308.         if installDir:
  309.             self.installLocations = [
  310.                 ('--install-lib', installDir),
  311.                 ('--install-headers', None),
  312.                 ('--install-scripts', None),
  313.                 ('--install-data', None)]
  314.         else:
  315.             installDir = DEFAULT_INSTALLDIR
  316.             self.installLocations = []
  317.         self.installDir = installDir
  318.  
  319.     
  320.     def isUserInstall(self):
  321.         return self.installDir != DEFAULT_INSTALLDIR
  322.  
  323.     
  324.     def check(self):
  325.         '''Check that the preferences make sense: directories exist and are
  326.         writable, the install directory is on sys.path, etc.'''
  327.         rv = ''
  328.         RWX_OK = os.R_OK | os.W_OK | os.X_OK
  329.         if not os.path.exists(self.downloadDir):
  330.             rv += 'Warning: Download directory "%s" does not exist\n' % self.downloadDir
  331.         elif not os.access(self.downloadDir, RWX_OK):
  332.             rv += 'Warning: Download directory "%s" is not writable or not readable\n' % self.downloadDir
  333.         
  334.         if not os.path.exists(self.buildDir):
  335.             rv += 'Warning: Build directory "%s" does not exist\n' % self.buildDir
  336.         elif not os.access(self.buildDir, RWX_OK):
  337.             rv += 'Warning: Build directory "%s" is not writable or not readable\n' % self.buildDir
  338.         
  339.         if not os.path.exists(self.installDir):
  340.             rv += 'Warning: Install directory "%s" does not exist\n' % self.installDir
  341.         elif not os.access(self.installDir, RWX_OK):
  342.             rv += 'Warning: Install directory "%s" is not writable or not readable\n' % self.installDir
  343.         else:
  344.             installDir = os.path.realpath(self.installDir)
  345.             for p in sys.path:
  346.                 
  347.                 try:
  348.                     realpath = os.path.realpath(p)
  349.                 except:
  350.                     pass
  351.  
  352.                 if installDir == realpath:
  353.                     break
  354.                     continue
  355.             else:
  356.                 rv += 'Warning: Install directory "%s" is not on sys.path\n' % self.installDir
  357.         return rv
  358.  
  359.     
  360.     def compareFlavors(self, left, right):
  361.         '''Compare two flavor strings. This is part of your preferences
  362.         because whether the user prefers installing from source or binary is.'''
  363.         if left in self.flavorOrder:
  364.             if right in self.flavorOrder:
  365.                 return cmp(self.flavorOrder.index(left), self.flavorOrder.index(right))
  366.             
  367.             return -1
  368.         
  369.         if right in self.flavorOrder:
  370.             return 1
  371.         
  372.         return cmp(left, right)
  373.  
  374.  
  375.  
  376. class PimpDatabase:
  377.     '''Class representing a pimp database. It can actually contain
  378.     information from multiple databases through inclusion, but the
  379.     toplevel database is considered the master, as its maintainer is
  380.     "responsible" for the contents.'''
  381.     
  382.     def __init__(self, prefs):
  383.         self._packages = []
  384.         self.preferences = prefs
  385.         self._url = ''
  386.         self._urllist = []
  387.         self._version = ''
  388.         self._maintainer = ''
  389.         self._description = ''
  390.  
  391.     
  392.     def url(self):
  393.         return self._url
  394.  
  395.     
  396.     def version(self):
  397.         return self._version
  398.  
  399.     
  400.     def maintainer(self):
  401.         return self._maintainer
  402.  
  403.     
  404.     def description(self):
  405.         return self._description
  406.  
  407.     
  408.     def close(self):
  409.         '''Clean up'''
  410.         self._packages = []
  411.         self.preferences = None
  412.  
  413.     
  414.     def appendURL(self, url, included = 0):
  415.         '''Append packages from the database with the given URL.
  416.         Only the first database should specify included=0, so the
  417.         global information (maintainer, description) get stored.'''
  418.         if url in self._urllist:
  419.             return None
  420.         
  421.         self._urllist.append(url)
  422.         fp = urllib2.urlopen(url).fp
  423.         plistdata = plistlib.Plist.fromFile(fp)
  424.         if included:
  425.             version = plistdata.get('Version')
  426.             if version and version > self._version:
  427.                 sys.stderr.write('Warning: included database %s is for pimp version %s\n' % (url, version))
  428.             
  429.         else:
  430.             self._version = plistdata.get('Version')
  431.             if not self._version:
  432.                 sys.stderr.write('Warning: database has no Version information\n')
  433.             elif self._version > PIMP_VERSION:
  434.                 sys.stderr.write('Warning: database version %s newer than pimp version %s\n' % (self._version, PIMP_VERSION))
  435.             
  436.             self._maintainer = plistdata.get('Maintainer', '')
  437.             self._description = plistdata.get('Description', '').strip()
  438.             self._url = url
  439.         self._appendPackages(plistdata['Packages'], url)
  440.         others = plistdata.get('Include', [])
  441.         for o in others:
  442.             o = urllib.basejoin(url, o)
  443.             self.appendURL(o, included = 1)
  444.         
  445.  
  446.     
  447.     def _appendPackages(self, packages, url):
  448.         '''Given a list of dictionaries containing package
  449.         descriptions create the PimpPackage objects and append them
  450.         to our internal storage.'''
  451.         for p in packages:
  452.             p = dict(p)
  453.             if p.has_key('Download-URL'):
  454.                 p['Download-URL'] = urllib.basejoin(url, p['Download-URL'])
  455.             
  456.             flavor = p.get('Flavor')
  457.             if flavor == 'source':
  458.                 pkg = PimpPackage_source(self, p)
  459.             elif flavor == 'binary':
  460.                 pkg = PimpPackage_binary(self, p)
  461.             elif flavor == 'installer':
  462.                 pkg = PimpPackage_installer(self, p)
  463.             elif flavor == 'hidden':
  464.                 pkg = PimpPackage_installer(self, p)
  465.             else:
  466.                 pkg = PimpPackage(self, dict(p))
  467.             self._packages.append(pkg)
  468.         
  469.  
  470.     
  471.     def list(self):
  472.         '''Return a list of all PimpPackage objects in the database.'''
  473.         return self._packages
  474.  
  475.     
  476.     def listnames(self):
  477.         '''Return a list of names of all packages in the database.'''
  478.         rv = []
  479.         for pkg in self._packages:
  480.             rv.append(pkg.fullname())
  481.         
  482.         rv.sort()
  483.         return rv
  484.  
  485.     
  486.     def dump(self, pathOrFile):
  487.         '''Dump the contents of the database to an XML .plist file.
  488.  
  489.         The file can be passed as either a file object or a pathname.
  490.         All data, including included databases, is dumped.'''
  491.         packages = []
  492.         for pkg in self._packages:
  493.             packages.append(pkg.dump())
  494.         
  495.         plistdata = {
  496.             'Version': self._version,
  497.             'Maintainer': self._maintainer,
  498.             'Description': self._description,
  499.             'Packages': packages }
  500.         plist = plistlib.Plist(**plistdata)
  501.         plist.write(pathOrFile)
  502.  
  503.     
  504.     def find(self, ident):
  505.         """Find a package. The package can be specified by name
  506.         or as a dictionary with name, version and flavor entries.
  507.  
  508.         Only name is obligatory. If there are multiple matches the
  509.         best one (higher version number, flavors ordered according to
  510.         users' preference) is returned."""
  511.         if type(ident) == str:
  512.             if ident[0] == '(' and ident[-1] == ')':
  513.                 ident = ident[1:-1]
  514.             
  515.             fields = ident.split('-')
  516.             if len(fields) < 1 or len(fields) > 3:
  517.                 return None
  518.             
  519.             name = fields[0]
  520.             if len(fields) > 1:
  521.                 version = fields[1]
  522.             else:
  523.                 version = None
  524.             if len(fields) > 2:
  525.                 flavor = fields[2]
  526.             else:
  527.                 flavor = None
  528.         else:
  529.             name = ident['Name']
  530.             version = ident.get('Version')
  531.             flavor = ident.get('Flavor')
  532.         found = None
  533.         for p in self._packages:
  534.             if name == p.name():
  535.                 if not version or version == p.version():
  536.                     if not flavor or flavor == p.flavor():
  537.                         if not found or found < p:
  538.                             found = p
  539.                         
  540.             found < p
  541.         
  542.         return found
  543.  
  544.  
  545. ALLOWED_KEYS = [
  546.     'Name',
  547.     'Version',
  548.     'Flavor',
  549.     'Description',
  550.     'Home-page',
  551.     'Download-URL',
  552.     'Install-test',
  553.     'Install-command',
  554.     'Pre-install-command',
  555.     'Post-install-command',
  556.     'Prerequisites',
  557.     'MD5Sum',
  558.     'User-install-skips',
  559.     'Systemwide-only']
  560.  
  561. class PimpPackage:
  562.     '''Class representing a single package.'''
  563.     
  564.     def __init__(self, db, plistdata):
  565.         self._db = db
  566.         name = plistdata['Name']
  567.         for k in plistdata.keys():
  568.             if k not in ALLOWED_KEYS:
  569.                 sys.stderr.write('Warning: %s: unknown key %s\n' % (name, k))
  570.                 continue
  571.         
  572.         self._dict = plistdata
  573.  
  574.     
  575.     def __getitem__(self, key):
  576.         return self._dict[key]
  577.  
  578.     
  579.     def name(self):
  580.         return self._dict['Name']
  581.  
  582.     
  583.     def version(self):
  584.         return self._dict.get('Version')
  585.  
  586.     
  587.     def flavor(self):
  588.         return self._dict.get('Flavor')
  589.  
  590.     
  591.     def description(self):
  592.         return self._dict['Description'].strip()
  593.  
  594.     
  595.     def shortdescription(self):
  596.         return self.description().splitlines()[0]
  597.  
  598.     
  599.     def homepage(self):
  600.         return self._dict.get('Home-page')
  601.  
  602.     
  603.     def downloadURL(self):
  604.         return self._dict.get('Download-URL')
  605.  
  606.     
  607.     def systemwideOnly(self):
  608.         return self._dict.get('Systemwide-only')
  609.  
  610.     
  611.     def fullname(self):
  612.         '''Return the full name "name-version-flavor" of a package.
  613.  
  614.         If the package is a pseudo-package, something that cannot be
  615.         installed through pimp, return the name in (parentheses).'''
  616.         rv = self._dict['Name']
  617.         if self._dict.has_key('Version'):
  618.             rv = rv + '-%s' % self._dict['Version']
  619.         
  620.         if self._dict.has_key('Flavor'):
  621.             rv = rv + '-%s' % self._dict['Flavor']
  622.         
  623.         if self._dict.get('Flavor') == 'hidden':
  624.             rv = '(%s)' % rv
  625.         
  626.         return rv
  627.  
  628.     
  629.     def dump(self):
  630.         '''Return a dict object containing the information on the package.'''
  631.         return self._dict
  632.  
  633.     
  634.     def __cmp__(self, other):
  635.         '''Compare two packages, where the "better" package sorts lower.'''
  636.         if not isinstance(other, PimpPackage):
  637.             return cmp(id(self), id(other))
  638.         
  639.         if self.name() != other.name():
  640.             return cmp(self.name(), other.name())
  641.         
  642.         if self.version() != other.version():
  643.             return -cmp(self.version(), other.version())
  644.         
  645.         return self._db.preferences.compareFlavors(self.flavor(), other.flavor())
  646.  
  647.     
  648.     def installed(self):
  649.         '''Test wheter the package is installed.
  650.  
  651.         Returns two values: a status indicator which is one of
  652.         "yes", "no", "old" (an older version is installed) or "bad"
  653.         (something went wrong during the install test) and a human
  654.         readable string which may contain more details.'''
  655.         namespace = {
  656.             'NotInstalled': _scriptExc_NotInstalled,
  657.             'OldInstalled': _scriptExc_OldInstalled,
  658.             'BadInstalled': _scriptExc_BadInstalled,
  659.             'os': os,
  660.             'sys': sys }
  661.         installTest = self._dict['Install-test'].strip() + '\n'
  662.         
  663.         try:
  664.             exec installTest in namespace
  665.         except ImportError:
  666.             arg = None
  667.             return ('no', str(arg))
  668.         except _scriptExc_NotInstalled:
  669.             arg = None
  670.             return ('no', str(arg))
  671.         except _scriptExc_OldInstalled:
  672.             arg = None
  673.             return ('old', str(arg))
  674.         except _scriptExc_BadInstalled:
  675.             arg = None
  676.             return ('bad', str(arg))
  677.         except:
  678.             sys.stderr.write('-------------------------------------\n')
  679.             sys.stderr.write('---- %s: install test got exception\n' % self.fullname())
  680.             sys.stderr.write('---- source:\n')
  681.             sys.stderr.write(installTest)
  682.             sys.stderr.write('---- exception:\n')
  683.             import traceback
  684.             traceback.print_exc(file = sys.stderr)
  685.             if self._db._maintainer:
  686.                 sys.stderr.write('---- Please copy this and mail to %s\n' % self._db._maintainer)
  687.             
  688.             sys.stderr.write('-------------------------------------\n')
  689.             return ('bad', 'Package install test got exception')
  690.  
  691.         return ('yes', '')
  692.  
  693.     
  694.     def prerequisites(self):
  695.         """Return a list of prerequisites for this package.
  696.  
  697.         The list contains 2-tuples, of which the first item is either
  698.         a PimpPackage object or None, and the second is a descriptive
  699.         string. The first item can be None if this package depends on
  700.         something that isn't pimp-installable, in which case the descriptive
  701.         string should tell the user what to do."""
  702.         rv = []
  703.         if not self._dict.get('Download-URL'):
  704.             (status, _) = self.installed()
  705.             if status == 'yes':
  706.                 return []
  707.             
  708.             return [
  709.                 (None, 'Package %s cannot be installed automatically, see the description' % self.fullname())]
  710.         
  711.         if self.systemwideOnly() and self._db.preferences.isUserInstall():
  712.             return [
  713.                 (None, 'Package %s can only be installed system-wide' % self.fullname())]
  714.         
  715.         if not self._dict.get('Prerequisites'):
  716.             return []
  717.         
  718.         for item in self._dict['Prerequisites']:
  719.             if type(item) == str:
  720.                 pkg = None
  721.                 descr = str(item)
  722.             else:
  723.                 name = item['Name']
  724.                 if item.has_key('Version'):
  725.                     name = name + '-' + item['Version']
  726.                 
  727.                 if item.has_key('Flavor'):
  728.                     name = name + '-' + item['Flavor']
  729.                 
  730.                 pkg = self._db.find(name)
  731.                 if not pkg:
  732.                     descr = 'Requires unknown %s' % name
  733.                 else:
  734.                     descr = pkg.shortdescription()
  735.             rv.append((pkg, descr))
  736.         
  737.         return rv
  738.  
  739.     
  740.     def downloadPackageOnly(self, output = None):
  741.         '''Download a single package, if needed.
  742.  
  743.         An MD5 signature is used to determine whether download is needed,
  744.         and to test that we actually downloaded what we expected.
  745.         If output is given it is a file-like object that will receive a log
  746.         of what happens.
  747.  
  748.         If anything unforeseen happened the method returns an error message
  749.         string.
  750.         '''
  751.         (scheme, loc, path, query, frag) = urlparse.urlsplit(self._dict['Download-URL'])
  752.         path = urllib.url2pathname(path)
  753.         filename = os.path.split(path)[1]
  754.         self.archiveFilename = os.path.join(self._db.preferences.downloadDir, filename)
  755.         if not self._archiveOK():
  756.             if scheme == 'manual':
  757.                 return 'Please download package manually and save as %s' % self.archiveFilename
  758.             
  759.             downloader = PimpUrllibDownloader(None, self._db.preferences.downloadDir, watcher = self._db.preferences.watcher)
  760.             if not downloader.download(self._dict['Download-URL'], self.archiveFilename, output):
  761.                 return 'download command failed'
  762.             
  763.         
  764.         if not os.path.exists(self.archiveFilename) and not NO_EXECUTE:
  765.             return 'archive not found after download'
  766.         
  767.         if not self._archiveOK():
  768.             return 'archive does not have correct MD5 checksum'
  769.         
  770.  
  771.     
  772.     def _archiveOK(self):
  773.         '''Test an archive. It should exist and the MD5 checksum should be correct.'''
  774.         if not os.path.exists(self.archiveFilename):
  775.             return 0
  776.         
  777.         if not self._dict.get('MD5Sum'):
  778.             sys.stderr.write('Warning: no MD5Sum for %s\n' % self.fullname())
  779.             return 1
  780.         
  781.         data = open(self.archiveFilename, 'rb').read()
  782.         checksum = md5.new(data).hexdigest()
  783.         return checksum == self._dict['MD5Sum']
  784.  
  785.     
  786.     def unpackPackageOnly(self, output = None):
  787.         '''Unpack a downloaded package archive.'''
  788.         filename = os.path.split(self.archiveFilename)[1]
  789.         for ext, unpackerClass, arg in ARCHIVE_FORMATS:
  790.             if filename[-len(ext):] == ext:
  791.                 break
  792.                 continue
  793.         else:
  794.             return 'unknown extension for archive file: %s' % filename
  795.         self.basename = filename[:-len(ext)]
  796.         unpacker = unpackerClass(arg, dir = self._db.preferences.buildDir, watcher = self._db.preferences.watcher)
  797.         rv = unpacker.unpack(self.archiveFilename, output = output)
  798.         if rv:
  799.             return rv
  800.         
  801.  
  802.     
  803.     def installPackageOnly(self, output = None):
  804.         '''Default install method, to be overridden by subclasses'''
  805.         return '%s: This package needs to be installed manually (no support for flavor="%s")' % (self.fullname(), self._dict.get(flavor, ''))
  806.  
  807.     
  808.     def installSinglePackage(self, output = None):
  809.         '''Download, unpack and install a single package.
  810.  
  811.         If output is given it should be a file-like object and it
  812.         will receive a log of what happened.'''
  813.         if not self._dict.get('Download-URL'):
  814.             return '%s: This package needs to be installed manually (no Download-URL field)' % self.fullname()
  815.         
  816.         msg = self.downloadPackageOnly(output)
  817.         if msg:
  818.             return '%s: download: %s' % (self.fullname(), msg)
  819.         
  820.         msg = self.unpackPackageOnly(output)
  821.         if msg:
  822.             return '%s: unpack: %s' % (self.fullname(), msg)
  823.         
  824.         return self.installPackageOnly(output)
  825.  
  826.     
  827.     def beforeInstall(self):
  828.         '''Bookkeeping before installation: remember what we have in site-packages'''
  829.         self._old_contents = os.listdir(self._db.preferences.installDir)
  830.  
  831.     
  832.     def afterInstall(self):
  833.         '''Bookkeeping after installation: interpret any new .pth files that have
  834.         appeared'''
  835.         new_contents = os.listdir(self._db.preferences.installDir)
  836.         for fn in new_contents:
  837.             if fn in self._old_contents:
  838.                 continue
  839.             
  840.             if fn[-4:] != '.pth':
  841.                 continue
  842.             
  843.             fullname = os.path.join(self._db.preferences.installDir, fn)
  844.             f = open(fullname)
  845.             for line in f.readlines():
  846.                 if not line:
  847.                     continue
  848.                 
  849.                 if line[0] == '#':
  850.                     continue
  851.                 
  852.                 if line[:6] == 'import':
  853.                     exec line
  854.                     continue
  855.                 
  856.                 if line[-1] == '\n':
  857.                     line = line[:-1]
  858.                 
  859.                 if not os.path.isabs(line):
  860.                     line = os.path.join(self._db.preferences.installDir, line)
  861.                 
  862.                 line = os.path.realpath(line)
  863.                 if line not in sys.path:
  864.                     sys.path.append(line)
  865.                     continue
  866.             
  867.         
  868.  
  869.     
  870.     def filterExpectedSkips(self, names):
  871.         '''Return a list that contains only unpexpected skips'''
  872.         if not self._db.preferences.isUserInstall():
  873.             return names
  874.         
  875.         expected_skips = self._dict.get('User-install-skips')
  876.         if not expected_skips:
  877.             return names
  878.         
  879.         newnames = []
  880.         for name in names:
  881.             for skip in expected_skips:
  882.                 if name[:len(skip)] == skip:
  883.                     break
  884.                     continue
  885.             
  886.         
  887.         return newnames
  888.  
  889.  
  890.  
  891. class PimpPackage_binary(PimpPackage):
  892.     
  893.     def unpackPackageOnly(self, output = None):
  894.         """We don't unpack binary packages until installing"""
  895.         pass
  896.  
  897.     
  898.     def installPackageOnly(self, output = None):
  899.         '''Install a single source package.
  900.  
  901.         If output is given it should be a file-like object and it
  902.         will receive a log of what happened.'''
  903.         if self._dict.has_key('Install-command'):
  904.             return '%s: Binary package cannot have Install-command' % self.fullname()
  905.         
  906.         if self._dict.has_key('Pre-install-command'):
  907.             if _cmd(output, '/tmp', self._dict['Pre-install-command']):
  908.                 return 'pre-install %s: running "%s" failed' % (self.fullname(), self._dict['Pre-install-command'])
  909.             
  910.         
  911.         self.beforeInstall()
  912.         filename = os.path.split(self.archiveFilename)[1]
  913.         for ext, unpackerClass, arg in ARCHIVE_FORMATS:
  914.             if filename[-len(ext):] == ext:
  915.                 break
  916.                 continue
  917.         else:
  918.             return '%s: unknown extension for archive file: %s' % (self.fullname(), filename)
  919.         self.basename = filename[:-len(ext)]
  920.         install_renames = []
  921.         for k, newloc in self._db.preferences.installLocations:
  922.             if not newloc:
  923.                 continue
  924.             
  925.             if k == '--install-lib':
  926.                 oldloc = DEFAULT_INSTALLDIR
  927.             else:
  928.                 return "%s: Don't know installLocation %s" % (self.fullname(), k)
  929.             install_renames.append((oldloc, newloc))
  930.         
  931.         unpacker = unpackerClass(arg, dir = '/', renames = install_renames)
  932.         rv = unpacker.unpack(self.archiveFilename, output = output, package = self)
  933.         if rv:
  934.             return rv
  935.         
  936.         self.afterInstall()
  937.         if self._dict.has_key('Post-install-command'):
  938.             if _cmd(output, '/tmp', self._dict['Post-install-command']):
  939.                 return '%s: post-install: running "%s" failed' % (self.fullname(), self._dict['Post-install-command'])
  940.             
  941.         
  942.  
  943.  
  944.  
  945. class PimpPackage_source(PimpPackage):
  946.     
  947.     def unpackPackageOnly(self, output = None):
  948.         '''Unpack a source package and check that setup.py exists'''
  949.         PimpPackage.unpackPackageOnly(self, output)
  950.         self._buildDirname = os.path.join(self._db.preferences.buildDir, self.basename)
  951.         setupname = os.path.join(self._buildDirname, 'setup.py')
  952.         if not os.path.exists(setupname) and not NO_EXECUTE:
  953.             return 'no setup.py found after unpack of archive'
  954.         
  955.  
  956.     
  957.     def installPackageOnly(self, output = None):
  958.         '''Install a single source package.
  959.  
  960.         If output is given it should be a file-like object and it
  961.         will receive a log of what happened.'''
  962.         if self._dict.has_key('Pre-install-command'):
  963.             if _cmd(output, self._buildDirname, self._dict['Pre-install-command']):
  964.                 return 'pre-install %s: running "%s" failed' % (self.fullname(), self._dict['Pre-install-command'])
  965.             
  966.         
  967.         self.beforeInstall()
  968.         installcmd = self._dict.get('Install-command')
  969.         if installcmd and self._install_renames:
  970.             return 'Package has install-command and can only be installed to standard location'
  971.         
  972.         unwanted_install_dir = None
  973.         if not installcmd:
  974.             extra_args = ''
  975.             for k, v in self._db.preferences.installLocations:
  976.                 if not v:
  977.                     if not unwanted_install_dir:
  978.                         unwanted_install_dir = tempfile.mkdtemp()
  979.                     
  980.                     v = unwanted_install_dir
  981.                 
  982.                 extra_args = extra_args + ' %s "%s"' % (k, v)
  983.             
  984.             installcmd = '"%s" setup.py install %s' % (sys.executable, extra_args)
  985.         
  986.         if _cmd(output, self._buildDirname, installcmd):
  987.             return 'install %s: running "%s" failed' % (self.fullname(), installcmd)
  988.         
  989.         if unwanted_install_dir and os.path.exists(unwanted_install_dir):
  990.             unwanted_files = os.listdir(unwanted_install_dir)
  991.             if unwanted_files:
  992.                 rv = 'Warning: some files were not installed: %s' % ' '.join(unwanted_files)
  993.             else:
  994.                 rv = None
  995.             shutil.rmtree(unwanted_install_dir)
  996.             return rv
  997.         
  998.         self.afterInstall()
  999.         if self._dict.has_key('Post-install-command'):
  1000.             if _cmd(output, self._buildDirname, self._dict['Post-install-command']):
  1001.                 return 'post-install %s: running "%s" failed' % (self.fullname(), self._dict['Post-install-command'])
  1002.             
  1003.         
  1004.  
  1005.  
  1006.  
  1007. class PimpPackage_installer(PimpPackage):
  1008.     
  1009.     def unpackPackageOnly(self, output = None):
  1010.         """We don't unpack dmg packages until installing"""
  1011.         pass
  1012.  
  1013.     
  1014.     def installPackageOnly(self, output = None):
  1015.         '''Install a single source package.
  1016.  
  1017.         If output is given it should be a file-like object and it
  1018.         will receive a log of what happened.'''
  1019.         if self._dict.has_key('Post-install-command'):
  1020.             return '%s: Installer package cannot have Post-install-command' % self.fullname()
  1021.         
  1022.         if self._dict.has_key('Pre-install-command'):
  1023.             if _cmd(output, '/tmp', self._dict['Pre-install-command']):
  1024.                 return 'pre-install %s: running "%s" failed' % (self.fullname(), self._dict['Pre-install-command'])
  1025.             
  1026.         
  1027.         self.beforeInstall()
  1028.         installcmd = self._dict.get('Install-command')
  1029.         if installcmd:
  1030.             if '%' in installcmd:
  1031.                 installcmd = installcmd % self.archiveFilename
  1032.             
  1033.         else:
  1034.             installcmd = 'open "%s"' % self.archiveFilename
  1035.         if _cmd(output, '/tmp', installcmd):
  1036.             return '%s: install command failed (use verbose for details)' % self.fullname()
  1037.         
  1038.         return '%s: downloaded and opened. Install manually and restart Package Manager' % self.archiveFilename
  1039.  
  1040.  
  1041.  
  1042. class PimpInstaller:
  1043.     '''Installer engine: computes dependencies and installs
  1044.     packages in the right order.'''
  1045.     
  1046.     def __init__(self, db):
  1047.         self._todo = []
  1048.         self._db = db
  1049.         self._curtodo = []
  1050.         self._curmessages = []
  1051.  
  1052.     
  1053.     def __contains__(self, package):
  1054.         return package in self._todo
  1055.  
  1056.     
  1057.     def _addPackages(self, packages):
  1058.         for package in packages:
  1059.             if package not in self._todo:
  1060.                 self._todo.append(package)
  1061.                 continue
  1062.         
  1063.  
  1064.     
  1065.     def _prepareInstall(self, package, force = 0, recursive = 1):
  1066.         '''Internal routine, recursive engine for prepareInstall.
  1067.  
  1068.         Test whether the package is installed and (if not installed
  1069.         or if force==1) prepend it to the temporary todo list and
  1070.         call ourselves recursively on all prerequisites.'''
  1071.         if not force:
  1072.             (status, message) = package.installed()
  1073.             if status == 'yes':
  1074.                 return None
  1075.             
  1076.         
  1077.         if package in self._todo or package in self._curtodo:
  1078.             return None
  1079.         
  1080.         self._curtodo.insert(0, package)
  1081.         if not recursive:
  1082.             return None
  1083.         
  1084.         prereqs = package.prerequisites()
  1085.         for pkg, descr in prereqs:
  1086.             if pkg:
  1087.                 self._prepareInstall(pkg, False, recursive)
  1088.                 continue
  1089.             self._curmessages.append('Problem with dependency: %s' % descr)
  1090.         
  1091.  
  1092.     
  1093.     def prepareInstall(self, package, force = 0, recursive = 1):
  1094.         '''Prepare installation of a package.
  1095.  
  1096.         If the package is already installed and force is false nothing
  1097.         is done. If recursive is true prerequisites are installed first.
  1098.  
  1099.         Returns a list of packages (to be passed to install) and a list
  1100.         of messages of any problems encountered.
  1101.         '''
  1102.         self._curtodo = []
  1103.         self._curmessages = []
  1104.         self._prepareInstall(package, force, recursive)
  1105.         rv = (self._curtodo, self._curmessages)
  1106.         self._curtodo = []
  1107.         self._curmessages = []
  1108.         return rv
  1109.  
  1110.     
  1111.     def install(self, packages, output):
  1112.         '''Install a list of packages.'''
  1113.         self._addPackages(packages)
  1114.         status = []
  1115.         for pkg in self._todo:
  1116.             msg = pkg.installSinglePackage(output)
  1117.             if msg:
  1118.                 status.append(msg)
  1119.                 continue
  1120.         
  1121.         return status
  1122.  
  1123.  
  1124.  
  1125. def _run(mode, verbose, force, args, prefargs, watcher):
  1126.     '''Engine for the main program'''
  1127.     prefs = PimpPreferences(**prefargs)
  1128.     if watcher:
  1129.         prefs.setWatcher(watcher)
  1130.     
  1131.     rv = prefs.check()
  1132.     if rv:
  1133.         sys.stdout.write(rv)
  1134.     
  1135.     db = PimpDatabase(prefs)
  1136.     db.appendURL(prefs.pimpDatabase)
  1137.     if mode == 'dump':
  1138.         db.dump(sys.stdout)
  1139.     elif mode == 'list':
  1140.         if not args:
  1141.             args = db.listnames()
  1142.         
  1143.         print '%-20.20s\t%s' % ('Package', 'Description')
  1144.         print 
  1145.         for pkgname in args:
  1146.             pkg = db.find(pkgname)
  1147.             if pkg:
  1148.                 description = pkg.shortdescription()
  1149.                 pkgname = pkg.fullname()
  1150.             else:
  1151.                 description = 'Error: no such package'
  1152.             print '%-20.20s\t%s' % (pkgname, description)
  1153.             if verbose:
  1154.                 print '\tHome page:\t', pkg.homepage()
  1155.                 
  1156.                 try:
  1157.                     print '\tDownload URL:\t', pkg.downloadURL()
  1158.                 except KeyError:
  1159.                     pass
  1160.  
  1161.                 description = pkg.description()
  1162.                 description = '\n\t\t\t\t\t'.join(description.splitlines())
  1163.                 print '\tDescription:\t%s' % description
  1164.                 continue
  1165.         
  1166.     elif mode == 'status':
  1167.         if not args:
  1168.             args = db.listnames()
  1169.             print '%-20.20s\t%s\t%s' % ('Package', 'Installed', 'Message')
  1170.             print 
  1171.         
  1172.         for pkgname in args:
  1173.             pkg = db.find(pkgname)
  1174.             if pkg:
  1175.                 (status, msg) = pkg.installed()
  1176.                 pkgname = pkg.fullname()
  1177.             else:
  1178.                 status = 'error'
  1179.                 msg = 'No such package'
  1180.             print '%-20.20s\t%-9.9s\t%s' % (pkgname, status, msg)
  1181.             if verbose and status == 'no':
  1182.                 prereq = pkg.prerequisites()
  1183.                 for pkg, msg in prereq:
  1184.                     if not pkg:
  1185.                         pkg = ''
  1186.                     else:
  1187.                         pkg = pkg.fullname()
  1188.                     print '%-20.20s\tRequirement: %s %s' % ('', pkg, msg)
  1189.                 
  1190.         
  1191.     elif mode == 'install':
  1192.         if not args:
  1193.             print 'Please specify packages to install'
  1194.             sys.exit(1)
  1195.         
  1196.         inst = PimpInstaller(db)
  1197.         for pkgname in args:
  1198.             pkg = db.find(pkgname)
  1199.             if not pkg:
  1200.                 print '%s: No such package' % pkgname
  1201.                 continue
  1202.             
  1203.             (list, messages) = inst.prepareInstall(pkg, force)
  1204.             if messages and not force:
  1205.                 print '%s: Not installed:' % pkgname
  1206.                 for m in messages:
  1207.                     print '\t', m
  1208.                 
  1209.             if verbose:
  1210.                 output = sys.stdout
  1211.             else:
  1212.                 output = None
  1213.             messages = inst.install(list, output)
  1214.             if messages:
  1215.                 print '%s: Not installed:' % pkgname
  1216.                 for m in messages:
  1217.                     print '\t', m
  1218.                 
  1219.         
  1220.     
  1221.  
  1222.  
  1223. def main():
  1224.     '''Minimal commandline tool to drive pimp.'''
  1225.     import getopt
  1226.     
  1227.     def _help():
  1228.         print 'Usage: pimp [options] -s [package ...]  List installed status'
  1229.         print '       pimp [options] -l [package ...]  Show package information'
  1230.         print '       pimp [options] -i package ...    Install packages'
  1231.         print '       pimp -d                          Dump database to stdout'
  1232.         print '       pimp -V                          Print version number'
  1233.         print 'Options:'
  1234.         print '       -v     Verbose'
  1235.         print '       -f     Force installation'
  1236.         print '       -D dir Set destination directory'
  1237.         print '              (default: %s)' % DEFAULT_INSTALLDIR
  1238.         print '       -u url URL for database'
  1239.         sys.exit(1)
  1240.  
  1241.     
  1242.     class _Watcher:
  1243.         
  1244.         def update(self, msg):
  1245.             sys.stderr.write(msg + '\r')
  1246.             return 1
  1247.  
  1248.  
  1249.     
  1250.     try:
  1251.         (opts, args) = getopt.getopt(sys.argv[1:], 'slifvdD:Vu:')
  1252.     except getopt.GetoptError:
  1253.         _help()
  1254.  
  1255.     if not opts and not args:
  1256.         _help()
  1257.     
  1258.     mode = None
  1259.     force = 0
  1260.     verbose = 0
  1261.     prefargs = { }
  1262.     watcher = None
  1263.     for o, a in opts:
  1264.         if o == '-s':
  1265.             if mode:
  1266.                 _help()
  1267.             
  1268.             mode = 'status'
  1269.         
  1270.         if o == '-l':
  1271.             if mode:
  1272.                 _help()
  1273.             
  1274.             mode = 'list'
  1275.         
  1276.         if o == '-d':
  1277.             if mode:
  1278.                 _help()
  1279.             
  1280.             mode = 'dump'
  1281.         
  1282.         if o == '-V':
  1283.             if mode:
  1284.                 _help()
  1285.             
  1286.             mode = 'version'
  1287.         
  1288.         if o == '-i':
  1289.             mode = 'install'
  1290.         
  1291.         if o == '-f':
  1292.             force = 1
  1293.         
  1294.         if o == '-v':
  1295.             verbose = 1
  1296.             watcher = _Watcher()
  1297.         
  1298.         if o == '-D':
  1299.             prefargs['installDir'] = a
  1300.         
  1301.         if o == '-u':
  1302.             prefargs['pimpDatabase'] = a
  1303.             continue
  1304.     
  1305.     if not mode:
  1306.         _help()
  1307.     
  1308.     if mode == 'version':
  1309.         print 'Pimp version %s; module name is %s' % (PIMP_VERSION, __name__)
  1310.     else:
  1311.         _run(mode, verbose, force, args, prefargs, watcher)
  1312.  
  1313. if __name__ != 'pimp_update':
  1314.     
  1315.     try:
  1316.         import pimp_update
  1317.     except ImportError:
  1318.         pass
  1319.  
  1320.     if pimp_update.PIMP_VERSION <= PIMP_VERSION:
  1321.         import warnings
  1322.         warnings.warn('pimp_update is version %s, not newer than pimp version %s' % (pimp_update.PIMP_VERSION, PIMP_VERSION))
  1323.     else:
  1324.         from pimp_update import *
  1325.  
  1326. if __name__ == '__main__':
  1327.     main()
  1328.  
  1329.