blob: 5cfea63b8598e83e1655a8a052afec8815524298 [file] [log] [blame]
Tarek Ziadé2900c442010-02-23 05:36:41 +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 Brandle78fbcc2008-07-05 10:13:36 +000011import fnmatch
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +000012from warnings import warn
Florent Xicluna1f3b4e12010-03-07 12:14:25 +000013import collections
Antoine Pitrou513d9ae2010-03-22 19:59:46 +000014import errno
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +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é2900c442010-02-23 05:36:41 +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 Pitrou1fc02312009-05-01 20:55:35 +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é48cc8dc2010-02-23 05:16:41 +000038class ExecError(EnvironmentError):
39 """Raised when a command could not be executed"""
40
Antoine Pitrou9fcd4b32008-08-11 17:21:36 +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éf1c28b72010-04-19 21:13:03 +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):
Tarek Ziadéf1c28b72010-04-19 21:13:03 +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 Pitrou1fc02312009-05-01 20:55:35 +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 Petersona663a372009-06-05 19:09:28 +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):
Martin v. Löwis382abef2007-02-19 10:55:19 +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)
Martin v. Löwis382abef2007-02-19 10:55:19 +0000108 if hasattr(os, 'chflags') and hasattr(st, 'st_flags'):
Antoine Pitrou513d9ae2010-03-22 19:59:46 +0000109 try:
110 os.chflags(dst, st.st_flags)
111 except OSError, why:
Tarek Ziadéf1c28b72010-04-19 21:13:03 +0000112 if (not hasattr(errno, 'EOPNOTSUPP') or
113 why.errno != errno.EOPNOTSUPP):
Antoine Pitrou513d9ae2010-03-22 19:59:46 +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 Brandle78fbcc2008-07-05 10:13:36 +0000138def ignore_patterns(*patterns):
139 """Function that can be used as copytree() ignore parameter.
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000140
Georg Brandle78fbcc2008-07-05 10:13:36 +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
150def copytree(src, dst, symlinks=False, ignore=None):
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000151 """Recursively copy a directory tree using copy2().
152
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
159 links are copied.
160
Georg Brandle78fbcc2008-07-05 10:13:36 +0000161 The optional ignore argument is a callable. If given, it
162 is called with the `src` parameter, which is the directory
163 being visited by copytree(), and `names` which is the list of
164 `src` contents, as returned by os.listdir():
165
166 callable(src, names) -> ignored_names
167
168 Since copytree() is called recursively, the callable will be
169 called once for each directory that is copied. It returns a
170 list of names relative to the `src` directory that should
171 not be copied.
172
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000173 XXX Consider this example code rather than the ultimate tool.
174
175 """
Guido van Rossuma2baf461997-04-29 14:06:46 +0000176 names = os.listdir(src)
Georg Brandle78fbcc2008-07-05 10:13:36 +0000177 if ignore is not None:
178 ignored_names = ignore(src, names)
179 else:
180 ignored_names = set()
181
Johannes Gijsberse4172ea2005-01-08 12:31:29 +0000182 os.makedirs(dst)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000183 errors = []
Guido van Rossuma2baf461997-04-29 14:06:46 +0000184 for name in names:
Georg Brandle78fbcc2008-07-05 10:13:36 +0000185 if name in ignored_names:
186 continue
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000187 srcname = os.path.join(src, name)
188 dstname = os.path.join(dst, name)
189 try:
190 if symlinks and os.path.islink(srcname):
191 linkto = os.readlink(srcname)
192 os.symlink(linkto, dstname)
193 elif os.path.isdir(srcname):
Georg Brandle78fbcc2008-07-05 10:13:36 +0000194 copytree(srcname, dstname, symlinks, ignore)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000195 else:
Antoine Pitrou1fc02312009-05-01 20:55:35 +0000196 # Will raise a SpecialFileError for unsupported file types
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000197 copy2(srcname, dstname)
Georg Brandla1be88e2005-08-31 22:48:45 +0000198 # catch the Error from the recursive copytree so that we can
199 # continue with other files
200 except Error, err:
201 errors.extend(err.args[0])
Antoine Pitrou1fc02312009-05-01 20:55:35 +0000202 except EnvironmentError, why:
203 errors.append((srcname, dstname, str(why)))
Martin v. Löwis4e678382006-07-30 13:00:31 +0000204 try:
205 copystat(src, dst)
Martin v. Löwis4e678382006-07-30 13:00:31 +0000206 except OSError, why:
Antoine Pitrou9fcd4b32008-08-11 17:21:36 +0000207 if WindowsError is not None and isinstance(why, WindowsError):
208 # Copying file access times may fail on Windows
209 pass
210 else:
211 errors.extend((src, dst, str(why)))
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000212 if errors:
213 raise Error, errors
Guido van Rossumd7673291998-02-06 21:38:09 +0000214
Barry Warsaw234d9a92003-01-24 17:36:15 +0000215def rmtree(path, ignore_errors=False, onerror=None):
Guido van Rossumd7673291998-02-06 21:38:09 +0000216 """Recursively delete a directory tree.
217
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000218 If ignore_errors is set, errors are ignored; otherwise, if onerror
219 is set, it is called to handle the error with arguments (func,
220 path, exc_info) where func is os.listdir, os.remove, or os.rmdir;
221 path is the argument to that function that caused it to fail; and
222 exc_info is a tuple returned by sys.exc_info(). If ignore_errors
223 is false and onerror is None, an exception is raised.
224
Guido van Rossumd7673291998-02-06 21:38:09 +0000225 """
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000226 if ignore_errors:
227 def onerror(*args):
Barry Warsaw234d9a92003-01-24 17:36:15 +0000228 pass
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000229 elif onerror is None:
230 def onerror(*args):
231 raise
Georg Brandl52353982008-01-20 14:17:42 +0000232 try:
233 if os.path.islink(path):
234 # symlinks to directories are forbidden, see bug #1669
235 raise OSError("Cannot call rmtree on a symbolic link")
236 except OSError:
237 onerror(os.path.islink, path, sys.exc_info())
238 # can't continue even if onerror hook returns
239 return
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000240 names = []
241 try:
242 names = os.listdir(path)
243 except os.error, err:
244 onerror(os.listdir, path, sys.exc_info())
245 for name in names:
246 fullname = os.path.join(path, name)
247 try:
248 mode = os.lstat(fullname).st_mode
249 except os.error:
250 mode = 0
251 if stat.S_ISDIR(mode):
252 rmtree(fullname, ignore_errors, onerror)
Barry Warsaw234d9a92003-01-24 17:36:15 +0000253 else:
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000254 try:
255 os.remove(fullname)
256 except os.error, err:
257 onerror(os.remove, fullname, sys.exc_info())
258 try:
259 os.rmdir(path)
260 except os.error:
261 onerror(os.rmdir, path, sys.exc_info())
Guido van Rossumd7673291998-02-06 21:38:09 +0000262
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000263
Sean Reifscheider493894c2008-03-18 17:24:12 +0000264def _basename(path):
265 # A basename() variant which first strips the trailing slash, if present.
266 # Thus we always get the last component of the path, even for directories.
267 return os.path.basename(path.rstrip(os.path.sep))
268
269def move(src, dst):
270 """Recursively move a file or directory to another location. This is
271 similar to the Unix "mv" command.
272
273 If the destination is a directory or a symlink to a directory, the source
274 is moved inside the directory. The destination path must not already
275 exist.
276
277 If the destination already exists but is not a directory, it may be
278 overwritten depending on os.rename() semantics.
279
280 If the destination is on our current filesystem, then rename() is used.
281 Otherwise, src is copied to the destination and then removed.
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000282 A lot more could be done here... A look at a mv.c shows a lot of
283 the issues this implementation glosses over.
284
285 """
Sean Reifscheider493894c2008-03-18 17:24:12 +0000286 real_dst = dst
287 if os.path.isdir(dst):
288 real_dst = os.path.join(dst, _basename(src))
289 if os.path.exists(real_dst):
290 raise Error, "Destination path '%s' already exists" % real_dst
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000291 try:
Sean Reifscheider493894c2008-03-18 17:24:12 +0000292 os.rename(src, real_dst)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000293 except OSError:
294 if os.path.isdir(src):
Benjamin Peterson096c3ad2009-02-07 19:08:22 +0000295 if _destinsrc(src, dst):
Brett Cannon1c3fa182004-06-19 21:11:35 +0000296 raise Error, "Cannot move a directory '%s' into itself '%s'." % (src, dst)
Sean Reifscheider493894c2008-03-18 17:24:12 +0000297 copytree(src, real_dst, symlinks=True)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000298 rmtree(src)
299 else:
Sean Reifscheider493894c2008-03-18 17:24:12 +0000300 copy2(src, real_dst)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000301 os.unlink(src)
Brett Cannon1c3fa182004-06-19 21:11:35 +0000302
Benjamin Peterson096c3ad2009-02-07 19:08:22 +0000303def _destinsrc(src, dst):
Antoine Pitrou707c5932009-01-29 20:19:34 +0000304 src = abspath(src)
305 dst = abspath(dst)
306 if not src.endswith(os.path.sep):
307 src += os.path.sep
308 if not dst.endswith(os.path.sep):
309 dst += os.path.sep
310 return dst.startswith(src)
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000311
312def _get_gid(name):
313 """Returns a gid, given a group name."""
314 if getgrnam is None or name is None:
315 return None
316 try:
317 result = getgrnam(name)
318 except KeyError:
319 result = None
320 if result is not None:
321 return result[2]
322 return None
323
324def _get_uid(name):
325 """Returns an uid, given a user name."""
326 if getpwnam is None or name is None:
327 return None
328 try:
329 result = getpwnam(name)
330 except KeyError:
331 result = None
332 if result is not None:
333 return result[2]
334 return None
335
336def _make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0,
337 owner=None, group=None, logger=None):
338 """Create a (possibly compressed) tar file from all the files under
339 'base_dir'.
340
341 'compress' must be "gzip" (the default), "compress", "bzip2", or None.
342 (compress will be deprecated in Python 3.2)
343
344 'owner' and 'group' can be used to define an owner and a group for the
345 archive that is being built. If not provided, the current owner and group
346 will be used.
347
348 The output tar file will be named 'base_dir' + ".tar", possibly plus
349 the appropriate compression extension (".gz", ".bz2" or ".Z").
350
351 Returns the output filename.
352 """
353 tar_compression = {'gzip': 'gz', 'bzip2': 'bz2', None: '', 'compress': ''}
354 compress_ext = {'gzip': '.gz', 'bzip2': '.bz2', 'compress': '.Z'}
355
356 # flags for compression program, each element of list will be an argument
357 if compress is not None and compress not in compress_ext.keys():
358 raise ValueError, \
359 ("bad value for 'compress': must be None, 'gzip', 'bzip2' "
360 "or 'compress'")
361
362 archive_name = base_name + '.tar'
363 if compress != 'compress':
364 archive_name += compress_ext.get(compress, '')
365
366 archive_dir = os.path.dirname(archive_name)
367 if not os.path.exists(archive_dir):
368 logger.info("creating %s" % archive_dir)
369 if not dry_run:
370 os.makedirs(archive_dir)
371
372
373 # creating the tarball
374 import tarfile # late import so Python build itself doesn't break
375
376 if logger is not None:
377 logger.info('Creating tar archive')
378
379 uid = _get_uid(owner)
380 gid = _get_gid(group)
381
382 def _set_uid_gid(tarinfo):
383 if gid is not None:
384 tarinfo.gid = gid
385 tarinfo.gname = group
386 if uid is not None:
387 tarinfo.uid = uid
388 tarinfo.uname = owner
389 return tarinfo
390
391 if not dry_run:
392 tar = tarfile.open(archive_name, 'w|%s' % tar_compression[compress])
393 try:
394 tar.add(base_dir, filter=_set_uid_gid)
395 finally:
396 tar.close()
397
398 # compression using `compress`
399 # XXX this block will be removed in Python 3.2
400 if compress == 'compress':
401 warn("'compress' will be deprecated.", PendingDeprecationWarning)
402 # the option varies depending on the platform
403 compressed_name = archive_name + compress_ext[compress]
404 if sys.platform == 'win32':
405 cmd = [compress, archive_name, compressed_name]
406 else:
407 cmd = [compress, '-f', archive_name]
408 from distutils.spawn import spawn
409 spawn(cmd, dry_run=dry_run)
410 return compressed_name
411
412 return archive_name
413
414def _call_external_zip(directory, verbose=False):
415 # XXX see if we want to keep an external call here
416 if verbose:
417 zipoptions = "-r"
418 else:
419 zipoptions = "-rq"
420 from distutils.errors import DistutilsExecError
421 from distutils.spawn import spawn
422 try:
423 spawn(["zip", zipoptions, zip_filename, base_dir], dry_run=dry_run)
424 except DistutilsExecError:
425 # XXX really should distinguish between "couldn't find
426 # external 'zip' command" and "zip failed".
427 raise ExecError, \
428 ("unable to create zip file '%s': "
429 "could neither import the 'zipfile' module nor "
430 "find a standalone zip utility") % zip_filename
431
432def _make_zipfile(base_name, base_dir, verbose=0, dry_run=0, logger=None):
433 """Create a zip file from all the files under 'base_dir'.
434
435 The output zip file will be named 'base_dir' + ".zip". Uses either the
436 "zipfile" Python module (if available) or the InfoZIP "zip" utility
437 (if installed and found on the default search path). If neither tool is
438 available, raises ExecError. Returns the name of the output zip
439 file.
440 """
441 zip_filename = base_name + ".zip"
442 archive_dir = os.path.dirname(base_name)
443
444 if not os.path.exists(archive_dir):
445 if logger is not None:
446 logger.info("creating %s", archive_dir)
447 if not dry_run:
448 os.makedirs(archive_dir)
449
450 # If zipfile module is not available, try spawning an external 'zip'
451 # command.
452 try:
453 import zipfile
454 except ImportError:
455 zipfile = None
456
457 if zipfile is None:
458 _call_external_zip(base_dir, verbose)
459 else:
460 if logger is not None:
461 logger.info("creating '%s' and adding '%s' to it",
462 zip_filename, base_dir)
463
464 if not dry_run:
465 zip = zipfile.ZipFile(zip_filename, "w",
466 compression=zipfile.ZIP_DEFLATED)
467
468 for dirpath, dirnames, filenames in os.walk(base_dir):
469 for name in filenames:
470 path = os.path.normpath(os.path.join(dirpath, name))
471 if os.path.isfile(path):
472 zip.write(path, path)
473 if logger is not None:
474 logger.info("adding '%s'", path)
475 zip.close()
476
477 return zip_filename
478
479_ARCHIVE_FORMATS = {
480 'gztar': (_make_tarball, [('compress', 'gzip')], "gzip'ed tar-file"),
481 'bztar': (_make_tarball, [('compress', 'bzip2')], "bzip2'ed tar-file"),
482 'ztar': (_make_tarball, [('compress', 'compress')],
483 "compressed tar file"),
484 'tar': (_make_tarball, [('compress', None)], "uncompressed tar file"),
485 'zip': (_make_zipfile, [],"ZIP file")
486 }
487
488def get_archive_formats():
489 """Returns a list of supported formats for archiving and unarchiving.
490
491 Each element of the returned sequence is a tuple (name, description)
492 """
493 formats = [(name, registry[2]) for name, registry in
494 _ARCHIVE_FORMATS.items()]
495 formats.sort()
496 return formats
497
498def register_archive_format(name, function, extra_args=None, description=''):
499 """Registers an archive format.
500
501 name is the name of the format. function is the callable that will be
502 used to create archives. If provided, extra_args is a sequence of
503 (name, value) tuples that will be passed as arguments to the callable.
504 description can be provided to describe the format, and will be returned
505 by the get_archive_formats() function.
506 """
507 if extra_args is None:
508 extra_args = []
Florent Xicluna1f3b4e12010-03-07 12:14:25 +0000509 if not isinstance(function, collections.Callable):
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000510 raise TypeError('The %s object is not callable' % function)
511 if not isinstance(extra_args, (tuple, list)):
512 raise TypeError('extra_args needs to be a sequence')
513 for element in extra_args:
514 if not isinstance(element, (tuple, list)) or len(element) !=2 :
515 raise TypeError('extra_args elements are : (arg_name, value)')
516
517 _ARCHIVE_FORMATS[name] = (function, extra_args, description)
518
519def unregister_archive_format(name):
520 del _ARCHIVE_FORMATS[name]
521
522def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
523 dry_run=0, owner=None, group=None, logger=None):
524 """Create an archive file (eg. zip or tar).
525
526 'base_name' is the name of the file to create, minus any format-specific
527 extension; 'format' is the archive format: one of "zip", "tar", "ztar",
Tarek Ziadé8a12f942010-04-19 21:28:21 +0000528 "bztar" or "gztar".
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000529
530 'root_dir' is a directory that will be the root directory of the
531 archive; ie. we typically chdir into 'root_dir' before creating the
532 archive. 'base_dir' is the directory where we start archiving from;
533 ie. 'base_dir' will be the common prefix of all files and
534 directories in the archive. 'root_dir' and 'base_dir' both default
535 to the current directory. Returns the name of the archive file.
536
537 'owner' and 'group' are used when creating a tar archive. By default,
538 uses the current owner and group.
539 """
540 save_cwd = os.getcwd()
541 if root_dir is not None:
542 if logger is not None:
543 logger.debug("changing into '%s'", root_dir)
544 base_name = os.path.abspath(base_name)
545 if not dry_run:
546 os.chdir(root_dir)
547
548 if base_dir is None:
549 base_dir = os.curdir
550
551 kwargs = {'dry_run': dry_run, 'logger': logger}
552
553 try:
554 format_info = _ARCHIVE_FORMATS[format]
555 except KeyError:
556 raise ValueError, "unknown archive format '%s'" % format
557
558 func = format_info[0]
559 for arg, val in format_info[1]:
560 kwargs[arg] = val
561
562 if format != 'zip':
563 kwargs['owner'] = owner
564 kwargs['group'] = group
565
566 try:
567 filename = func(base_name, base_dir, **kwargs)
568 finally:
569 if root_dir is not None:
570 if logger is not None:
571 logger.debug("changing back to '%s'", save_cwd)
572 os.chdir(save_cwd)
573
574 return filename