blob: 3462f7c5e91c2030d74b26cbf8a884df592ac711 [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:
Serhiy Storchaka30ad6e22016-12-16 19:04:17 +020016 import zlib
17 del zlib
18 _ZLIB_SUPPORTED = True
19except ImportError:
20 _ZLIB_SUPPORTED = False
21
22try:
23 import bz2
24 del bz2
25 _BZ2_SUPPORTED = True
26except ImportError:
27 _BZ2_SUPPORTED = False
28
29try:
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +000030 from pwd import getpwnam
31except ImportError:
32 getpwnam = None
33
34try:
35 from grp import getgrnam
36except ImportError:
37 getgrnam = None
Guido van Rossumc6360141990-10-13 19:23:40 +000038
Tarek Ziadé2900c442010-02-23 05:36:41 +000039__all__ = ["copyfileobj", "copyfile", "copymode", "copystat", "copy", "copy2",
40 "copytree", "move", "rmtree", "Error", "SpecialFileError",
41 "ExecError", "make_archive", "get_archive_formats",
Éric Araujo04295002011-08-21 16:14:01 +020042 "register_archive_format", "unregister_archive_format",
43 "ignore_patterns"]
Martin v. Löwise9ce0b02002-10-07 13:23:24 +000044
Neal Norwitz4ce69a52005-09-01 00:45:28 +000045class Error(EnvironmentError):
Martin v. Löwise9ce0b02002-10-07 13:23:24 +000046 pass
Guido van Rossumc6360141990-10-13 19:23:40 +000047
Antoine Pitrou1fc02312009-05-01 20:55:35 +000048class SpecialFileError(EnvironmentError):
49 """Raised when trying to do a kind of operation (e.g. copying) which is
50 not supported on a special file (e.g. a named pipe)"""
51
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +000052class ExecError(EnvironmentError):
53 """Raised when a command could not be executed"""
54
Antoine Pitrou9fcd4b32008-08-11 17:21:36 +000055try:
56 WindowsError
57except NameError:
58 WindowsError = None
59
Greg Stein42bb8b32000-07-12 09:55:30 +000060def copyfileobj(fsrc, fdst, length=16*1024):
61 """copy data from file-like object fsrc to file-like object fdst"""
62 while 1:
63 buf = fsrc.read(length)
64 if not buf:
65 break
66 fdst.write(buf)
67
Johannes Gijsbers46f14592004-08-14 13:30:02 +000068def _samefile(src, dst):
69 # Macintosh, Unix.
Tarek Ziadéf1c28b72010-04-19 21:13:03 +000070 if hasattr(os.path, 'samefile'):
Johannes Gijsbersf9a098e2004-08-14 14:51:01 +000071 try:
72 return os.path.samefile(src, dst)
73 except OSError:
74 return False
Johannes Gijsbers46f14592004-08-14 13:30:02 +000075
76 # All other platforms: check for same pathname.
77 return (os.path.normcase(os.path.abspath(src)) ==
78 os.path.normcase(os.path.abspath(dst)))
Tim Peters495ad3c2001-01-15 01:36:40 +000079
Guido van Rossumc6360141990-10-13 19:23:40 +000080def copyfile(src, dst):
Guido van Rossum9d0a3df1997-04-29 14:45:19 +000081 """Copy data from src to dst"""
Johannes Gijsbers46f14592004-08-14 13:30:02 +000082 if _samefile(src, dst):
Tarek Ziadéf1c28b72010-04-19 21:13:03 +000083 raise Error("`%s` and `%s` are the same file" % (src, dst))
Johannes Gijsbers46f14592004-08-14 13:30:02 +000084
Antoine Pitrou1fc02312009-05-01 20:55:35 +000085 for fn in [src, dst]:
86 try:
87 st = os.stat(fn)
88 except OSError:
89 # File most likely does not exist
90 pass
Benjamin Petersona663a372009-06-05 19:09:28 +000091 else:
92 # XXX What about other special files? (sockets, devices...)
93 if stat.S_ISFIFO(st.st_mode):
94 raise SpecialFileError("`%s` is a named pipe" % fn)
Tarek Ziadé31a673d2010-05-05 22:41:25 +000095
Tarek Ziadé38f81222010-05-05 22:15:31 +000096 with open(src, 'rb') as fsrc:
97 with open(dst, 'wb') as fdst:
98 copyfileobj(fsrc, fdst)
Guido van Rossumc6360141990-10-13 19:23:40 +000099
Guido van Rossumc6360141990-10-13 19:23:40 +0000100def copymode(src, dst):
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000101 """Copy mode bits from src to dst"""
Tim Peters0c947242001-01-21 20:00:00 +0000102 if hasattr(os, 'chmod'):
103 st = os.stat(src)
Walter Dörwald294bbf32002-06-06 09:48:13 +0000104 mode = stat.S_IMODE(st.st_mode)
Tim Peters0c947242001-01-21 20:00:00 +0000105 os.chmod(dst, mode)
Guido van Rossumc6360141990-10-13 19:23:40 +0000106
Guido van Rossumc6360141990-10-13 19:23:40 +0000107def copystat(src, dst):
Martin v. Löwis382abef2007-02-19 10:55:19 +0000108 """Copy all stat info (mode bits, atime, mtime, flags) from src to dst"""
Guido van Rossuma2baf461997-04-29 14:06:46 +0000109 st = os.stat(src)
Walter Dörwald294bbf32002-06-06 09:48:13 +0000110 mode = stat.S_IMODE(st.st_mode)
Tim Peters0c947242001-01-21 20:00:00 +0000111 if hasattr(os, 'utime'):
Walter Dörwald294bbf32002-06-06 09:48:13 +0000112 os.utime(dst, (st.st_atime, st.st_mtime))
Tim Peters0c947242001-01-21 20:00:00 +0000113 if hasattr(os, 'chmod'):
114 os.chmod(dst, mode)
Martin v. Löwis382abef2007-02-19 10:55:19 +0000115 if hasattr(os, 'chflags') and hasattr(st, 'st_flags'):
Antoine Pitrou513d9ae2010-03-22 19:59:46 +0000116 try:
117 os.chflags(dst, st.st_flags)
118 except OSError, why:
Ned Deilyacdc56d2012-05-10 17:45:49 -0700119 for err in 'EOPNOTSUPP', 'ENOTSUP':
120 if hasattr(errno, err) and why.errno == getattr(errno, err):
121 break
122 else:
Antoine Pitrou513d9ae2010-03-22 19:59:46 +0000123 raise
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000124
Guido van Rossumc6360141990-10-13 19:23:40 +0000125def copy(src, dst):
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000126 """Copy data and mode bits ("cp src dst").
Tim Peters495ad3c2001-01-15 01:36:40 +0000127
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000128 The destination may be a directory.
129
130 """
Guido van Rossuma2baf461997-04-29 14:06:46 +0000131 if os.path.isdir(dst):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000132 dst = os.path.join(dst, os.path.basename(src))
Guido van Rossuma2baf461997-04-29 14:06:46 +0000133 copyfile(src, dst)
134 copymode(src, dst)
Guido van Rossumc6360141990-10-13 19:23:40 +0000135
Guido van Rossumc6360141990-10-13 19:23:40 +0000136def copy2(src, dst):
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000137 """Copy data and all stat info ("cp -p src dst").
138
139 The destination may be a directory.
140
141 """
Guido van Rossuma2baf461997-04-29 14:06:46 +0000142 if os.path.isdir(dst):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000143 dst = os.path.join(dst, os.path.basename(src))
Guido van Rossuma2baf461997-04-29 14:06:46 +0000144 copyfile(src, dst)
145 copystat(src, dst)
Guido van Rossumc6360141990-10-13 19:23:40 +0000146
Georg Brandle78fbcc2008-07-05 10:13:36 +0000147def ignore_patterns(*patterns):
148 """Function that can be used as copytree() ignore parameter.
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000149
Georg Brandle78fbcc2008-07-05 10:13:36 +0000150 Patterns is a sequence of glob-style patterns
151 that are used to exclude files"""
152 def _ignore_patterns(path, names):
153 ignored_names = []
154 for pattern in patterns:
155 ignored_names.extend(fnmatch.filter(names, pattern))
156 return set(ignored_names)
157 return _ignore_patterns
158
159def copytree(src, dst, symlinks=False, ignore=None):
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000160 """Recursively copy a directory tree using copy2().
161
162 The destination directory must not already exist.
Neal Norwitza4c93b62003-02-23 21:36:32 +0000163 If exception(s) occur, an Error is raised with a list of reasons.
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000164
165 If the optional symlinks flag is true, symbolic links in the
166 source tree result in symbolic links in the destination tree; if
167 it is false, the contents of the files pointed to by symbolic
168 links are copied.
169
Georg Brandle78fbcc2008-07-05 10:13:36 +0000170 The optional ignore argument is a callable. If given, it
171 is called with the `src` parameter, which is the directory
172 being visited by copytree(), and `names` which is the list of
173 `src` contents, as returned by os.listdir():
174
175 callable(src, names) -> ignored_names
176
177 Since copytree() is called recursively, the callable will be
178 called once for each directory that is copied. It returns a
179 list of names relative to the `src` directory that should
180 not be copied.
181
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000182 XXX Consider this example code rather than the ultimate tool.
183
184 """
Guido van Rossuma2baf461997-04-29 14:06:46 +0000185 names = os.listdir(src)
Georg Brandle78fbcc2008-07-05 10:13:36 +0000186 if ignore is not None:
187 ignored_names = ignore(src, names)
188 else:
189 ignored_names = set()
190
Johannes Gijsberse4172ea2005-01-08 12:31:29 +0000191 os.makedirs(dst)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000192 errors = []
Guido van Rossuma2baf461997-04-29 14:06:46 +0000193 for name in names:
Georg Brandle78fbcc2008-07-05 10:13:36 +0000194 if name in ignored_names:
195 continue
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000196 srcname = os.path.join(src, name)
197 dstname = os.path.join(dst, name)
198 try:
199 if symlinks and os.path.islink(srcname):
200 linkto = os.readlink(srcname)
201 os.symlink(linkto, dstname)
202 elif os.path.isdir(srcname):
Georg Brandle78fbcc2008-07-05 10:13:36 +0000203 copytree(srcname, dstname, symlinks, ignore)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000204 else:
Antoine Pitrou1fc02312009-05-01 20:55:35 +0000205 # Will raise a SpecialFileError for unsupported file types
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000206 copy2(srcname, dstname)
Georg Brandla1be88e2005-08-31 22:48:45 +0000207 # catch the Error from the recursive copytree so that we can
208 # continue with other files
209 except Error, err:
210 errors.extend(err.args[0])
Antoine Pitrou1fc02312009-05-01 20:55:35 +0000211 except EnvironmentError, why:
212 errors.append((srcname, dstname, str(why)))
Martin v. Löwis4e678382006-07-30 13:00:31 +0000213 try:
214 copystat(src, dst)
Martin v. Löwis4e678382006-07-30 13:00:31 +0000215 except OSError, why:
Antoine Pitrou9fcd4b32008-08-11 17:21:36 +0000216 if WindowsError is not None and isinstance(why, WindowsError):
217 # Copying file access times may fail on Windows
218 pass
219 else:
Georg Brandl31965292012-08-25 10:11:57 +0200220 errors.append((src, dst, str(why)))
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000221 if errors:
222 raise Error, errors
Guido van Rossumd7673291998-02-06 21:38:09 +0000223
Barry Warsaw234d9a92003-01-24 17:36:15 +0000224def rmtree(path, ignore_errors=False, onerror=None):
Guido van Rossumd7673291998-02-06 21:38:09 +0000225 """Recursively delete a directory tree.
226
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000227 If ignore_errors is set, errors are ignored; otherwise, if onerror
228 is set, it is called to handle the error with arguments (func,
229 path, exc_info) where func is os.listdir, os.remove, or os.rmdir;
230 path is the argument to that function that caused it to fail; and
231 exc_info is a tuple returned by sys.exc_info(). If ignore_errors
232 is false and onerror is None, an exception is raised.
233
Guido van Rossumd7673291998-02-06 21:38:09 +0000234 """
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000235 if ignore_errors:
236 def onerror(*args):
Barry Warsaw234d9a92003-01-24 17:36:15 +0000237 pass
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000238 elif onerror is None:
239 def onerror(*args):
240 raise
Georg Brandl52353982008-01-20 14:17:42 +0000241 try:
242 if os.path.islink(path):
243 # symlinks to directories are forbidden, see bug #1669
244 raise OSError("Cannot call rmtree on a symbolic link")
245 except OSError:
246 onerror(os.path.islink, path, sys.exc_info())
247 # can't continue even if onerror hook returns
248 return
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000249 names = []
250 try:
251 names = os.listdir(path)
252 except os.error, err:
253 onerror(os.listdir, path, sys.exc_info())
254 for name in names:
255 fullname = os.path.join(path, name)
256 try:
257 mode = os.lstat(fullname).st_mode
258 except os.error:
259 mode = 0
260 if stat.S_ISDIR(mode):
261 rmtree(fullname, ignore_errors, onerror)
Barry Warsaw234d9a92003-01-24 17:36:15 +0000262 else:
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000263 try:
264 os.remove(fullname)
265 except os.error, err:
266 onerror(os.remove, fullname, sys.exc_info())
267 try:
268 os.rmdir(path)
269 except os.error:
270 onerror(os.rmdir, path, sys.exc_info())
Guido van Rossumd7673291998-02-06 21:38:09 +0000271
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000272
Sean Reifscheider493894c2008-03-18 17:24:12 +0000273def _basename(path):
274 # A basename() variant which first strips the trailing slash, if present.
275 # Thus we always get the last component of the path, even for directories.
Serhiy Storchakaa4b9c872014-02-11 10:30:06 +0200276 sep = os.path.sep + (os.path.altsep or '')
277 return os.path.basename(path.rstrip(sep))
Sean Reifscheider493894c2008-03-18 17:24:12 +0000278
279def move(src, dst):
280 """Recursively move a file or directory to another location. This is
281 similar to the Unix "mv" command.
282
283 If the destination is a directory or a symlink to a directory, the source
284 is moved inside the directory. The destination path must not already
285 exist.
286
287 If the destination already exists but is not a directory, it may be
288 overwritten depending on os.rename() semantics.
289
290 If the destination is on our current filesystem, then rename() is used.
291 Otherwise, src is copied to the destination and then removed.
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000292 A lot more could be done here... A look at a mv.c shows a lot of
293 the issues this implementation glosses over.
294
295 """
Sean Reifscheider493894c2008-03-18 17:24:12 +0000296 real_dst = dst
297 if os.path.isdir(dst):
Ronald Oussoren58d6b1b2011-05-06 11:31:33 +0200298 if _samefile(src, dst):
299 # We might be on a case insensitive filesystem,
300 # perform the rename anyway.
301 os.rename(src, dst)
302 return
303
Sean Reifscheider493894c2008-03-18 17:24:12 +0000304 real_dst = os.path.join(dst, _basename(src))
305 if os.path.exists(real_dst):
306 raise Error, "Destination path '%s' already exists" % real_dst
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000307 try:
Sean Reifscheider493894c2008-03-18 17:24:12 +0000308 os.rename(src, real_dst)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000309 except OSError:
310 if os.path.isdir(src):
Benjamin Peterson096c3ad2009-02-07 19:08:22 +0000311 if _destinsrc(src, dst):
Brett Cannon1c3fa182004-06-19 21:11:35 +0000312 raise Error, "Cannot move a directory '%s' into itself '%s'." % (src, dst)
Sean Reifscheider493894c2008-03-18 17:24:12 +0000313 copytree(src, real_dst, symlinks=True)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000314 rmtree(src)
315 else:
Sean Reifscheider493894c2008-03-18 17:24:12 +0000316 copy2(src, real_dst)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000317 os.unlink(src)
Brett Cannon1c3fa182004-06-19 21:11:35 +0000318
Benjamin Peterson096c3ad2009-02-07 19:08:22 +0000319def _destinsrc(src, dst):
Antoine Pitrou707c5932009-01-29 20:19:34 +0000320 src = abspath(src)
321 dst = abspath(dst)
322 if not src.endswith(os.path.sep):
323 src += os.path.sep
324 if not dst.endswith(os.path.sep):
325 dst += os.path.sep
326 return dst.startswith(src)
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000327
328def _get_gid(name):
329 """Returns a gid, given a group name."""
330 if getgrnam is None or name is None:
331 return None
332 try:
333 result = getgrnam(name)
334 except KeyError:
335 result = None
336 if result is not None:
337 return result[2]
338 return None
339
340def _get_uid(name):
341 """Returns an uid, given a user name."""
342 if getpwnam is None or name is None:
343 return None
344 try:
345 result = getpwnam(name)
346 except KeyError:
347 result = None
348 if result is not None:
349 return result[2]
350 return None
351
352def _make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0,
353 owner=None, group=None, logger=None):
354 """Create a (possibly compressed) tar file from all the files under
355 'base_dir'.
356
Tarek Ziadée593fad2010-04-20 21:09:06 +0000357 'compress' must be "gzip" (the default), "bzip2", or None.
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000358
359 'owner' and 'group' can be used to define an owner and a group for the
360 archive that is being built. If not provided, the current owner and group
361 will be used.
362
Éric Araujo6e52cf32010-12-15 20:33:50 +0000363 The output tar file will be named 'base_name' + ".tar", possibly plus
Tarek Ziadée593fad2010-04-20 21:09:06 +0000364 the appropriate compression extension (".gz", or ".bz2").
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000365
366 Returns the output filename.
367 """
Serhiy Storchaka30ad6e22016-12-16 19:04:17 +0200368 if compress is None:
369 tar_compression = ''
370 elif _ZLIB_SUPPORTED and compress == 'gzip':
371 tar_compression = 'gz'
372 elif _BZ2_SUPPORTED and compress == 'bzip2':
373 tar_compression = 'bz2'
374 else:
375 raise ValueError("bad value for 'compress', or compression format not "
376 "supported : {0}".format(compress))
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000377
Serhiy Storchaka30ad6e22016-12-16 19:04:17 +0200378 compress_ext = '.' + tar_compression if compress else ''
379 archive_name = base_name + '.tar' + compress_ext
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000380 archive_dir = os.path.dirname(archive_name)
Tarek Ziadée593fad2010-04-20 21:09:06 +0000381
Serhiy Storchakac3542852014-11-28 00:50:06 +0200382 if archive_dir and not os.path.exists(archive_dir):
Éric Araujoe7329f42011-08-19 03:07:39 +0200383 if logger is not None:
384 logger.info("creating %s", archive_dir)
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000385 if not dry_run:
386 os.makedirs(archive_dir)
387
388
389 # creating the tarball
390 import tarfile # late import so Python build itself doesn't break
391
392 if logger is not None:
393 logger.info('Creating tar archive')
394
395 uid = _get_uid(owner)
396 gid = _get_gid(group)
397
398 def _set_uid_gid(tarinfo):
399 if gid is not None:
400 tarinfo.gid = gid
401 tarinfo.gname = group
402 if uid is not None:
403 tarinfo.uid = uid
404 tarinfo.uname = owner
405 return tarinfo
406
407 if not dry_run:
Serhiy Storchaka30ad6e22016-12-16 19:04:17 +0200408 tar = tarfile.open(archive_name, 'w|%s' % tar_compression)
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000409 try:
410 tar.add(base_dir, filter=_set_uid_gid)
411 finally:
412 tar.close()
413
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000414 return archive_name
415
Tarek Ziadé62e17ad2010-04-21 13:32:26 +0000416def _call_external_zip(base_dir, zip_filename, verbose=False, dry_run=False):
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000417 # XXX see if we want to keep an external call here
418 if verbose:
419 zipoptions = "-r"
420 else:
421 zipoptions = "-rq"
422 from distutils.errors import DistutilsExecError
423 from distutils.spawn import spawn
424 try:
425 spawn(["zip", zipoptions, zip_filename, base_dir], dry_run=dry_run)
426 except DistutilsExecError:
427 # XXX really should distinguish between "couldn't find
428 # external 'zip' command" and "zip failed".
429 raise ExecError, \
430 ("unable to create zip file '%s': "
431 "could neither import the 'zipfile' module nor "
432 "find a standalone zip utility") % zip_filename
433
434def _make_zipfile(base_name, base_dir, verbose=0, dry_run=0, logger=None):
435 """Create a zip file from all the files under 'base_dir'.
436
Éric Araujo6e52cf32010-12-15 20:33:50 +0000437 The output zip file will be named 'base_name' + ".zip". Uses either the
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000438 "zipfile" Python module (if available) or the InfoZIP "zip" utility
439 (if installed and found on the default search path). If neither tool is
440 available, raises ExecError. Returns the name of the output zip
441 file.
442 """
443 zip_filename = base_name + ".zip"
444 archive_dir = os.path.dirname(base_name)
445
Serhiy Storchakac3542852014-11-28 00:50:06 +0200446 if archive_dir and not os.path.exists(archive_dir):
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000447 if logger is not None:
448 logger.info("creating %s", archive_dir)
449 if not dry_run:
450 os.makedirs(archive_dir)
451
452 # If zipfile module is not available, try spawning an external 'zip'
453 # command.
454 try:
Serhiy Storchaka30ad6e22016-12-16 19:04:17 +0200455 import zlib
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000456 import zipfile
457 except ImportError:
458 zipfile = None
459
460 if zipfile is None:
Tarek Ziadé62e17ad2010-04-21 13:32:26 +0000461 _call_external_zip(base_dir, zip_filename, verbose, dry_run)
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000462 else:
463 if logger is not None:
464 logger.info("creating '%s' and adding '%s' to it",
465 zip_filename, base_dir)
466
467 if not dry_run:
Benjamin Petersonb898b4f2014-02-02 15:30:22 -0500468 with zipfile.ZipFile(zip_filename, "w",
469 compression=zipfile.ZIP_DEFLATED) as zf:
Serhiy Storchakafe45f652015-09-08 05:47:01 +0300470 path = os.path.normpath(base_dir)
Serhiy Storchakaef5c24a2016-10-23 15:52:01 +0300471 if path != os.curdir:
472 zf.write(path, path)
473 if logger is not None:
474 logger.info("adding '%s'", path)
Benjamin Petersonb898b4f2014-02-02 15:30:22 -0500475 for dirpath, dirnames, filenames in os.walk(base_dir):
Serhiy Storchakafe45f652015-09-08 05:47:01 +0300476 for name in sorted(dirnames):
477 path = os.path.normpath(os.path.join(dirpath, name))
478 zf.write(path, path)
479 if logger is not None:
480 logger.info("adding '%s'", path)
Benjamin Petersonb898b4f2014-02-02 15:30:22 -0500481 for name in filenames:
482 path = os.path.normpath(os.path.join(dirpath, name))
483 if os.path.isfile(path):
484 zf.write(path, path)
485 if logger is not None:
486 logger.info("adding '%s'", path)
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000487
488 return zip_filename
489
490_ARCHIVE_FORMATS = {
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000491 'tar': (_make_tarball, [('compress', None)], "uncompressed tar file"),
Serhiy Storchaka30ad6e22016-12-16 19:04:17 +0200492 'zip': (_make_zipfile, [], "ZIP file")
493}
494
495if _ZLIB_SUPPORTED:
496 _ARCHIVE_FORMATS['gztar'] = (_make_tarball, [('compress', 'gzip')],
497 "gzip'ed tar-file")
498
499if _BZ2_SUPPORTED:
500 _ARCHIVE_FORMATS['bztar'] = (_make_tarball, [('compress', 'bzip2')],
501 "bzip2'ed tar-file")
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000502
503def get_archive_formats():
504 """Returns a list of supported formats for archiving and unarchiving.
505
506 Each element of the returned sequence is a tuple (name, description)
507 """
508 formats = [(name, registry[2]) for name, registry in
509 _ARCHIVE_FORMATS.items()]
510 formats.sort()
511 return formats
512
513def register_archive_format(name, function, extra_args=None, description=''):
514 """Registers an archive format.
515
516 name is the name of the format. function is the callable that will be
517 used to create archives. If provided, extra_args is a sequence of
518 (name, value) tuples that will be passed as arguments to the callable.
519 description can be provided to describe the format, and will be returned
520 by the get_archive_formats() function.
521 """
522 if extra_args is None:
523 extra_args = []
Florent Xicluna1f3b4e12010-03-07 12:14:25 +0000524 if not isinstance(function, collections.Callable):
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000525 raise TypeError('The %s object is not callable' % function)
526 if not isinstance(extra_args, (tuple, list)):
527 raise TypeError('extra_args needs to be a sequence')
528 for element in extra_args:
529 if not isinstance(element, (tuple, list)) or len(element) !=2 :
530 raise TypeError('extra_args elements are : (arg_name, value)')
531
532 _ARCHIVE_FORMATS[name] = (function, extra_args, description)
533
534def unregister_archive_format(name):
535 del _ARCHIVE_FORMATS[name]
536
537def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
538 dry_run=0, owner=None, group=None, logger=None):
539 """Create an archive file (eg. zip or tar).
540
541 'base_name' is the name of the file to create, minus any format-specific
Serhiy Storchaka30ad6e22016-12-16 19:04:17 +0200542 extension; 'format' is the archive format: one of "zip", "tar", "gztar",
543 or "bztar". Or any other registered format.
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000544
545 'root_dir' is a directory that will be the root directory of the
546 archive; ie. we typically chdir into 'root_dir' before creating the
547 archive. 'base_dir' is the directory where we start archiving from;
548 ie. 'base_dir' will be the common prefix of all files and
549 directories in the archive. 'root_dir' and 'base_dir' both default
550 to the current directory. Returns the name of the archive file.
551
552 'owner' and 'group' are used when creating a tar archive. By default,
553 uses the current owner and group.
554 """
555 save_cwd = os.getcwd()
556 if root_dir is not None:
557 if logger is not None:
558 logger.debug("changing into '%s'", root_dir)
559 base_name = os.path.abspath(base_name)
560 if not dry_run:
561 os.chdir(root_dir)
562
563 if base_dir is None:
564 base_dir = os.curdir
565
566 kwargs = {'dry_run': dry_run, 'logger': logger}
567
568 try:
569 format_info = _ARCHIVE_FORMATS[format]
570 except KeyError:
571 raise ValueError, "unknown archive format '%s'" % format
572
573 func = format_info[0]
574 for arg, val in format_info[1]:
575 kwargs[arg] = val
576
577 if format != 'zip':
578 kwargs['owner'] = owner
579 kwargs['group'] = group
580
581 try:
582 filename = func(base_name, base_dir, **kwargs)
583 finally:
584 if root_dir is not None:
585 if logger is not None:
586 logger.debug("changing back to '%s'", save_cwd)
587 os.chdir(save_cwd)
588
589 return filename