blob: e12f791fd1db23b82ab96debdeee57ceee9b6310 [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",
Éric Araujo04295002011-08-21 16:14:01 +020028 "register_archive_format", "unregister_archive_format",
29 "ignore_patterns"]
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
Antoine Pitrou1fc02312009-05-01 20:55:35 +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 Petersona663a372009-06-05 19:09:28 +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)
Tarek Ziadé31a673d2010-05-05 22:41:25 +000081
Tarek Ziadé38f81222010-05-05 22:15:31 +000082 with open(src, 'rb') as fsrc:
83 with open(dst, 'wb') as fdst:
84 copyfileobj(fsrc, fdst)
Guido van Rossumc6360141990-10-13 19:23:40 +000085
Guido van Rossumc6360141990-10-13 19:23:40 +000086def copymode(src, dst):
Guido van Rossum9d0a3df1997-04-29 14:45:19 +000087 """Copy mode bits from src to dst"""
Tim Peters0c947242001-01-21 20:00:00 +000088 if hasattr(os, 'chmod'):
89 st = os.stat(src)
Walter Dörwald294bbf32002-06-06 09:48:13 +000090 mode = stat.S_IMODE(st.st_mode)
Tim Peters0c947242001-01-21 20:00:00 +000091 os.chmod(dst, mode)
Guido van Rossumc6360141990-10-13 19:23:40 +000092
Guido van Rossumc6360141990-10-13 19:23:40 +000093def copystat(src, dst):
Martin v. Löwis382abef2007-02-19 10:55:19 +000094 """Copy all stat info (mode bits, atime, mtime, flags) from src to dst"""
Guido van Rossuma2baf461997-04-29 14:06:46 +000095 st = os.stat(src)
Walter Dörwald294bbf32002-06-06 09:48:13 +000096 mode = stat.S_IMODE(st.st_mode)
Tim Peters0c947242001-01-21 20:00:00 +000097 if hasattr(os, 'utime'):
Walter Dörwald294bbf32002-06-06 09:48:13 +000098 os.utime(dst, (st.st_atime, st.st_mtime))
Tim Peters0c947242001-01-21 20:00:00 +000099 if hasattr(os, 'chmod'):
100 os.chmod(dst, mode)
Martin v. Löwis382abef2007-02-19 10:55:19 +0000101 if hasattr(os, 'chflags') and hasattr(st, 'st_flags'):
Antoine Pitrou513d9ae2010-03-22 19:59:46 +0000102 try:
103 os.chflags(dst, st.st_flags)
104 except OSError, why:
Ned Deilyacdc56d2012-05-10 17:45:49 -0700105 for err in 'EOPNOTSUPP', 'ENOTSUP':
106 if hasattr(errno, err) and why.errno == getattr(errno, err):
107 break
108 else:
Antoine Pitrou513d9ae2010-03-22 19:59:46 +0000109 raise
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000110
Guido van Rossumc6360141990-10-13 19:23:40 +0000111def copy(src, dst):
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000112 """Copy data and mode bits ("cp src dst").
Tim Peters495ad3c2001-01-15 01:36:40 +0000113
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000114 The destination may be a directory.
115
116 """
Guido van Rossuma2baf461997-04-29 14:06:46 +0000117 if os.path.isdir(dst):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000118 dst = os.path.join(dst, os.path.basename(src))
Guido van Rossuma2baf461997-04-29 14:06:46 +0000119 copyfile(src, dst)
120 copymode(src, dst)
Guido van Rossumc6360141990-10-13 19:23:40 +0000121
Guido van Rossumc6360141990-10-13 19:23:40 +0000122def copy2(src, dst):
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000123 """Copy data and all stat info ("cp -p src dst").
124
125 The destination may be a directory.
126
127 """
Guido van Rossuma2baf461997-04-29 14:06:46 +0000128 if os.path.isdir(dst):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000129 dst = os.path.join(dst, os.path.basename(src))
Guido van Rossuma2baf461997-04-29 14:06:46 +0000130 copyfile(src, dst)
131 copystat(src, dst)
Guido van Rossumc6360141990-10-13 19:23:40 +0000132
Georg Brandle78fbcc2008-07-05 10:13:36 +0000133def ignore_patterns(*patterns):
134 """Function that can be used as copytree() ignore parameter.
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000135
Georg Brandle78fbcc2008-07-05 10:13:36 +0000136 Patterns is a sequence of glob-style patterns
137 that are used to exclude files"""
138 def _ignore_patterns(path, names):
139 ignored_names = []
140 for pattern in patterns:
141 ignored_names.extend(fnmatch.filter(names, pattern))
142 return set(ignored_names)
143 return _ignore_patterns
144
145def copytree(src, dst, symlinks=False, ignore=None):
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000146 """Recursively copy a directory tree using copy2().
147
148 The destination directory must not already exist.
Neal Norwitza4c93b62003-02-23 21:36:32 +0000149 If exception(s) occur, an Error is raised with a list of reasons.
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000150
151 If the optional symlinks flag is true, symbolic links in the
152 source tree result in symbolic links in the destination tree; if
153 it is false, the contents of the files pointed to by symbolic
154 links are copied.
155
Georg Brandle78fbcc2008-07-05 10:13:36 +0000156 The optional ignore argument is a callable. If given, it
157 is called with the `src` parameter, which is the directory
158 being visited by copytree(), and `names` which is the list of
159 `src` contents, as returned by os.listdir():
160
161 callable(src, names) -> ignored_names
162
163 Since copytree() is called recursively, the callable will be
164 called once for each directory that is copied. It returns a
165 list of names relative to the `src` directory that should
166 not be copied.
167
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000168 XXX Consider this example code rather than the ultimate tool.
169
170 """
Guido van Rossuma2baf461997-04-29 14:06:46 +0000171 names = os.listdir(src)
Georg Brandle78fbcc2008-07-05 10:13:36 +0000172 if ignore is not None:
173 ignored_names = ignore(src, names)
174 else:
175 ignored_names = set()
176
Johannes Gijsberse4172ea2005-01-08 12:31:29 +0000177 os.makedirs(dst)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000178 errors = []
Guido van Rossuma2baf461997-04-29 14:06:46 +0000179 for name in names:
Georg Brandle78fbcc2008-07-05 10:13:36 +0000180 if name in ignored_names:
181 continue
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000182 srcname = os.path.join(src, name)
183 dstname = os.path.join(dst, name)
184 try:
185 if symlinks and os.path.islink(srcname):
186 linkto = os.readlink(srcname)
187 os.symlink(linkto, dstname)
188 elif os.path.isdir(srcname):
Georg Brandle78fbcc2008-07-05 10:13:36 +0000189 copytree(srcname, dstname, symlinks, ignore)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000190 else:
Antoine Pitrou1fc02312009-05-01 20:55:35 +0000191 # Will raise a SpecialFileError for unsupported file types
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000192 copy2(srcname, dstname)
Georg Brandla1be88e2005-08-31 22:48:45 +0000193 # catch the Error from the recursive copytree so that we can
194 # continue with other files
195 except Error, err:
196 errors.extend(err.args[0])
Antoine Pitrou1fc02312009-05-01 20:55:35 +0000197 except EnvironmentError, why:
198 errors.append((srcname, dstname, str(why)))
Martin v. Löwis4e678382006-07-30 13:00:31 +0000199 try:
200 copystat(src, dst)
Martin v. Löwis4e678382006-07-30 13:00:31 +0000201 except OSError, why:
Antoine Pitrou9fcd4b32008-08-11 17:21:36 +0000202 if WindowsError is not None and isinstance(why, WindowsError):
203 # Copying file access times may fail on Windows
204 pass
205 else:
Georg Brandl31965292012-08-25 10:11:57 +0200206 errors.append((src, dst, str(why)))
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000207 if errors:
208 raise Error, errors
Guido van Rossumd7673291998-02-06 21:38:09 +0000209
Barry Warsaw234d9a92003-01-24 17:36:15 +0000210def rmtree(path, ignore_errors=False, onerror=None):
Guido van Rossumd7673291998-02-06 21:38:09 +0000211 """Recursively delete a directory tree.
212
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000213 If ignore_errors is set, errors are ignored; otherwise, if onerror
214 is set, it is called to handle the error with arguments (func,
215 path, exc_info) where func is os.listdir, os.remove, or os.rmdir;
216 path is the argument to that function that caused it to fail; and
217 exc_info is a tuple returned by sys.exc_info(). If ignore_errors
218 is false and onerror is None, an exception is raised.
219
Guido van Rossumd7673291998-02-06 21:38:09 +0000220 """
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000221 if ignore_errors:
222 def onerror(*args):
Barry Warsaw234d9a92003-01-24 17:36:15 +0000223 pass
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000224 elif onerror is None:
225 def onerror(*args):
226 raise
Georg Brandl52353982008-01-20 14:17:42 +0000227 try:
228 if os.path.islink(path):
229 # symlinks to directories are forbidden, see bug #1669
230 raise OSError("Cannot call rmtree on a symbolic link")
231 except OSError:
232 onerror(os.path.islink, path, sys.exc_info())
233 # can't continue even if onerror hook returns
234 return
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000235 names = []
236 try:
237 names = os.listdir(path)
238 except os.error, err:
239 onerror(os.listdir, path, sys.exc_info())
240 for name in names:
241 fullname = os.path.join(path, name)
242 try:
243 mode = os.lstat(fullname).st_mode
244 except os.error:
245 mode = 0
246 if stat.S_ISDIR(mode):
247 rmtree(fullname, ignore_errors, onerror)
Barry Warsaw234d9a92003-01-24 17:36:15 +0000248 else:
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000249 try:
250 os.remove(fullname)
251 except os.error, err:
252 onerror(os.remove, fullname, sys.exc_info())
253 try:
254 os.rmdir(path)
255 except os.error:
256 onerror(os.rmdir, path, sys.exc_info())
Guido van Rossumd7673291998-02-06 21:38:09 +0000257
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000258
Sean Reifscheider493894c2008-03-18 17:24:12 +0000259def _basename(path):
260 # A basename() variant which first strips the trailing slash, if present.
261 # Thus we always get the last component of the path, even for directories.
Serhiy Storchakaa4b9c872014-02-11 10:30:06 +0200262 sep = os.path.sep + (os.path.altsep or '')
263 return os.path.basename(path.rstrip(sep))
Sean Reifscheider493894c2008-03-18 17:24:12 +0000264
265def move(src, dst):
266 """Recursively move a file or directory to another location. This is
267 similar to the Unix "mv" command.
268
269 If the destination is a directory or a symlink to a directory, the source
270 is moved inside the directory. The destination path must not already
271 exist.
272
273 If the destination already exists but is not a directory, it may be
274 overwritten depending on os.rename() semantics.
275
276 If the destination is on our current filesystem, then rename() is used.
277 Otherwise, src is copied to the destination and then removed.
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000278 A lot more could be done here... A look at a mv.c shows a lot of
279 the issues this implementation glosses over.
280
281 """
Sean Reifscheider493894c2008-03-18 17:24:12 +0000282 real_dst = dst
283 if os.path.isdir(dst):
Ronald Oussoren58d6b1b2011-05-06 11:31:33 +0200284 if _samefile(src, dst):
285 # We might be on a case insensitive filesystem,
286 # perform the rename anyway.
287 os.rename(src, dst)
288 return
289
Sean Reifscheider493894c2008-03-18 17:24:12 +0000290 real_dst = os.path.join(dst, _basename(src))
291 if os.path.exists(real_dst):
292 raise Error, "Destination path '%s' already exists" % real_dst
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000293 try:
Sean Reifscheider493894c2008-03-18 17:24:12 +0000294 os.rename(src, real_dst)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000295 except OSError:
296 if os.path.isdir(src):
Benjamin Peterson096c3ad2009-02-07 19:08:22 +0000297 if _destinsrc(src, dst):
Brett Cannon1c3fa182004-06-19 21:11:35 +0000298 raise Error, "Cannot move a directory '%s' into itself '%s'." % (src, dst)
Sean Reifscheider493894c2008-03-18 17:24:12 +0000299 copytree(src, real_dst, symlinks=True)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000300 rmtree(src)
301 else:
Sean Reifscheider493894c2008-03-18 17:24:12 +0000302 copy2(src, real_dst)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000303 os.unlink(src)
Brett Cannon1c3fa182004-06-19 21:11:35 +0000304
Benjamin Peterson096c3ad2009-02-07 19:08:22 +0000305def _destinsrc(src, dst):
Antoine Pitrou707c5932009-01-29 20:19:34 +0000306 src = abspath(src)
307 dst = abspath(dst)
308 if not src.endswith(os.path.sep):
309 src += os.path.sep
310 if not dst.endswith(os.path.sep):
311 dst += os.path.sep
312 return dst.startswith(src)
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000313
314def _get_gid(name):
315 """Returns a gid, given a group name."""
316 if getgrnam is None or name is None:
317 return None
318 try:
319 result = getgrnam(name)
320 except KeyError:
321 result = None
322 if result is not None:
323 return result[2]
324 return None
325
326def _get_uid(name):
327 """Returns an uid, given a user name."""
328 if getpwnam is None or name is None:
329 return None
330 try:
331 result = getpwnam(name)
332 except KeyError:
333 result = None
334 if result is not None:
335 return result[2]
336 return None
337
338def _make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0,
339 owner=None, group=None, logger=None):
340 """Create a (possibly compressed) tar file from all the files under
341 'base_dir'.
342
Tarek Ziadée593fad2010-04-20 21:09:06 +0000343 'compress' must be "gzip" (the default), "bzip2", or None.
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000344
345 'owner' and 'group' can be used to define an owner and a group for the
346 archive that is being built. If not provided, the current owner and group
347 will be used.
348
Éric Araujo6e52cf32010-12-15 20:33:50 +0000349 The output tar file will be named 'base_name' + ".tar", possibly plus
Tarek Ziadée593fad2010-04-20 21:09:06 +0000350 the appropriate compression extension (".gz", or ".bz2").
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000351
352 Returns the output filename.
353 """
Tarek Ziadée593fad2010-04-20 21:09:06 +0000354 tar_compression = {'gzip': 'gz', 'bzip2': 'bz2', None: ''}
355 compress_ext = {'gzip': '.gz', 'bzip2': '.bz2'}
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000356
357 # flags for compression program, each element of list will be an argument
358 if compress is not None and compress not in compress_ext.keys():
359 raise ValueError, \
Tarek Ziadée593fad2010-04-20 21:09:06 +0000360 ("bad value for 'compress': must be None, 'gzip' or 'bzip2'")
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000361
Tarek Ziadée593fad2010-04-20 21:09:06 +0000362 archive_name = base_name + '.tar' + compress_ext.get(compress, '')
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000363 archive_dir = os.path.dirname(archive_name)
Tarek Ziadée593fad2010-04-20 21:09:06 +0000364
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000365 if not os.path.exists(archive_dir):
Éric Araujoe7329f42011-08-19 03:07:39 +0200366 if logger is not None:
367 logger.info("creating %s", archive_dir)
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000368 if not dry_run:
369 os.makedirs(archive_dir)
370
371
372 # creating the tarball
373 import tarfile # late import so Python build itself doesn't break
374
375 if logger is not None:
376 logger.info('Creating tar archive')
377
378 uid = _get_uid(owner)
379 gid = _get_gid(group)
380
381 def _set_uid_gid(tarinfo):
382 if gid is not None:
383 tarinfo.gid = gid
384 tarinfo.gname = group
385 if uid is not None:
386 tarinfo.uid = uid
387 tarinfo.uname = owner
388 return tarinfo
389
390 if not dry_run:
391 tar = tarfile.open(archive_name, 'w|%s' % tar_compression[compress])
392 try:
393 tar.add(base_dir, filter=_set_uid_gid)
394 finally:
395 tar.close()
396
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000397 return archive_name
398
Tarek Ziadé62e17ad2010-04-21 13:32:26 +0000399def _call_external_zip(base_dir, zip_filename, verbose=False, dry_run=False):
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000400 # XXX see if we want to keep an external call here
401 if verbose:
402 zipoptions = "-r"
403 else:
404 zipoptions = "-rq"
405 from distutils.errors import DistutilsExecError
406 from distutils.spawn import spawn
407 try:
408 spawn(["zip", zipoptions, zip_filename, base_dir], dry_run=dry_run)
409 except DistutilsExecError:
410 # XXX really should distinguish between "couldn't find
411 # external 'zip' command" and "zip failed".
412 raise ExecError, \
413 ("unable to create zip file '%s': "
414 "could neither import the 'zipfile' module nor "
415 "find a standalone zip utility") % zip_filename
416
417def _make_zipfile(base_name, base_dir, verbose=0, dry_run=0, logger=None):
418 """Create a zip file from all the files under 'base_dir'.
419
Éric Araujo6e52cf32010-12-15 20:33:50 +0000420 The output zip file will be named 'base_name' + ".zip". Uses either the
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000421 "zipfile" Python module (if available) or the InfoZIP "zip" utility
422 (if installed and found on the default search path). If neither tool is
423 available, raises ExecError. Returns the name of the output zip
424 file.
425 """
426 zip_filename = base_name + ".zip"
427 archive_dir = os.path.dirname(base_name)
428
429 if not os.path.exists(archive_dir):
430 if logger is not None:
431 logger.info("creating %s", archive_dir)
432 if not dry_run:
433 os.makedirs(archive_dir)
434
435 # If zipfile module is not available, try spawning an external 'zip'
436 # command.
437 try:
438 import zipfile
439 except ImportError:
440 zipfile = None
441
442 if zipfile is None:
Tarek Ziadé62e17ad2010-04-21 13:32:26 +0000443 _call_external_zip(base_dir, zip_filename, verbose, dry_run)
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000444 else:
445 if logger is not None:
446 logger.info("creating '%s' and adding '%s' to it",
447 zip_filename, base_dir)
448
449 if not dry_run:
Benjamin Petersonb898b4f2014-02-02 15:30:22 -0500450 with zipfile.ZipFile(zip_filename, "w",
451 compression=zipfile.ZIP_DEFLATED) as zf:
452 for dirpath, dirnames, filenames in os.walk(base_dir):
453 for name in filenames:
454 path = os.path.normpath(os.path.join(dirpath, name))
455 if os.path.isfile(path):
456 zf.write(path, path)
457 if logger is not None:
458 logger.info("adding '%s'", path)
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000459
460 return zip_filename
461
462_ARCHIVE_FORMATS = {
463 'gztar': (_make_tarball, [('compress', 'gzip')], "gzip'ed tar-file"),
464 'bztar': (_make_tarball, [('compress', 'bzip2')], "bzip2'ed tar-file"),
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000465 'tar': (_make_tarball, [('compress', None)], "uncompressed tar file"),
466 'zip': (_make_zipfile, [],"ZIP file")
467 }
468
469def get_archive_formats():
470 """Returns a list of supported formats for archiving and unarchiving.
471
472 Each element of the returned sequence is a tuple (name, description)
473 """
474 formats = [(name, registry[2]) for name, registry in
475 _ARCHIVE_FORMATS.items()]
476 formats.sort()
477 return formats
478
479def register_archive_format(name, function, extra_args=None, description=''):
480 """Registers an archive format.
481
482 name is the name of the format. function is the callable that will be
483 used to create archives. If provided, extra_args is a sequence of
484 (name, value) tuples that will be passed as arguments to the callable.
485 description can be provided to describe the format, and will be returned
486 by the get_archive_formats() function.
487 """
488 if extra_args is None:
489 extra_args = []
Florent Xicluna1f3b4e12010-03-07 12:14:25 +0000490 if not isinstance(function, collections.Callable):
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000491 raise TypeError('The %s object is not callable' % function)
492 if not isinstance(extra_args, (tuple, list)):
493 raise TypeError('extra_args needs to be a sequence')
494 for element in extra_args:
495 if not isinstance(element, (tuple, list)) or len(element) !=2 :
496 raise TypeError('extra_args elements are : (arg_name, value)')
497
498 _ARCHIVE_FORMATS[name] = (function, extra_args, description)
499
500def unregister_archive_format(name):
501 del _ARCHIVE_FORMATS[name]
502
503def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
504 dry_run=0, owner=None, group=None, logger=None):
505 """Create an archive file (eg. zip or tar).
506
507 'base_name' is the name of the file to create, minus any format-specific
Tarek Ziadée593fad2010-04-20 21:09:06 +0000508 extension; 'format' is the archive format: one of "zip", "tar", "bztar"
509 or "gztar".
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000510
511 'root_dir' is a directory that will be the root directory of the
512 archive; ie. we typically chdir into 'root_dir' before creating the
513 archive. 'base_dir' is the directory where we start archiving from;
514 ie. 'base_dir' will be the common prefix of all files and
515 directories in the archive. 'root_dir' and 'base_dir' both default
516 to the current directory. Returns the name of the archive file.
517
518 'owner' and 'group' are used when creating a tar archive. By default,
519 uses the current owner and group.
520 """
521 save_cwd = os.getcwd()
522 if root_dir is not None:
523 if logger is not None:
524 logger.debug("changing into '%s'", root_dir)
525 base_name = os.path.abspath(base_name)
526 if not dry_run:
527 os.chdir(root_dir)
528
529 if base_dir is None:
530 base_dir = os.curdir
531
532 kwargs = {'dry_run': dry_run, 'logger': logger}
533
534 try:
535 format_info = _ARCHIVE_FORMATS[format]
536 except KeyError:
537 raise ValueError, "unknown archive format '%s'" % format
538
539 func = format_info[0]
540 for arg, val in format_info[1]:
541 kwargs[arg] = val
542
543 if format != 'zip':
544 kwargs['owner'] = owner
545 kwargs['group'] = group
546
547 try:
548 filename = func(base_name, base_dir, **kwargs)
549 finally:
550 if root_dir is not None:
551 if logger is not None:
552 logger.debug("changing back to '%s'", save_cwd)
553 os.chdir(save_cwd)
554
555 return filename