aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Lib/plat-mac/pimp.py249
1 files changed, 187 insertions, 62 deletions
diff --git a/Lib/plat-mac/pimp.py b/Lib/plat-mac/pimp.py
index 786f40a43f5..89989901cc9 100644
--- a/Lib/plat-mac/pimp.py
+++ b/Lib/plat-mac/pimp.py
@@ -22,6 +22,9 @@ import plistlib
import distutils.util
import distutils.sysconfig
import md5
+import tarfile
+import tempfile
+import shutil
__all__ = ["PimpPreferences", "PimpDatabase", "PimpPackage", "main"]
@@ -42,13 +45,101 @@ DEFAULT_BUILDDIR='/tmp'
DEFAULT_INSTALLDIR=distutils.sysconfig.get_python_lib()
DEFAULT_PIMPDATABASE="http://www.cwi.nl/~jack/pimp/pimp-%s.plist" % distutils.util.get_platform()
+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 = popen2.Popen4(cmd)
+ child.tochild.close()
+ while 1:
+ line = child.fromchild.readline()
+ if not line:
+ break
+ if output:
+ output.write(line)
+ return child.wait()
+
+class PimpUnpacker:
+ """Abstract base class - Unpacker for archives"""
+
+ _can_rename = False
+
+ def __init__(self, argument,
+ dir="",
+ renames=[]):
+ self.argument = argument
+ if renames and not self._can_rename:
+ raise RuntimeError, "This unpacker cannot rename files"
+ self._dir = dir
+ self._renames = renames
+
+ def unpack(self, archive, output=None):
+ return None
+
+class PimpCommandUnpacker(PimpUnpacker):
+ """Unpack archives by calling a Unix utility"""
+
+ _can_rename = False
+
+ def unpack(self, archive, output=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):
+ 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:
+ continue
+ tf.extract(member, self._dir)
+ if skip:
+ names = [member.name for member in skip if member.name[-1] != '/']
+ return "Not all files were unpacked: %s" % " ".join(names)
+
ARCHIVE_FORMATS = [
- (".tar.Z", "zcat \"%s\" | tar -xf -"),
- (".taz", "zcat \"%s\" | tar -xf -"),
- (".tar.gz", "zcat \"%s\" | tar -xf -"),
- (".tgz", "zcat \"%s\" | tar -xf -"),
- (".tar.bz", "bzcat \"%s\" | tar -xf -"),
- (".zip", "unzip \"%s\""),
+ (".tar.Z", PimpTarUnpacker, None),
+ (".taz", PimpTarUnpacker, None),
+ (".tar.gz", PimpTarUnpacker, None),
+ (".tgz", PimpTarUnpacker, None),
+ (".tar.bz", PimpTarUnpacker, None),
+ (".zip", PimpCommandUnpacker, "unzip \"%s\""),
]
class PimpPreferences:
@@ -67,10 +158,18 @@ class PimpPreferences:
downloadDir = DEFAULT_DOWNLOADDIR
if not buildDir:
buildDir = DEFAULT_BUILDDIR
- if not installDir:
- installDir = DEFAULT_INSTALLDIR
if not pimpDatabase:
pimpDatabase = DEFAULT_PIMPDATABASE
+ 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.flavorOrder = flavorOrder
self.downloadDir = downloadDir
self.buildDir = buildDir
@@ -106,7 +205,7 @@ class PimpPreferences:
break
else:
rv += "Warning: Install directory \"%s\" is not on sys.path\n" % self.installDir
- return rv
+ return rv
def compareFlavors(self, left, right):
"""Compare two flavor strings. This is part of your preferences
@@ -388,23 +487,6 @@ class PimpPackage:
rv.append((pkg, descr))
return rv
- def _cmd(self, 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 = popen2.Popen4(cmd)
- child.tochild.close()
- while 1:
- line = child.fromchild.readline()
- if not line:
- break
- if output:
- output.write(line)
- return child.wait()
def downloadPackageOnly(self, output=None):
"""Download a single package, if needed.
@@ -425,7 +507,7 @@ class PimpPackage:
if not self._archiveOK():
if scheme == 'manual':
return "Please download package manually and save as %s" % self.archiveFilename
- if self._cmd(output, self._db.preferences.downloadDir,
+ if _cmd(output, self._db.preferences.downloadDir,
"curl",
"--output", self.archiveFilename,
self._dict['Download-URL']):
@@ -451,15 +533,16 @@ class PimpPackage:
"""Unpack a downloaded package archive."""
filename = os.path.split(self.archiveFilename)[1]
- for ext, cmd in ARCHIVE_FORMATS:
+ 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)]
- cmd = cmd % self.archiveFilename
- if self._cmd(output, self._db.preferences.buildDir, cmd):
- return "unpack command failed"
+ unpacker = unpackerClass(arg, dir=self._db.preferences.buildDir)
+ rv = unpacker.unpack(self.archiveFilename, output=output)
+ if rv:
+ return rv
def installPackageOnly(self, output=None):
"""Default install method, to be overridden by subclasses"""
@@ -527,37 +610,48 @@ class PimpPackage_binary(PimpPackage):
If output is given it should be a file-like object and it
will receive a log of what happened."""
- print 'PimpPackage_binary installPackageOnly'
- msgs = []
- if self._dict.has_key('Pre-install-command'):
- msg.append("%s: Pre-install-command ignored" % self.fullname())
if self._dict.has_key('Install-command'):
- msgs.append("%s: Install-command ignored" % self.fullname())
- if self._dict.has_key('Post-install-command'):
- msgs.append("%s: Post-install-command ignored" % self.fullname())
+ return "%s: Binary package cannot have Install-command" % self.fullname()
+
+ if self._dict.has_key('Pre-install-command'):
+ 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()
# Install by unpacking
filename = os.path.split(self.archiveFilename)[1]
- for ext, cmd in ARCHIVE_FORMATS:
+ for ext, unpackerClass, arg in ARCHIVE_FORMATS:
if filename[-len(ext):] == ext:
break
else:
- return "unknown extension for archive file: %s" % filename
+ return "%s: unknown extension for archive file: %s" % (self.fullname(), filename)
+ self.basename = filename[:-len(ext)]
- # Extract the files in the root folder.
- cmd = cmd % self.archiveFilename
- if self._cmd(output, "/", cmd):
- return "unpack command failed"
+ 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)
+ if rv:
+ return rv
self.afterInstall()
if self._dict.has_key('Post-install-command'):
- if self._cmd(output, self._buildDirname, self._dict['Post-install-command']):
- return "post-install %s: running \"%s\" failed" % \
+ if _cmd(output, self._buildDirname, self._dict['Post-install-command']):
+ return "%s: post-install: running \"%s\" failed" % \
(self.fullname(), self._dict['Post-install-command'])
+
return None
@@ -579,22 +673,44 @@ class PimpPackage_source(PimpPackage):
will receive a log of what happened."""
if self._dict.has_key('Pre-install-command'):
- if self._cmd(output, self._buildDirname, self._dict['Pre-install-command']):
+ 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:
- installcmd = '"%s" setup.py install' % sys.executable
- if self._cmd(output, self._buildDirname, 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 self._dict.has_key('Post-install-command'):
- if self._cmd(output, self._buildDirname, self._dict['Post-install-command']):
+ 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
@@ -672,11 +788,13 @@ class PimpInstaller:
-def _run(mode, verbose, force, args):
+def _run(mode, verbose, force, args, prefargs):
"""Engine for the main program"""
- prefs = PimpPreferences()
- prefs.check()
+ prefs = PimpPreferences(**prefargs)
+ rv = prefs.check()
+ if rv:
+ sys.stdout.write(rv)
db = PimpDatabase(prefs)
db.appendURL(prefs.pimpDatabase)
@@ -697,7 +815,10 @@ def _run(mode, verbose, force, args):
print "%-20.20s\t%s" % (pkgname, description)
if verbose:
print "\tHome page:\t", pkg.homepage()
- print "\tDownload URL:\t", pkg.downloadURL()
+ try:
+ print "\tDownload URL:\t", pkg.downloadURL()
+ except KeyError:
+ pass
elif mode =='status':
if not args:
args = db.listnames()
@@ -751,17 +872,18 @@ def main():
import getopt
def _help():
- print "Usage: pimp [-v] -s [package ...] List installed status"
- print " pimp [-v] -l [package ...] Show package information"
- print " pimp [-vf] -i package ... Install packages"
- print " pimp -d Dump database to stdout"
+ 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 "Options:"
- print " -v Verbose"
- print " -f Force installation"
+ print " -v Verbose"
+ print " -f Force installation"
+ print " -D dir Set destination directory (default: site-packages)"
sys.exit(1)
try:
- opts, args = getopt.getopt(sys.argv[1:], "slifvd")
+ opts, args = getopt.getopt(sys.argv[1:], "slifvdD:")
except getopt.Error:
_help()
if not opts and not args:
@@ -769,6 +891,7 @@ def main():
mode = None
force = 0
verbose = 0
+ prefargs = {}
for o, a in opts:
if o == '-s':
if mode:
@@ -788,9 +911,11 @@ def main():
force = 1
if o == '-v':
verbose = 1
+ if o == '-D':
+ prefargs['installDir'] = a
if not mode:
_help()
- _run(mode, verbose, force, args)
+ _run(mode, verbose, force, args, prefargs)
if __name__ == '__main__':
main()