- 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:
parent
299b3dffd2
commit
6fde1cef4a
|
@ -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()
|
||||||
|
|
Loading…
Reference in New Issue