blob: 9d922fbefc3f4d8f3e09167e0d5a9df19cd39db0 [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
Florent Xicluna1f3b4e12010-03-07 12:14:25 +000012import collections
Antoine Pitrou513d9ae2010-03-22 19:59:46 +000013import errno
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +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é2900c442010-02-23 05:36:41 +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 Pitrou1fc02312009-05-01 20:55:35 +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é48cc8dc2010-02-23 05:16:41 +000037class ExecError(EnvironmentError):
38 """Raised when a command could not be executed"""
39
Antoine Pitrou9fcd4b32008-08-11 17:21:36 +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éf1c28b72010-04-19 21:13:03 +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):
Tarek Ziadéf1c28b72010-04-19 21:13:03 +000068 raise Error("`%s` and `%s` are the same file" % (src, dst))
Johannes Gijsbers46f14592004-08-14 13:30:02 +000069
Antoine Pitrou1fc02312009-05-01 20:55:35 +000070 for fn in [src, dst]:
71 try:
72 st = os.stat(fn)
73 except OSError:
74 # File most likely does not exist
75 pass
Benjamin Petersona663a372009-06-05 19:09:28 +000076 else:
77 # XXX What about other special files? (sockets, devices...)
78 if stat.S_ISFIFO(st.st_mode):
79 raise SpecialFileError("`%s` is a named pipe" % fn)
Tarek Ziadé31a673d2010-05-05 22:41:25 +000080
Tarek Ziadé38f81222010-05-05 22:15:31 +000081 with open(src, 'rb') as fsrc:
82 with open(dst, 'wb') as fdst:
83 copyfileobj(fsrc, fdst)
Guido van Rossumc6360141990-10-13 19:23:40 +000084
Guido van Rossumc6360141990-10-13 19:23:40 +000085def copymode(src, dst):
Guido van Rossum9d0a3df1997-04-29 14:45:19 +000086 """Copy mode bits from src to dst"""
Tim Peters0c947242001-01-21 20:00:00 +000087 if hasattr(os, 'chmod'):
88 st = os.stat(src)
Walter Dörwald294bbf32002-06-06 09:48:13 +000089 mode = stat.S_IMODE(st.st_mode)
Tim Peters0c947242001-01-21 20:00:00 +000090 os.chmod(dst, mode)
Guido van Rossumc6360141990-10-13 19:23:40 +000091
Guido van Rossumc6360141990-10-13 19:23:40 +000092def copystat(src, dst):
Martin v. Löwis382abef2007-02-19 10:55:19 +000093 """Copy all stat info (mode bits, atime, mtime, flags) from src to dst"""
Guido van Rossuma2baf461997-04-29 14:06:46 +000094 st = os.stat(src)
Walter Dörwald294bbf32002-06-06 09:48:13 +000095 mode = stat.S_IMODE(st.st_mode)
Tim Peters0c947242001-01-21 20:00:00 +000096 if hasattr(os, 'utime'):
Walter Dörwald294bbf32002-06-06 09:48:13 +000097 os.utime(dst, (st.st_atime, st.st_mtime))
Tim Peters0c947242001-01-21 20:00:00 +000098 if hasattr(os, 'chmod'):
99 os.chmod(dst, mode)
Martin v. Löwis382abef2007-02-19 10:55:19 +0000100 if hasattr(os, 'chflags') and hasattr(st, 'st_flags'):
Antoine Pitrou513d9ae2010-03-22 19:59:46 +0000101 try:
102 os.chflags(dst, st.st_flags)
103 except OSError, why:
Tarek Ziadéf1c28b72010-04-19 21:13:03 +0000104 if (not hasattr(errno, 'EOPNOTSUPP') or
105 why.errno != errno.EOPNOTSUPP):
Antoine Pitrou513d9ae2010-03-22 19:59:46 +0000106 raise
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000107
Guido van Rossumc6360141990-10-13 19:23:40 +0000108def copy(src, dst):
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000109 """Copy data and mode bits ("cp src dst").
Tim Peters495ad3c2001-01-15 01:36:40 +0000110
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000111 The destination may be a directory.
112
113 """
Guido van Rossuma2baf461997-04-29 14:06:46 +0000114 if os.path.isdir(dst):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000115 dst = os.path.join(dst, os.path.basename(src))
Guido van Rossuma2baf461997-04-29 14:06:46 +0000116 copyfile(src, dst)
117 copymode(src, dst)
Guido van Rossumc6360141990-10-13 19:23:40 +0000118
Guido van Rossumc6360141990-10-13 19:23:40 +0000119def copy2(src, dst):
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000120 """Copy data and all stat info ("cp -p src dst").
121
122 The destination may be a directory.
123
124 """
Guido van Rossuma2baf461997-04-29 14:06:46 +0000125 if os.path.isdir(dst):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000126 dst = os.path.join(dst, os.path.basename(src))
Guido van Rossuma2baf461997-04-29 14:06:46 +0000127 copyfile(src, dst)
128 copystat(src, dst)
Guido van Rossumc6360141990-10-13 19:23:40 +0000129
Georg Brandle78fbcc2008-07-05 10:13:36 +0000130def ignore_patterns(*patterns):
131 """Function that can be used as copytree() ignore parameter.
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000132
Georg Brandle78fbcc2008-07-05 10:13:36 +0000133 Patterns is a sequence of glob-style patterns
134 that are used to exclude files"""
135 def _ignore_patterns(path, names):
136 ignored_names = []
137 for pattern in patterns:
138 ignored_names.extend(fnmatch.filter(names, pattern))
139 return set(ignored_names)
140 return _ignore_patterns
141
142def copytree(src, dst, symlinks=False, ignore=None):
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000143 """Recursively copy a directory tree using copy2().
144
145 The destination directory must not already exist.
Neal Norwitza4c93b62003-02-23 21:36:32 +0000146 If exception(s) occur, an Error is raised with a list of reasons.
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000147
148 If the optional symlinks flag is true, symbolic links in the
149 source tree result in symbolic links in the destination tree; if
150 it is false, the contents of the files pointed to by symbolic
151 links are copied.
152
Georg Brandle78fbcc2008-07-05 10:13:36 +0000153 The optional ignore argument is a callable. If given, it
154 is called with the `src` parameter, which is the directory
155 being visited by copytree(), and `names` which is the list of
156 `src` contents, as returned by os.listdir():
157
158 callable(src, names) -> ignored_names
159
160 Since copytree() is called recursively, the callable will be
161 called once for each directory that is copied. It returns a
162 list of names relative to the `src` directory that should
163 not be copied.
164
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000165 XXX Consider this example code rather than the ultimate tool.
166
167 """
Guido van Rossuma2baf461997-04-29 14:06:46 +0000168 names = os.listdir(src)
Georg Brandle78fbcc2008-07-05 10:13:36 +0000169 if ignore is not None:
170 ignored_names = ignore(src, names)
171 else:
172 ignored_names = set()
173
Johannes Gijsberse4172ea2005-01-08 12:31:29 +0000174 os.makedirs(dst)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000175 errors = []
Guido van Rossuma2baf461997-04-29 14:06:46 +0000176 for name in names:
Georg Brandle78fbcc2008-07-05 10:13:36 +0000177 if name in ignored_names:
178 continue
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000179 srcname = os.path.join(src, name)
180 dstname = os.path.join(dst, name)
181 try:
182 if symlinks and os.path.islink(srcname):
183 linkto = os.readlink(srcname)
184 os.symlink(linkto, dstname)
185 elif os.path.isdir(srcname):
Georg Brandle78fbcc2008-07-05 10:13:36 +0000186 copytree(srcname, dstname, symlinks, ignore)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000187 else:
Antoine Pitrou1fc02312009-05-01 20:55:35 +0000188 # Will raise a SpecialFileError for unsupported file types
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000189 copy2(srcname, dstname)
Georg Brandla1be88e2005-08-31 22:48:45 +0000190 # catch the Error from the recursive copytree so that we can
191 # continue with other files
192 except Error, err:
193 errors.extend(err.args[0])
Antoine Pitrou1fc02312009-05-01 20:55:35 +0000194 except EnvironmentError, why:
195 errors.append((srcname, dstname, str(why)))
Martin v. Löwis4e678382006-07-30 13:00:31 +0000196 try:
197 copystat(src, dst)
Martin v. Löwis4e678382006-07-30 13:00:31 +0000198 except OSError, why:
Antoine Pitrou9fcd4b32008-08-11 17:21:36 +0000199 if WindowsError is not None and isinstance(why, WindowsError):
200 # Copying file access times may fail on Windows
201 pass
202 else:
203 errors.extend((src, dst, str(why)))
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000204 if errors:
205 raise Error, errors
Guido van Rossumd7673291998-02-06 21:38:09 +0000206
Barry Warsaw234d9a92003-01-24 17:36:15 +0000207def rmtree(path, ignore_errors=False, onerror=None):
Guido van Rossumd7673291998-02-06 21:38:09 +0000208 """Recursively delete a directory tree.
209
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000210 If ignore_errors is set, errors are ignored; otherwise, if onerror
211 is set, it is called to handle the error with arguments (func,
212 path, exc_info) where func is os.listdir, os.remove, or os.rmdir;
213 path is the argument to that function that caused it to fail; and
214 exc_info is a tuple returned by sys.exc_info(). If ignore_errors
215 is false and onerror is None, an exception is raised.
216
Guido van Rossumd7673291998-02-06 21:38:09 +0000217 """
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000218 if ignore_errors:
219 def onerror(*args):
Barry Warsaw234d9a92003-01-24 17:36:15 +0000220 pass
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000221 elif onerror is None:
222 def onerror(*args):
223 raise
Georg Brandl52353982008-01-20 14:17:42 +0000224 try:
225 if os.path.islink(path):
226 # symlinks to directories are forbidden, see bug #1669
227 raise OSError("Cannot call rmtree on a symbolic link")
228 except OSError:
229 onerror(os.path.islink, path, sys.exc_info())
230 # can't continue even if onerror hook returns
231 return
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000232 names = []
233 try:
234 names = os.listdir(path)
235 except os.error, err:
236 onerror(os.listdir, path, sys.exc_info())
237 for name in names:
238 fullname = os.path.join(path, name)
239 try:
240 mode = os.lstat(fullname).st_mode
241 except os.error:
242 mode = 0
243 if stat.S_ISDIR(mode):
244 rmtree(fullname, ignore_errors, onerror)
Barry Warsaw234d9a92003-01-24 17:36:15 +0000245 else:
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000246 try:
247 os.remove(fullname)
248 except os.error, err:
249 onerror(os.remove, fullname, sys.exc_info())
250 try:
251 os.rmdir(path)
252 except os.error:
253 onerror(os.rmdir, path, sys.exc_info())
Guido van Rossumd7673291998-02-06 21:38:09 +0000254
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000255
Sean Reifscheider493894c2008-03-18 17:24:12 +0000256def _basename(path):
257 # A basename() variant which first strips the trailing slash, if present.
258 # Thus we always get the last component of the path, even for directories.
259 return os.path.basename(path.rstrip(os.path.sep))
260
261def move(src, dst):
262 """Recursively move a file or directory to another location. This is
263 similar to the Unix "mv" command.
264
265 If the destination is a directory or a symlink to a directory, the source
266 is moved inside the directory. The destination path must not already
267 exist.
268
269 If the destination already exists but is not a directory, it may be
270 overwritten depending on os.rename() semantics.
271
272 If the destination is on our current filesystem, then rename() is used.
273 Otherwise, src is copied to the destination and then removed.
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000274 A lot more could be done here... A look at a mv.c shows a lot of
275 the issues this implementation glosses over.
276
277 """
Sean Reifscheider493894c2008-03-18 17:24:12 +0000278 real_dst = dst
279 if os.path.isdir(dst):
Ronald Oussoren58d6b1b2011-05-06 11:31:33 +0200280 if _samefile(src, dst):
281 # We might be on a case insensitive filesystem,
282 # perform the rename anyway.
283 os.rename(src, dst)
284 return
285
Sean Reifscheider493894c2008-03-18 17:24:12 +0000286 real_dst = os.path.join(dst, _basename(src))
287 if os.path.exists(real_dst):
288 raise Error, "Destination path '%s' already exists" % real_dst
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000289 try:
Sean Reifscheider493894c2008-03-18 17:24:12 +0000290 os.rename(src, real_dst)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000291 except OSError:
292 if os.path.isdir(src):
Benjamin Peterson096c3ad2009-02-07 19:08:22 +0000293 if _destinsrc(src, dst):
Brett Cannon1c3fa182004-06-19 21:11:35 +0000294 raise Error, "Cannot move a directory '%s' into itself '%s'." % (src, dst)
Sean Reifscheider493894c2008-03-18 17:24:12 +0000295 copytree(src, real_dst, symlinks=True)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000296 rmtree(src)
297 else:
Sean Reifscheider493894c2008-03-18 17:24:12 +0000298 copy2(src, real_dst)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000299 os.unlink(src)
Brett Cannon1c3fa182004-06-19 21:11:35 +0000300
Benjamin Peterson096c3ad2009-02-07 19:08:22 +0000301def _destinsrc(src, dst):
Antoine Pitrou707c5932009-01-29 20:19:34 +0000302 src = abspath(src)
303 dst = abspath(dst)
304 if not src.endswith(os.path.sep):
305 src += os.path.sep
306 if not dst.endswith(os.path.sep):
307 dst += os.path.sep
308 return dst.startswith(src)
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000309
310def _get_gid(name):
311 """Returns a gid, given a group name."""
312 if getgrnam is None or name is None:
313 return None
314 try:
315 result = getgrnam(name)
316 except KeyError:
317 result = None
318 if result is not None:
319 return result[2]
320 return None
321
322def _get_uid(name):
323 """Returns an uid, given a user name."""
324 if getpwnam is None or name is None:
325 return None
326 try:
327 result = getpwnam(name)
328 except KeyError:
329 result = None
330 if result is not None:
331 return result[2]
332 return None
333
334def _make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0,
335 owner=None, group=None, logger=None):
336 """Create a (possibly compressed) tar file from all the files under
337 'base_dir'.
338
Tarek Ziadée593fad2010-04-20 21:09:06 +0000339 'compress' must be "gzip" (the default), "bzip2", or None.
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000340
341 'owner' and 'group' can be used to define an owner and a group for the
342 archive that is being built. If not provided, the current owner and group
343 will be used.
344
Éric Araujo6e52cf32010-12-15 20:33:50 +0000345 The output tar file will be named 'base_name' + ".tar", possibly plus
Tarek Ziadée593fad2010-04-20 21:09:06 +0000346 the appropriate compression extension (".gz", or ".bz2").
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000347
348 Returns the output filename.
349 """
Tarek Ziadée593fad2010-04-20 21:09:06 +0000350 tar_compression = {'gzip': 'gz', 'bzip2': 'bz2', None: ''}
351 compress_ext = {'gzip': '.gz', 'bzip2': '.bz2'}
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000352
353 # flags for compression program, each element of list will be an argument
354 if compress is not None and compress not in compress_ext.keys():
355 raise ValueError, \
Tarek Ziadée593fad2010-04-20 21:09:06 +0000356 ("bad value for 'compress': must be None, 'gzip' or 'bzip2'")
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000357
Tarek Ziadée593fad2010-04-20 21:09:06 +0000358 archive_name = base_name + '.tar' + compress_ext.get(compress, '')
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000359 archive_dir = os.path.dirname(archive_name)
Tarek Ziadée593fad2010-04-20 21:09:06 +0000360
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000361 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
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000392 return archive_name
393
Tarek Ziadé62e17ad2010-04-21 13:32:26 +0000394def _call_external_zip(base_dir, zip_filename, verbose=False, dry_run=False):
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000395 # XXX see if we want to keep an external call here
396 if verbose:
397 zipoptions = "-r"
398 else:
399 zipoptions = "-rq"
400 from distutils.errors import DistutilsExecError
401 from distutils.spawn import spawn
402 try:
403 spawn(["zip", zipoptions, zip_filename, base_dir], dry_run=dry_run)
404 except DistutilsExecError:
405 # XXX really should distinguish between "couldn't find
406 # external 'zip' command" and "zip failed".
407 raise ExecError, \
408 ("unable to create zip file '%s': "
409 "could neither import the 'zipfile' module nor "
410 "find a standalone zip utility") % zip_filename
411
412def _make_zipfile(base_name, base_dir, verbose=0, dry_run=0, logger=None):
413 """Create a zip file from all the files under 'base_dir'.
414
Éric Araujo6e52cf32010-12-15 20:33:50 +0000415 The output zip file will be named 'base_name' + ".zip". Uses either the
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000416 "zipfile" Python module (if available) or the InfoZIP "zip" utility
417 (if installed and found on the default search path). If neither tool is
418 available, raises ExecError. Returns the name of the output zip
419 file.
420 """
421 zip_filename = base_name + ".zip"
422 archive_dir = os.path.dirname(base_name)
423
424 if not os.path.exists(archive_dir):
425 if logger is not None:
426 logger.info("creating %s", archive_dir)
427 if not dry_run:
428 os.makedirs(archive_dir)
429
430 # If zipfile module is not available, try spawning an external 'zip'
431 # command.
432 try:
433 import zipfile
434 except ImportError:
435 zipfile = None
436
437 if zipfile is None:
Tarek Ziadé62e17ad2010-04-21 13:32:26 +0000438 _call_external_zip(base_dir, zip_filename, verbose, dry_run)
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000439 else:
440 if logger is not None:
441 logger.info("creating '%s' and adding '%s' to it",
442 zip_filename, base_dir)
443
444 if not dry_run:
445 zip = zipfile.ZipFile(zip_filename, "w",
446 compression=zipfile.ZIP_DEFLATED)
447
448 for dirpath, dirnames, filenames in os.walk(base_dir):
449 for name in filenames:
450 path = os.path.normpath(os.path.join(dirpath, name))
451 if os.path.isfile(path):
452 zip.write(path, path)
453 if logger is not None:
454 logger.info("adding '%s'", path)
455 zip.close()
456
457 return zip_filename
458
459_ARCHIVE_FORMATS = {
460 'gztar': (_make_tarball, [('compress', 'gzip')], "gzip'ed tar-file"),
461 'bztar': (_make_tarball, [('compress', 'bzip2')], "bzip2'ed tar-file"),
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000462 'tar': (_make_tarball, [('compress', None)], "uncompressed tar file"),
463 'zip': (_make_zipfile, [],"ZIP file")
464 }
465
466def get_archive_formats():
467 """Returns a list of supported formats for archiving and unarchiving.
468
469 Each element of the returned sequence is a tuple (name, description)
470 """
471 formats = [(name, registry[2]) for name, registry in
472 _ARCHIVE_FORMATS.items()]
473 formats.sort()
474 return formats
475
476def register_archive_format(name, function, extra_args=None, description=''):
477 """Registers an archive format.
478
479 name is the name of the format. function is the callable that will be
480 used to create archives. If provided, extra_args is a sequence of
481 (name, value) tuples that will be passed as arguments to the callable.
482 description can be provided to describe the format, and will be returned
483 by the get_archive_formats() function.
484 """
485 if extra_args is None:
486 extra_args = []
Florent Xicluna1f3b4e12010-03-07 12:14:25 +0000487 if not isinstance(function, collections.Callable):
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000488 raise TypeError('The %s object is not callable' % function)
489 if not isinstance(extra_args, (tuple, list)):
490 raise TypeError('extra_args needs to be a sequence')
491 for element in extra_args:
492 if not isinstance(element, (tuple, list)) or len(element) !=2 :
493 raise TypeError('extra_args elements are : (arg_name, value)')
494
495 _ARCHIVE_FORMATS[name] = (function, extra_args, description)
496
497def unregister_archive_format(name):
498 del _ARCHIVE_FORMATS[name]
499
500def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
501 dry_run=0, owner=None, group=None, logger=None):
502 """Create an archive file (eg. zip or tar).
503
504 'base_name' is the name of the file to create, minus any format-specific
Tarek Ziadée593fad2010-04-20 21:09:06 +0000505 extension; 'format' is the archive format: one of "zip", "tar", "bztar"
506 or "gztar".
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000507
508 'root_dir' is a directory that will be the root directory of the
509 archive; ie. we typically chdir into 'root_dir' before creating the
510 archive. 'base_dir' is the directory where we start archiving from;
511 ie. 'base_dir' will be the common prefix of all files and
512 directories in the archive. 'root_dir' and 'base_dir' both default
513 to the current directory. Returns the name of the archive file.
514
515 'owner' and 'group' are used when creating a tar archive. By default,
516 uses the current owner and group.
517 """
518 save_cwd = os.getcwd()
519 if root_dir is not None:
520 if logger is not None:
521 logger.debug("changing into '%s'", root_dir)
522 base_name = os.path.abspath(base_name)
523 if not dry_run:
524 os.chdir(root_dir)
525
526 if base_dir is None:
527 base_dir = os.curdir
528
529 kwargs = {'dry_run': dry_run, 'logger': logger}
530
531 try:
532 format_info = _ARCHIVE_FORMATS[format]
533 except KeyError:
534 raise ValueError, "unknown archive format '%s'" % format
535
536 func = format_info[0]
537 for arg, val in format_info[1]:
538 kwargs[arg] = val
539
540 if format != 'zip':
541 kwargs['owner'] = owner
542 kwargs['group'] = group
543
544 try:
545 filename = func(base_name, base_dir, **kwargs)
546 finally:
547 if root_dir is not None:
548 if logger is not None:
549 logger.debug("changing back to '%s'", save_cwd)
550 os.chdir(save_cwd)
551
552 return filename