|
1 """distutils.archive_util |
|
2 |
|
3 Utility functions for creating archive files (tarballs, zip files, |
|
4 that sort of thing).""" |
|
5 |
|
6 # This module should be kept compatible with Python 2.1. |
|
7 |
|
8 __revision__ = "$Id: archive_util.py 37828 2004-11-10 22:23:15Z loewis $" |
|
9 |
|
10 import os |
|
11 from distutils.errors import DistutilsExecError |
|
12 from distutils.spawn import spawn |
|
13 from distutils.dir_util import mkpath |
|
14 from distutils import log |
|
15 |
|
16 def make_tarball (base_name, base_dir, compress="gzip", |
|
17 verbose=0, dry_run=0): |
|
18 """Create a (possibly compressed) tar file from all the files under |
|
19 'base_dir'. 'compress' must be "gzip" (the default), "compress", |
|
20 "bzip2", or None. Both "tar" and the compression utility named by |
|
21 'compress' must be on the default program search path, so this is |
|
22 probably Unix-specific. The output tar file will be named 'base_dir' + |
|
23 ".tar", possibly plus the appropriate compression extension (".gz", |
|
24 ".bz2" or ".Z"). Return the output filename. |
|
25 """ |
|
26 # XXX GNU tar 1.13 has a nifty option to add a prefix directory. |
|
27 # It's pretty new, though, so we certainly can't require it -- |
|
28 # but it would be nice to take advantage of it to skip the |
|
29 # "create a tree of hardlinks" step! (Would also be nice to |
|
30 # detect GNU tar to use its 'z' option and save a step.) |
|
31 |
|
32 compress_ext = { 'gzip': ".gz", |
|
33 'bzip2': '.bz2', |
|
34 'compress': ".Z" } |
|
35 |
|
36 # flags for compression program, each element of list will be an argument |
|
37 compress_flags = {'gzip': ["-f9"], |
|
38 'compress': ["-f"], |
|
39 'bzip2': ['-f9']} |
|
40 |
|
41 if compress is not None and compress not in compress_ext.keys(): |
|
42 raise ValueError, \ |
|
43 "bad value for 'compress': must be None, 'gzip', or 'compress'" |
|
44 |
|
45 archive_name = base_name + ".tar" |
|
46 mkpath(os.path.dirname(archive_name), dry_run=dry_run) |
|
47 cmd = ["tar", "-cf", archive_name, base_dir] |
|
48 spawn(cmd, dry_run=dry_run) |
|
49 |
|
50 if compress: |
|
51 spawn([compress] + compress_flags[compress] + [archive_name], |
|
52 dry_run=dry_run) |
|
53 return archive_name + compress_ext[compress] |
|
54 else: |
|
55 return archive_name |
|
56 |
|
57 # make_tarball () |
|
58 |
|
59 |
|
60 def make_zipfile (base_name, base_dir, verbose=0, dry_run=0): |
|
61 """Create a zip file from all the files under 'base_dir'. The output |
|
62 zip file will be named 'base_dir' + ".zip". Uses either the "zipfile" |
|
63 Python module (if available) or the InfoZIP "zip" utility (if installed |
|
64 and found on the default search path). If neither tool is available, |
|
65 raises DistutilsExecError. Returns the name of the output zip file. |
|
66 """ |
|
67 try: |
|
68 import zipfile |
|
69 except ImportError: |
|
70 zipfile = None |
|
71 |
|
72 zip_filename = base_name + ".zip" |
|
73 mkpath(os.path.dirname(zip_filename), dry_run=dry_run) |
|
74 |
|
75 # If zipfile module is not available, try spawning an external |
|
76 # 'zip' command. |
|
77 if zipfile is None: |
|
78 if verbose: |
|
79 zipoptions = "-r" |
|
80 else: |
|
81 zipoptions = "-rq" |
|
82 |
|
83 try: |
|
84 spawn(["zip", zipoptions, zip_filename, base_dir], |
|
85 dry_run=dry_run) |
|
86 except DistutilsExecError: |
|
87 # XXX really should distinguish between "couldn't find |
|
88 # external 'zip' command" and "zip failed". |
|
89 raise DistutilsExecError, \ |
|
90 ("unable to create zip file '%s': " |
|
91 "could neither import the 'zipfile' module nor " |
|
92 "find a standalone zip utility") % zip_filename |
|
93 |
|
94 else: |
|
95 log.info("creating '%s' and adding '%s' to it", |
|
96 zip_filename, base_dir) |
|
97 |
|
98 def visit (z, dirname, names): |
|
99 for name in names: |
|
100 path = os.path.normpath(os.path.join(dirname, name)) |
|
101 if os.path.isfile(path): |
|
102 z.write(path, path) |
|
103 log.info("adding '%s'" % path) |
|
104 |
|
105 if not dry_run: |
|
106 z = zipfile.ZipFile(zip_filename, "w", |
|
107 compression=zipfile.ZIP_DEFLATED) |
|
108 |
|
109 os.path.walk(base_dir, visit, z) |
|
110 z.close() |
|
111 |
|
112 return zip_filename |
|
113 |
|
114 # make_zipfile () |
|
115 |
|
116 |
|
117 ARCHIVE_FORMATS = { |
|
118 'gztar': (make_tarball, [('compress', 'gzip')], "gzip'ed tar-file"), |
|
119 'bztar': (make_tarball, [('compress', 'bzip2')], "bzip2'ed tar-file"), |
|
120 'ztar': (make_tarball, [('compress', 'compress')], "compressed tar file"), |
|
121 'tar': (make_tarball, [('compress', None)], "uncompressed tar file"), |
|
122 'zip': (make_zipfile, [],"ZIP file") |
|
123 } |
|
124 |
|
125 def check_archive_formats (formats): |
|
126 for format in formats: |
|
127 if not ARCHIVE_FORMATS.has_key(format): |
|
128 return format |
|
129 else: |
|
130 return None |
|
131 |
|
132 def make_archive (base_name, format, |
|
133 root_dir=None, base_dir=None, |
|
134 verbose=0, dry_run=0): |
|
135 """Create an archive file (eg. zip or tar). 'base_name' is the name |
|
136 of the file to create, minus any format-specific extension; 'format' |
|
137 is the archive format: one of "zip", "tar", "ztar", or "gztar". |
|
138 'root_dir' is a directory that will be the root directory of the |
|
139 archive; ie. we typically chdir into 'root_dir' before creating the |
|
140 archive. 'base_dir' is the directory where we start archiving from; |
|
141 ie. 'base_dir' will be the common prefix of all files and |
|
142 directories in the archive. 'root_dir' and 'base_dir' both default |
|
143 to the current directory. Returns the name of the archive file. |
|
144 """ |
|
145 save_cwd = os.getcwd() |
|
146 if root_dir is not None: |
|
147 log.debug("changing into '%s'", root_dir) |
|
148 base_name = os.path.abspath(base_name) |
|
149 if not dry_run: |
|
150 os.chdir(root_dir) |
|
151 |
|
152 if base_dir is None: |
|
153 base_dir = os.curdir |
|
154 |
|
155 kwargs = { 'dry_run': dry_run } |
|
156 |
|
157 try: |
|
158 format_info = ARCHIVE_FORMATS[format] |
|
159 except KeyError: |
|
160 raise ValueError, "unknown archive format '%s'" % format |
|
161 |
|
162 func = format_info[0] |
|
163 for (arg,val) in format_info[1]: |
|
164 kwargs[arg] = val |
|
165 filename = apply(func, (base_name, base_dir), kwargs) |
|
166 |
|
167 if root_dir is not None: |
|
168 log.debug("changing back to '%s'", save_cwd) |
|
169 os.chdir(save_cwd) |
|
170 |
|
171 return filename |
|
172 |
|
173 # make_archive () |