You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							257 lines
						
					
					
						
							8.4 KiB
						
					
					
				
			
		
		
	
	
							257 lines
						
					
					
						
							8.4 KiB
						
					
					
				"""distutils.archive_util
 | 
						|
 | 
						|
Utility functions for creating archive files (tarballs, zip files,
 | 
						|
that sort of thing)."""
 | 
						|
 | 
						|
import os
 | 
						|
from warnings import warn
 | 
						|
import sys
 | 
						|
 | 
						|
try:
 | 
						|
    import zipfile
 | 
						|
except ImportError:
 | 
						|
    zipfile = None
 | 
						|
 | 
						|
 | 
						|
from distutils.errors import DistutilsExecError
 | 
						|
from distutils.spawn import spawn
 | 
						|
from distutils.dir_util import mkpath
 | 
						|
from distutils import log
 | 
						|
 | 
						|
try:
 | 
						|
    from pwd import getpwnam
 | 
						|
except ImportError:
 | 
						|
    getpwnam = None
 | 
						|
 | 
						|
try:
 | 
						|
    from grp import getgrnam
 | 
						|
except ImportError:
 | 
						|
    getgrnam = None
 | 
						|
 | 
						|
def _get_gid(name):
 | 
						|
    """Returns a gid, given a group name."""
 | 
						|
    if getgrnam is None or name is None:
 | 
						|
        return None
 | 
						|
    try:
 | 
						|
        result = getgrnam(name)
 | 
						|
    except KeyError:
 | 
						|
        result = None
 | 
						|
    if result is not None:
 | 
						|
        return result[2]
 | 
						|
    return None
 | 
						|
 | 
						|
def _get_uid(name):
 | 
						|
    """Returns an uid, given a user name."""
 | 
						|
    if getpwnam is None or name is None:
 | 
						|
        return None
 | 
						|
    try:
 | 
						|
        result = getpwnam(name)
 | 
						|
    except KeyError:
 | 
						|
        result = None
 | 
						|
    if result is not None:
 | 
						|
        return result[2]
 | 
						|
    return None
 | 
						|
 | 
						|
def make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0,
 | 
						|
                 owner=None, group=None):
 | 
						|
    """Create a (possibly compressed) tar file from all the files under
 | 
						|
    'base_dir'.
 | 
						|
 | 
						|
    'compress' must be "gzip" (the default), "bzip2", "xz", "compress", or
 | 
						|
    None.  ("compress" will be deprecated in Python 3.2)
 | 
						|
 | 
						|
    'owner' and 'group' can be used to define an owner and a group for the
 | 
						|
    archive that is being built. If not provided, the current owner and group
 | 
						|
    will be used.
 | 
						|
 | 
						|
    The output tar file will be named 'base_dir' +  ".tar", possibly plus
 | 
						|
    the appropriate compression extension (".gz", ".bz2", ".xz" or ".Z").
 | 
						|
 | 
						|
    Returns the output filename.
 | 
						|
    """
 | 
						|
    tar_compression = {'gzip': 'gz', 'bzip2': 'bz2', 'xz': 'xz', None: '',
 | 
						|
                       'compress': ''}
 | 
						|
    compress_ext = {'gzip': '.gz', 'bzip2': '.bz2', 'xz': '.xz',
 | 
						|
                    'compress': '.Z'}
 | 
						|
 | 
						|
    # flags for compression program, each element of list will be an argument
 | 
						|
    if compress is not None and compress not in compress_ext.keys():
 | 
						|
        raise ValueError(
 | 
						|
              "bad value for 'compress': must be None, 'gzip', 'bzip2', "
 | 
						|
              "'xz' or 'compress'")
 | 
						|
 | 
						|
    archive_name = base_name + '.tar'
 | 
						|
    if compress != 'compress':
 | 
						|
        archive_name += compress_ext.get(compress, '')
 | 
						|
 | 
						|
    mkpath(os.path.dirname(archive_name), dry_run=dry_run)
 | 
						|
 | 
						|
    # creating the tarball
 | 
						|
    import tarfile  # late import so Python build itself doesn't break
 | 
						|
 | 
						|
    log.info('Creating tar archive')
 | 
						|
 | 
						|
    uid = _get_uid(owner)
 | 
						|
    gid = _get_gid(group)
 | 
						|
 | 
						|
    def _set_uid_gid(tarinfo):
 | 
						|
        if gid is not None:
 | 
						|
            tarinfo.gid = gid
 | 
						|
            tarinfo.gname = group
 | 
						|
        if uid is not None:
 | 
						|
            tarinfo.uid = uid
 | 
						|
            tarinfo.uname = owner
 | 
						|
        return tarinfo
 | 
						|
 | 
						|
    if not dry_run:
 | 
						|
        tar = tarfile.open(archive_name, 'w|%s' % tar_compression[compress])
 | 
						|
        try:
 | 
						|
            tar.add(base_dir, filter=_set_uid_gid)
 | 
						|
        finally:
 | 
						|
            tar.close()
 | 
						|
 | 
						|
    # compression using `compress`
 | 
						|
    if compress == 'compress':
 | 
						|
        warn("'compress' will be deprecated.", PendingDeprecationWarning)
 | 
						|
        # the option varies depending on the platform
 | 
						|
        compressed_name = archive_name + compress_ext[compress]
 | 
						|
        if sys.platform == 'win32':
 | 
						|
            cmd = [compress, archive_name, compressed_name]
 | 
						|
        else:
 | 
						|
            cmd = [compress, '-f', archive_name]
 | 
						|
        spawn(cmd, dry_run=dry_run)
 | 
						|
        return compressed_name
 | 
						|
 | 
						|
    return archive_name
 | 
						|
 | 
						|
def make_zipfile(base_name, base_dir, verbose=0, dry_run=0):
 | 
						|
    """Create a zip file from all the files under 'base_dir'.
 | 
						|
 | 
						|
    The output zip file will be named 'base_name' + ".zip".  Uses either the
 | 
						|
    "zipfile" Python module (if available) or the InfoZIP "zip" utility
 | 
						|
    (if installed and found on the default search path).  If neither tool is
 | 
						|
    available, raises DistutilsExecError.  Returns the name of the output zip
 | 
						|
    file.
 | 
						|
    """
 | 
						|
    zip_filename = base_name + ".zip"
 | 
						|
    mkpath(os.path.dirname(zip_filename), dry_run=dry_run)
 | 
						|
 | 
						|
    # If zipfile module is not available, try spawning an external
 | 
						|
    # 'zip' command.
 | 
						|
    if zipfile is None:
 | 
						|
        if verbose:
 | 
						|
            zipoptions = "-r"
 | 
						|
        else:
 | 
						|
            zipoptions = "-rq"
 | 
						|
 | 
						|
        try:
 | 
						|
            spawn(["zip", zipoptions, zip_filename, base_dir],
 | 
						|
                  dry_run=dry_run)
 | 
						|
        except DistutilsExecError:
 | 
						|
            # XXX really should distinguish between "couldn't find
 | 
						|
            # external 'zip' command" and "zip failed".
 | 
						|
            raise DistutilsExecError(("unable to create zip file '%s': "
 | 
						|
                   "could neither import the 'zipfile' module nor "
 | 
						|
                   "find a standalone zip utility") % zip_filename)
 | 
						|
 | 
						|
    else:
 | 
						|
        log.info("creating '%s' and adding '%s' to it",
 | 
						|
                 zip_filename, base_dir)
 | 
						|
 | 
						|
        if not dry_run:
 | 
						|
            try:
 | 
						|
                zip = zipfile.ZipFile(zip_filename, "w",
 | 
						|
                                      compression=zipfile.ZIP_DEFLATED)
 | 
						|
            except RuntimeError:
 | 
						|
                zip = zipfile.ZipFile(zip_filename, "w",
 | 
						|
                                      compression=zipfile.ZIP_STORED)
 | 
						|
 | 
						|
            with zip:
 | 
						|
                if base_dir != os.curdir:
 | 
						|
                    path = os.path.normpath(os.path.join(base_dir, ''))
 | 
						|
                    zip.write(path, path)
 | 
						|
                    log.info("adding '%s'", path)
 | 
						|
                for dirpath, dirnames, filenames in os.walk(base_dir):
 | 
						|
                    for name in dirnames:
 | 
						|
                        path = os.path.normpath(os.path.join(dirpath, name, ''))
 | 
						|
                        zip.write(path, path)
 | 
						|
                        log.info("adding '%s'", path)
 | 
						|
                    for name in filenames:
 | 
						|
                        path = os.path.normpath(os.path.join(dirpath, name))
 | 
						|
                        if os.path.isfile(path):
 | 
						|
                            zip.write(path, path)
 | 
						|
                            log.info("adding '%s'", path)
 | 
						|
 | 
						|
    return zip_filename
 | 
						|
 | 
						|
ARCHIVE_FORMATS = {
 | 
						|
    'gztar': (make_tarball, [('compress', 'gzip')], "gzip'ed tar-file"),
 | 
						|
    'bztar': (make_tarball, [('compress', 'bzip2')], "bzip2'ed tar-file"),
 | 
						|
    'xztar': (make_tarball, [('compress', 'xz')], "xz'ed tar-file"),
 | 
						|
    'ztar':  (make_tarball, [('compress', 'compress')], "compressed tar file"),
 | 
						|
    'tar':   (make_tarball, [('compress', None)], "uncompressed tar file"),
 | 
						|
    'zip':   (make_zipfile, [],"ZIP file")
 | 
						|
    }
 | 
						|
 | 
						|
def check_archive_formats(formats):
 | 
						|
    """Returns the first format from the 'format' list that is unknown.
 | 
						|
 | 
						|
    If all formats are known, returns None
 | 
						|
    """
 | 
						|
    for format in formats:
 | 
						|
        if format not in ARCHIVE_FORMATS:
 | 
						|
            return format
 | 
						|
    return None
 | 
						|
 | 
						|
def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
 | 
						|
                 dry_run=0, owner=None, group=None):
 | 
						|
    """Create an archive file (eg. zip or tar).
 | 
						|
 | 
						|
    'base_name' is the name of the file to create, minus any format-specific
 | 
						|
    extension; 'format' is the archive format: one of "zip", "tar", "gztar",
 | 
						|
    "bztar", "xztar", or "ztar".
 | 
						|
 | 
						|
    'root_dir' is a directory that will be the root directory of the
 | 
						|
    archive; ie. we typically chdir into 'root_dir' before creating the
 | 
						|
    archive.  'base_dir' is the directory where we start archiving from;
 | 
						|
    ie. 'base_dir' will be the common prefix of all files and
 | 
						|
    directories in the archive.  'root_dir' and 'base_dir' both default
 | 
						|
    to the current directory.  Returns the name of the archive file.
 | 
						|
 | 
						|
    'owner' and 'group' are used when creating a tar archive. By default,
 | 
						|
    uses the current owner and group.
 | 
						|
    """
 | 
						|
    save_cwd = os.getcwd()
 | 
						|
    if root_dir is not None:
 | 
						|
        log.debug("changing into '%s'", root_dir)
 | 
						|
        base_name = os.path.abspath(base_name)
 | 
						|
        if not dry_run:
 | 
						|
            os.chdir(root_dir)
 | 
						|
 | 
						|
    if base_dir is None:
 | 
						|
        base_dir = os.curdir
 | 
						|
 | 
						|
    kwargs = {'dry_run': dry_run}
 | 
						|
 | 
						|
    try:
 | 
						|
        format_info = ARCHIVE_FORMATS[format]
 | 
						|
    except KeyError:
 | 
						|
        raise ValueError("unknown archive format '%s'" % format)
 | 
						|
 | 
						|
    func = format_info[0]
 | 
						|
    for arg, val in format_info[1]:
 | 
						|
        kwargs[arg] = val
 | 
						|
 | 
						|
    if format != 'zip':
 | 
						|
        kwargs['owner'] = owner
 | 
						|
        kwargs['group'] = group
 | 
						|
 | 
						|
    try:
 | 
						|
        filename = func(base_name, base_dir, **kwargs)
 | 
						|
    finally:
 | 
						|
        if root_dir is not None:
 | 
						|
            log.debug("changing back to '%s'", save_cwd)
 | 
						|
            os.chdir(save_cwd)
 | 
						|
 | 
						|
    return filename
 |