1
"""Utility functions for copying and archiving files and directory trees.
3
XXX The functions here don't copy the resource fork or other metadata on Mac.
11
from pwd import getpwnam
16
from grp import getgrnam
20
class ExecError(EnvironmentError):
21
"""Raised when a command could not be executed"""
24
"""Returns a gid, given a group name."""
25
if getgrnam is None or name is None:
28
result = getgrnam(name)
31
if result is not None:
36
"""Returns an uid, given a user name."""
37
if getpwnam is None or name is None:
40
result = getpwnam(name)
43
if result is not None:
47
def _make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0,
48
owner=None, group=None, logger=None):
49
"""Create a (possibly compressed) tar file from all the files under
52
'compress' must be "gzip" (the default), "bzip2", or None.
54
'owner' and 'group' can be used to define an owner and a group for the
55
archive that is being built. If not provided, the current owner and group
58
The output tar file will be named 'base_dir' + ".tar", possibly plus
59
the appropriate compression extension (".gz", or ".bz2").
61
Returns the output filename.
63
tar_compression = {'gzip': 'gz', 'bzip2': 'bz2', None: ''}
64
compress_ext = {'gzip': '.gz', 'bzip2': '.bz2'}
66
# flags for compression program, each element of list will be an argument
67
if compress is not None and compress not in compress_ext.keys():
69
("bad value for 'compress': must be None, 'gzip' or 'bzip2'")
71
archive_name = base_name + '.tar' + compress_ext.get(compress, '')
72
archive_dir = os.path.dirname(archive_name)
74
if not os.path.exists(archive_dir):
75
if logger is not None:
76
logger.info("creating %s" % archive_dir)
78
os.makedirs(archive_dir)
81
# creating the tarball
82
import _tarfile27 as tarfile # late import so Python build itself doesn't break
84
if logger is not None:
85
logger.info('Creating tar archive')
90
def _set_uid_gid(tarinfo):
100
tar = tarfile.open(archive_name, 'w|%s' % tar_compression[compress])
102
tar.add(base_dir, filter=_set_uid_gid)
108
def _call_external_zip(base_dir, zip_filename, verbose=False, dry_run=False):
109
# XXX see if we want to keep an external call here
114
from distutils.errors import DistutilsExecError
115
from distutils.spawn import spawn
117
spawn(["zip", zipoptions, zip_filename, base_dir], dry_run=dry_run)
118
except DistutilsExecError:
119
# XXX really should distinguish between "couldn't find
120
# external 'zip' command" and "zip failed".
122
("unable to create zip file '%s': "
123
"could neither import the 'zipfile' module nor "
124
"find a standalone zip utility") % zip_filename
126
def _make_zipfile(base_name, base_dir, verbose=0, dry_run=0, logger=None):
127
"""Create a zip file from all the files under 'base_dir'.
129
The output zip file will be named 'base_dir' + ".zip". Uses either the
130
"zipfile" Python module (if available) or the InfoZIP "zip" utility
131
(if installed and found on the default search path). If neither tool is
132
available, raises ExecError. Returns the name of the output zip
135
zip_filename = base_name + ".zip"
136
archive_dir = os.path.dirname(base_name)
138
if not os.path.exists(archive_dir):
139
if logger is not None:
140
logger.info("creating %s", archive_dir)
142
os.makedirs(archive_dir)
144
# If zipfile module is not available, try spawning an external 'zip'
152
_call_external_zip(base_dir, zip_filename, verbose, dry_run)
154
if logger is not None:
155
logger.info("creating '%s' and adding '%s' to it",
156
zip_filename, base_dir)
159
zip = zipfile.ZipFile(zip_filename, "w",
160
compression=zipfile.ZIP_DEFLATED)
162
for dirpath, dirnames, filenames in os.walk(base_dir):
163
for name in filenames:
164
path = os.path.normpath(os.path.join(dirpath, name))
165
if os.path.isfile(path):
166
zip.write(path, path)
167
if logger is not None:
168
logger.info("adding '%s'", path)
174
'gztar': (_make_tarball, [('compress', 'gzip')], "gzip'ed tar-file"),
175
'bztar': (_make_tarball, [('compress', 'bzip2')], "bzip2'ed tar-file"),
176
'tar': (_make_tarball, [('compress', None)], "uncompressed tar file"),
177
'zip': (_make_zipfile, [],"ZIP file")
180
def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
181
dry_run=0, owner=None, group=None, logger=None):
182
"""Create an archive file (eg. zip or tar).
184
'base_name' is the name of the file to create, minus any format-specific
185
extension; 'format' is the archive format: one of "zip", "tar", "bztar"
188
'root_dir' is a directory that will be the root directory of the
189
archive; ie. we typically chdir into 'root_dir' before creating the
190
archive. 'base_dir' is the directory where we start archiving from;
191
ie. 'base_dir' will be the common prefix of all files and
192
directories in the archive. 'root_dir' and 'base_dir' both default
193
to the current directory. Returns the name of the archive file.
195
'owner' and 'group' are used when creating a tar archive. By default,
196
uses the current owner and group.
198
save_cwd = os.getcwd()
199
if root_dir is not None:
200
if logger is not None:
201
logger.debug("changing into '%s'", root_dir)
202
base_name = os.path.abspath(base_name)
209
kwargs = {'dry_run': dry_run, 'logger': logger}
212
format_info = _ARCHIVE_FORMATS[format]
214
raise ValueError, "unknown archive format '%s'" % format
216
func = format_info[0]
217
for arg, val in format_info[1]:
221
kwargs['owner'] = owner
222
kwargs['group'] = group
225
filename = func(base_name, base_dir, **kwargs)
227
if root_dir is not None:
228
if logger is not None:
229
logger.debug("changing back to '%s'", save_cwd)