17db96d56Sopenharmony_ci"""distutils.archive_util
27db96d56Sopenharmony_ci
37db96d56Sopenharmony_ciUtility functions for creating archive files (tarballs, zip files,
47db96d56Sopenharmony_cithat sort of thing)."""
57db96d56Sopenharmony_ci
67db96d56Sopenharmony_ciimport os
77db96d56Sopenharmony_cifrom warnings import warn
87db96d56Sopenharmony_ciimport sys
97db96d56Sopenharmony_ci
107db96d56Sopenharmony_citry:
117db96d56Sopenharmony_ci    import zipfile
127db96d56Sopenharmony_ciexcept ImportError:
137db96d56Sopenharmony_ci    zipfile = None
147db96d56Sopenharmony_ci
157db96d56Sopenharmony_ci
167db96d56Sopenharmony_cifrom distutils.errors import DistutilsExecError
177db96d56Sopenharmony_cifrom distutils.spawn import spawn
187db96d56Sopenharmony_cifrom distutils.dir_util import mkpath
197db96d56Sopenharmony_cifrom distutils import log
207db96d56Sopenharmony_ci
217db96d56Sopenharmony_citry:
227db96d56Sopenharmony_ci    from pwd import getpwnam
237db96d56Sopenharmony_ciexcept ImportError:
247db96d56Sopenharmony_ci    getpwnam = None
257db96d56Sopenharmony_ci
267db96d56Sopenharmony_citry:
277db96d56Sopenharmony_ci    from grp import getgrnam
287db96d56Sopenharmony_ciexcept ImportError:
297db96d56Sopenharmony_ci    getgrnam = None
307db96d56Sopenharmony_ci
317db96d56Sopenharmony_cidef _get_gid(name):
327db96d56Sopenharmony_ci    """Returns a gid, given a group name."""
337db96d56Sopenharmony_ci    if getgrnam is None or name is None:
347db96d56Sopenharmony_ci        return None
357db96d56Sopenharmony_ci    try:
367db96d56Sopenharmony_ci        result = getgrnam(name)
377db96d56Sopenharmony_ci    except KeyError:
387db96d56Sopenharmony_ci        result = None
397db96d56Sopenharmony_ci    if result is not None:
407db96d56Sopenharmony_ci        return result[2]
417db96d56Sopenharmony_ci    return None
427db96d56Sopenharmony_ci
437db96d56Sopenharmony_cidef _get_uid(name):
447db96d56Sopenharmony_ci    """Returns an uid, given a user name."""
457db96d56Sopenharmony_ci    if getpwnam is None or name is None:
467db96d56Sopenharmony_ci        return None
477db96d56Sopenharmony_ci    try:
487db96d56Sopenharmony_ci        result = getpwnam(name)
497db96d56Sopenharmony_ci    except KeyError:
507db96d56Sopenharmony_ci        result = None
517db96d56Sopenharmony_ci    if result is not None:
527db96d56Sopenharmony_ci        return result[2]
537db96d56Sopenharmony_ci    return None
547db96d56Sopenharmony_ci
557db96d56Sopenharmony_cidef make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0,
567db96d56Sopenharmony_ci                 owner=None, group=None):
577db96d56Sopenharmony_ci    """Create a (possibly compressed) tar file from all the files under
587db96d56Sopenharmony_ci    'base_dir'.
597db96d56Sopenharmony_ci
607db96d56Sopenharmony_ci    'compress' must be "gzip" (the default), "bzip2", "xz", "compress", or
617db96d56Sopenharmony_ci    None.  ("compress" will be deprecated in Python 3.2)
627db96d56Sopenharmony_ci
637db96d56Sopenharmony_ci    'owner' and 'group' can be used to define an owner and a group for the
647db96d56Sopenharmony_ci    archive that is being built. If not provided, the current owner and group
657db96d56Sopenharmony_ci    will be used.
667db96d56Sopenharmony_ci
677db96d56Sopenharmony_ci    The output tar file will be named 'base_dir' +  ".tar", possibly plus
687db96d56Sopenharmony_ci    the appropriate compression extension (".gz", ".bz2", ".xz" or ".Z").
697db96d56Sopenharmony_ci
707db96d56Sopenharmony_ci    Returns the output filename.
717db96d56Sopenharmony_ci    """
727db96d56Sopenharmony_ci    tar_compression = {'gzip': 'gz', 'bzip2': 'bz2', 'xz': 'xz', None: '',
737db96d56Sopenharmony_ci                       'compress': ''}
747db96d56Sopenharmony_ci    compress_ext = {'gzip': '.gz', 'bzip2': '.bz2', 'xz': '.xz',
757db96d56Sopenharmony_ci                    'compress': '.Z'}
767db96d56Sopenharmony_ci
777db96d56Sopenharmony_ci    # flags for compression program, each element of list will be an argument
787db96d56Sopenharmony_ci    if compress is not None and compress not in compress_ext.keys():
797db96d56Sopenharmony_ci        raise ValueError(
807db96d56Sopenharmony_ci              "bad value for 'compress': must be None, 'gzip', 'bzip2', "
817db96d56Sopenharmony_ci              "'xz' or 'compress'")
827db96d56Sopenharmony_ci
837db96d56Sopenharmony_ci    archive_name = base_name + '.tar'
847db96d56Sopenharmony_ci    if compress != 'compress':
857db96d56Sopenharmony_ci        archive_name += compress_ext.get(compress, '')
867db96d56Sopenharmony_ci
877db96d56Sopenharmony_ci    mkpath(os.path.dirname(archive_name), dry_run=dry_run)
887db96d56Sopenharmony_ci
897db96d56Sopenharmony_ci    # creating the tarball
907db96d56Sopenharmony_ci    import tarfile  # late import so Python build itself doesn't break
917db96d56Sopenharmony_ci
927db96d56Sopenharmony_ci    log.info('Creating tar archive')
937db96d56Sopenharmony_ci
947db96d56Sopenharmony_ci    uid = _get_uid(owner)
957db96d56Sopenharmony_ci    gid = _get_gid(group)
967db96d56Sopenharmony_ci
977db96d56Sopenharmony_ci    def _set_uid_gid(tarinfo):
987db96d56Sopenharmony_ci        if gid is not None:
997db96d56Sopenharmony_ci            tarinfo.gid = gid
1007db96d56Sopenharmony_ci            tarinfo.gname = group
1017db96d56Sopenharmony_ci        if uid is not None:
1027db96d56Sopenharmony_ci            tarinfo.uid = uid
1037db96d56Sopenharmony_ci            tarinfo.uname = owner
1047db96d56Sopenharmony_ci        return tarinfo
1057db96d56Sopenharmony_ci
1067db96d56Sopenharmony_ci    if not dry_run:
1077db96d56Sopenharmony_ci        tar = tarfile.open(archive_name, 'w|%s' % tar_compression[compress])
1087db96d56Sopenharmony_ci        try:
1097db96d56Sopenharmony_ci            tar.add(base_dir, filter=_set_uid_gid)
1107db96d56Sopenharmony_ci        finally:
1117db96d56Sopenharmony_ci            tar.close()
1127db96d56Sopenharmony_ci
1137db96d56Sopenharmony_ci    # compression using `compress`
1147db96d56Sopenharmony_ci    if compress == 'compress':
1157db96d56Sopenharmony_ci        warn("'compress' will be deprecated.", PendingDeprecationWarning)
1167db96d56Sopenharmony_ci        # the option varies depending on the platform
1177db96d56Sopenharmony_ci        compressed_name = archive_name + compress_ext[compress]
1187db96d56Sopenharmony_ci        if sys.platform == 'win32':
1197db96d56Sopenharmony_ci            cmd = [compress, archive_name, compressed_name]
1207db96d56Sopenharmony_ci        else:
1217db96d56Sopenharmony_ci            cmd = [compress, '-f', archive_name]
1227db96d56Sopenharmony_ci        spawn(cmd, dry_run=dry_run)
1237db96d56Sopenharmony_ci        return compressed_name
1247db96d56Sopenharmony_ci
1257db96d56Sopenharmony_ci    return archive_name
1267db96d56Sopenharmony_ci
1277db96d56Sopenharmony_cidef make_zipfile(base_name, base_dir, verbose=0, dry_run=0):
1287db96d56Sopenharmony_ci    """Create a zip file from all the files under 'base_dir'.
1297db96d56Sopenharmony_ci
1307db96d56Sopenharmony_ci    The output zip file will be named 'base_name' + ".zip".  Uses either the
1317db96d56Sopenharmony_ci    "zipfile" Python module (if available) or the InfoZIP "zip" utility
1327db96d56Sopenharmony_ci    (if installed and found on the default search path).  If neither tool is
1337db96d56Sopenharmony_ci    available, raises DistutilsExecError.  Returns the name of the output zip
1347db96d56Sopenharmony_ci    file.
1357db96d56Sopenharmony_ci    """
1367db96d56Sopenharmony_ci    zip_filename = base_name + ".zip"
1377db96d56Sopenharmony_ci    mkpath(os.path.dirname(zip_filename), dry_run=dry_run)
1387db96d56Sopenharmony_ci
1397db96d56Sopenharmony_ci    # If zipfile module is not available, try spawning an external
1407db96d56Sopenharmony_ci    # 'zip' command.
1417db96d56Sopenharmony_ci    if zipfile is None:
1427db96d56Sopenharmony_ci        if verbose:
1437db96d56Sopenharmony_ci            zipoptions = "-r"
1447db96d56Sopenharmony_ci        else:
1457db96d56Sopenharmony_ci            zipoptions = "-rq"
1467db96d56Sopenharmony_ci
1477db96d56Sopenharmony_ci        try:
1487db96d56Sopenharmony_ci            spawn(["zip", zipoptions, zip_filename, base_dir],
1497db96d56Sopenharmony_ci                  dry_run=dry_run)
1507db96d56Sopenharmony_ci        except DistutilsExecError:
1517db96d56Sopenharmony_ci            # XXX really should distinguish between "couldn't find
1527db96d56Sopenharmony_ci            # external 'zip' command" and "zip failed".
1537db96d56Sopenharmony_ci            raise DistutilsExecError(("unable to create zip file '%s': "
1547db96d56Sopenharmony_ci                   "could neither import the 'zipfile' module nor "
1557db96d56Sopenharmony_ci                   "find a standalone zip utility") % zip_filename)
1567db96d56Sopenharmony_ci
1577db96d56Sopenharmony_ci    else:
1587db96d56Sopenharmony_ci        log.info("creating '%s' and adding '%s' to it",
1597db96d56Sopenharmony_ci                 zip_filename, base_dir)
1607db96d56Sopenharmony_ci
1617db96d56Sopenharmony_ci        if not dry_run:
1627db96d56Sopenharmony_ci            try:
1637db96d56Sopenharmony_ci                zip = zipfile.ZipFile(zip_filename, "w",
1647db96d56Sopenharmony_ci                                      compression=zipfile.ZIP_DEFLATED)
1657db96d56Sopenharmony_ci            except RuntimeError:
1667db96d56Sopenharmony_ci                zip = zipfile.ZipFile(zip_filename, "w",
1677db96d56Sopenharmony_ci                                      compression=zipfile.ZIP_STORED)
1687db96d56Sopenharmony_ci
1697db96d56Sopenharmony_ci            with zip:
1707db96d56Sopenharmony_ci                if base_dir != os.curdir:
1717db96d56Sopenharmony_ci                    path = os.path.normpath(os.path.join(base_dir, ''))
1727db96d56Sopenharmony_ci                    zip.write(path, path)
1737db96d56Sopenharmony_ci                    log.info("adding '%s'", path)
1747db96d56Sopenharmony_ci                for dirpath, dirnames, filenames in os.walk(base_dir):
1757db96d56Sopenharmony_ci                    for name in dirnames:
1767db96d56Sopenharmony_ci                        path = os.path.normpath(os.path.join(dirpath, name, ''))
1777db96d56Sopenharmony_ci                        zip.write(path, path)
1787db96d56Sopenharmony_ci                        log.info("adding '%s'", path)
1797db96d56Sopenharmony_ci                    for name in filenames:
1807db96d56Sopenharmony_ci                        path = os.path.normpath(os.path.join(dirpath, name))
1817db96d56Sopenharmony_ci                        if os.path.isfile(path):
1827db96d56Sopenharmony_ci                            zip.write(path, path)
1837db96d56Sopenharmony_ci                            log.info("adding '%s'", path)
1847db96d56Sopenharmony_ci
1857db96d56Sopenharmony_ci    return zip_filename
1867db96d56Sopenharmony_ci
1877db96d56Sopenharmony_ciARCHIVE_FORMATS = {
1887db96d56Sopenharmony_ci    'gztar': (make_tarball, [('compress', 'gzip')], "gzip'ed tar-file"),
1897db96d56Sopenharmony_ci    'bztar': (make_tarball, [('compress', 'bzip2')], "bzip2'ed tar-file"),
1907db96d56Sopenharmony_ci    'xztar': (make_tarball, [('compress', 'xz')], "xz'ed tar-file"),
1917db96d56Sopenharmony_ci    'ztar':  (make_tarball, [('compress', 'compress')], "compressed tar file"),
1927db96d56Sopenharmony_ci    'tar':   (make_tarball, [('compress', None)], "uncompressed tar file"),
1937db96d56Sopenharmony_ci    'zip':   (make_zipfile, [],"ZIP file")
1947db96d56Sopenharmony_ci    }
1957db96d56Sopenharmony_ci
1967db96d56Sopenharmony_cidef check_archive_formats(formats):
1977db96d56Sopenharmony_ci    """Returns the first format from the 'format' list that is unknown.
1987db96d56Sopenharmony_ci
1997db96d56Sopenharmony_ci    If all formats are known, returns None
2007db96d56Sopenharmony_ci    """
2017db96d56Sopenharmony_ci    for format in formats:
2027db96d56Sopenharmony_ci        if format not in ARCHIVE_FORMATS:
2037db96d56Sopenharmony_ci            return format
2047db96d56Sopenharmony_ci    return None
2057db96d56Sopenharmony_ci
2067db96d56Sopenharmony_cidef make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
2077db96d56Sopenharmony_ci                 dry_run=0, owner=None, group=None):
2087db96d56Sopenharmony_ci    """Create an archive file (eg. zip or tar).
2097db96d56Sopenharmony_ci
2107db96d56Sopenharmony_ci    'base_name' is the name of the file to create, minus any format-specific
2117db96d56Sopenharmony_ci    extension; 'format' is the archive format: one of "zip", "tar", "gztar",
2127db96d56Sopenharmony_ci    "bztar", "xztar", or "ztar".
2137db96d56Sopenharmony_ci
2147db96d56Sopenharmony_ci    'root_dir' is a directory that will be the root directory of the
2157db96d56Sopenharmony_ci    archive; ie. we typically chdir into 'root_dir' before creating the
2167db96d56Sopenharmony_ci    archive.  'base_dir' is the directory where we start archiving from;
2177db96d56Sopenharmony_ci    ie. 'base_dir' will be the common prefix of all files and
2187db96d56Sopenharmony_ci    directories in the archive.  'root_dir' and 'base_dir' both default
2197db96d56Sopenharmony_ci    to the current directory.  Returns the name of the archive file.
2207db96d56Sopenharmony_ci
2217db96d56Sopenharmony_ci    'owner' and 'group' are used when creating a tar archive. By default,
2227db96d56Sopenharmony_ci    uses the current owner and group.
2237db96d56Sopenharmony_ci    """
2247db96d56Sopenharmony_ci    save_cwd = os.getcwd()
2257db96d56Sopenharmony_ci    if root_dir is not None:
2267db96d56Sopenharmony_ci        log.debug("changing into '%s'", root_dir)
2277db96d56Sopenharmony_ci        base_name = os.path.abspath(base_name)
2287db96d56Sopenharmony_ci        if not dry_run:
2297db96d56Sopenharmony_ci            os.chdir(root_dir)
2307db96d56Sopenharmony_ci
2317db96d56Sopenharmony_ci    if base_dir is None:
2327db96d56Sopenharmony_ci        base_dir = os.curdir
2337db96d56Sopenharmony_ci
2347db96d56Sopenharmony_ci    kwargs = {'dry_run': dry_run}
2357db96d56Sopenharmony_ci
2367db96d56Sopenharmony_ci    try:
2377db96d56Sopenharmony_ci        format_info = ARCHIVE_FORMATS[format]
2387db96d56Sopenharmony_ci    except KeyError:
2397db96d56Sopenharmony_ci        raise ValueError("unknown archive format '%s'" % format)
2407db96d56Sopenharmony_ci
2417db96d56Sopenharmony_ci    func = format_info[0]
2427db96d56Sopenharmony_ci    for arg, val in format_info[1]:
2437db96d56Sopenharmony_ci        kwargs[arg] = val
2447db96d56Sopenharmony_ci
2457db96d56Sopenharmony_ci    if format != 'zip':
2467db96d56Sopenharmony_ci        kwargs['owner'] = owner
2477db96d56Sopenharmony_ci        kwargs['group'] = group
2487db96d56Sopenharmony_ci
2497db96d56Sopenharmony_ci    try:
2507db96d56Sopenharmony_ci        filename = func(base_name, base_dir, **kwargs)
2517db96d56Sopenharmony_ci    finally:
2527db96d56Sopenharmony_ci        if root_dir is not None:
2537db96d56Sopenharmony_ci            log.debug("changing back to '%s'", save_cwd)
2547db96d56Sopenharmony_ci            os.chdir(save_cwd)
2557db96d56Sopenharmony_ci
2567db96d56Sopenharmony_ci    return filename
257