blob: f5959f5e6421a48b60439fa3e96144a954e68c8d [file] [log] [blame]
Greg Wardaebf7062000-04-04 02:05:59 +00001"""distutils.archive_util
2
3Utility functions for creating archive files (tarballs, zip files,
4that sort of thing)."""
5
Greg Wardaebf7062000-04-04 02:05:59 +00006__revision__ = "$Id$"
7
8import os
9from distutils.errors import DistutilsExecError
10from distutils.spawn import spawn
Greg Ward04e25a12000-08-22 01:48:54 +000011from distutils.dir_util import mkpath
Jeremy Hyltoncd8a1142002-06-04 20:14:43 +000012from distutils import log
Greg Wardaebf7062000-04-04 02:05:59 +000013
Tarek Ziadé6f826ed2009-05-17 12:04:57 +000014def make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0):
Greg Wardaebf7062000-04-04 02:05:59 +000015 """Create a (possibly compressed) tar file from all the files under
Tarek Ziadé6f826ed2009-05-17 12:04:57 +000016 'base_dir'.
17
18 'compress' must be "gzip" (the default), "compress", "bzip2", or None.
19 Both "tar" and the compression utility named by 'compress' must be on
20 the default program search path, so this is probably Unix-specific.
21 The output tar file will be named 'base_dir' + ".tar", possibly plus
22 the appropriate compression extension (".gz", ".bz2" or ".Z").
23 Returns the output filename.
Greg Wardca4289f2000-09-26 02:13:49 +000024 """
Greg Wardaebf7062000-04-04 02:05:59 +000025 # XXX GNU tar 1.13 has a nifty option to add a prefix directory.
26 # It's pretty new, though, so we certainly can't require it --
27 # but it would be nice to take advantage of it to skip the
28 # "create a tree of hardlinks" step! (Would also be nice to
29 # detect GNU tar to use its 'z' option and save a step.)
30
Tarek Ziadé6f826ed2009-05-17 12:04:57 +000031 compress_ext = {'gzip': ".gz",
32 'bzip2': '.bz2',
33 'compress': ".Z" }
Fred Drakeb94b8492001-12-06 20:51:35 +000034
Greg Wardf1948782000-04-25 01:38:20 +000035 # flags for compression program, each element of list will be an argument
36 compress_flags = {'gzip': ["-f9"],
37 'compress': ["-f"],
38 'bzip2': ['-f9']}
Greg Wardaebf7062000-04-04 02:05:59 +000039
Greg Wardf1948782000-04-25 01:38:20 +000040 if compress is not None and compress not in compress_ext.keys():
Greg Wardaebf7062000-04-04 02:05:59 +000041 raise ValueError, \
42 "bad value for 'compress': must be None, 'gzip', or 'compress'"
43
44 archive_name = base_name + ".tar"
Jeremy Hyltoncd8a1142002-06-04 20:14:43 +000045 mkpath(os.path.dirname(archive_name), dry_run=dry_run)
Greg Wardaebf7062000-04-04 02:05:59 +000046 cmd = ["tar", "-cf", archive_name, base_dir]
Jeremy Hyltoncd8a1142002-06-04 20:14:43 +000047 spawn(cmd, dry_run=dry_run)
Greg Wardaebf7062000-04-04 02:05:59 +000048
49 if compress:
Greg Wardca4289f2000-09-26 02:13:49 +000050 spawn([compress] + compress_flags[compress] + [archive_name],
Jeremy Hyltoncd8a1142002-06-04 20:14:43 +000051 dry_run=dry_run)
Greg Wardaebf7062000-04-04 02:05:59 +000052 return archive_name + compress_ext[compress]
53 else:
54 return archive_name
55
Tarek Ziadé6f826ed2009-05-17 12:04:57 +000056def make_zipfile(base_name, base_dir, verbose=0, dry_run=0):
57 """Create a zip file from all the files under 'base_dir'.
Greg Wardaebf7062000-04-04 02:05:59 +000058
Tarek Ziadé6f826ed2009-05-17 12:04:57 +000059 The output zip file will be named 'base_dir' + ".zip". Uses either the
60 "zipfile" Python module (if available) or the InfoZIP "zip" utility
61 (if installed and found on the default search path). If neither tool is
62 available, raises DistutilsExecError. Returns the name of the output zip
63 file.
Greg Wardca4289f2000-09-26 02:13:49 +000064 """
Andrew M. Kuchlingcdd21572002-11-21 18:33:28 +000065 try:
66 import zipfile
67 except ImportError:
68 zipfile = None
Tim Peters182b5ac2004-07-18 06:16:08 +000069
Greg Wardaebf7062000-04-04 02:05:59 +000070 zip_filename = base_name + ".zip"
Jeremy Hyltoncd8a1142002-06-04 20:14:43 +000071 mkpath(os.path.dirname(zip_filename), dry_run=dry_run)
Greg Wardaebf7062000-04-04 02:05:59 +000072
Andrew M. Kuchlingcdd21572002-11-21 18:33:28 +000073 # If zipfile module is not available, try spawning an external
74 # 'zip' command.
75 if zipfile is None:
76 if verbose:
77 zipoptions = "-r"
78 else:
79 zipoptions = "-rq"
Tim Peters182b5ac2004-07-18 06:16:08 +000080
Andrew M. Kuchlingcdd21572002-11-21 18:33:28 +000081 try:
82 spawn(["zip", zipoptions, zip_filename, base_dir],
83 dry_run=dry_run)
84 except DistutilsExecError:
85 # XXX really should distinguish between "couldn't find
86 # external 'zip' command" and "zip failed".
87 raise DistutilsExecError, \
88 ("unable to create zip file '%s': "
89 "could neither import the 'zipfile' module nor "
90 "find a standalone zip utility") % zip_filename
91
92 else:
93 log.info("creating '%s' and adding '%s' to it",
Jeremy Hyltoncd8a1142002-06-04 20:14:43 +000094 zip_filename, base_dir)
Andrew M. Kuchlingcdd21572002-11-21 18:33:28 +000095
Greg Wardaebf7062000-04-04 02:05:59 +000096 if not dry_run:
Tarek Ziadé6f826ed2009-05-17 12:04:57 +000097 zip = zipfile.ZipFile(zip_filename, "w",
98 compression=zipfile.ZIP_DEFLATED)
Greg Wardaebf7062000-04-04 02:05:59 +000099
Benjamin Peterson9ec4aa02008-05-08 22:09:54 +0000100 for dirpath, dirnames, filenames in os.walk(base_dir):
101 for name in filenames:
102 path = os.path.normpath(os.path.join(dirpath, name))
103 if os.path.isfile(path):
Tarek Ziadé6f826ed2009-05-17 12:04:57 +0000104 zip.write(path, path)
Benjamin Peterson9ec4aa02008-05-08 22:09:54 +0000105 log.info("adding '%s'" % path)
Tarek Ziadé6f826ed2009-05-17 12:04:57 +0000106 zip.close()
Greg Wardaebf7062000-04-04 02:05:59 +0000107
108 return zip_filename
109
Greg Warddb807542000-04-22 03:09:56 +0000110ARCHIVE_FORMATS = {
Greg Ward2ff78872000-06-24 00:23:20 +0000111 'gztar': (make_tarball, [('compress', 'gzip')], "gzip'ed tar-file"),
112 'bztar': (make_tarball, [('compress', 'bzip2')], "bzip2'ed tar-file"),
113 'ztar': (make_tarball, [('compress', 'compress')], "compressed tar file"),
114 'tar': (make_tarball, [('compress', None)], "uncompressed tar file"),
Greg Ward04e25a12000-08-22 01:48:54 +0000115 'zip': (make_zipfile, [],"ZIP file")
Greg Warddb807542000-04-22 03:09:56 +0000116 }
117
Tarek Ziadé6f826ed2009-05-17 12:04:57 +0000118def check_archive_formats(formats):
119 """Returns the first format from the 'format' list that is unknown.
120
121 If all formats are known, returns None
122 """
Greg Warddb807542000-04-22 03:09:56 +0000123 for format in formats:
Guido van Rossum8bc09652008-02-21 18:18:37 +0000124 if format not in ARCHIVE_FORMATS:
Greg Warddb807542000-04-22 03:09:56 +0000125 return format
Tarek Ziadé6f826ed2009-05-17 12:04:57 +0000126 return None
Greg Warddb807542000-04-22 03:09:56 +0000127
Tarek Ziadé6f826ed2009-05-17 12:04:57 +0000128def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
129 dry_run=0):
130 """Create an archive file (eg. zip or tar).
131
132 'base_name' is the name of the file to create, minus any format-specific
133 extension; 'format' is the archive format: one of "zip", "tar", "ztar",
134 or "gztar".
135
Greg Wardaebf7062000-04-04 02:05:59 +0000136 'root_dir' is a directory that will be the root directory of the
137 archive; ie. we typically chdir into 'root_dir' before creating the
138 archive. 'base_dir' is the directory where we start archiving from;
139 ie. 'base_dir' will be the common prefix of all files and
140 directories in the archive. 'root_dir' and 'base_dir' both default
Greg Ward87909612000-06-01 01:07:55 +0000141 to the current directory. Returns the name of the archive file.
142 """
Greg Wardaebf7062000-04-04 02:05:59 +0000143 save_cwd = os.getcwd()
144 if root_dir is not None:
Jeremy Hyltoncd8a1142002-06-04 20:14:43 +0000145 log.debug("changing into '%s'", root_dir)
Greg Wardca4289f2000-09-26 02:13:49 +0000146 base_name = os.path.abspath(base_name)
Greg Wardaebf7062000-04-04 02:05:59 +0000147 if not dry_run:
Greg Wardca4289f2000-09-26 02:13:49 +0000148 os.chdir(root_dir)
Greg Wardaebf7062000-04-04 02:05:59 +0000149
150 if base_dir is None:
151 base_dir = os.curdir
152
Tarek Ziadé6f826ed2009-05-17 12:04:57 +0000153 kwargs = {'dry_run': dry_run}
Fred Drakeb94b8492001-12-06 20:51:35 +0000154
Greg Warddb807542000-04-22 03:09:56 +0000155 try:
156 format_info = ARCHIVE_FORMATS[format]
157 except KeyError:
158 raise ValueError, "unknown archive format '%s'" % format
Greg Wardaebf7062000-04-04 02:05:59 +0000159
Greg Warddb807542000-04-22 03:09:56 +0000160 func = format_info[0]
Tarek Ziadé6f826ed2009-05-17 12:04:57 +0000161 for arg, val in format_info[1]:
Greg Warddb807542000-04-22 03:09:56 +0000162 kwargs[arg] = val
Greg Wardca4289f2000-09-26 02:13:49 +0000163 filename = apply(func, (base_name, base_dir), kwargs)
Greg Wardaebf7062000-04-04 02:05:59 +0000164
165 if root_dir is not None:
Jeremy Hyltoncd8a1142002-06-04 20:14:43 +0000166 log.debug("changing back to '%s'", save_cwd)
Greg Wardca4289f2000-09-26 02:13:49 +0000167 os.chdir(save_cwd)
Greg Wardaebf7062000-04-04 02:05:59 +0000168
Greg Ward87909612000-06-01 01:07:55 +0000169 return filename