blob: 2ddeb2a37301ee41dc5afe67ea54f1085a25b7ea [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
14
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.
55 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'):
108 os.chflags(dst, st.st_flags)
Guido van Rossumc6360141990-10-13 19:23:40 +0000109
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000110
Guido van Rossumc6360141990-10-13 19:23:40 +0000111def copy(src, dst):
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000112 """Copy data and mode bits ("cp src dst").
Tim Peters495ad3c2001-01-15 01:36:40 +0000113
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000114 The destination may be a directory.
115
116 """
Guido van Rossuma2baf461997-04-29 14:06:46 +0000117 if os.path.isdir(dst):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000118 dst = os.path.join(dst, os.path.basename(src))
Guido van Rossuma2baf461997-04-29 14:06:46 +0000119 copyfile(src, dst)
120 copymode(src, dst)
Guido van Rossumc6360141990-10-13 19:23:40 +0000121
Guido van Rossumc6360141990-10-13 19:23:40 +0000122def copy2(src, dst):
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000123 """Copy data and all stat info ("cp -p src dst").
124
125 The destination may be a directory.
126
127 """
Guido van Rossuma2baf461997-04-29 14:06:46 +0000128 if os.path.isdir(dst):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000129 dst = os.path.join(dst, os.path.basename(src))
Guido van Rossuma2baf461997-04-29 14:06:46 +0000130 copyfile(src, dst)
131 copystat(src, dst)
Guido van Rossumc6360141990-10-13 19:23:40 +0000132
Georg Brandl2ee470f2008-07-16 12:55:28 +0000133def ignore_patterns(*patterns):
134 """Function that can be used as copytree() ignore parameter.
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000135
Georg Brandl2ee470f2008-07-16 12:55:28 +0000136 Patterns is a sequence of glob-style patterns
137 that are used to exclude files"""
138 def _ignore_patterns(path, names):
139 ignored_names = []
140 for pattern in patterns:
141 ignored_names.extend(fnmatch.filter(names, pattern))
142 return set(ignored_names)
143 return _ignore_patterns
144
145def copytree(src, dst, symlinks=False, ignore=None):
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000146 """Recursively copy a directory tree using copy2().
147
148 The destination directory must not already exist.
Neal Norwitza4c93b62003-02-23 21:36:32 +0000149 If exception(s) occur, an Error is raised with a list of reasons.
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000150
151 If the optional symlinks flag is true, symbolic links in the
152 source tree result in symbolic links in the destination tree; if
153 it is false, the contents of the files pointed to by symbolic
154 links are copied.
155
Georg Brandl2ee470f2008-07-16 12:55:28 +0000156 The optional ignore argument is a callable. If given, it
157 is called with the `src` parameter, which is the directory
158 being visited by copytree(), and `names` which is the list of
159 `src` contents, as returned by os.listdir():
160
161 callable(src, names) -> ignored_names
162
163 Since copytree() is called recursively, the callable will be
164 called once for each directory that is copied. It returns a
165 list of names relative to the `src` directory that should
166 not be copied.
167
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000168 XXX Consider this example code rather than the ultimate tool.
169
170 """
Guido van Rossuma2baf461997-04-29 14:06:46 +0000171 names = os.listdir(src)
Georg Brandl2ee470f2008-07-16 12:55:28 +0000172 if ignore is not None:
173 ignored_names = ignore(src, names)
174 else:
175 ignored_names = set()
176
Johannes Gijsberse4172ea2005-01-08 12:31:29 +0000177 os.makedirs(dst)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000178 errors = []
Guido van Rossuma2baf461997-04-29 14:06:46 +0000179 for name in names:
Georg Brandl2ee470f2008-07-16 12:55:28 +0000180 if name in ignored_names:
181 continue
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000182 srcname = os.path.join(src, name)
183 dstname = os.path.join(dst, name)
184 try:
185 if symlinks and os.path.islink(srcname):
186 linkto = os.readlink(srcname)
187 os.symlink(linkto, dstname)
188 elif os.path.isdir(srcname):
Georg Brandl2ee470f2008-07-16 12:55:28 +0000189 copytree(srcname, dstname, symlinks, ignore)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000190 else:
Antoine Pitrou7fff0962009-05-01 21:09:44 +0000191 # Will raise a SpecialFileError for unsupported file types
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000192 copy2(srcname, dstname)
Georg Brandla1be88e2005-08-31 22:48:45 +0000193 # catch the Error from the recursive copytree so that we can
194 # continue with other files
Guido van Rossumb940e112007-01-10 16:19:56 +0000195 except Error as err:
Georg Brandla1be88e2005-08-31 22:48:45 +0000196 errors.extend(err.args[0])
Antoine Pitrou7fff0962009-05-01 21:09:44 +0000197 except EnvironmentError as why:
198 errors.append((srcname, dstname, str(why)))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000199 try:
200 copystat(src, dst)
Guido van Rossumb940e112007-01-10 16:19:56 +0000201 except OSError as why:
Georg Brandl6aa2d1f2008-08-12 08:35:52 +0000202 if WindowsError is not None and isinstance(why, WindowsError):
203 # Copying file access times may fail on Windows
204 pass
205 else:
206 errors.extend((src, dst, str(why)))
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000207 if errors:
Collin Winterce36ad82007-08-30 01:19:48 +0000208 raise Error(errors)
Guido van Rossumd7673291998-02-06 21:38:09 +0000209
Barry Warsaw234d9a92003-01-24 17:36:15 +0000210def rmtree(path, ignore_errors=False, onerror=None):
Guido van Rossumd7673291998-02-06 21:38:09 +0000211 """Recursively delete a directory tree.
212
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000213 If ignore_errors is set, errors are ignored; otherwise, if onerror
214 is set, it is called to handle the error with arguments (func,
215 path, exc_info) where func is os.listdir, os.remove, or os.rmdir;
216 path is the argument to that function that caused it to fail; and
217 exc_info is a tuple returned by sys.exc_info(). If ignore_errors
218 is false and onerror is None, an exception is raised.
219
Guido van Rossumd7673291998-02-06 21:38:09 +0000220 """
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000221 if ignore_errors:
222 def onerror(*args):
Barry Warsaw234d9a92003-01-24 17:36:15 +0000223 pass
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000224 elif onerror is None:
225 def onerror(*args):
226 raise
Christian Heimes9bd667a2008-01-20 15:14:11 +0000227 try:
228 if os.path.islink(path):
229 # symlinks to directories are forbidden, see bug #1669
230 raise OSError("Cannot call rmtree on a symbolic link")
231 except OSError:
232 onerror(os.path.islink, path, sys.exc_info())
233 # can't continue even if onerror hook returns
234 return
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000235 names = []
236 try:
237 names = os.listdir(path)
Guido van Rossumb940e112007-01-10 16:19:56 +0000238 except os.error as err:
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000239 onerror(os.listdir, path, sys.exc_info())
240 for name in names:
241 fullname = os.path.join(path, name)
242 try:
243 mode = os.lstat(fullname).st_mode
244 except os.error:
245 mode = 0
246 if stat.S_ISDIR(mode):
247 rmtree(fullname, ignore_errors, onerror)
Barry Warsaw234d9a92003-01-24 17:36:15 +0000248 else:
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000249 try:
250 os.remove(fullname)
Guido van Rossumb940e112007-01-10 16:19:56 +0000251 except os.error as err:
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000252 onerror(os.remove, fullname, sys.exc_info())
253 try:
254 os.rmdir(path)
255 except os.error:
256 onerror(os.rmdir, path, sys.exc_info())
Guido van Rossumd7673291998-02-06 21:38:09 +0000257
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000258
Christian Heimesada8c3b2008-03-18 18:26:33 +0000259def _basename(path):
260 # A basename() variant which first strips the trailing slash, if present.
261 # Thus we always get the last component of the path, even for directories.
262 return os.path.basename(path.rstrip(os.path.sep))
263
264def move(src, dst):
265 """Recursively move a file or directory to another location. This is
266 similar to the Unix "mv" command.
267
268 If the destination is a directory or a symlink to a directory, the source
269 is moved inside the directory. The destination path must not already
270 exist.
271
272 If the destination already exists but is not a directory, it may be
273 overwritten depending on os.rename() semantics.
274
275 If the destination is on our current filesystem, then rename() is used.
276 Otherwise, src is copied to the destination and then removed.
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000277 A lot more could be done here... A look at a mv.c shows a lot of
278 the issues this implementation glosses over.
279
280 """
Christian Heimesada8c3b2008-03-18 18:26:33 +0000281 real_dst = dst
282 if os.path.isdir(dst):
283 real_dst = os.path.join(dst, _basename(src))
284 if os.path.exists(real_dst):
285 raise Error("Destination path '%s' already exists" % real_dst)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000286 try:
Christian Heimesada8c3b2008-03-18 18:26:33 +0000287 os.rename(src, real_dst)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000288 except OSError:
289 if os.path.isdir(src):
Benjamin Peterson247a9b82009-02-20 04:09:19 +0000290 if _destinsrc(src, dst):
Collin Winterce36ad82007-08-30 01:19:48 +0000291 raise Error("Cannot move a directory '%s' into itself '%s'." % (src, dst))
Christian Heimesada8c3b2008-03-18 18:26:33 +0000292 copytree(src, real_dst, symlinks=True)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000293 rmtree(src)
294 else:
Christian Heimesada8c3b2008-03-18 18:26:33 +0000295 copy2(src, real_dst)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000296 os.unlink(src)
Brett Cannon1c3fa182004-06-19 21:11:35 +0000297
Benjamin Peterson247a9b82009-02-20 04:09:19 +0000298def _destinsrc(src, dst):
Antoine Pitrou0dcc3cd2009-01-29 20:26:59 +0000299 src = abspath(src)
300 dst = abspath(dst)
301 if not src.endswith(os.path.sep):
302 src += os.path.sep
303 if not dst.endswith(os.path.sep):
304 dst += os.path.sep
305 return dst.startswith(src)
Tarek Ziadé396fad72010-02-23 05:30:31 +0000306
307def _get_gid(name):
308 """Returns a gid, given a group name."""
309 if getgrnam is None or name is None:
310 return None
311 try:
312 result = getgrnam(name)
313 except KeyError:
314 result = None
315 if result is not None:
316 return result[2]
317 return None
318
319def _get_uid(name):
320 """Returns an uid, given a user name."""
321 if getpwnam is None or name is None:
322 return None
323 try:
324 result = getpwnam(name)
325 except KeyError:
326 result = None
327 if result is not None:
328 return result[2]
329 return None
330
331def _make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0,
332 owner=None, group=None, logger=None):
333 """Create a (possibly compressed) tar file from all the files under
334 'base_dir'.
335
336 'compress' must be "gzip" (the default), "compress", "bzip2", or None.
337 (compress will be deprecated in Python 3.2)
338
339 'owner' and 'group' can be used to define an owner and a group for the
340 archive that is being built. If not provided, the current owner and group
341 will be used.
342
343 The output tar file will be named 'base_dir' + ".tar", possibly plus
344 the appropriate compression extension (".gz", ".bz2" or ".Z").
345
346 Returns the output filename.
347 """
348 tar_compression = {'gzip': 'gz', 'bzip2': 'bz2', None: '', 'compress': ''}
349 compress_ext = {'gzip': '.gz', 'bzip2': '.bz2', 'compress': '.Z'}
350
351 # flags for compression program, each element of list will be an argument
352 if compress is not None and compress not in compress_ext.keys():
353 raise ValueError("bad value for 'compress': must be None, 'gzip', "
354 "'bzip2' or 'compress'")
355
356 archive_name = base_name + '.tar'
357 if compress != 'compress':
358 archive_name += compress_ext.get(compress, '')
359
360 archive_dir = os.path.dirname(archive_name)
361 if not os.path.exists(archive_dir):
362 logger.info("creating %s" % archive_dir)
363 if not dry_run:
364 os.makedirs(archive_dir)
365
366
367 # creating the tarball
368 import tarfile # late import so Python build itself doesn't break
369
370 if logger is not None:
371 logger.info('Creating tar archive')
372
373 uid = _get_uid(owner)
374 gid = _get_gid(group)
375
376 def _set_uid_gid(tarinfo):
377 if gid is not None:
378 tarinfo.gid = gid
379 tarinfo.gname = group
380 if uid is not None:
381 tarinfo.uid = uid
382 tarinfo.uname = owner
383 return tarinfo
384
385 if not dry_run:
386 tar = tarfile.open(archive_name, 'w|%s' % tar_compression[compress])
387 try:
388 tar.add(base_dir, filter=_set_uid_gid)
389 finally:
390 tar.close()
391
392 # compression using `compress`
393 # XXX this block will be removed in Python 3.2
394 if compress == 'compress':
395 warn("'compress' will be deprecated.", PendingDeprecationWarning)
396 # the option varies depending on the platform
397 compressed_name = archive_name + compress_ext[compress]
398 if sys.platform == 'win32':
399 cmd = [compress, archive_name, compressed_name]
400 else:
401 cmd = [compress, '-f', archive_name]
402 from distutils.spawn import spawn
403 spawn(cmd, dry_run=dry_run)
404 return compressed_name
405
406 return archive_name
407
408def _call_external_zip(directory, verbose=False):
409 # XXX see if we want to keep an external call here
410 if verbose:
411 zipoptions = "-r"
412 else:
413 zipoptions = "-rq"
414 from distutils.errors import DistutilsExecError
415 from distutils.spawn import spawn
416 try:
417 spawn(["zip", zipoptions, zip_filename, base_dir], dry_run=dry_run)
418 except DistutilsExecError:
419 # XXX really should distinguish between "couldn't find
420 # external 'zip' command" and "zip failed".
421 raise ExecError("unable to create zip file '%s': "
422 "could neither import the 'zipfile' module nor "
423 "find a standalone zip utility") % zip_filename
424
425def _make_zipfile(base_name, base_dir, verbose=0, dry_run=0, logger=None):
426 """Create a zip file from all the files under 'base_dir'.
427
428 The output zip file will be named 'base_dir' + ".zip". Uses either the
429 "zipfile" Python module (if available) or the InfoZIP "zip" utility
430 (if installed and found on the default search path). If neither tool is
431 available, raises ExecError. Returns the name of the output zip
432 file.
433 """
434 zip_filename = base_name + ".zip"
435 archive_dir = os.path.dirname(base_name)
436
437 if not os.path.exists(archive_dir):
438 if logger is not None:
439 logger.info("creating %s", archive_dir)
440 if not dry_run:
441 os.makedirs(archive_dir)
442
443 # If zipfile module is not available, try spawning an external 'zip'
444 # command.
445 try:
446 import zipfile
447 except ImportError:
448 zipfile = None
449
450 if zipfile is None:
451 _call_external_zip(base_dir, verbose)
452 else:
453 if logger is not None:
454 logger.info("creating '%s' and adding '%s' to it",
455 zip_filename, base_dir)
456
457 if not dry_run:
458 zip = zipfile.ZipFile(zip_filename, "w",
459 compression=zipfile.ZIP_DEFLATED)
460
461 for dirpath, dirnames, filenames in os.walk(base_dir):
462 for name in filenames:
463 path = os.path.normpath(os.path.join(dirpath, name))
464 if os.path.isfile(path):
465 zip.write(path, path)
466 if logger is not None:
467 logger.info("adding '%s'", path)
468 zip.close()
469
470 return zip_filename
471
472_ARCHIVE_FORMATS = {
473 'gztar': (_make_tarball, [('compress', 'gzip')], "gzip'ed tar-file"),
474 'bztar': (_make_tarball, [('compress', 'bzip2')], "bzip2'ed tar-file"),
475 'ztar': (_make_tarball, [('compress', 'compress')],
476 "compressed tar file"),
477 'tar': (_make_tarball, [('compress', None)], "uncompressed tar file"),
478 'zip': (_make_zipfile, [],"ZIP file")
479 }
480
481def get_archive_formats():
482 """Returns a list of supported formats for archiving and unarchiving.
483
484 Each element of the returned sequence is a tuple (name, description)
485 """
486 formats = [(name, registry[2]) for name, registry in
487 _ARCHIVE_FORMATS.items()]
488 formats.sort()
489 return formats
490
491def register_archive_format(name, function, extra_args=None, description=''):
492 """Registers an archive format.
493
494 name is the name of the format. function is the callable that will be
495 used to create archives. If provided, extra_args is a sequence of
496 (name, value) tuples that will be passed as arguments to the callable.
497 description can be provided to describe the format, and will be returned
498 by the get_archive_formats() function.
499 """
500 if extra_args is None:
501 extra_args = []
502 if not isinstance(function, collections.Callable):
503 raise TypeError('The %s object is not callable' % function)
504 if not isinstance(extra_args, (tuple, list)):
505 raise TypeError('extra_args needs to be a sequence')
506 for element in extra_args:
507 if not isinstance(element, (tuple, list)) or len(element) !=2 :
508 raise TypeError('extra_args elements are : (arg_name, value)')
509
510 _ARCHIVE_FORMATS[name] = (function, extra_args, description)
511
512def unregister_archive_format(name):
513 del _ARCHIVE_FORMATS[name]
514
515def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
516 dry_run=0, owner=None, group=None, logger=None):
517 """Create an archive file (eg. zip or tar).
518
519 'base_name' is the name of the file to create, minus any format-specific
520 extension; 'format' is the archive format: one of "zip", "tar", "ztar",
521 or "gztar".
522
523 'root_dir' is a directory that will be the root directory of the
524 archive; ie. we typically chdir into 'root_dir' before creating the
525 archive. 'base_dir' is the directory where we start archiving from;
526 ie. 'base_dir' will be the common prefix of all files and
527 directories in the archive. 'root_dir' and 'base_dir' both default
528 to the current directory. Returns the name of the archive file.
529
530 'owner' and 'group' are used when creating a tar archive. By default,
531 uses the current owner and group.
532 """
533 save_cwd = os.getcwd()
534 if root_dir is not None:
535 if logger is not None:
536 logger.debug("changing into '%s'", root_dir)
537 base_name = os.path.abspath(base_name)
538 if not dry_run:
539 os.chdir(root_dir)
540
541 if base_dir is None:
542 base_dir = os.curdir
543
544 kwargs = {'dry_run': dry_run, 'logger': logger}
545
546 try:
547 format_info = _ARCHIVE_FORMATS[format]
548 except KeyError:
549 raise ValueError("unknown archive format '%s'" % format)
550
551 func = format_info[0]
552 for arg, val in format_info[1]:
553 kwargs[arg] = val
554
555 if format != 'zip':
556 kwargs['owner'] = owner
557 kwargs['group'] = group
558
559 try:
560 filename = func(base_name, base_dir, **kwargs)
561 finally:
562 if root_dir is not None:
563 if logger is not None:
564 logger.debug("changing back to '%s'", save_cwd)
565 os.chdir(save_cwd)
566
567 return filename