- Use the tarfile module to unpack tarfiles.

- Allow setting the destination install directory. If this is set then
  it is used for the modules, other items (header files, etc) are not
  installed, and warnings are printed if the package would have liked to.

Unfortunaltey binary installs seem broken due to a tarfile bug (#721871)
or my misunderstanding of how tarfile works.
This commit is contained in:
Jack Jansen 2003-04-15 14:43:05 +00:00
parent 299b3dffd2
commit 6fde1cef4a
1 changed files with 187 additions and 62 deletions

View File

@ -22,6 +22,9 @@ import plistlib
import distutils.util import distutils.util
import distutils.sysconfig import distutils.sysconfig
import md5 import md5
import tarfile
import tempfile
import shutil
__all__ = ["PimpPreferences", "PimpDatabase", "PimpPackage", "main"] __all__ = ["PimpPreferences", "PimpDatabase", "PimpPackage", "main"]
@ -42,13 +45,101 @@ DEFAULT_BUILDDIR='/tmp'
DEFAULT_INSTALLDIR=distutils.sysconfig.get_python_lib() DEFAULT_INSTALLDIR=distutils.sysconfig.get_python_lib()
DEFAULT_PIMPDATABASE="http://www.cwi.nl/~jack/pimp/pimp-%s.plist" % distutils.util.get_platform() 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 = [ ARCHIVE_FORMATS = [
(".tar.Z", "zcat \"%s\" | tar -xf -"), (".tar.Z", PimpTarUnpacker, None),
(".taz", "zcat \"%s\" | tar -xf -"), (".taz", PimpTarUnpacker, None),
(".tar.gz", "zcat \"%s\" | tar -xf -"), (".tar.gz", PimpTarUnpacker, None),
(".tgz", "zcat \"%s\" | tar -xf -"), (".tgz", PimpTarUnpacker, None),
(".tar.bz", "bzcat \"%s\" | tar -xf -"), (".tar.bz", PimpTarUnpacker, None),
(".zip", "unzip \"%s\""), (".zip", PimpCommandUnpacker, "unzip \"%s\""),
] ]
class PimpPreferences: class PimpPreferences:
@ -67,10 +158,18 @@ class PimpPreferences:
downloadDir = DEFAULT_DOWNLOADDIR downloadDir = DEFAULT_DOWNLOADDIR
if not buildDir: if not buildDir:
buildDir = DEFAULT_BUILDDIR buildDir = DEFAULT_BUILDDIR
if not installDir:
installDir = DEFAULT_INSTALLDIR
if not pimpDatabase: if not pimpDatabase:
pimpDatabase = DEFAULT_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.flavorOrder = flavorOrder
self.downloadDir = downloadDir self.downloadDir = downloadDir
self.buildDir = buildDir self.buildDir = buildDir
@ -388,23 +487,6 @@ class PimpPackage:
rv.append((pkg, descr)) rv.append((pkg, descr))
return rv 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): def downloadPackageOnly(self, output=None):
"""Download a single package, if needed. """Download a single package, if needed.
@ -425,7 +507,7 @@ class PimpPackage:
if not self._archiveOK(): if not self._archiveOK():
if scheme == 'manual': if scheme == 'manual':
return "Please download package manually and save as %s" % self.archiveFilename 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", "curl",
"--output", self.archiveFilename, "--output", self.archiveFilename,
self._dict['Download-URL']): self._dict['Download-URL']):
@ -451,15 +533,16 @@ class PimpPackage:
"""Unpack a downloaded package archive.""" """Unpack a downloaded package archive."""
filename = os.path.split(self.archiveFilename)[1] 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: if filename[-len(ext):] == ext:
break break
else: else:
return "unknown extension for archive file: %s" % filename return "unknown extension for archive file: %s" % filename
self.basename = filename[:-len(ext)] self.basename = filename[:-len(ext)]
cmd = cmd % self.archiveFilename unpacker = unpackerClass(arg, dir=self._db.preferences.buildDir)
if self._cmd(output, self._db.preferences.buildDir, cmd): rv = unpacker.unpack(self.archiveFilename, output=output)
return "unpack command failed" if rv:
return rv
def installPackageOnly(self, output=None): def installPackageOnly(self, output=None):
"""Default install method, to be overridden by subclasses""" """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 If output is given it should be a file-like object and it
will receive a log of what happened.""" 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'): if self._dict.has_key('Install-command'):
msgs.append("%s: Install-command ignored" % self.fullname()) return "%s: Binary package cannot have Install-command" % self.fullname()
if self._dict.has_key('Post-install-command'):
msgs.append("%s: Post-install-command ignored" % 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() self.beforeInstall()
# Install by unpacking # Install by unpacking
filename = os.path.split(self.archiveFilename)[1] 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: if filename[-len(ext):] == ext:
break break
else: 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. install_renames = []
cmd = cmd % self.archiveFilename for k, newloc in self._db.preferences.installLocations:
if self._cmd(output, "/", cmd): if not newloc:
return "unpack command failed" 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() self.afterInstall()
if self._dict.has_key('Post-install-command'): 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" % \ return "%s: post-install: running \"%s\" failed" % \
(self.fullname(), self._dict['Post-install-command']) (self.fullname(), self._dict['Post-install-command'])
return None return None
@ -579,22 +673,44 @@ class PimpPackage_source(PimpPackage):
will receive a log of what happened.""" will receive a log of what happened."""
if self._dict.has_key('Pre-install-command'): 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" % \ return "pre-install %s: running \"%s\" failed" % \
(self.fullname(), self._dict['Pre-install-command']) (self.fullname(), self._dict['Pre-install-command'])
self.beforeInstall() self.beforeInstall()
installcmd = self._dict.get('Install-command') 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: if not installcmd:
installcmd = '"%s" setup.py install' % sys.executable extra_args = ""
if self._cmd(output, self._buildDirname, installcmd): 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" % \ return "install %s: running \"%s\" failed" % \
(self.fullname(), installcmd) (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() self.afterInstall()
if self._dict.has_key('Post-install-command'): 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" % \ return "post-install %s: running \"%s\" failed" % \
(self.fullname(), self._dict['Post-install-command']) (self.fullname(), self._dict['Post-install-command'])
return None 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""" """Engine for the main program"""
prefs = PimpPreferences() prefs = PimpPreferences(**prefargs)
prefs.check() rv = prefs.check()
if rv:
sys.stdout.write(rv)
db = PimpDatabase(prefs) db = PimpDatabase(prefs)
db.appendURL(prefs.pimpDatabase) db.appendURL(prefs.pimpDatabase)
@ -697,7 +815,10 @@ def _run(mode, verbose, force, args):
print "%-20.20s\t%s" % (pkgname, description) print "%-20.20s\t%s" % (pkgname, description)
if verbose: if verbose:
print "\tHome page:\t", pkg.homepage() print "\tHome page:\t", pkg.homepage()
try:
print "\tDownload URL:\t", pkg.downloadURL() print "\tDownload URL:\t", pkg.downloadURL()
except KeyError:
pass
elif mode =='status': elif mode =='status':
if not args: if not args:
args = db.listnames() args = db.listnames()
@ -751,17 +872,18 @@ def main():
import getopt import getopt
def _help(): def _help():
print "Usage: pimp [-v] -s [package ...] List installed status" print "Usage: pimp [options] -s [package ...] List installed status"
print " pimp [-v] -l [package ...] Show package information" print " pimp [options] -l [package ...] Show package information"
print " pimp [-vf] -i package ... Install packages" print " pimp [options] -i package ... Install packages"
print " pimp -d Dump database to stdout" print " pimp -d Dump database to stdout"
print "Options:" print "Options:"
print " -v Verbose" print " -v Verbose"
print " -f Force installation" print " -f Force installation"
print " -D dir Set destination directory (default: site-packages)"
sys.exit(1) sys.exit(1)
try: try:
opts, args = getopt.getopt(sys.argv[1:], "slifvd") opts, args = getopt.getopt(sys.argv[1:], "slifvdD:")
except getopt.Error: except getopt.Error:
_help() _help()
if not opts and not args: if not opts and not args:
@ -769,6 +891,7 @@ def main():
mode = None mode = None
force = 0 force = 0
verbose = 0 verbose = 0
prefargs = {}
for o, a in opts: for o, a in opts:
if o == '-s': if o == '-s':
if mode: if mode:
@ -788,9 +911,11 @@ def main():
force = 1 force = 1
if o == '-v': if o == '-v':
verbose = 1 verbose = 1
if o == '-D':
prefargs['installDir'] = a
if not mode: if not mode:
_help() _help()
_run(mode, verbose, force, args) _run(mode, verbose, force, args, prefargs)
if __name__ == '__main__': if __name__ == '__main__':
main() main()