blob: 8890d24a54f2db3774dd69fafe9c0122b5d044b2 [file] [log] [blame]
Tarek Ziadéc3399782010-02-23 05:39:18 +00001"""Utility functions for copying and archiving files and directory trees.
Guido van Rossum9d0a3df1997-04-29 14:45:19 +00002
Guido van Rossum959fa011999-08-18 20:03:17 +00003XXX The functions here don't copy the resource fork or other metadata on Mac.
Guido van Rossum9d0a3df1997-04-29 14:45:19 +00004
5"""
Guido van Rossumc6360141990-10-13 19:23:40 +00006
Guido van Rossumc96207a1992-03-31 18:55:40 +00007import os
Guido van Rossum83c03e21999-02-23 23:07:51 +00008import sys
Guido van Rossum9d0a3df1997-04-29 14:45:19 +00009import stat
Brett Cannon1c3fa182004-06-19 21:11:35 +000010from os.path import abspath
Georg Brandl2ee470f2008-07-16 12:55:28 +000011import fnmatch
Tarek Ziadé396fad72010-02-23 05:30:31 +000012import collections
Antoine Pitrou910bd512010-03-22 20:11:09 +000013import errno
Tarek Ziadé396fad72010-02-23 05:30:31 +000014
15try:
16 from pwd import getpwnam
17except ImportError:
18 getpwnam = None
19
20try:
21 from grp import getgrnam
22except ImportError:
23 getgrnam = None
Guido van Rossumc6360141990-10-13 19:23:40 +000024
Tarek Ziadéc3399782010-02-23 05:39:18 +000025__all__ = ["copyfileobj", "copyfile", "copymode", "copystat", "copy", "copy2",
26 "copytree", "move", "rmtree", "Error", "SpecialFileError",
27 "ExecError", "make_archive", "get_archive_formats",
28 "register_archive_format", "unregister_archive_format"]
Martin v. Löwise9ce0b02002-10-07 13:23:24 +000029
Neal Norwitz4ce69a52005-09-01 00:45:28 +000030class Error(EnvironmentError):
Martin v. Löwise9ce0b02002-10-07 13:23:24 +000031 pass
Guido van Rossumc6360141990-10-13 19:23:40 +000032
Antoine Pitrou7fff0962009-05-01 21:09:44 +000033class SpecialFileError(EnvironmentError):
34 """Raised when trying to do a kind of operation (e.g. copying) which is
35 not supported on a special file (e.g. a named pipe)"""
36
Tarek Ziadé396fad72010-02-23 05:30:31 +000037class ExecError(EnvironmentError):
38 """Raised when a command could not be executed"""
39
Georg Brandl6aa2d1f2008-08-12 08:35:52 +000040try:
41 WindowsError
42except NameError:
43 WindowsError = None
44
Greg Stein42bb8b32000-07-12 09:55:30 +000045def copyfileobj(fsrc, fdst, length=16*1024):
46 """copy data from file-like object fsrc to file-like object fdst"""
47 while 1:
48 buf = fsrc.read(length)
49 if not buf:
50 break
51 fdst.write(buf)
52
Johannes Gijsbers46f14592004-08-14 13:30:02 +000053def _samefile(src, dst):
54 # Macintosh, Unix.
Tarek Ziadé1eab9cc2010-04-19 21:19:57 +000055 if hasattr(os.path, 'samefile'):
Johannes Gijsbersf9a098e2004-08-14 14:51:01 +000056 try:
57 return os.path.samefile(src, dst)
58 except OSError:
59 return False
Johannes Gijsbers46f14592004-08-14 13:30:02 +000060
61 # All other platforms: check for same pathname.
62 return (os.path.normcase(os.path.abspath(src)) ==
63 os.path.normcase(os.path.abspath(dst)))
Tim Peters495ad3c2001-01-15 01:36:40 +000064
Guido van Rossumc6360141990-10-13 19:23:40 +000065def copyfile(src, dst):
Guido van Rossum9d0a3df1997-04-29 14:45:19 +000066 """Copy data from src to dst"""
Johannes Gijsbers46f14592004-08-14 13:30:02 +000067 if _samefile(src, dst):
Collin Winterce36ad82007-08-30 01:19:48 +000068 raise Error("`%s` and `%s` are the same file" % (src, dst))
Johannes Gijsbers46f14592004-08-14 13:30:02 +000069
Guido van Rossuma2baf461997-04-29 14:06:46 +000070 fsrc = None
71 fdst = None
Antoine Pitrou7fff0962009-05-01 21:09:44 +000072 for fn in [src, dst]:
73 try:
74 st = os.stat(fn)
75 except OSError:
76 # File most likely does not exist
77 pass
Benjamin Petersonc0d98aa2009-06-05 19:13:27 +000078 else:
79 # XXX What about other special files? (sockets, devices...)
80 if stat.S_ISFIFO(st.st_mode):
81 raise SpecialFileError("`%s` is a named pipe" % fn)
Guido van Rossuma2baf461997-04-29 14:06:46 +000082 try:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000083 fsrc = open(src, 'rb')
84 fdst = open(dst, 'wb')
Greg Stein42bb8b32000-07-12 09:55:30 +000085 copyfileobj(fsrc, fdst)
Guido van Rossuma2baf461997-04-29 14:06:46 +000086 finally:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000087 if fdst:
88 fdst.close()
89 if fsrc:
90 fsrc.close()
Guido van Rossumc6360141990-10-13 19:23:40 +000091
Guido van Rossumc6360141990-10-13 19:23:40 +000092def copymode(src, dst):
Guido van Rossum9d0a3df1997-04-29 14:45:19 +000093 """Copy mode bits from src to dst"""
Tim Peters0c947242001-01-21 20:00:00 +000094 if hasattr(os, 'chmod'):
95 st = os.stat(src)
Walter Dörwald294bbf32002-06-06 09:48:13 +000096 mode = stat.S_IMODE(st.st_mode)
Tim Peters0c947242001-01-21 20:00:00 +000097 os.chmod(dst, mode)
Guido van Rossumc6360141990-10-13 19:23:40 +000098
Guido van Rossumc6360141990-10-13 19:23:40 +000099def copystat(src, dst):
Thomas Wouterscf297e42007-02-23 15:07:44 +0000100 """Copy all stat info (mode bits, atime, mtime, flags) from src to dst"""
Guido van Rossuma2baf461997-04-29 14:06:46 +0000101 st = os.stat(src)
Walter Dörwald294bbf32002-06-06 09:48:13 +0000102 mode = stat.S_IMODE(st.st_mode)
Tim Peters0c947242001-01-21 20:00:00 +0000103 if hasattr(os, 'utime'):
Walter Dörwald294bbf32002-06-06 09:48:13 +0000104 os.utime(dst, (st.st_atime, st.st_mtime))
Tim Peters0c947242001-01-21 20:00:00 +0000105 if hasattr(os, 'chmod'):
106 os.chmod(dst, mode)
Thomas Wouterscf297e42007-02-23 15:07:44 +0000107 if hasattr(os, 'chflags') and hasattr(st, 'st_flags'):
Antoine Pitrou910bd512010-03-22 20:11:09 +0000108 try:
109 os.chflags(dst, st.st_flags)
110 except OSError as why:
Tarek Ziadé1eab9cc2010-04-19 21:19:57 +0000111 if (not hasattr(errno, 'EOPNOTSUPP') or
112 why.errno != errno.EOPNOTSUPP):
Antoine Pitrou910bd512010-03-22 20:11:09 +0000113 raise
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000114
Guido van Rossumc6360141990-10-13 19:23:40 +0000115def copy(src, dst):
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000116 """Copy data and mode bits ("cp src dst").
Tim Peters495ad3c2001-01-15 01:36:40 +0000117
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000118 The destination may be a directory.
119
120 """
Guido van Rossuma2baf461997-04-29 14:06:46 +0000121 if os.path.isdir(dst):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000122 dst = os.path.join(dst, os.path.basename(src))
Guido van Rossuma2baf461997-04-29 14:06:46 +0000123 copyfile(src, dst)
124 copymode(src, dst)
Guido van Rossumc6360141990-10-13 19:23:40 +0000125
Guido van Rossumc6360141990-10-13 19:23:40 +0000126def copy2(src, dst):
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000127 """Copy data and all stat info ("cp -p src dst").
128
129 The destination may be a directory.
130
131 """
Guido van Rossuma2baf461997-04-29 14:06:46 +0000132 if os.path.isdir(dst):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000133 dst = os.path.join(dst, os.path.basename(src))
Guido van Rossuma2baf461997-04-29 14:06:46 +0000134 copyfile(src, dst)
135 copystat(src, dst)
Guido van Rossumc6360141990-10-13 19:23:40 +0000136
Georg Brandl2ee470f2008-07-16 12:55:28 +0000137def ignore_patterns(*patterns):
138 """Function that can be used as copytree() ignore parameter.
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000139
Georg Brandl2ee470f2008-07-16 12:55:28 +0000140 Patterns is a sequence of glob-style patterns
141 that are used to exclude files"""
142 def _ignore_patterns(path, names):
143 ignored_names = []
144 for pattern in patterns:
145 ignored_names.extend(fnmatch.filter(names, pattern))
146 return set(ignored_names)
147 return _ignore_patterns
148
Tarek Ziadéfb437512010-04-20 08:57:33 +0000149def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2,
150 ignore_dangling_symlinks=False):
Tarek Ziadé5340db32010-04-19 22:30:51 +0000151 """Recursively copy a directory tree.
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000152
153 The destination directory must not already exist.
Neal Norwitza4c93b62003-02-23 21:36:32 +0000154 If exception(s) occur, an Error is raised with a list of reasons.
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000155
156 If the optional symlinks flag is true, symbolic links in the
157 source tree result in symbolic links in the destination tree; if
158 it is false, the contents of the files pointed to by symbolic
Tarek Ziadéfb437512010-04-20 08:57:33 +0000159 links are copied. If the file pointed by the symlink doesn't
160 exist, an exception will be added in the list of errors raised in
161 an Error exception at the end of the copy process.
162
163 You can set the optional ignore_dangling_symlinks flag to true if you
Tarek Ziadé8c26c7d2010-04-23 13:03:50 +0000164 want to silence this exception. Notice that this has no effect on
165 platforms that don't support os.symlink.
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000166
Georg Brandl2ee470f2008-07-16 12:55:28 +0000167 The optional ignore argument is a callable. If given, it
168 is called with the `src` parameter, which is the directory
169 being visited by copytree(), and `names` which is the list of
170 `src` contents, as returned by os.listdir():
171
172 callable(src, names) -> ignored_names
173
174 Since copytree() is called recursively, the callable will be
175 called once for each directory that is copied. It returns a
176 list of names relative to the `src` directory that should
177 not be copied.
178
Tarek Ziadé5340db32010-04-19 22:30:51 +0000179 The optional copy_function argument is a callable that will be used
180 to copy each file. It will be called with the source path and the
181 destination path as arguments. By default, copy2() is used, but any
182 function that supports the same signature (like copy()) can be used.
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000183
184 """
Guido van Rossuma2baf461997-04-29 14:06:46 +0000185 names = os.listdir(src)
Georg Brandl2ee470f2008-07-16 12:55:28 +0000186 if ignore is not None:
187 ignored_names = ignore(src, names)
188 else:
189 ignored_names = set()
190
Johannes Gijsberse4172ea2005-01-08 12:31:29 +0000191 os.makedirs(dst)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000192 errors = []
Guido van Rossuma2baf461997-04-29 14:06:46 +0000193 for name in names:
Georg Brandl2ee470f2008-07-16 12:55:28 +0000194 if name in ignored_names:
195 continue
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000196 srcname = os.path.join(src, name)
197 dstname = os.path.join(dst, name)
198 try:
Tarek Ziadéfb437512010-04-20 08:57:33 +0000199 if os.path.islink(srcname):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000200 linkto = os.readlink(srcname)
Tarek Ziadéfb437512010-04-20 08:57:33 +0000201 if symlinks:
202 os.symlink(linkto, dstname)
203 else:
204 # ignore dangling symlink if the flag is on
205 if not os.path.exists(linkto) and ignore_dangling_symlinks:
206 continue
207 # otherwise let the copy occurs. copy2 will raise an error
208 copy_function(srcname, dstname)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000209 elif os.path.isdir(srcname):
Tarek Ziadé5340db32010-04-19 22:30:51 +0000210 copytree(srcname, dstname, symlinks, ignore, copy_function)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000211 else:
Antoine Pitrou7fff0962009-05-01 21:09:44 +0000212 # Will raise a SpecialFileError for unsupported file types
Tarek Ziadé5340db32010-04-19 22:30:51 +0000213 copy_function(srcname, dstname)
Georg Brandla1be88e2005-08-31 22:48:45 +0000214 # catch the Error from the recursive copytree so that we can
215 # continue with other files
Guido van Rossumb940e112007-01-10 16:19:56 +0000216 except Error as err:
Georg Brandla1be88e2005-08-31 22:48:45 +0000217 errors.extend(err.args[0])
Antoine Pitrou7fff0962009-05-01 21:09:44 +0000218 except EnvironmentError as why:
219 errors.append((srcname, dstname, str(why)))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000220 try:
221 copystat(src, dst)
Guido van Rossumb940e112007-01-10 16:19:56 +0000222 except OSError as why:
Georg Brandl6aa2d1f2008-08-12 08:35:52 +0000223 if WindowsError is not None and isinstance(why, WindowsError):
224 # Copying file access times may fail on Windows
225 pass
226 else:
227 errors.extend((src, dst, str(why)))
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000228 if errors:
Collin Winterce36ad82007-08-30 01:19:48 +0000229 raise Error(errors)
Guido van Rossumd7673291998-02-06 21:38:09 +0000230
Barry Warsaw234d9a92003-01-24 17:36:15 +0000231def rmtree(path, ignore_errors=False, onerror=None):
Guido van Rossumd7673291998-02-06 21:38:09 +0000232 """Recursively delete a directory tree.
233
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000234 If ignore_errors is set, errors are ignored; otherwise, if onerror
235 is set, it is called to handle the error with arguments (func,
236 path, exc_info) where func is os.listdir, os.remove, or os.rmdir;
237 path is the argument to that function that caused it to fail; and
238 exc_info is a tuple returned by sys.exc_info(). If ignore_errors
239 is false and onerror is None, an exception is raised.
240
Guido van Rossumd7673291998-02-06 21:38:09 +0000241 """
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000242 if ignore_errors:
243 def onerror(*args):
Barry Warsaw234d9a92003-01-24 17:36:15 +0000244 pass
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000245 elif onerror is None:
246 def onerror(*args):
247 raise
Christian Heimes9bd667a2008-01-20 15:14:11 +0000248 try:
249 if os.path.islink(path):
250 # symlinks to directories are forbidden, see bug #1669
251 raise OSError("Cannot call rmtree on a symbolic link")
252 except OSError:
253 onerror(os.path.islink, path, sys.exc_info())
254 # can't continue even if onerror hook returns
255 return
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000256 names = []
257 try:
258 names = os.listdir(path)
Guido van Rossumb940e112007-01-10 16:19:56 +0000259 except os.error as err:
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000260 onerror(os.listdir, path, sys.exc_info())
261 for name in names:
262 fullname = os.path.join(path, name)
263 try:
264 mode = os.lstat(fullname).st_mode
265 except os.error:
266 mode = 0
267 if stat.S_ISDIR(mode):
268 rmtree(fullname, ignore_errors, onerror)
Barry Warsaw234d9a92003-01-24 17:36:15 +0000269 else:
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000270 try:
271 os.remove(fullname)
Guido van Rossumb940e112007-01-10 16:19:56 +0000272 except os.error as err:
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000273 onerror(os.remove, fullname, sys.exc_info())
274 try:
275 os.rmdir(path)
276 except os.error:
277 onerror(os.rmdir, path, sys.exc_info())
Guido van Rossumd7673291998-02-06 21:38:09 +0000278
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000279
Christian Heimesada8c3b2008-03-18 18:26:33 +0000280def _basename(path):
281 # A basename() variant which first strips the trailing slash, if present.
282 # Thus we always get the last component of the path, even for directories.
283 return os.path.basename(path.rstrip(os.path.sep))
284
285def move(src, dst):
286 """Recursively move a file or directory to another location. This is
287 similar to the Unix "mv" command.
288
289 If the destination is a directory or a symlink to a directory, the source
290 is moved inside the directory. The destination path must not already
291 exist.
292
293 If the destination already exists but is not a directory, it may be
294 overwritten depending on os.rename() semantics.
295
296 If the destination is on our current filesystem, then rename() is used.
297 Otherwise, src is copied to the destination and then removed.
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000298 A lot more could be done here... A look at a mv.c shows a lot of
299 the issues this implementation glosses over.
300
301 """
Christian Heimesada8c3b2008-03-18 18:26:33 +0000302 real_dst = dst
303 if os.path.isdir(dst):
304 real_dst = os.path.join(dst, _basename(src))
305 if os.path.exists(real_dst):
306 raise Error("Destination path '%s' already exists" % real_dst)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000307 try:
Christian Heimesada8c3b2008-03-18 18:26:33 +0000308 os.rename(src, real_dst)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000309 except OSError:
310 if os.path.isdir(src):
Benjamin Peterson247a9b82009-02-20 04:09:19 +0000311 if _destinsrc(src, dst):
Collin Winterce36ad82007-08-30 01:19:48 +0000312 raise Error("Cannot move a directory '%s' into itself '%s'." % (src, dst))
Christian Heimesada8c3b2008-03-18 18:26:33 +0000313 copytree(src, real_dst, symlinks=True)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000314 rmtree(src)
315 else:
Christian Heimesada8c3b2008-03-18 18:26:33 +0000316 copy2(src, real_dst)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000317 os.unlink(src)
Brett Cannon1c3fa182004-06-19 21:11:35 +0000318
Benjamin Peterson247a9b82009-02-20 04:09:19 +0000319def _destinsrc(src, dst):
Antoine Pitrou0dcc3cd2009-01-29 20:26:59 +0000320 src = abspath(src)
321 dst = abspath(dst)
322 if not src.endswith(os.path.sep):
323 src += os.path.sep
324 if not dst.endswith(os.path.sep):
325 dst += os.path.sep
326 return dst.startswith(src)
Tarek Ziadé396fad72010-02-23 05:30:31 +0000327
328def _get_gid(name):
329 """Returns a gid, given a group name."""
330 if getgrnam is None or name is None:
331 return None
332 try:
333 result = getgrnam(name)
334 except KeyError:
335 result = None
336 if result is not None:
337 return result[2]
338 return None
339
340def _get_uid(name):
341 """Returns an uid, given a user name."""
342 if getpwnam is None or name is None:
343 return None
344 try:
345 result = getpwnam(name)
346 except KeyError:
347 result = None
348 if result is not None:
349 return result[2]
350 return None
351
352def _make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0,
353 owner=None, group=None, logger=None):
354 """Create a (possibly compressed) tar file from all the files under
355 'base_dir'.
356
Tarek Ziadé5e2be872010-04-20 21:40:47 +0000357 'compress' must be "gzip" (the default), "bzip2", or None.
Tarek Ziadé396fad72010-02-23 05:30:31 +0000358
359 'owner' and 'group' can be used to define an owner and a group for the
360 archive that is being built. If not provided, the current owner and group
361 will be used.
362
363 The output tar file will be named 'base_dir' + ".tar", possibly plus
Tarek Ziadé5e2be872010-04-20 21:40:47 +0000364 the appropriate compression extension (".gz", or ".bz2").
Tarek Ziadé396fad72010-02-23 05:30:31 +0000365
366 Returns the output filename.
367 """
Tarek Ziadé5e2be872010-04-20 21:40:47 +0000368 tar_compression = {'gzip': 'gz', 'bzip2': 'bz2', None: ''}
369 compress_ext = {'gzip': '.gz', 'bzip2': '.bz2'}
Tarek Ziadé396fad72010-02-23 05:30:31 +0000370
371 # flags for compression program, each element of list will be an argument
372 if compress is not None and compress not in compress_ext.keys():
Tarek Ziadé5e2be872010-04-20 21:40:47 +0000373 raise ValueError("bad value for 'compress': must be None, 'gzip', or "
374 "'bzip2'")
Tarek Ziadé396fad72010-02-23 05:30:31 +0000375
Tarek Ziadé5e2be872010-04-20 21:40:47 +0000376 archive_name = base_name + '.tar' + compress_ext.get(compress, '')
Tarek Ziadé396fad72010-02-23 05:30:31 +0000377 archive_dir = os.path.dirname(archive_name)
Tarek Ziadé5e2be872010-04-20 21:40:47 +0000378
Tarek Ziadé396fad72010-02-23 05:30:31 +0000379 if not os.path.exists(archive_dir):
380 logger.info("creating %s" % archive_dir)
381 if not dry_run:
382 os.makedirs(archive_dir)
383
384
385 # creating the tarball
386 import tarfile # late import so Python build itself doesn't break
387
388 if logger is not None:
389 logger.info('Creating tar archive')
390
391 uid = _get_uid(owner)
392 gid = _get_gid(group)
393
394 def _set_uid_gid(tarinfo):
395 if gid is not None:
396 tarinfo.gid = gid
397 tarinfo.gname = group
398 if uid is not None:
399 tarinfo.uid = uid
400 tarinfo.uname = owner
401 return tarinfo
402
403 if not dry_run:
404 tar = tarfile.open(archive_name, 'w|%s' % tar_compression[compress])
405 try:
406 tar.add(base_dir, filter=_set_uid_gid)
407 finally:
408 tar.close()
409
Tarek Ziadé396fad72010-02-23 05:30:31 +0000410 return archive_name
411
Tarek Ziadée2124162010-04-21 13:35:21 +0000412def _call_external_zip(base_dir, zip_filename, verbose=False, dry_run=False):
Tarek Ziadé396fad72010-02-23 05:30:31 +0000413 # XXX see if we want to keep an external call here
414 if verbose:
415 zipoptions = "-r"
416 else:
417 zipoptions = "-rq"
418 from distutils.errors import DistutilsExecError
419 from distutils.spawn import spawn
420 try:
421 spawn(["zip", zipoptions, zip_filename, base_dir], dry_run=dry_run)
422 except DistutilsExecError:
423 # XXX really should distinguish between "couldn't find
424 # external 'zip' command" and "zip failed".
425 raise ExecError("unable to create zip file '%s': "
426 "could neither import the 'zipfile' module nor "
427 "find a standalone zip utility") % zip_filename
428
429def _make_zipfile(base_name, base_dir, verbose=0, dry_run=0, logger=None):
430 """Create a zip file from all the files under 'base_dir'.
431
432 The output zip file will be named 'base_dir' + ".zip". Uses either the
433 "zipfile" Python module (if available) or the InfoZIP "zip" utility
434 (if installed and found on the default search path). If neither tool is
435 available, raises ExecError. Returns the name of the output zip
436 file.
437 """
438 zip_filename = base_name + ".zip"
439 archive_dir = os.path.dirname(base_name)
440
441 if not os.path.exists(archive_dir):
442 if logger is not None:
443 logger.info("creating %s", archive_dir)
444 if not dry_run:
445 os.makedirs(archive_dir)
446
447 # If zipfile module is not available, try spawning an external 'zip'
448 # command.
449 try:
450 import zipfile
451 except ImportError:
452 zipfile = None
453
454 if zipfile is None:
Tarek Ziadée2124162010-04-21 13:35:21 +0000455 _call_external_zip(base_dir, zip_filename, verbose, dry_run)
Tarek Ziadé396fad72010-02-23 05:30:31 +0000456 else:
457 if logger is not None:
458 logger.info("creating '%s' and adding '%s' to it",
459 zip_filename, base_dir)
460
461 if not dry_run:
462 zip = zipfile.ZipFile(zip_filename, "w",
463 compression=zipfile.ZIP_DEFLATED)
464
465 for dirpath, dirnames, filenames in os.walk(base_dir):
466 for name in filenames:
467 path = os.path.normpath(os.path.join(dirpath, name))
468 if os.path.isfile(path):
469 zip.write(path, path)
470 if logger is not None:
471 logger.info("adding '%s'", path)
472 zip.close()
473
474 return zip_filename
475
476_ARCHIVE_FORMATS = {
477 'gztar': (_make_tarball, [('compress', 'gzip')], "gzip'ed tar-file"),
478 'bztar': (_make_tarball, [('compress', 'bzip2')], "bzip2'ed tar-file"),
Tarek Ziadé396fad72010-02-23 05:30:31 +0000479 'tar': (_make_tarball, [('compress', None)], "uncompressed tar file"),
480 'zip': (_make_zipfile, [],"ZIP file")
481 }
482
483def get_archive_formats():
484 """Returns a list of supported formats for archiving and unarchiving.
485
486 Each element of the returned sequence is a tuple (name, description)
487 """
488 formats = [(name, registry[2]) for name, registry in
489 _ARCHIVE_FORMATS.items()]
490 formats.sort()
491 return formats
492
493def register_archive_format(name, function, extra_args=None, description=''):
494 """Registers an archive format.
495
496 name is the name of the format. function is the callable that will be
497 used to create archives. If provided, extra_args is a sequence of
498 (name, value) tuples that will be passed as arguments to the callable.
499 description can be provided to describe the format, and will be returned
500 by the get_archive_formats() function.
501 """
502 if extra_args is None:
503 extra_args = []
504 if not isinstance(function, collections.Callable):
505 raise TypeError('The %s object is not callable' % function)
506 if not isinstance(extra_args, (tuple, list)):
507 raise TypeError('extra_args needs to be a sequence')
508 for element in extra_args:
509 if not isinstance(element, (tuple, list)) or len(element) !=2 :
510 raise TypeError('extra_args elements are : (arg_name, value)')
511
512 _ARCHIVE_FORMATS[name] = (function, extra_args, description)
513
514def unregister_archive_format(name):
515 del _ARCHIVE_FORMATS[name]
516
517def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
518 dry_run=0, owner=None, group=None, logger=None):
519 """Create an archive file (eg. zip or tar).
520
521 'base_name' is the name of the file to create, minus any format-specific
Tarek Ziadé5e2be872010-04-20 21:40:47 +0000522 extension; 'format' is the archive format: one of "zip", "tar", "bztar"
523 or "gztar".
Tarek Ziadé396fad72010-02-23 05:30:31 +0000524
525 'root_dir' is a directory that will be the root directory of the
526 archive; ie. we typically chdir into 'root_dir' before creating the
527 archive. 'base_dir' is the directory where we start archiving from;
528 ie. 'base_dir' will be the common prefix of all files and
529 directories in the archive. 'root_dir' and 'base_dir' both default
530 to the current directory. Returns the name of the archive file.
531
532 'owner' and 'group' are used when creating a tar archive. By default,
533 uses the current owner and group.
534 """
535 save_cwd = os.getcwd()
536 if root_dir is not None:
537 if logger is not None:
538 logger.debug("changing into '%s'", root_dir)
539 base_name = os.path.abspath(base_name)
540 if not dry_run:
541 os.chdir(root_dir)
542
543 if base_dir is None:
544 base_dir = os.curdir
545
546 kwargs = {'dry_run': dry_run, 'logger': logger}
547
548 try:
549 format_info = _ARCHIVE_FORMATS[format]
550 except KeyError:
551 raise ValueError("unknown archive format '%s'" % format)
552
553 func = format_info[0]
554 for arg, val in format_info[1]:
555 kwargs[arg] = val
556
557 if format != 'zip':
558 kwargs['owner'] = owner
559 kwargs['group'] = group
560
561 try:
562 filename = func(base_name, base_dir, **kwargs)
563 finally:
564 if root_dir is not None:
565 if logger is not None:
566 logger.debug("changing back to '%s'", save_cwd)
567 os.chdir(save_cwd)
568
569 return filename