diff options
author | Benjamin Peterson <benjamin@python.org> | 2008-05-12 22:25:16 (GMT) |
---|---|---|
committer | Benjamin Peterson <benjamin@python.org> | 2008-05-12 22:25:16 (GMT) |
commit | 69a07fbd9b2c1e2d203532d4babbc6d874d389ee (patch) | |
tree | 822d06c6602339d309b30584ca0bfd05f5b56edc /Lib/plat-mac/pimp.py | |
parent | a005b34f14fd4548c84886244b68d2c34e75edbd (diff) | |
download | cpython-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.py | 1178 |
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() |