summaryrefslogtreecommitdiffstats
path: root/Lib/plat-mac/pimp.py
diff options
context:
space:
mode:
authorBenjamin Peterson <benjamin@python.org>2008-05-12 22:25:16 (GMT)
committerBenjamin Peterson <benjamin@python.org>2008-05-12 22:25:16 (GMT)
commit69a07fbd9b2c1e2d203532d4babbc6d874d389ee (patch)
tree822d06c6602339d309b30584ca0bfd05f5b56edc /Lib/plat-mac/pimp.py
parenta005b34f14fd4548c84886244b68d2c34e75edbd (diff)
downloadcpython-69a07fbd9b2c1e2d203532d4babbc6d874d389ee.zip
cpython-69a07fbd9b2c1e2d203532d4babbc6d874d389ee.tar.gz
cpython-69a07fbd9b2c1e2d203532d4babbc6d874d389ee.tar.bz2
Remove the Mac modules
Diffstat (limited to 'Lib/plat-mac/pimp.py')
-rw-r--r--Lib/plat-mac/pimp.py1178
1 files changed, 0 insertions, 1178 deletions
diff --git a/Lib/plat-mac/pimp.py b/Lib/plat-mac/pimp.py
deleted file mode 100644
index e52d0dc..0000000
--- a/Lib/plat-mac/pimp.py
+++ /dev/null
@@ -1,1178 +0,0 @@
-"""Package Install Manager for Python.
-
-This is currently a MacOSX-only strawman implementation.
-Despite other rumours the name stands for "Packman IMPlementation".
-
-Tools to allow easy installation of packages. The idea is that there is
-an online XML database per (platform, python-version) containing packages
-known to work with that combination. This module contains tools for getting
-and parsing the database, testing whether packages are installed, computing
-dependencies and installing packages.
-
-There is a minimal main program that works as a command line tool, but the
-intention is that the end user will use this through a GUI.
-"""
-import sys
-import os
-import subprocess
-import urllib
-import urllib2
-import urlparse
-import plistlib
-import distutils.util
-import distutils.sysconfig
-import hashlib
-import tarfile
-import tempfile
-import shutil
-import time
-
-__all__ = ["PimpPreferences", "PimpDatabase", "PimpPackage", "main",
- "getDefaultDatabase", "PIMP_VERSION", "main"]
-
-_scriptExc_NotInstalled = "pimp._scriptExc_NotInstalled"
-_scriptExc_OldInstalled = "pimp._scriptExc_OldInstalled"
-_scriptExc_BadInstalled = "pimp._scriptExc_BadInstalled"
-
-NO_EXECUTE=0
-
-PIMP_VERSION="0.5"
-
-# Flavors:
-# source: setup-based package
-# binary: tar (or other) archive created with setup.py bdist.
-# installer: something that can be opened
-DEFAULT_FLAVORORDER=['source', 'binary', 'installer']
-DEFAULT_DOWNLOADDIR='/tmp'
-DEFAULT_BUILDDIR='/tmp'
-DEFAULT_INSTALLDIR=distutils.sysconfig.get_python_lib()
-DEFAULT_PIMPDATABASE_FMT="http://www.python.org/packman/version-%s/%s-%s-%s-%s-%s.plist"
-
-def getDefaultDatabase(experimental=False):
- if experimental:
- status = "exp"
- else:
- status = "prod"
-
- major, minor, micro, state, extra = sys.version_info
- pyvers = '%d.%d' % (major, minor)
- if micro == 0 and state != 'final':
- pyvers = pyvers + '%s%d' % (state, extra)
-
- longplatform = distutils.util.get_platform()
- osname, release, machine = longplatform.split('-')
- # For some platforms we may want to differentiate between
- # installation types
- if osname == 'darwin':
- if sys.prefix.startswith('/System/Library/Frameworks/Python.framework'):
- osname = 'darwin_apple'
- elif sys.prefix.startswith('/Library/Frameworks/Python.framework'):
- osname = 'darwin_macpython'
- # Otherwise we don't know...
- # Now we try various URLs by playing with the release string.
- # We remove numbers off the end until we find a match.
- rel = release
- while True:
- url = DEFAULT_PIMPDATABASE_FMT % (PIMP_VERSION, status, pyvers, osname, rel, machine)
- try:
- urllib2.urlopen(url)
- except urllib2.HTTPError as arg:
- pass
- else:
- break
- if not rel:
- # We're out of version numbers to try. Use the
- # full release number, this will give a reasonable
- # error message later
- url = DEFAULT_PIMPDATABASE_FMT % (PIMP_VERSION, status, pyvers, osname, release, machine)
- break
- idx = rel.rfind('.')
- if idx < 0:
- rel = ''
- else:
- rel = rel[:idx]
- return url
-
-def _cmd(output, dir, *cmditems):
- """Internal routine to run a shell command in a given directory."""
-
- cmd = ("cd \"%s\"; " % dir) + " ".join(cmditems)
- if output:
- output.write("+ %s\n" % cmd)
- if NO_EXECUTE:
- return 0
- child = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
- stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
- child.stdin.close()
- while 1:
- line = child.stdout.readline()
- if not line:
- break
- if output:
- output.write(line)
- return child.wait()
-
-class PimpDownloader:
- """Abstract base class - Downloader for archives"""
-
- def __init__(self, argument,
- dir="",
- watcher=None):
- self.argument = argument
- self._dir = dir
- self._watcher = watcher
-
- def download(self, url, filename, output=None):
- return None
-
- def update(self, str):
- if self._watcher:
- return self._watcher.update(str)
- return True
-
-class PimpCurlDownloader(PimpDownloader):
-
- def download(self, url, filename, output=None):
- self.update("Downloading %s..." % url)
- exitstatus = _cmd(output, self._dir,
- "curl",
- "--output", filename,
- url)
- self.update("Downloading %s: finished" % url)
- return (not exitstatus)
-
-class PimpUrllibDownloader(PimpDownloader):
-
- def download(self, url, filename, output=None):
- output = open(filename, 'wb')
- self.update("Downloading %s: opening connection" % url)
- keepgoing = True
- download = urllib2.urlopen(url)
- if "content-length" in download.headers:
- length = int(download.headers['content-length'])
- else:
- length = -1
-
- data = download.read(4096) #read 4K at a time
- dlsize = 0
- lasttime = 0
- while keepgoing:
- dlsize = dlsize + len(data)
- if len(data) == 0:
- #this is our exit condition
- break
- output.write(data)
- if int(time.time()) != lasttime:
- # Update at most once per second
- lasttime = int(time.time())
- if length == -1:
- keepgoing = self.update("Downloading %s: %d bytes..." % (url, dlsize))
- else:
- keepgoing = self.update("Downloading %s: %d%% (%d bytes)..." % (url, int(100.0*dlsize/length), dlsize))
- data = download.read(4096)
- if keepgoing:
- self.update("Downloading %s: finished" % url)
- return keepgoing
-
-class PimpUnpacker:
- """Abstract base class - Unpacker for archives"""
-
- _can_rename = False
-
- def __init__(self, argument,
- dir="",
- renames=[],
- watcher=None):
- self.argument = argument
- if renames and not self._can_rename:
- raise RuntimeError("This unpacker cannot rename files")
- self._dir = dir
- self._renames = renames
- self._watcher = watcher
-
- def unpack(self, archive, output=None, package=None):
- return None
-
- def update(self, str):
- if self._watcher:
- return self._watcher.update(str)
- return True
-
-class PimpCommandUnpacker(PimpUnpacker):
- """Unpack archives by calling a Unix utility"""
-
- _can_rename = False
-
- def unpack(self, archive, output=None, package=None):
- cmd = self.argument % archive
- if _cmd(output, self._dir, cmd):
- return "unpack command failed"
-
-class PimpTarUnpacker(PimpUnpacker):
- """Unpack tarfiles using the builtin tarfile module"""
-
- _can_rename = True
-
- def unpack(self, archive, output=None, package=None):
- tf = tarfile.open(archive, "r")
- members = tf.getmembers()
- skip = []
- if self._renames:
- for member in members:
- for oldprefix, newprefix in self._renames:
- if oldprefix[:len(self._dir)] == self._dir:
- oldprefix2 = oldprefix[len(self._dir):]
- else:
- oldprefix2 = None
- if member.name[:len(oldprefix)] == oldprefix:
- if newprefix is None:
- skip.append(member)
- #print 'SKIP', member.name
- else:
- member.name = newprefix + member.name[len(oldprefix):]
- print(' ', member.name)
- break
- elif oldprefix2 and member.name[:len(oldprefix2)] == oldprefix2:
- if newprefix is None:
- skip.append(member)
- #print 'SKIP', member.name
- else:
- member.name = newprefix + member.name[len(oldprefix2):]
- #print ' ', member.name
- break
- else:
- skip.append(member)
- #print '????', member.name
- for member in members:
- if member in skip:
- self.update("Skipping %s" % member.name)
- continue
- self.update("Extracting %s" % member.name)
- tf.extract(member, self._dir)
- if skip:
- names = [member.name for member in skip if member.name[-1] != '/']
- if package:
- names = package.filterExpectedSkips(names)
- if names:
- return "Not all files were unpacked: %s" % " ".join(names)
-
-ARCHIVE_FORMATS = [
- (".tar.Z", PimpTarUnpacker, None),
- (".taz", PimpTarUnpacker, None),
- (".tar.gz", PimpTarUnpacker, None),
- (".tgz", PimpTarUnpacker, None),
- (".tar.bz", PimpTarUnpacker, None),
- (".zip", PimpCommandUnpacker, "unzip \"%s\""),
-]
-
-class PimpPreferences:
- """Container for per-user preferences, such as the database to use
- and where to install packages."""
-
- def __init__(self,
- flavorOrder=None,
- downloadDir=None,
- buildDir=None,
- installDir=None,
- pimpDatabase=None):
- if not flavorOrder:
- flavorOrder = DEFAULT_FLAVORORDER
- if not downloadDir:
- downloadDir = DEFAULT_DOWNLOADDIR
- if not buildDir:
- buildDir = DEFAULT_BUILDDIR
- if not pimpDatabase:
- pimpDatabase = getDefaultDatabase()
- self.setInstallDir(installDir)
- self.flavorOrder = flavorOrder
- self.downloadDir = downloadDir
- self.buildDir = buildDir
- self.pimpDatabase = pimpDatabase
- self.watcher = None
-
- def setWatcher(self, watcher):
- self.watcher = watcher
-
- def setInstallDir(self, installDir=None):
- if installDir:
- # Installing to non-standard location.
- self.installLocations = [
- ('--install-lib', installDir),
- ('--install-headers', None),
- ('--install-scripts', None),
- ('--install-data', None)]
- else:
- installDir = DEFAULT_INSTALLDIR
- self.installLocations = []
- self.installDir = installDir
-
- def isUserInstall(self):
- return self.installDir != DEFAULT_INSTALLDIR
-
- def check(self):
- """Check that the preferences make sense: directories exist and are
- writable, the install directory is on sys.path, etc."""
-
- rv = ""
- RWX_OK = os.R_OK|os.W_OK|os.X_OK
- if not os.path.exists(self.downloadDir):
- rv += "Warning: Download directory \"%s\" does not exist\n" % self.downloadDir
- elif not os.access(self.downloadDir, RWX_OK):
- rv += "Warning: Download directory \"%s\" is not writable or not readable\n" % self.downloadDir
- if not os.path.exists(self.buildDir):
- rv += "Warning: Build directory \"%s\" does not exist\n" % self.buildDir
- elif not os.access(self.buildDir, RWX_OK):
- rv += "Warning: Build directory \"%s\" is not writable or not readable\n" % self.buildDir
- if not os.path.exists(self.installDir):
- rv += "Warning: Install directory \"%s\" does not exist\n" % self.installDir
- elif not os.access(self.installDir, RWX_OK):
- rv += "Warning: Install directory \"%s\" is not writable or not readable\n" % self.installDir
- else:
- installDir = os.path.realpath(self.installDir)
- for p in sys.path:
- try:
- realpath = os.path.realpath(p)
- except:
- pass
- if installDir == realpath:
- break
- else:
- rv += "Warning: Install directory \"%s\" is not on sys.path\n" % self.installDir
- return rv
-
- def compareFlavors(self, left, right):
- """Compare two flavor strings. This is part of your preferences
- because whether the user prefers installing from source or binary is."""
- if left in self.flavorOrder:
- if right in self.flavorOrder:
- return cmp(self.flavorOrder.index(left), self.flavorOrder.index(right))
- return -1
- if right in self.flavorOrder:
- return 1
- return cmp(left, right)
-
-class PimpDatabase:
- """Class representing a pimp database. It can actually contain
- information from multiple databases through inclusion, but the
- toplevel database is considered the master, as its maintainer is
- "responsible" for the contents."""
-
- def __init__(self, prefs):
- self._packages = []
- self.preferences = prefs
- self._url = ""
- self._urllist = []
- self._version = ""
- self._maintainer = ""
- self._description = ""
-
- # Accessor functions
- def url(self): return self._url
- def version(self): return self._version
- def maintainer(self): return self._maintainer
- def description(self): return self._description
-
- def close(self):
- """Clean up"""
- self._packages = []
- self.preferences = None
-
- def appendURL(self, url, included=0):
- """Append packages from the database with the given URL.
- Only the first database should specify included=0, so the
- global information (maintainer, description) get stored."""
-
- if url in self._urllist:
- return
- self._urllist.append(url)
- fp = urllib2.urlopen(url).fp
- plistdata = plistlib.Plist.fromFile(fp)
- # Test here for Pimp version, etc
- if included:
- version = plistdata.get('Version')
- if version and version > self._version:
- sys.stderr.write("Warning: included database %s is for pimp version %s\n" %
- (url, version))
- else:
- self._version = plistdata.get('Version')
- if not self._version:
- sys.stderr.write("Warning: database has no Version information\n")
- elif self._version > PIMP_VERSION:
- sys.stderr.write("Warning: database version %s newer than pimp version %s\n"
- % (self._version, PIMP_VERSION))
- self._maintainer = plistdata.get('Maintainer', '')
- self._description = plistdata.get('Description', '').strip()
- self._url = url
- self._appendPackages(plistdata['Packages'], url)
- others = plistdata.get('Include', [])
- for o in others:
- o = urllib.basejoin(url, o)
- self.appendURL(o, included=1)
-
- def _appendPackages(self, packages, url):
- """Given a list of dictionaries containing package
- descriptions create the PimpPackage objects and append them
- to our internal storage."""
-
- for p in packages:
- p = dict(p)
- if 'Download-URL' in p:
- p['Download-URL'] = urllib.basejoin(url, p['Download-URL'])
- flavor = p.get('Flavor')
- if flavor == 'source':
- pkg = PimpPackage_source(self, p)
- elif flavor == 'binary':
- pkg = PimpPackage_binary(self, p)
- elif flavor == 'installer':
- pkg = PimpPackage_installer(self, p)
- elif flavor == 'hidden':
- pkg = PimpPackage_installer(self, p)
- else:
- pkg = PimpPackage(self, dict(p))
- self._packages.append(pkg)
-
- def list(self):
- """Return a list of all PimpPackage objects in the database."""
-
- return self._packages
-
- def listnames(self):
- """Return a list of names of all packages in the database."""
-
- rv = []
- for pkg in self._packages:
- rv.append(pkg.fullname())
- rv.sort()
- return rv
-
- def dump(self, pathOrFile):
- """Dump the contents of the database to an XML .plist file.
-
- The file can be passed as either a file object or a pathname.
- All data, including included databases, is dumped."""
-
- packages = []
- for pkg in self._packages:
- packages.append(pkg.dump())
- plistdata = {
- 'Version': self._version,
- 'Maintainer': self._maintainer,
- 'Description': self._description,
- 'Packages': packages
- }
- plist = plistlib.Plist(**plistdata)
- plist.write(pathOrFile)
-
- def find(self, ident):
- """Find a package. The package can be specified by name
- or as a dictionary with name, version and flavor entries.
-
- Only name is obligatory. If there are multiple matches the
- best one (higher version number, flavors ordered according to
- users' preference) is returned."""
-
- if type(ident) == str:
- # Remove ( and ) for pseudo-packages
- if ident[0] == '(' and ident[-1] == ')':
- ident = ident[1:-1]
- # Split into name-version-flavor
- fields = ident.split('-')
- if len(fields) < 1 or len(fields) > 3:
- return None
- name = fields[0]
- if len(fields) > 1:
- version = fields[1]
- else:
- version = None
- if len(fields) > 2:
- flavor = fields[2]
- else:
- flavor = None
- else:
- name = ident['Name']
- version = ident.get('Version')
- flavor = ident.get('Flavor')
- found = None
- for p in self._packages:
- if name == p.name() and \
- (not version or version == p.version()) and \
- (not flavor or flavor == p.flavor()):
- if not found or found < p:
- found = p
- return found
-
-ALLOWED_KEYS = [
- "Name",
- "Version",
- "Flavor",
- "Description",
- "Home-page",
- "Download-URL",
- "Install-test",
- "Install-command",
- "Pre-install-command",
- "Post-install-command",
- "Prerequisites",
- "MD5Sum",
- "User-install-skips",
- "Systemwide-only",
-]
-
-class PimpPackage:
- """Class representing a single package."""
-
- def __init__(self, db, plistdata):
- self._db = db
- name = plistdata["Name"]
- for k in plistdata.keys():
- if not k in ALLOWED_KEYS:
- sys.stderr.write("Warning: %s: unknown key %s\n" % (name, k))
- self._dict = plistdata
-
- def __getitem__(self, key):
- return self._dict[key]
-
- def name(self): return self._dict['Name']
- def version(self): return self._dict.get('Version')
- def flavor(self): return self._dict.get('Flavor')
- def description(self): return self._dict['Description'].strip()
- def shortdescription(self): return self.description().splitlines()[0]
- def homepage(self): return self._dict.get('Home-page')
- def downloadURL(self): return self._dict.get('Download-URL')
- def systemwideOnly(self): return self._dict.get('Systemwide-only')
-
- def fullname(self):
- """Return the full name "name-version-flavor" of a package.
-
- If the package is a pseudo-package, something that cannot be
- installed through pimp, return the name in (parentheses)."""
-
- rv = self._dict['Name']
- if 'Version' in self._dict:
- rv = rv + '-%s' % self._dict['Version']
- if 'Flavor' in self._dict:
- rv = rv + '-%s' % self._dict['Flavor']
- if self._dict.get('Flavor') == 'hidden':
- # Pseudo-package, show in parentheses
- rv = '(%s)' % rv
- return rv
-
- def dump(self):
- """Return a dict object containing the information on the package."""
- return self._dict
-
- def __cmp__(self, other):
- """Compare two packages, where the "better" package sorts lower."""
-
- if not isinstance(other, PimpPackage):
- return cmp(id(self), id(other))
- if self.name() != other.name():
- return cmp(self.name(), other.name())
- if self.version() != other.version():
- return -cmp(self.version(), other.version())
- return self._db.preferences.compareFlavors(self.flavor(), other.flavor())
-
- def installed(self):
- """Test wheter the package is installed.
-
- Returns two values: a status indicator which is one of
- "yes", "no", "old" (an older version is installed) or "bad"
- (something went wrong during the install test) and a human
- readable string which may contain more details."""
-
- namespace = {
- "NotInstalled": _scriptExc_NotInstalled,
- "OldInstalled": _scriptExc_OldInstalled,
- "BadInstalled": _scriptExc_BadInstalled,
- "os": os,
- "sys": sys,
- }
- installTest = self._dict['Install-test'].strip() + '\n'
- try:
- exec(installTest, namespace)
- except ImportError as arg:
- return "no", str(arg)
- except _scriptExc_NotInstalled as arg:
- return "no", str(arg)
- except _scriptExc_OldInstalled as arg:
- return "old", str(arg)
- except _scriptExc_BadInstalled as arg:
- return "bad", str(arg)
- except:
- sys.stderr.write("-------------------------------------\n")
- sys.stderr.write("---- %s: install test got exception\n" % self.fullname())
- sys.stderr.write("---- source:\n")
- sys.stderr.write(installTest)
- sys.stderr.write("---- exception:\n")
- import traceback
- traceback.print_exc(file=sys.stderr)
- if self._db._maintainer:
- sys.stderr.write("---- Please copy this and mail to %s\n" % self._db._maintainer)
- sys.stderr.write("-------------------------------------\n")
- return "bad", "Package install test got exception"
- return "yes", ""
-
- def prerequisites(self):
- """Return a list of prerequisites for this package.
-
- The list contains 2-tuples, of which the first item is either
- a PimpPackage object or None, and the second is a descriptive
- string. The first item can be None if this package depends on
- something that isn't pimp-installable, in which case the descriptive
- string should tell the user what to do."""
-
- rv = []
- if not self._dict.get('Download-URL'):
- # For pseudo-packages that are already installed we don't
- # return an error message
- status, _ = self.installed()
- if status == "yes":
- return []
- return [(None,
- "Package %s cannot be installed automatically, see the description" %
- self.fullname())]
- if self.systemwideOnly() and self._db.preferences.isUserInstall():
- return [(None,
- "Package %s can only be installed system-wide" %
- self.fullname())]
- if not self._dict.get('Prerequisites'):
- return []
- for item in self._dict['Prerequisites']:
- if type(item) == str:
- pkg = None
- descr = str(item)
- else:
- name = item['Name']
- if 'Version' in item:
- name = name + '-' + item['Version']
- if 'Flavor' in item:
- name = name + '-' + item['Flavor']
- pkg = self._db.find(name)
- if not pkg:
- descr = "Requires unknown %s"%name
- else:
- descr = pkg.shortdescription()
- rv.append((pkg, descr))
- return rv
-
-
- def downloadPackageOnly(self, output=None):
- """Download a single package, if needed.
-
- An MD5 signature is used to determine whether download is needed,
- and to test that we actually downloaded what we expected.
- If output is given it is a file-like object that will receive a log
- of what happens.
-
- If anything unforeseen happened the method returns an error message
- string.
- """
-
- scheme, loc, path, query, frag = urlparse.urlsplit(self._dict['Download-URL'])
- path = urllib.url2pathname(path)
- filename = os.path.split(path)[1]
- self.archiveFilename = os.path.join(self._db.preferences.downloadDir, filename)
- if not self._archiveOK():
- if scheme == 'manual':
- return "Please download package manually and save as %s" % self.archiveFilename
- downloader = PimpUrllibDownloader(None, self._db.preferences.downloadDir,
- watcher=self._db.preferences.watcher)
- if not downloader.download(self._dict['Download-URL'],
- self.archiveFilename, output):
- return "download command failed"
- if not os.path.exists(self.archiveFilename) and not NO_EXECUTE:
- return "archive not found after download"
- if not self._archiveOK():
- return "archive does not have correct MD5 checksum"
-
- def _archiveOK(self):
- """Test an archive. It should exist and the MD5 checksum should be correct."""
-
- if not os.path.exists(self.archiveFilename):
- return 0
- if not self._dict.get('MD5Sum'):
- sys.stderr.write("Warning: no MD5Sum for %s\n" % self.fullname())
- return 1
- data = open(self.archiveFilename, 'rb').read()
- checksum = hashlib.md5(data).hexdigest()
- return checksum == self._dict['MD5Sum']
-
- def unpackPackageOnly(self, output=None):
- """Unpack a downloaded package archive."""
-
- filename = os.path.split(self.archiveFilename)[1]
- for ext, unpackerClass, arg in ARCHIVE_FORMATS:
- if filename[-len(ext):] == ext:
- break
- else:
- return "unknown extension for archive file: %s" % filename
- self.basename = filename[:-len(ext)]
- unpacker = unpackerClass(arg, dir=self._db.preferences.buildDir,
- watcher=self._db.preferences.watcher)
- rv = unpacker.unpack(self.archiveFilename, output=output)
- if rv:
- return rv
-
- def installPackageOnly(self, output=None):
- """Default install method, to be overridden by subclasses"""
- return "%s: This package needs to be installed manually (no support for flavor=\"%s\")" \
- % (self.fullname(), self._dict.get(flavor, ""))
-
- def installSinglePackage(self, output=None):
- """Download, unpack and install a single package.
-
- If output is given it should be a file-like object and it
- will receive a log of what happened."""
-
- if not self._dict.get('Download-URL'):
- return "%s: This package needs to be installed manually (no Download-URL field)" % self.fullname()
- msg = self.downloadPackageOnly(output)
- if msg:
- return "%s: download: %s" % (self.fullname(), msg)
-
- msg = self.unpackPackageOnly(output)
- if msg:
- return "%s: unpack: %s" % (self.fullname(), msg)
-
- return self.installPackageOnly(output)
-
- def beforeInstall(self):
- """Bookkeeping before installation: remember what we have in site-packages"""
- self._old_contents = os.listdir(self._db.preferences.installDir)
-
- def afterInstall(self):
- """Bookkeeping after installation: interpret any new .pth files that have
- appeared"""
-
- new_contents = os.listdir(self._db.preferences.installDir)
- for fn in new_contents:
- if fn in self._old_contents:
- continue
- if fn[-4:] != '.pth':
- continue
- fullname = os.path.join(self._db.preferences.installDir, fn)
- f = open(fullname)
- for line in f.readlines():
- if not line:
- continue
- if line[0] == '#':
- continue
- if line[:6] == 'import':
- exec(line)
- continue
- if line[-1] == '\n':
- line = line[:-1]
- if not os.path.isabs(line):
- line = os.path.join(self._db.preferences.installDir, line)
- line = os.path.realpath(line)
- if not line in sys.path:
- sys.path.append(line)
-
- def filterExpectedSkips(self, names):
- """Return a list that contains only unpexpected skips"""
- if not self._db.preferences.isUserInstall():
- return names
- expected_skips = self._dict.get('User-install-skips')
- if not expected_skips:
- return names
- newnames = []
- for name in names:
- for skip in expected_skips:
- if name[:len(skip)] == skip:
- break
- else:
- newnames.append(name)
- return newnames
-
-class PimpPackage_binary(PimpPackage):
-
- def unpackPackageOnly(self, output=None):
- """We don't unpack binary packages until installing"""
- pass
-
- def installPackageOnly(self, output=None):
- """Install a single source package.
-
- If output is given it should be a file-like object and it
- will receive a log of what happened."""
-
- if 'Install-command' in self._dict:
- return "%s: Binary package cannot have Install-command" % self.fullname()
-
- if 'Pre-install-command' in self._dict:
- if _cmd(output, '/tmp', self._dict['Pre-install-command']):
- return "pre-install %s: running \"%s\" failed" % \
- (self.fullname(), self._dict['Pre-install-command'])
-
- self.beforeInstall()
-
- # Install by unpacking
- filename = os.path.split(self.archiveFilename)[1]
- for ext, unpackerClass, arg in ARCHIVE_FORMATS:
- if filename[-len(ext):] == ext:
- break
- else:
- return "%s: unknown extension for archive file: %s" % (self.fullname(), filename)
- self.basename = filename[:-len(ext)]
-
- install_renames = []
- for k, newloc in self._db.preferences.installLocations:
- if not newloc:
- continue
- if k == "--install-lib":
- oldloc = DEFAULT_INSTALLDIR
- else:
- return "%s: Don't know installLocation %s" % (self.fullname(), k)
- install_renames.append((oldloc, newloc))
-
- unpacker = unpackerClass(arg, dir="/", renames=install_renames)
- rv = unpacker.unpack(self.archiveFilename, output=output, package=self)
- if rv:
- return rv
-
- self.afterInstall()
-
- if 'Post-install-command' in self._dict:
- if _cmd(output, '/tmp', self._dict['Post-install-command']):
- return "%s: post-install: running \"%s\" failed" % \
- (self.fullname(), self._dict['Post-install-command'])
-
- return None
-
-
-class PimpPackage_source(PimpPackage):
-
- def unpackPackageOnly(self, output=None):
- """Unpack a source package and check that setup.py exists"""
- PimpPackage.unpackPackageOnly(self, output)
- # Test that a setup script has been create
- self._buildDirname = os.path.join(self._db.preferences.buildDir, self.basename)
- setupname = os.path.join(self._buildDirname, "setup.py")
- if not os.path.exists(setupname) and not NO_EXECUTE:
- return "no setup.py found after unpack of archive"
-
- def installPackageOnly(self, output=None):
- """Install a single source package.
-
- If output is given it should be a file-like object and it
- will receive a log of what happened."""
-
- if 'Pre-install-command' in self._dict:
- if _cmd(output, self._buildDirname, self._dict['Pre-install-command']):
- return "pre-install %s: running \"%s\" failed" % \
- (self.fullname(), self._dict['Pre-install-command'])
-
- self.beforeInstall()
- installcmd = self._dict.get('Install-command')
- if installcmd and self._install_renames:
- return "Package has install-command and can only be installed to standard location"
- # This is the "bit-bucket" for installations: everything we don't
- # want. After installation we check that it is actually empty
- unwanted_install_dir = None
- if not installcmd:
- extra_args = ""
- for k, v in self._db.preferences.installLocations:
- if not v:
- # We don't want these files installed. Send them
- # to the bit-bucket.
- if not unwanted_install_dir:
- unwanted_install_dir = tempfile.mkdtemp()
- v = unwanted_install_dir
- extra_args = extra_args + " %s \"%s\"" % (k, v)
- installcmd = '"%s" setup.py install %s' % (sys.executable, extra_args)
- if _cmd(output, self._buildDirname, installcmd):
- return "install %s: running \"%s\" failed" % \
- (self.fullname(), installcmd)
- if unwanted_install_dir and os.path.exists(unwanted_install_dir):
- unwanted_files = os.listdir(unwanted_install_dir)
- if unwanted_files:
- rv = "Warning: some files were not installed: %s" % " ".join(unwanted_files)
- else:
- rv = None
- shutil.rmtree(unwanted_install_dir)
- return rv
-
- self.afterInstall()
-
- if 'Post-install-command' in self._dict:
- if _cmd(output, self._buildDirname, self._dict['Post-install-command']):
- return "post-install %s: running \"%s\" failed" % \
- (self.fullname(), self._dict['Post-install-command'])
- return None
-
-class PimpPackage_installer(PimpPackage):
-
- def unpackPackageOnly(self, output=None):
- """We don't unpack dmg packages until installing"""
- pass
-
- def installPackageOnly(self, output=None):
- """Install a single source package.
-
- If output is given it should be a file-like object and it
- will receive a log of what happened."""
-
- if 'Post-install-command' in self._dict:
- return "%s: Installer package cannot have Post-install-command" % self.fullname()
-
- if 'Pre-install-command' in self._dict:
- if _cmd(output, '/tmp', self._dict['Pre-install-command']):
- return "pre-install %s: running \"%s\" failed" % \
- (self.fullname(), self._dict['Pre-install-command'])
-
- self.beforeInstall()
-
- installcmd = self._dict.get('Install-command')
- if installcmd:
- if '%' in installcmd:
- installcmd = installcmd % self.archiveFilename
- else:
- installcmd = 'open \"%s\"' % self.archiveFilename
- if _cmd(output, "/tmp", installcmd):
- return '%s: install command failed (use verbose for details)' % self.fullname()
- return '%s: downloaded and opened. Install manually and restart Package Manager' % self.archiveFilename
-
-class PimpInstaller:
- """Installer engine: computes dependencies and installs
- packages in the right order."""
-
- def __init__(self, db):
- self._todo = []
- self._db = db
- self._curtodo = []
- self._curmessages = []
-
- def __contains__(self, package):
- return package in self._todo
-
- def _addPackages(self, packages):
- for package in packages:
- if not package in self._todo:
- self._todo.append(package)
-
- def _prepareInstall(self, package, force=0, recursive=1):
- """Internal routine, recursive engine for prepareInstall.
-
- Test whether the package is installed and (if not installed
- or if force==1) prepend it to the temporary todo list and
- call ourselves recursively on all prerequisites."""
-
- if not force:
- status, message = package.installed()
- if status == "yes":
- return
- if package in self._todo or package in self._curtodo:
- return
- self._curtodo.insert(0, package)
- if not recursive:
- return
- prereqs = package.prerequisites()
- for pkg, descr in prereqs:
- if pkg:
- self._prepareInstall(pkg, False, recursive)
- else:
- self._curmessages.append("Problem with dependency: %s" % descr)
-
- def prepareInstall(self, package, force=0, recursive=1):
- """Prepare installation of a package.
-
- If the package is already installed and force is false nothing
- is done. If recursive is true prerequisites are installed first.
-
- Returns a list of packages (to be passed to install) and a list
- of messages of any problems encountered.
- """
-
- self._curtodo = []
- self._curmessages = []
- self._prepareInstall(package, force, recursive)
- rv = self._curtodo, self._curmessages
- self._curtodo = []
- self._curmessages = []
- return rv
-
- def install(self, packages, output):
- """Install a list of packages."""
-
- self._addPackages(packages)
- status = []
- for pkg in self._todo:
- msg = pkg.installSinglePackage(output)
- if msg:
- status.append(msg)
- return status
-
-
-
-def _run(mode, verbose, force, args, prefargs, watcher):
- """Engine for the main program"""
-
- prefs = PimpPreferences(**prefargs)
- if watcher:
- prefs.setWatcher(watcher)
- rv = prefs.check()
- if rv:
- sys.stdout.write(rv)
- db = PimpDatabase(prefs)
- db.appendURL(prefs.pimpDatabase)
-
- if mode == 'dump':
- db.dump(sys.stdout)
- elif mode =='list':
- if not args:
- args = db.listnames()
- print("%-20.20s\t%s" % ("Package", "Description"))
- print()
- for pkgname in args:
- pkg = db.find(pkgname)
- if pkg:
- description = pkg.shortdescription()
- pkgname = pkg.fullname()
- else:
- description = 'Error: no such package'
- print("%-20.20s\t%s" % (pkgname, description))
- if verbose:
- print("\tHome page:\t", pkg.homepage())
- try:
- print("\tDownload URL:\t", pkg.downloadURL())
- except KeyError:
- pass
- description = pkg.description()
- description = '\n\t\t\t\t\t'.join(description.splitlines())
- print("\tDescription:\t%s" % description)
- elif mode =='status':
- if not args:
- args = db.listnames()
- print("%-20.20s\t%s\t%s" % ("Package", "Installed", "Message"))
- print()
- for pkgname in args:
- pkg = db.find(pkgname)
- if pkg:
- status, msg = pkg.installed()
- pkgname = pkg.fullname()
- else:
- status = 'error'
- msg = 'No such package'
- print("%-20.20s\t%-9.9s\t%s" % (pkgname, status, msg))
- if verbose and status == "no":
- prereq = pkg.prerequisites()
- for pkg, msg in prereq:
- if not pkg:
- pkg = ''
- else:
- pkg = pkg.fullname()
- print("%-20.20s\tRequirement: %s %s" % ("", pkg, msg))
- elif mode == 'install':
- if not args:
- print('Please specify packages to install')
- sys.exit(1)
- inst = PimpInstaller(db)
- for pkgname in args:
- pkg = db.find(pkgname)
- if not pkg:
- print('%s: No such package' % pkgname)
- continue
- list, messages = inst.prepareInstall(pkg, force)
- if messages and not force:
- print("%s: Not installed:" % pkgname)
- for m in messages:
- print("\t", m)
- else:
- if verbose:
- output = sys.stdout
- else:
- output = None
- messages = inst.install(list, output)
- if messages:
- print("%s: Not installed:" % pkgname)
- for m in messages:
- print("\t", m)
-
-def main():
- """Minimal commandline tool to drive pimp."""
-
- import getopt
- def _help():
- print("Usage: pimp [options] -s [package ...] List installed status")
- print(" pimp [options] -l [package ...] Show package information")
- print(" pimp [options] -i package ... Install packages")
- print(" pimp -d Dump database to stdout")
- print(" pimp -V Print version number")
- print("Options:")
- print(" -v Verbose")
- print(" -f Force installation")
- print(" -D dir Set destination directory")
- print(" (default: %s)" % DEFAULT_INSTALLDIR)
- print(" -u url URL for database")
- sys.exit(1)
-
- class _Watcher:
- def update(self, msg):
- sys.stderr.write(msg + '\r')
- return 1
-
- try:
- opts, args = getopt.getopt(sys.argv[1:], "slifvdD:Vu:")
- except getopt.GetoptError:
- _help()
- if not opts and not args:
- _help()
- mode = None
- force = 0
- verbose = 0
- prefargs = {}
- watcher = None
- for o, a in opts:
- if o == '-s':
- if mode:
- _help()
- mode = 'status'
- if o == '-l':
- if mode:
- _help()
- mode = 'list'
- if o == '-d':
- if mode:
- _help()
- mode = 'dump'
- if o == '-V':
- if mode:
- _help()
- mode = 'version'
- if o == '-i':
- mode = 'install'
- if o == '-f':
- force = 1
- if o == '-v':
- verbose = 1
- watcher = _Watcher()
- if o == '-D':
- prefargs['installDir'] = a
- if o == '-u':
- prefargs['pimpDatabase'] = a
- if not mode:
- _help()
- if mode == 'version':
- print('Pimp version %s; module name is %s' % (PIMP_VERSION, __name__))
- else:
- _run(mode, verbose, force, args, prefargs, watcher)
-
-# Finally, try to update ourselves to a newer version.
-# If the end-user updates pimp through pimp the new version
-# will be called pimp_update and live in site-packages
-# or somewhere similar
-if __name__ != 'pimp_update':
- try:
- import pimp_update
- except ImportError:
- pass
- else:
- if pimp_update.PIMP_VERSION <= PIMP_VERSION:
- import warnings
- warnings.warn("pimp_update is version %s, not newer than pimp version %s" %
- (pimp_update.PIMP_VERSION, PIMP_VERSION))
- else:
- from pimp_update import *
-
-if __name__ == '__main__':
- main()