blob: e3a79efde0270f0ff9af89387f74712865dbb69b [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 +000012from warnings import warn
13import collections
Antoine Pitrou910bd512010-03-22 20:11:09 +000014import errno
Tarek Ziadé396fad72010-02-23 05:30:31 +000015
16try:
17 from pwd import getpwnam
18except ImportError:
19 getpwnam = None
20
21try:
22 from grp import getgrnam
23except ImportError:
24 getgrnam = None
Guido van Rossumc6360141990-10-13 19:23:40 +000025
Tarek Ziadéc3399782010-02-23 05:39:18 +000026__all__ = ["copyfileobj", "copyfile", "copymode", "copystat", "copy", "copy2",
27 "copytree", "move", "rmtree", "Error", "SpecialFileError",
28 "ExecError", "make_archive", "get_archive_formats",
29 "register_archive_format", "unregister_archive_format"]
Martin v. Löwise9ce0b02002-10-07 13:23:24 +000030
Neal Norwitz4ce69a52005-09-01 00:45:28 +000031class Error(EnvironmentError):
Martin v. Löwise9ce0b02002-10-07 13:23:24 +000032 pass
Guido van Rossumc6360141990-10-13 19:23:40 +000033
Antoine Pitrou7fff0962009-05-01 21:09:44 +000034class SpecialFileError(EnvironmentError):
35 """Raised when trying to do a kind of operation (e.g. copying) which is
36 not supported on a special file (e.g. a named pipe)"""
37
Tarek Ziadé396fad72010-02-23 05:30:31 +000038class ExecError(EnvironmentError):
39 """Raised when a command could not be executed"""
40
Georg Brandl6aa2d1f2008-08-12 08:35:52 +000041try:
42 WindowsError
43except NameError:
44 WindowsError = None
45
Greg Stein42bb8b32000-07-12 09:55:30 +000046def copyfileobj(fsrc, fdst, length=16*1024):
47 """copy data from file-like object fsrc to file-like object fdst"""
48 while 1:
49 buf = fsrc.read(length)
50 if not buf:
51 break
52 fdst.write(buf)
53
Johannes Gijsbers46f14592004-08-14 13:30:02 +000054def _samefile(src, dst):
55 # Macintosh, Unix.
Tarek Ziadé1eab9cc2010-04-19 21:19:57 +000056 if hasattr(os.path, 'samefile'):
Johannes Gijsbersf9a098e2004-08-14 14:51:01 +000057 try:
58 return os.path.samefile(src, dst)
59 except OSError:
60 return False
Johannes Gijsbers46f14592004-08-14 13:30:02 +000061
62 # All other platforms: check for same pathname.
63 return (os.path.normcase(os.path.abspath(src)) ==
64 os.path.normcase(os.path.abspath(dst)))
Tim Peters495ad3c2001-01-15 01:36:40 +000065
Guido van Rossumc6360141990-10-13 19:23:40 +000066def copyfile(src, dst):
Guido van Rossum9d0a3df1997-04-29 14:45:19 +000067 """Copy data from src to dst"""
Johannes Gijsbers46f14592004-08-14 13:30:02 +000068 if _samefile(src, dst):
Collin Winterce36ad82007-08-30 01:19:48 +000069 raise Error("`%s` and `%s` are the same file" % (src, dst))
Johannes Gijsbers46f14592004-08-14 13:30:02 +000070
Guido van Rossuma2baf461997-04-29 14:06:46 +000071 fsrc = None
72 fdst = None
Antoine Pitrou7fff0962009-05-01 21:09:44 +000073 for fn in [src, dst]:
74 try:
75 st = os.stat(fn)
76 except OSError:
77 # File most likely does not exist
78 pass
Benjamin Petersonc0d98aa2009-06-05 19:13:27 +000079 else:
80 # XXX What about other special files? (sockets, devices...)
81 if stat.S_ISFIFO(st.st_mode):
82 raise SpecialFileError("`%s` is a named pipe" % fn)
Guido van Rossuma2baf461997-04-29 14:06:46 +000083 try:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000084 fsrc = open(src, 'rb')
85 fdst = open(dst, 'wb')
Greg Stein42bb8b32000-07-12 09:55:30 +000086 copyfileobj(fsrc, fdst)
Guido van Rossuma2baf461997-04-29 14:06:46 +000087 finally:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000088 if fdst:
89 fdst.close()
90 if fsrc:
91 fsrc.close()
Guido van Rossumc6360141990-10-13 19:23:40 +000092
Guido van Rossumc6360141990-10-13 19:23:40 +000093def copymode(src, dst):
Guido van Rossum9d0a3df1997-04-29 14:45:19 +000094 """Copy mode bits from src to dst"""
Tim Peters0c947242001-01-21 20:00:00 +000095 if hasattr(os, 'chmod'):
96 st = os.stat(src)
Walter Dörwald294bbf32002-06-06 09:48:13 +000097 mode = stat.S_IMODE(st.st_mode)
Tim Peters0c947242001-01-21 20:00:00 +000098 os.chmod(dst, mode)
Guido van Rossumc6360141990-10-13 19:23:40 +000099
Guido van Rossumc6360141990-10-13 19:23:40 +0000100def copystat(src, dst):
Thomas Wouterscf297e42007-02-23 15:07:44 +0000101 """Copy all stat info (mode bits, atime, mtime, flags) from src to dst"""
Guido van Rossuma2baf461997-04-29 14:06:46 +0000102 st = os.stat(src)
Walter Dörwald294bbf32002-06-06 09:48:13 +0000103 mode = stat.S_IMODE(st.st_mode)
Tim Peters0c947242001-01-21 20:00:00 +0000104 if hasattr(os, 'utime'):
Walter Dörwald294bbf32002-06-06 09:48:13 +0000105 os.utime(dst, (st.st_atime, st.st_mtime))
Tim Peters0c947242001-01-21 20:00:00 +0000106 if hasattr(os, 'chmod'):
107 os.chmod(dst, mode)
Thomas Wouterscf297e42007-02-23 15:07:44 +0000108 if hasattr(os, 'chflags') and hasattr(st, 'st_flags'):
Antoine Pitrou910bd512010-03-22 20:11:09 +0000109 try:
110 os.chflags(dst, st.st_flags)
111 except OSError as why:
Tarek Ziadé1eab9cc2010-04-19 21:19:57 +0000112 if (not hasattr(errno, 'EOPNOTSUPP') or
113 why.errno != errno.EOPNOTSUPP):
Antoine Pitrou910bd512010-03-22 20:11:09 +0000114 raise
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000115
Guido van Rossumc6360141990-10-13 19:23:40 +0000116def copy(src, dst):
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000117 """Copy data and mode bits ("cp src dst").
Tim Peters495ad3c2001-01-15 01:36:40 +0000118
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000119 The destination may be a directory.
120
121 """
Guido van Rossuma2baf461997-04-29 14:06:46 +0000122 if os.path.isdir(dst):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000123 dst = os.path.join(dst, os.path.basename(src))
Guido van Rossuma2baf461997-04-29 14:06:46 +0000124 copyfile(src, dst)
125 copymode(src, dst)
Guido van Rossumc6360141990-10-13 19:23:40 +0000126
Guido van Rossumc6360141990-10-13 19:23:40 +0000127def copy2(src, dst):
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000128 """Copy data and all stat info ("cp -p src dst").
129
130 The destination may be a directory.
131
132 """
Guido van Rossuma2baf461997-04-29 14:06:46 +0000133 if os.path.isdir(dst):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000134 dst = os.path.join(dst, os.path.basename(src))
Guido van Rossuma2baf461997-04-29 14:06:46 +0000135 copyfile(src, dst)
136 copystat(src, dst)
Guido van Rossumc6360141990-10-13 19:23:40 +0000137
Georg Brandl2ee470f2008-07-16 12:55:28 +0000138def ignore_patterns(*patterns):
139 """Function that can be used as copytree() ignore parameter.
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000140
Georg Brandl2ee470f2008-07-16 12:55:28 +0000141 Patterns is a sequence of glob-style patterns
142 that are used to exclude files"""
143 def _ignore_patterns(path, names):
144 ignored_names = []
145 for pattern in patterns:
146 ignored_names.extend(fnmatch.filter(names, pattern))
147 return set(ignored_names)
148 return _ignore_patterns
149
Tarek Ziadéfb437512010-04-20 08:57:33 +0000150def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2,
151 ignore_dangling_symlinks=False):
Tarek Ziadé5340db32010-04-19 22:30:51 +0000152 """Recursively copy a directory tree.
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000153
154 The destination directory must not already exist.
Neal Norwitza4c93b62003-02-23 21:36:32 +0000155 If exception(s) occur, an Error is raised with a list of reasons.
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000156
157 If the optional symlinks flag is true, symbolic links in the
158 source tree result in symbolic links in the destination tree; if
159 it is false, the contents of the files pointed to by symbolic
Tarek Ziadéfb437512010-04-20 08:57:33 +0000160 links are copied. If the file pointed by the symlink doesn't
161 exist, an exception will be added in the list of errors raised in
162 an Error exception at the end of the copy process.
163
164 You can set the optional ignore_dangling_symlinks flag to true if you
165 want to silent this exception.
166
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000167
Georg Brandl2ee470f2008-07-16 12:55:28 +0000168 The optional ignore argument is a callable. If given, it
169 is called with the `src` parameter, which is the directory
170 being visited by copytree(), and `names` which is the list of
171 `src` contents, as returned by os.listdir():
172
173 callable(src, names) -> ignored_names
174
175 Since copytree() is called recursively, the callable will be
176 called once for each directory that is copied. It returns a
177 list of names relative to the `src` directory that should
178 not be copied.
179
Tarek Ziadé5340db32010-04-19 22:30:51 +0000180 The optional copy_function argument is a callable that will be used
181 to copy each file. It will be called with the source path and the
182 destination path as arguments. By default, copy2() is used, but any
183 function that supports the same signature (like copy()) can be used.
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000184
185 """
Guido van Rossuma2baf461997-04-29 14:06:46 +0000186 names = os.listdir(src)
Georg Brandl2ee470f2008-07-16 12:55:28 +0000187 if ignore is not None:
188 ignored_names = ignore(src, names)
189 else:
190 ignored_names = set()
191
Johannes Gijsberse4172ea2005-01-08 12:31:29 +0000192 os.makedirs(dst)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000193 errors = []
Guido van Rossuma2baf461997-04-29 14:06:46 +0000194 for name in names:
Georg Brandl2ee470f2008-07-16 12:55:28 +0000195 if name in ignored_names:
196 continue
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000197 srcname = os.path.join(src, name)
198 dstname = os.path.join(dst, name)
199 try:
Tarek Ziadéfb437512010-04-20 08:57:33 +0000200 if os.path.islink(srcname):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000201 linkto = os.readlink(srcname)
Tarek Ziadéfb437512010-04-20 08:57:33 +0000202 if symlinks:
203 os.symlink(linkto, dstname)
204 else:
205 # ignore dangling symlink if the flag is on
206 if not os.path.exists(linkto) and ignore_dangling_symlinks:
207 continue
208 # otherwise let the copy occurs. copy2 will raise an error
209 copy_function(srcname, dstname)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000210 elif os.path.isdir(srcname):
Tarek Ziadé5340db32010-04-19 22:30:51 +0000211 copytree(srcname, dstname, symlinks, ignore, copy_function)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000212 else:
Antoine Pitrou7fff0962009-05-01 21:09:44 +0000213 # Will raise a SpecialFileError for unsupported file types
Tarek Ziadé5340db32010-04-19 22:30:51 +0000214 copy_function(srcname, dstname)
Georg Brandla1be88e2005-08-31 22:48:45 +0000215 # catch the Error from the recursive copytree so that we can
216 # continue with other files
Guido van Rossumb940e112007-01-10 16:19:56 +0000217 except Error as err:
Georg Brandla1be88e2005-08-31 22:48:45 +0000218 errors.extend(err.args[0])
Antoine Pitrou7fff0962009-05-01 21:09:44 +0000219 except EnvironmentError as why:
220 errors.append((srcname, dstname, str(why)))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000221 try:
222 copystat(src, dst)
Guido van Rossumb940e112007-01-10 16:19:56 +0000223 except OSError as why:
Georg Brandl6aa2d1f2008-08-12 08:35:52 +0000224 if WindowsError is not None and isinstance(why, WindowsError):
225 # Copying file access times may fail on Windows
226 pass
227 else:
228 errors.extend((src, dst, str(why)))
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000229 if errors:
Collin Winterce36ad82007-08-30 01:19:48 +0000230 raise Error(errors)
Guido van Rossumd7673291998-02-06 21:38:09 +0000231
Barry Warsaw234d9a92003-01-24 17:36:15 +0000232def rmtree(path, ignore_errors=False, onerror=None):
Guido van Rossumd7673291998-02-06 21:38:09 +0000233 """Recursively delete a directory tree.
234
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000235 If ignore_errors is set, errors are ignored; otherwise, if onerror
236 is set, it is called to handle the error with arguments (func,
237 path, exc_info) where func is os.listdir, os.remove, or os.rmdir;
238 path is the argument to that function that caused it to fail; and
239 exc_info is a tuple returned by sys.exc_info(). If ignore_errors
240 is false and onerror is None, an exception is raised.
241
Guido van Rossumd7673291998-02-06 21:38:09 +0000242 """
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000243 if ignore_errors:
244 def onerror(*args):
Barry Warsaw234d9a92003-01-24 17:36:15 +0000245 pass
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000246 elif onerror is None:
247 def onerror(*args):
248 raise
Christian Heimes9bd667a2008-01-20 15:14:11 +0000249 try:
250 if os.path.islink(path):
251 # symlinks to directories are forbidden, see bug #1669
252 raise OSError("Cannot call rmtree on a symbolic link")
253 except OSError:
254 onerror(os.path.islink, path, sys.exc_info())
255 # can't continue even if onerror hook returns
256 return
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000257 names = []
258 try:
259 names = os.listdir(path)
Guido van Rossumb940e112007-01-10 16:19:56 +0000260 except os.error as err:
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000261 onerror(os.listdir, path, sys.exc_info())
262 for name in names:
263 fullname = os.path.join(path, name)
264 try:
265 mode = os.lstat(fullname).st_mode
266 except os.error:
267 mode = 0
268 if stat.S_ISDIR(mode):
269 rmtree(fullname, ignore_errors, onerror)
Barry Warsaw234d9a92003-01-24 17:36:15 +0000270 else:
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000271 try:
272 os.remove(fullname)
Guido van Rossumb940e112007-01-10 16:19:56 +0000273 except os.error as err:
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000274 onerror(os.remove, fullname, sys.exc_info())
275 try:
276 os.rmdir(path)
277 except os.error:
278 onerror(os.rmdir, path, sys.exc_info())
Guido van Rossumd7673291998-02-06 21:38:09 +0000279
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000280
Christian Heimesada8c3b2008-03-18 18:26:33 +0000281def _basename(path):
282 # A basename() variant which first strips the trailing slash, if present.
283 # Thus we always get the last component of the path, even for directories.
284 return os.path.basename(path.rstrip(os.path.sep))
285
286def move(src, dst):
287 """Recursively move a file or directory to another location. This is
288 similar to the Unix "mv" command.
289
290 If the destination is a directory or a symlink to a directory, the source
291 is moved inside the directory. The destination path must not already
292 exist.
293
294 If the destination already exists but is not a directory, it may be
295 overwritten depending on os.rename() semantics.
296
297 If the destination is on our current filesystem, then rename() is used.
298 Otherwise, src is copied to the destination and then removed.
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000299 A lot more could be done here... A look at a mv.c shows a lot of
300 the issues this implementation glosses over.
301
302 """
Christian Heimesada8c3b2008-03-18 18:26:33 +0000303 real_dst = dst
304 if os.path.isdir(dst):
305 real_dst = os.path.join(dst, _basename(src))
306 if os.path.exists(real_dst):
307 raise Error("Destination path '%s' already exists" % real_dst)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000308 try:
Christian Heimesada8c3b2008-03-18 18:26:33 +0000309 os.rename(src, real_dst)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000310 except OSError:
311 if os.path.isdir(src):
Benjamin Peterson247a9b82009-02-20 04:09:19 +0000312 if _destinsrc(src, dst):
Collin Winterce36ad82007-08-30 01:19:48 +0000313 raise Error("Cannot move a directory '%s' into itself '%s'." % (src, dst))
Christian Heimesada8c3b2008-03-18 18:26:33 +0000314 copytree(src, real_dst, symlinks=True)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000315 rmtree(src)
316 else:
Christian Heimesada8c3b2008-03-18 18:26:33 +0000317 copy2(src, real_dst)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000318 os.unlink(src)
Brett Cannon1c3fa182004-06-19 21:11:35 +0000319
Benjamin Peterson247a9b82009-02-20 04:09:19 +0000320def _destinsrc(src, dst):
Antoine Pitrou0dcc3cd2009-01-29 20:26:59 +0000321 src = abspath(src)
322 dst = abspath(dst)
323 if not src.endswith(os.path.sep):
324 src += os.path.sep
325 if not dst.endswith(os.path.sep):
326 dst += os.path.sep
327 return dst.startswith(src)
Tarek Ziadé396fad72010-02-23 05:30:31 +0000328
329def _get_gid(name):
330 """Returns a gid, given a group name."""
331 if getgrnam is None or name is None:
332 return None
333 try:
334 result = getgrnam(name)
335 except KeyError:
336 result = None
337 if result is not None:
338 return result[2]
339 return None
340
341def _get_uid(name):
342 """Returns an uid, given a user name."""
343 if getpwnam is None or name is None:
344 return None
345 try:
346 result = getpwnam(name)
347 except KeyError:
348 result = None
349 if result is not None:
350 return result[2]
351 return None
352
353def _make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0,
354 owner=None, group=None, logger=None):
355 """Create a (possibly compressed) tar file from all the files under
356 'base_dir'.
357
358 'compress' must be "gzip" (the default), "compress", "bzip2", or None.
359 (compress will be deprecated in Python 3.2)
360
361 'owner' and 'group' can be used to define an owner and a group for the
362 archive that is being built. If not provided, the current owner and group
363 will be used.
364
365 The output tar file will be named 'base_dir' + ".tar", possibly plus
366 the appropriate compression extension (".gz", ".bz2" or ".Z").
367
368 Returns the output filename.
369 """
370 tar_compression = {'gzip': 'gz', 'bzip2': 'bz2', None: '', 'compress': ''}
371 compress_ext = {'gzip': '.gz', 'bzip2': '.bz2', 'compress': '.Z'}
372
373 # flags for compression program, each element of list will be an argument
374 if compress is not None and compress not in compress_ext.keys():
375 raise ValueError("bad value for 'compress': must be None, 'gzip', "
376 "'bzip2' or 'compress'")
377
378 archive_name = base_name + '.tar'
379 if compress != 'compress':
380 archive_name += compress_ext.get(compress, '')
381
382 archive_dir = os.path.dirname(archive_name)
383 if not os.path.exists(archive_dir):
384 logger.info("creating %s" % archive_dir)
385 if not dry_run:
386 os.makedirs(archive_dir)
387
388
389 # creating the tarball
390 import tarfile # late import so Python build itself doesn't break
391
392 if logger is not None:
393 logger.info('Creating tar archive')
394
395 uid = _get_uid(owner)
396 gid = _get_gid(group)
397
398 def _set_uid_gid(tarinfo):
399 if gid is not None:
400 tarinfo.gid = gid
401 tarinfo.gname = group
402 if uid is not None:
403 tarinfo.uid = uid
404 tarinfo.uname = owner
405 return tarinfo
406
407 if not dry_run:
408 tar = tarfile.open(archive_name, 'w|%s' % tar_compression[compress])
409 try:
410 tar.add(base_dir, filter=_set_uid_gid)
411 finally:
412 tar.close()
413
414 # compression using `compress`
415 # XXX this block will be removed in Python 3.2
416 if compress == 'compress':
417 warn("'compress' will be deprecated.", PendingDeprecationWarning)
418 # the option varies depending on the platform
419 compressed_name = archive_name + compress_ext[compress]
420 if sys.platform == 'win32':
421 cmd = [compress, archive_name, compressed_name]
422 else:
423 cmd = [compress, '-f', archive_name]
424 from distutils.spawn import spawn
425 spawn(cmd, dry_run=dry_run)
426 return compressed_name
427
428 return archive_name
429
430def _call_external_zip(directory, verbose=False):
431 # XXX see if we want to keep an external call here
432 if verbose:
433 zipoptions = "-r"
434 else:
435 zipoptions = "-rq"
436 from distutils.errors import DistutilsExecError
437 from distutils.spawn import spawn
438 try:
439 spawn(["zip", zipoptions, zip_filename, base_dir], dry_run=dry_run)
440 except DistutilsExecError:
441 # XXX really should distinguish between "couldn't find
442 # external 'zip' command" and "zip failed".
443 raise ExecError("unable to create zip file '%s': "
444 "could neither import the 'zipfile' module nor "
445 "find a standalone zip utility") % zip_filename
446
447def _make_zipfile(base_name, base_dir, verbose=0, dry_run=0, logger=None):
448 """Create a zip file from all the files under 'base_dir'.
449
450 The output zip file will be named 'base_dir' + ".zip". Uses either the
451 "zipfile" Python module (if available) or the InfoZIP "zip" utility
452 (if installed and found on the default search path). If neither tool is
453 available, raises ExecError. Returns the name of the output zip
454 file.
455 """
456 zip_filename = base_name + ".zip"
457 archive_dir = os.path.dirname(base_name)
458
459 if not os.path.exists(archive_dir):
460 if logger is not None:
461 logger.info("creating %s", archive_dir)
462 if not dry_run:
463 os.makedirs(archive_dir)
464
465 # If zipfile module is not available, try spawning an external 'zip'
466 # command.
467 try:
468 import zipfile
469 except ImportError:
470 zipfile = None
471
472 if zipfile is None:
473 _call_external_zip(base_dir, verbose)
474 else:
475 if logger is not None:
476 logger.info("creating '%s' and adding '%s' to it",
477 zip_filename, base_dir)
478
479 if not dry_run:
480 zip = zipfile.ZipFile(zip_filename, "w",
481 compression=zipfile.ZIP_DEFLATED)
482
483 for dirpath, dirnames, filenames in os.walk(base_dir):
484 for name in filenames:
485 path = os.path.normpath(os.path.join(dirpath, name))
486 if os.path.isfile(path):
487 zip.write(path, path)
488 if logger is not None:
489 logger.info("adding '%s'", path)
490 zip.close()
491
492 return zip_filename
493
494_ARCHIVE_FORMATS = {
495 'gztar': (_make_tarball, [('compress', 'gzip')], "gzip'ed tar-file"),
496 'bztar': (_make_tarball, [('compress', 'bzip2')], "bzip2'ed tar-file"),
497 'ztar': (_make_tarball, [('compress', 'compress')],
498 "compressed tar file"),
499 'tar': (_make_tarball, [('compress', None)], "uncompressed tar file"),
500 'zip': (_make_zipfile, [],"ZIP file")
501 }
502
503def get_archive_formats():
504 """Returns a list of supported formats for archiving and unarchiving.
505
506 Each element of the returned sequence is a tuple (name, description)
507 """
508 formats = [(name, registry[2]) for name, registry in
509 _ARCHIVE_FORMATS.items()]
510 formats.sort()
511 return formats
512
513def register_archive_format(name, function, extra_args=None, description=''):
514 """Registers an archive format.
515
516 name is the name of the format. function is the callable that will be
517 used to create archives. If provided, extra_args is a sequence of
518 (name, value) tuples that will be passed as arguments to the callable.
519 description can be provided to describe the format, and will be returned
520 by the get_archive_formats() function.
521 """
522 if extra_args is None:
523 extra_args = []
524 if not isinstance(function, collections.Callable):
525 raise TypeError('The %s object is not callable' % function)
526 if not isinstance(extra_args, (tuple, list)):
527 raise TypeError('extra_args needs to be a sequence')
528 for element in extra_args:
529 if not isinstance(element, (tuple, list)) or len(element) !=2 :
530 raise TypeError('extra_args elements are : (arg_name, value)')
531
532 _ARCHIVE_FORMATS[name] = (function, extra_args, description)
533
534def unregister_archive_format(name):
535 del _ARCHIVE_FORMATS[name]
536
537def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
538 dry_run=0, owner=None, group=None, logger=None):
539 """Create an archive file (eg. zip or tar).
540
541 'base_name' is the name of the file to create, minus any format-specific
542 extension; 'format' is the archive format: one of "zip", "tar", "ztar",
Tarek Ziadé10a51af2010-04-19 21:31:42 +0000543 "bztar" or "gztar".
Tarek Ziadé396fad72010-02-23 05:30:31 +0000544
545 'root_dir' is a directory that will be the root directory of the
546 archive; ie. we typically chdir into 'root_dir' before creating the
547 archive. 'base_dir' is the directory where we start archiving from;
548 ie. 'base_dir' will be the common prefix of all files and
549 directories in the archive. 'root_dir' and 'base_dir' both default
550 to the current directory. Returns the name of the archive file.
551
552 'owner' and 'group' are used when creating a tar archive. By default,
553 uses the current owner and group.
554 """
555 save_cwd = os.getcwd()
556 if root_dir is not None:
557 if logger is not None:
558 logger.debug("changing into '%s'", root_dir)
559 base_name = os.path.abspath(base_name)
560 if not dry_run:
561 os.chdir(root_dir)
562
563 if base_dir is None:
564 base_dir = os.curdir
565
566 kwargs = {'dry_run': dry_run, 'logger': logger}
567
568 try:
569 format_info = _ARCHIVE_FORMATS[format]
570 except KeyError:
571 raise ValueError("unknown archive format '%s'" % format)
572
573 func = format_info[0]
574 for arg, val in format_info[1]:
575 kwargs[arg] = val
576
577 if format != 'zip':
578 kwargs['owner'] = owner
579 kwargs['group'] = group
580
581 try:
582 filename = func(base_name, base_dir, **kwargs)
583 finally:
584 if root_dir is not None:
585 if logger is not None:
586 logger.debug("changing back to '%s'", save_cwd)
587 os.chdir(save_cwd)
588
589 return filename