blob: a324b436c87c063e01d4ca63d7d4ac9faa38dd0d [file] [log] [blame]
Guido van Rossume7b146f2000-02-04 15:28:42 +00001"""Utility functions for copying 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
Skip Montanaro0de65802001-02-15 22:15:14 +000025__all__ = ["copyfileobj","copyfile","copymode","copystat","copy","copy2",
Tarek Ziadé396fad72010-02-23 05:30:31 +000026 "copytree","move","rmtree","Error", "SpecialFileError",
27 "ExecError","make_archive"]
Martin v. Löwise9ce0b02002-10-07 13:23:24 +000028
Neal Norwitz4ce69a52005-09-01 00:45:28 +000029class Error(EnvironmentError):
Martin v. Löwise9ce0b02002-10-07 13:23:24 +000030 pass
Guido van Rossumc6360141990-10-13 19:23:40 +000031
Antoine Pitrou7fff0962009-05-01 21:09:44 +000032class SpecialFileError(EnvironmentError):
33 """Raised when trying to do a kind of operation (e.g. copying) which is
34 not supported on a special file (e.g. a named pipe)"""
35
Tarek Ziadé396fad72010-02-23 05:30:31 +000036class ExecError(EnvironmentError):
37 """Raised when a command could not be executed"""
38
Georg Brandl6aa2d1f2008-08-12 08:35:52 +000039try:
40 WindowsError
41except NameError:
42 WindowsError = None
43
Greg Stein42bb8b32000-07-12 09:55:30 +000044def copyfileobj(fsrc, fdst, length=16*1024):
45 """copy data from file-like object fsrc to file-like object fdst"""
46 while 1:
47 buf = fsrc.read(length)
48 if not buf:
49 break
50 fdst.write(buf)
51
Johannes Gijsbers46f14592004-08-14 13:30:02 +000052def _samefile(src, dst):
53 # Macintosh, Unix.
54 if hasattr(os.path,'samefile'):
Johannes Gijsbersf9a098e2004-08-14 14:51:01 +000055 try:
56 return os.path.samefile(src, dst)
57 except OSError:
58 return False
Johannes Gijsbers46f14592004-08-14 13:30:02 +000059
60 # All other platforms: check for same pathname.
61 return (os.path.normcase(os.path.abspath(src)) ==
62 os.path.normcase(os.path.abspath(dst)))
Tim Peters495ad3c2001-01-15 01:36:40 +000063
Guido van Rossumc6360141990-10-13 19:23:40 +000064def copyfile(src, dst):
Guido van Rossum9d0a3df1997-04-29 14:45:19 +000065 """Copy data from src to dst"""
Johannes Gijsbers46f14592004-08-14 13:30:02 +000066 if _samefile(src, dst):
Collin Winterce36ad82007-08-30 01:19:48 +000067 raise Error("`%s` and `%s` are the same file" % (src, dst))
Johannes Gijsbers46f14592004-08-14 13:30:02 +000068
Guido van Rossuma2baf461997-04-29 14:06:46 +000069 fsrc = None
70 fdst = None
Antoine Pitrou7fff0962009-05-01 21:09:44 +000071 for fn in [src, dst]:
72 try:
73 st = os.stat(fn)
74 except OSError:
75 # File most likely does not exist
76 pass
Benjamin Petersonc0d98aa2009-06-05 19:13:27 +000077 else:
78 # XXX What about other special files? (sockets, devices...)
79 if stat.S_ISFIFO(st.st_mode):
80 raise SpecialFileError("`%s` is a named pipe" % fn)
Guido van Rossuma2baf461997-04-29 14:06:46 +000081 try:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000082 fsrc = open(src, 'rb')
83 fdst = open(dst, 'wb')
Greg Stein42bb8b32000-07-12 09:55:30 +000084 copyfileobj(fsrc, fdst)
Guido van Rossuma2baf461997-04-29 14:06:46 +000085 finally:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000086 if fdst:
87 fdst.close()
88 if fsrc:
89 fsrc.close()
Guido van Rossumc6360141990-10-13 19:23:40 +000090
Guido van Rossumc6360141990-10-13 19:23:40 +000091def copymode(src, dst):
Guido van Rossum9d0a3df1997-04-29 14:45:19 +000092 """Copy mode bits from src to dst"""
Tim Peters0c947242001-01-21 20:00:00 +000093 if hasattr(os, 'chmod'):
94 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 os.chmod(dst, mode)
Guido van Rossumc6360141990-10-13 19:23:40 +000097
Guido van Rossumc6360141990-10-13 19:23:40 +000098def copystat(src, dst):
Thomas Wouterscf297e42007-02-23 15:07:44 +000099 """Copy all stat info (mode bits, atime, mtime, flags) from src to dst"""
Guido van Rossuma2baf461997-04-29 14:06:46 +0000100 st = os.stat(src)
Walter Dörwald294bbf32002-06-06 09:48:13 +0000101 mode = stat.S_IMODE(st.st_mode)
Tim Peters0c947242001-01-21 20:00:00 +0000102 if hasattr(os, 'utime'):
Walter Dörwald294bbf32002-06-06 09:48:13 +0000103 os.utime(dst, (st.st_atime, st.st_mtime))
Tim Peters0c947242001-01-21 20:00:00 +0000104 if hasattr(os, 'chmod'):
105 os.chmod(dst, mode)
Thomas Wouterscf297e42007-02-23 15:07:44 +0000106 if hasattr(os, 'chflags') and hasattr(st, 'st_flags'):
107 os.chflags(dst, st.st_flags)
Guido van Rossumc6360141990-10-13 19:23:40 +0000108
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000109
Guido van Rossumc6360141990-10-13 19:23:40 +0000110def copy(src, dst):
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000111 """Copy data and mode bits ("cp src dst").
Tim Peters495ad3c2001-01-15 01:36:40 +0000112
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000113 The destination may be a directory.
114
115 """
Guido van Rossuma2baf461997-04-29 14:06:46 +0000116 if os.path.isdir(dst):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000117 dst = os.path.join(dst, os.path.basename(src))
Guido van Rossuma2baf461997-04-29 14:06:46 +0000118 copyfile(src, dst)
119 copymode(src, dst)
Guido van Rossumc6360141990-10-13 19:23:40 +0000120
Guido van Rossumc6360141990-10-13 19:23:40 +0000121def copy2(src, dst):
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000122 """Copy data and all stat info ("cp -p src dst").
123
124 The destination may be a directory.
125
126 """
Guido van Rossuma2baf461997-04-29 14:06:46 +0000127 if os.path.isdir(dst):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000128 dst = os.path.join(dst, os.path.basename(src))
Guido van Rossuma2baf461997-04-29 14:06:46 +0000129 copyfile(src, dst)
130 copystat(src, dst)
Guido van Rossumc6360141990-10-13 19:23:40 +0000131
Georg Brandl2ee470f2008-07-16 12:55:28 +0000132def ignore_patterns(*patterns):
133 """Function that can be used as copytree() ignore parameter.
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000134
Georg Brandl2ee470f2008-07-16 12:55:28 +0000135 Patterns is a sequence of glob-style patterns
136 that are used to exclude files"""
137 def _ignore_patterns(path, names):
138 ignored_names = []
139 for pattern in patterns:
140 ignored_names.extend(fnmatch.filter(names, pattern))
141 return set(ignored_names)
142 return _ignore_patterns
143
144def copytree(src, dst, symlinks=False, ignore=None):
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000145 """Recursively copy a directory tree using copy2().
146
147 The destination directory must not already exist.
Neal Norwitza4c93b62003-02-23 21:36:32 +0000148 If exception(s) occur, an Error is raised with a list of reasons.
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000149
150 If the optional symlinks flag is true, symbolic links in the
151 source tree result in symbolic links in the destination tree; if
152 it is false, the contents of the files pointed to by symbolic
153 links are copied.
154
Georg Brandl2ee470f2008-07-16 12:55:28 +0000155 The optional ignore argument is a callable. If given, it
156 is called with the `src` parameter, which is the directory
157 being visited by copytree(), and `names` which is the list of
158 `src` contents, as returned by os.listdir():
159
160 callable(src, names) -> ignored_names
161
162 Since copytree() is called recursively, the callable will be
163 called once for each directory that is copied. It returns a
164 list of names relative to the `src` directory that should
165 not be copied.
166
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000167 XXX Consider this example code rather than the ultimate tool.
168
169 """
Guido van Rossuma2baf461997-04-29 14:06:46 +0000170 names = os.listdir(src)
Georg Brandl2ee470f2008-07-16 12:55:28 +0000171 if ignore is not None:
172 ignored_names = ignore(src, names)
173 else:
174 ignored_names = set()
175
Johannes Gijsberse4172ea2005-01-08 12:31:29 +0000176 os.makedirs(dst)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000177 errors = []
Guido van Rossuma2baf461997-04-29 14:06:46 +0000178 for name in names:
Georg Brandl2ee470f2008-07-16 12:55:28 +0000179 if name in ignored_names:
180 continue
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000181 srcname = os.path.join(src, name)
182 dstname = os.path.join(dst, name)
183 try:
184 if symlinks and os.path.islink(srcname):
185 linkto = os.readlink(srcname)
186 os.symlink(linkto, dstname)
187 elif os.path.isdir(srcname):
Georg Brandl2ee470f2008-07-16 12:55:28 +0000188 copytree(srcname, dstname, symlinks, ignore)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000189 else:
Antoine Pitrou7fff0962009-05-01 21:09:44 +0000190 # Will raise a SpecialFileError for unsupported file types
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000191 copy2(srcname, dstname)
Georg Brandla1be88e2005-08-31 22:48:45 +0000192 # catch the Error from the recursive copytree so that we can
193 # continue with other files
Guido van Rossumb940e112007-01-10 16:19:56 +0000194 except Error as err:
Georg Brandla1be88e2005-08-31 22:48:45 +0000195 errors.extend(err.args[0])
Antoine Pitrou7fff0962009-05-01 21:09:44 +0000196 except EnvironmentError as why:
197 errors.append((srcname, dstname, str(why)))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000198 try:
199 copystat(src, dst)
Guido van Rossumb940e112007-01-10 16:19:56 +0000200 except OSError as why:
Georg Brandl6aa2d1f2008-08-12 08:35:52 +0000201 if WindowsError is not None and isinstance(why, WindowsError):
202 # Copying file access times may fail on Windows
203 pass
204 else:
205 errors.extend((src, dst, str(why)))
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000206 if errors:
Collin Winterce36ad82007-08-30 01:19:48 +0000207 raise Error(errors)
Guido van Rossumd7673291998-02-06 21:38:09 +0000208
Barry Warsaw234d9a92003-01-24 17:36:15 +0000209def rmtree(path, ignore_errors=False, onerror=None):
Guido van Rossumd7673291998-02-06 21:38:09 +0000210 """Recursively delete a directory tree.
211
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000212 If ignore_errors is set, errors are ignored; otherwise, if onerror
213 is set, it is called to handle the error with arguments (func,
214 path, exc_info) where func is os.listdir, os.remove, or os.rmdir;
215 path is the argument to that function that caused it to fail; and
216 exc_info is a tuple returned by sys.exc_info(). If ignore_errors
217 is false and onerror is None, an exception is raised.
218
Guido van Rossumd7673291998-02-06 21:38:09 +0000219 """
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000220 if ignore_errors:
221 def onerror(*args):
Barry Warsaw234d9a92003-01-24 17:36:15 +0000222 pass
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000223 elif onerror is None:
224 def onerror(*args):
225 raise
Christian Heimes9bd667a2008-01-20 15:14:11 +0000226 try:
227 if os.path.islink(path):
228 # symlinks to directories are forbidden, see bug #1669
229 raise OSError("Cannot call rmtree on a symbolic link")
230 except OSError:
231 onerror(os.path.islink, path, sys.exc_info())
232 # can't continue even if onerror hook returns
233 return
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000234 names = []
235 try:
236 names = os.listdir(path)
Guido van Rossumb940e112007-01-10 16:19:56 +0000237 except os.error as err:
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000238 onerror(os.listdir, path, sys.exc_info())
239 for name in names:
240 fullname = os.path.join(path, name)
241 try:
242 mode = os.lstat(fullname).st_mode
243 except os.error:
244 mode = 0
245 if stat.S_ISDIR(mode):
246 rmtree(fullname, ignore_errors, onerror)
Barry Warsaw234d9a92003-01-24 17:36:15 +0000247 else:
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000248 try:
249 os.remove(fullname)
Guido van Rossumb940e112007-01-10 16:19:56 +0000250 except os.error as err:
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000251 onerror(os.remove, fullname, sys.exc_info())
252 try:
253 os.rmdir(path)
254 except os.error:
255 onerror(os.rmdir, path, sys.exc_info())
Guido van Rossumd7673291998-02-06 21:38:09 +0000256
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000257
Christian Heimesada8c3b2008-03-18 18:26:33 +0000258def _basename(path):
259 # A basename() variant which first strips the trailing slash, if present.
260 # Thus we always get the last component of the path, even for directories.
261 return os.path.basename(path.rstrip(os.path.sep))
262
263def move(src, dst):
264 """Recursively move a file or directory to another location. This is
265 similar to the Unix "mv" command.
266
267 If the destination is a directory or a symlink to a directory, the source
268 is moved inside the directory. The destination path must not already
269 exist.
270
271 If the destination already exists but is not a directory, it may be
272 overwritten depending on os.rename() semantics.
273
274 If the destination is on our current filesystem, then rename() is used.
275 Otherwise, src is copied to the destination and then removed.
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000276 A lot more could be done here... A look at a mv.c shows a lot of
277 the issues this implementation glosses over.
278
279 """
Christian Heimesada8c3b2008-03-18 18:26:33 +0000280 real_dst = dst
281 if os.path.isdir(dst):
282 real_dst = os.path.join(dst, _basename(src))
283 if os.path.exists(real_dst):
284 raise Error("Destination path '%s' already exists" % real_dst)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000285 try:
Christian Heimesada8c3b2008-03-18 18:26:33 +0000286 os.rename(src, real_dst)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000287 except OSError:
288 if os.path.isdir(src):
Benjamin Peterson247a9b82009-02-20 04:09:19 +0000289 if _destinsrc(src, dst):
Collin Winterce36ad82007-08-30 01:19:48 +0000290 raise Error("Cannot move a directory '%s' into itself '%s'." % (src, dst))
Christian Heimesada8c3b2008-03-18 18:26:33 +0000291 copytree(src, real_dst, symlinks=True)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000292 rmtree(src)
293 else:
Christian Heimesada8c3b2008-03-18 18:26:33 +0000294 copy2(src, real_dst)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000295 os.unlink(src)
Brett Cannon1c3fa182004-06-19 21:11:35 +0000296
Benjamin Peterson247a9b82009-02-20 04:09:19 +0000297def _destinsrc(src, dst):
Antoine Pitrou0dcc3cd2009-01-29 20:26:59 +0000298 src = abspath(src)
299 dst = abspath(dst)
300 if not src.endswith(os.path.sep):
301 src += os.path.sep
302 if not dst.endswith(os.path.sep):
303 dst += os.path.sep
304 return dst.startswith(src)
Tarek Ziadé396fad72010-02-23 05:30:31 +0000305
306def _get_gid(name):
307 """Returns a gid, given a group name."""
308 if getgrnam is None or name is None:
309 return None
310 try:
311 result = getgrnam(name)
312 except KeyError:
313 result = None
314 if result is not None:
315 return result[2]
316 return None
317
318def _get_uid(name):
319 """Returns an uid, given a user name."""
320 if getpwnam is None or name is None:
321 return None
322 try:
323 result = getpwnam(name)
324 except KeyError:
325 result = None
326 if result is not None:
327 return result[2]
328 return None
329
330def _make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0,
331 owner=None, group=None, logger=None):
332 """Create a (possibly compressed) tar file from all the files under
333 'base_dir'.
334
335 'compress' must be "gzip" (the default), "compress", "bzip2", or None.
336 (compress will be deprecated in Python 3.2)
337
338 'owner' and 'group' can be used to define an owner and a group for the
339 archive that is being built. If not provided, the current owner and group
340 will be used.
341
342 The output tar file will be named 'base_dir' + ".tar", possibly plus
343 the appropriate compression extension (".gz", ".bz2" or ".Z").
344
345 Returns the output filename.
346 """
347 tar_compression = {'gzip': 'gz', 'bzip2': 'bz2', None: '', 'compress': ''}
348 compress_ext = {'gzip': '.gz', 'bzip2': '.bz2', 'compress': '.Z'}
349
350 # flags for compression program, each element of list will be an argument
351 if compress is not None and compress not in compress_ext.keys():
352 raise ValueError("bad value for 'compress': must be None, 'gzip', "
353 "'bzip2' or 'compress'")
354
355 archive_name = base_name + '.tar'
356 if compress != 'compress':
357 archive_name += compress_ext.get(compress, '')
358
359 archive_dir = os.path.dirname(archive_name)
360 if not os.path.exists(archive_dir):
361 logger.info("creating %s" % archive_dir)
362 if not dry_run:
363 os.makedirs(archive_dir)
364
365
366 # creating the tarball
367 import tarfile # late import so Python build itself doesn't break
368
369 if logger is not None:
370 logger.info('Creating tar archive')
371
372 uid = _get_uid(owner)
373 gid = _get_gid(group)
374
375 def _set_uid_gid(tarinfo):
376 if gid is not None:
377 tarinfo.gid = gid
378 tarinfo.gname = group
379 if uid is not None:
380 tarinfo.uid = uid
381 tarinfo.uname = owner
382 return tarinfo
383
384 if not dry_run:
385 tar = tarfile.open(archive_name, 'w|%s' % tar_compression[compress])
386 try:
387 tar.add(base_dir, filter=_set_uid_gid)
388 finally:
389 tar.close()
390
391 # compression using `compress`
392 # XXX this block will be removed in Python 3.2
393 if compress == 'compress':
394 warn("'compress' will be deprecated.", PendingDeprecationWarning)
395 # the option varies depending on the platform
396 compressed_name = archive_name + compress_ext[compress]
397 if sys.platform == 'win32':
398 cmd = [compress, archive_name, compressed_name]
399 else:
400 cmd = [compress, '-f', archive_name]
401 from distutils.spawn import spawn
402 spawn(cmd, dry_run=dry_run)
403 return compressed_name
404
405 return archive_name
406
407def _call_external_zip(directory, verbose=False):
408 # XXX see if we want to keep an external call here
409 if verbose:
410 zipoptions = "-r"
411 else:
412 zipoptions = "-rq"
413 from distutils.errors import DistutilsExecError
414 from distutils.spawn import spawn
415 try:
416 spawn(["zip", zipoptions, zip_filename, base_dir], dry_run=dry_run)
417 except DistutilsExecError:
418 # XXX really should distinguish between "couldn't find
419 # external 'zip' command" and "zip failed".
420 raise ExecError("unable to create zip file '%s': "
421 "could neither import the 'zipfile' module nor "
422 "find a standalone zip utility") % zip_filename
423
424def _make_zipfile(base_name, base_dir, verbose=0, dry_run=0, logger=None):
425 """Create a zip file from all the files under 'base_dir'.
426
427 The output zip file will be named 'base_dir' + ".zip". Uses either the
428 "zipfile" Python module (if available) or the InfoZIP "zip" utility
429 (if installed and found on the default search path). If neither tool is
430 available, raises ExecError. Returns the name of the output zip
431 file.
432 """
433 zip_filename = base_name + ".zip"
434 archive_dir = os.path.dirname(base_name)
435
436 if not os.path.exists(archive_dir):
437 if logger is not None:
438 logger.info("creating %s", archive_dir)
439 if not dry_run:
440 os.makedirs(archive_dir)
441
442 # If zipfile module is not available, try spawning an external 'zip'
443 # command.
444 try:
445 import zipfile
446 except ImportError:
447 zipfile = None
448
449 if zipfile is None:
450 _call_external_zip(base_dir, verbose)
451 else:
452 if logger is not None:
453 logger.info("creating '%s' and adding '%s' to it",
454 zip_filename, base_dir)
455
456 if not dry_run:
457 zip = zipfile.ZipFile(zip_filename, "w",
458 compression=zipfile.ZIP_DEFLATED)
459
460 for dirpath, dirnames, filenames in os.walk(base_dir):
461 for name in filenames:
462 path = os.path.normpath(os.path.join(dirpath, name))
463 if os.path.isfile(path):
464 zip.write(path, path)
465 if logger is not None:
466 logger.info("adding '%s'", path)
467 zip.close()
468
469 return zip_filename
470
471_ARCHIVE_FORMATS = {
472 'gztar': (_make_tarball, [('compress', 'gzip')], "gzip'ed tar-file"),
473 'bztar': (_make_tarball, [('compress', 'bzip2')], "bzip2'ed tar-file"),
474 'ztar': (_make_tarball, [('compress', 'compress')],
475 "compressed tar file"),
476 'tar': (_make_tarball, [('compress', None)], "uncompressed tar file"),
477 'zip': (_make_zipfile, [],"ZIP file")
478 }
479
480def get_archive_formats():
481 """Returns a list of supported formats for archiving and unarchiving.
482
483 Each element of the returned sequence is a tuple (name, description)
484 """
485 formats = [(name, registry[2]) for name, registry in
486 _ARCHIVE_FORMATS.items()]
487 formats.sort()
488 return formats
489
490def register_archive_format(name, function, extra_args=None, description=''):
491 """Registers an archive format.
492
493 name is the name of the format. function is the callable that will be
494 used to create archives. If provided, extra_args is a sequence of
495 (name, value) tuples that will be passed as arguments to the callable.
496 description can be provided to describe the format, and will be returned
497 by the get_archive_formats() function.
498 """
499 if extra_args is None:
500 extra_args = []
501 if not isinstance(function, collections.Callable):
502 raise TypeError('The %s object is not callable' % function)
503 if not isinstance(extra_args, (tuple, list)):
504 raise TypeError('extra_args needs to be a sequence')
505 for element in extra_args:
506 if not isinstance(element, (tuple, list)) or len(element) !=2 :
507 raise TypeError('extra_args elements are : (arg_name, value)')
508
509 _ARCHIVE_FORMATS[name] = (function, extra_args, description)
510
511def unregister_archive_format(name):
512 del _ARCHIVE_FORMATS[name]
513
514def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
515 dry_run=0, owner=None, group=None, logger=None):
516 """Create an archive file (eg. zip or tar).
517
518 'base_name' is the name of the file to create, minus any format-specific
519 extension; 'format' is the archive format: one of "zip", "tar", "ztar",
520 or "gztar".
521
522 'root_dir' is a directory that will be the root directory of the
523 archive; ie. we typically chdir into 'root_dir' before creating the
524 archive. 'base_dir' is the directory where we start archiving from;
525 ie. 'base_dir' will be the common prefix of all files and
526 directories in the archive. 'root_dir' and 'base_dir' both default
527 to the current directory. Returns the name of the archive file.
528
529 'owner' and 'group' are used when creating a tar archive. By default,
530 uses the current owner and group.
531 """
532 save_cwd = os.getcwd()
533 if root_dir is not None:
534 if logger is not None:
535 logger.debug("changing into '%s'", root_dir)
536 base_name = os.path.abspath(base_name)
537 if not dry_run:
538 os.chdir(root_dir)
539
540 if base_dir is None:
541 base_dir = os.curdir
542
543 kwargs = {'dry_run': dry_run, 'logger': logger}
544
545 try:
546 format_info = _ARCHIVE_FORMATS[format]
547 except KeyError:
548 raise ValueError("unknown archive format '%s'" % format)
549
550 func = format_info[0]
551 for arg, val in format_info[1]:
552 kwargs[arg] = val
553
554 if format != 'zip':
555 kwargs['owner'] = owner
556 kwargs['group'] = group
557
558 try:
559 filename = func(base_name, base_dir, **kwargs)
560 finally:
561 if root_dir is not None:
562 if logger is not None:
563 logger.debug("changing back to '%s'", save_cwd)
564 os.chdir(save_cwd)
565
566 return filename