diff options
Diffstat (limited to 'Lib/distutils/util.py')
-rw-r--r-- | Lib/distutils/util.py | 671 |
1 files changed, 7 insertions, 664 deletions
diff --git a/Lib/distutils/util.py b/Lib/distutils/util.py index 22fc437..ddcf0d2 100644 --- a/Lib/distutils/util.py +++ b/Lib/distutils/util.py @@ -1,11 +1,7 @@ """distutils.util -General-purpose utility functions used throughout the Distutils -(especially in command classes). Mostly filesystem manipulation, but -not limited to that. The functions in this module generally raise -DistutilsFileError when they have problems with the filesystem, because -os.error in pre-1.5.2 Python only gives the error message and not the -file causing it.""" +Miscellaneous utility functions -- anything that doesn't fit into +one of the other *util.py modules.""" # created 1999/03/08, Greg Ward @@ -15,526 +11,11 @@ import sys, os, string, re, shutil from distutils.errors import * from distutils.spawn import spawn -# cache for by mkpath() -- in addition to cheapening redundant calls, -# eliminates redundant "creating /foo/bar/baz" messages in dry-run mode -PATH_CREATED = {} - -# for generating verbose output in 'copy_file()' -_copy_action = { None: 'copying', - 'hard': 'hard linking', - 'sym': 'symbolically linking' } - -# I don't use os.makedirs because a) it's new to Python 1.5.2, and -# b) it blows up if the directory already exists (I want to silently -# succeed in that case). -def mkpath (name, mode=0777, verbose=0, dry_run=0): - """Create a directory and any missing ancestor directories. If the - directory already exists (or if 'name' is the empty string, which - means the current directory, which of course exists), then do - nothing. Raise DistutilsFileError if unable to create some - directory along the way (eg. some sub-path exists, but is a file - rather than a directory). If 'verbose' is true, print a one-line - summary of each mkdir to stdout. Return the list of directories - actually created.""" - - global PATH_CREATED - - # XXX what's the better way to handle verbosity? print as we create - # each directory in the path (the current behaviour), or only announce - # the creation of the whole path? (quite easy to do the latter since - # we're not using a recursive algorithm) - - name = os.path.normpath (name) - created_dirs = [] - if os.path.isdir (name) or name == '': - return created_dirs - if PATH_CREATED.get (name): - return created_dirs - - (head, tail) = os.path.split (name) - tails = [tail] # stack of lone dirs to create - - while head and tail and not os.path.isdir (head): - #print "splitting '%s': " % head, - (head, tail) = os.path.split (head) - #print "to ('%s','%s')" % (head, tail) - tails.insert (0, tail) # push next higher dir onto stack - - #print "stack of tails:", tails - - # now 'head' contains the deepest directory that already exists - # (that is, the child of 'head' in 'name' is the highest directory - # that does *not* exist) - for d in tails: - #print "head = %s, d = %s: " % (head, d), - head = os.path.join (head, d) - if PATH_CREATED.get (head): - continue - - if verbose: - print "creating", head - - if not dry_run: - try: - os.mkdir (head) - created_dirs.append(head) - except OSError, exc: - raise DistutilsFileError, \ - "could not create '%s': %s" % (head, exc[-1]) - - PATH_CREATED[head] = 1 - return created_dirs - -# mkpath () - - -def create_tree (base_dir, files, mode=0777, verbose=0, dry_run=0): - - """Create all the empty directories under 'base_dir' needed to - put 'files' there. 'base_dir' is just the a name of a directory - which doesn't necessarily exist yet; 'files' is a list of filenames - to be interpreted relative to 'base_dir'. 'base_dir' + the - directory portion of every file in 'files' will be created if it - doesn't already exist. 'mode', 'verbose' and 'dry_run' flags are as - for 'mkpath()'.""" - - # First get the list of directories to create - need_dir = {} - for file in files: - need_dir[os.path.join (base_dir, os.path.dirname (file))] = 1 - need_dirs = need_dir.keys() - need_dirs.sort() - - # Now create them - for dir in need_dirs: - mkpath (dir, mode, verbose, dry_run) - -# create_tree () - - -def newer (source, target): - """Return true if 'source' exists and is more recently modified than - 'target', or if 'source' exists and 'target' doesn't. Return - false if both exist and 'target' is the same age or younger than - 'source'. Raise DistutilsFileError if 'source' does not - exist.""" - - if not os.path.exists (source): - raise DistutilsFileError, "file '%s' does not exist" % source - if not os.path.exists (target): - return 1 - - from stat import ST_MTIME - mtime1 = os.stat(source)[ST_MTIME] - mtime2 = os.stat(target)[ST_MTIME] - - return mtime1 > mtime2 - -# newer () - - -def newer_pairwise (sources, targets): - """Walk two filename lists in parallel, testing if each source is newer - than its corresponding target. Return a pair of lists (sources, - targets) where source is newer than target, according to the - semantics of 'newer()'.""" - - if len (sources) != len (targets): - raise ValueError, "'sources' and 'targets' must be same length" - - # build a pair of lists (sources, targets) where source is newer - n_sources = [] - n_targets = [] - for i in range (len (sources)): - if newer (sources[i], targets[i]): - n_sources.append (sources[i]) - n_targets.append (targets[i]) - - return (n_sources, n_targets) - -# newer_pairwise () - - -def newer_group (sources, target, missing='error'): - """Return true if 'target' is out-of-date with respect to any - file listed in 'sources'. In other words, if 'target' exists and - is newer than every file in 'sources', return false; otherwise - return true. 'missing' controls what we do when a source file is - missing; the default ("error") is to blow up with an OSError from - inside 'stat()'; if it is "ignore", we silently drop any missing - source files; if it is "newer", any missing source files make us - assume that 'target' is out-of-date (this is handy in "dry-run" - mode: it'll make you pretend to carry out commands that wouldn't - work because inputs are missing, but that doesn't matter because - you're not actually going to run the commands).""" - - # If the target doesn't even exist, then it's definitely out-of-date. - if not os.path.exists (target): - return 1 - - # Otherwise we have to find out the hard way: if *any* source file - # is more recent than 'target', then 'target' is out-of-date and - # we can immediately return true. If we fall through to the end - # of the loop, then 'target' is up-to-date and we return false. - from stat import ST_MTIME - target_mtime = os.stat (target)[ST_MTIME] - for source in sources: - if not os.path.exists (source): - if missing == 'error': # blow up when we stat() the file - pass - elif missing == 'ignore': # missing source dropped from - continue # target's dependency list - elif missing == 'newer': # missing source means target is - return 1 # out-of-date - - source_mtime = os.stat(source)[ST_MTIME] - if source_mtime > target_mtime: - return 1 - else: - return 0 - -# newer_group () - - -# XXX this isn't used anywhere, and worse, it has the same name as a method -# in Command with subtly different semantics. (This one just has one -# source -> one dest; that one has many sources -> one dest.) Nuke it? -def make_file (src, dst, func, args, - verbose=0, update_message=None, noupdate_message=None): - """Makes 'dst' from 'src' (both filenames) by calling 'func' with - 'args', but only if it needs to: i.e. if 'dst' does not exist or - 'src' is newer than 'dst'.""" - - if newer (src, dst): - if verbose and update_message: - print update_message - apply (func, args) - else: - if verbose and noupdate_message: - print noupdate_message - -# make_file () - - -def _copy_file_contents (src, dst, buffer_size=16*1024): - """Copy the file 'src' to 'dst'; both must be filenames. Any error - opening either file, reading from 'src', or writing to 'dst', - raises DistutilsFileError. Data is read/written in chunks of - 'buffer_size' bytes (default 16k). No attempt is made to handle - anything apart from regular files.""" - - # Stolen from shutil module in the standard library, but with - # custom error-handling added. - - fsrc = None - fdst = None - try: - try: - fsrc = open(src, 'rb') - except os.error, (errno, errstr): - raise DistutilsFileError, \ - "could not open '%s': %s" % (src, errstr) - - try: - fdst = open(dst, 'wb') - except os.error, (errno, errstr): - raise DistutilsFileError, \ - "could not create '%s': %s" % (dst, errstr) - - while 1: - try: - buf = fsrc.read (buffer_size) - except os.error, (errno, errstr): - raise DistutilsFileError, \ - "could not read from '%s': %s" % (src, errstr) - - if not buf: - break - - try: - fdst.write(buf) - except os.error, (errno, errstr): - raise DistutilsFileError, \ - "could not write to '%s': %s" % (dst, errstr) - - finally: - if fdst: - fdst.close() - if fsrc: - fsrc.close() - -# _copy_file_contents() - - -def copy_file (src, dst, - preserve_mode=1, - preserve_times=1, - update=0, - link=None, - verbose=0, - dry_run=0): - - """Copy a file 'src' to 'dst'. If 'dst' is a directory, then 'src' - is copied there with the same name; otherwise, it must be a - filename. (If the file exists, it will be ruthlessly clobbered.) - If 'preserve_mode' is true (the default), the file's mode (type - and permission bits, or whatever is analogous on the current - platform) is copied. If 'preserve_times' is true (the default), - the last-modified and last-access times are copied as well. If - 'update' is true, 'src' will only be copied if 'dst' does not - exist, or if 'dst' does exist but is older than 'src'. If - 'verbose' is true, then a one-line summary of the copy will be - printed to stdout. - - 'link' allows you to make hard links (os.link) or symbolic links - (os.symlink) instead of copying: set it to "hard" or "sym"; if it - is None (the default), files are copied. Don't set 'link' on - systems that don't support it: 'copy_file()' doesn't check if - hard or symbolic linking is availalble. - - Under Mac OS, uses the native file copy function in macostools; - on other systems, uses '_copy_file_contents()' to copy file - contents. - - Return true if the file was copied (or would have been copied), - false otherwise (ie. 'update' was true and the destination is - up-to-date).""" - - # XXX if the destination file already exists, we clobber it if - # copying, but blow up if linking. Hmmm. And I don't know what - # macostools.copyfile() does. Should definitely be consistent, and - # should probably blow up if destination exists and we would be - # changing it (ie. it's not already a hard/soft link to src OR - # (not update) and (src newer than dst). - - from stat import * - - if not os.path.isfile (src): - raise DistutilsFileError, \ - "can't copy '%s': doesn't exist or not a regular file" % src - - if os.path.isdir (dst): - dir = dst - dst = os.path.join (dst, os.path.basename (src)) - else: - dir = os.path.dirname (dst) - - if update and not newer (src, dst): - if verbose: - print "not copying %s (output up-to-date)" % src - return 0 - - try: - action = _copy_action[link] - except KeyError: - raise ValueError, \ - "invalid value '%s' for 'link' argument" % link - if verbose: - print "%s %s -> %s" % (action, src, dir) - - if dry_run: - return 1 - - # On a Mac, use the native file copy routine - if os.name == 'mac': - import macostools - try: - macostools.copy (src, dst, 0, preserve_times) - except OSError, exc: - raise DistutilsFileError, \ - "could not copy '%s' to '%s': %s" % (src, dst, exc[-1]) - - # If linking (hard or symbolic), use the appropriate system call - # (Unix only, of course, but that's the caller's responsibility) - elif link == 'hard': - if not (os.path.exists (dst) and os.path.samefile (src, dst)): - os.link (src, dst) - elif link == 'sym': - if not (os.path.exists (dst) and os.path.samefile (src, dst)): - os.symlink (src, dst) - - # Otherwise (non-Mac, not linking), copy the file contents and - # (optionally) copy the times and mode. - else: - _copy_file_contents (src, dst) - if preserve_mode or preserve_times: - st = os.stat (src) - - # According to David Ascher <da@ski.org>, utime() should be done - # before chmod() (at least under NT). - if preserve_times: - os.utime (dst, (st[ST_ATIME], st[ST_MTIME])) - if preserve_mode: - os.chmod (dst, S_IMODE (st[ST_MODE])) - - return 1 - -# copy_file () - - -def copy_tree (src, dst, - preserve_mode=1, - preserve_times=1, - preserve_symlinks=0, - update=0, - verbose=0, - dry_run=0): - - """Copy an entire directory tree 'src' to a new location 'dst'. Both - 'src' and 'dst' must be directory names. If 'src' is not a - directory, raise DistutilsFileError. If 'dst' does not exist, it is - created with 'mkpath()'. The end result of the copy is that every - file in 'src' is copied to 'dst', and directories under 'src' are - recursively copied to 'dst'. Return the list of files that were - copied or might have been copied, using their output name. The - return value is unaffected by 'update' or 'dry_run': it is simply - the list of all files under 'src', with the names changed to be - under 'dst'. - - 'preserve_mode' and 'preserve_times' are the same as for - 'copy_file'; note that they only apply to regular files, not to - directories. If 'preserve_symlinks' is true, symlinks will be - copied as symlinks (on platforms that support them!); otherwise - (the default), the destination of the symlink will be copied. - 'update' and 'verbose' are the same as for 'copy_file'.""" - - if not dry_run and not os.path.isdir (src): - raise DistutilsFileError, \ - "cannot copy tree '%s': not a directory" % src - try: - names = os.listdir (src) - except os.error, (errno, errstr): - if dry_run: - names = [] - else: - raise DistutilsFileError, \ - "error listing files in '%s': %s" % (src, errstr) - - if not dry_run: - mkpath (dst, verbose=verbose) - - outputs = [] - - for n in names: - src_name = os.path.join (src, n) - dst_name = os.path.join (dst, n) - - if preserve_symlinks and os.path.islink (src_name): - link_dest = os.readlink (src_name) - if verbose: - print "linking %s -> %s" % (dst_name, link_dest) - if not dry_run: - os.symlink (link_dest, dst_name) - outputs.append (dst_name) - - elif os.path.isdir (src_name): - outputs.extend ( - copy_tree (src_name, dst_name, - preserve_mode, preserve_times, preserve_symlinks, - update, verbose, dry_run)) - else: - copy_file (src_name, dst_name, - preserve_mode, preserve_times, - update, None, verbose, dry_run) - outputs.append (dst_name) - - return outputs - -# copy_tree () - - -def remove_tree (directory, verbose=0, dry_run=0): - """Recursively remove an entire directory tree. Any errors are ignored - (apart from being reported to stdout if 'verbose' is true).""" - - if verbose: - print "removing '%s' (and everything under it)" % directory - if dry_run: - return - try: - shutil.rmtree(directory,1) - except (IOError, OSError), exc: - if verbose: - if exc.filename: - print "error removing %s: %s (%s)" % \ - (directory, exc.strerror, exc.filename) - else: - print "error removing %s: %s" % (directory, exc.strerror) - - -# XXX I suspect this is Unix-specific -- need porting help! -def move_file (src, dst, - verbose=0, - dry_run=0): - - """Move a file 'src' to 'dst'. If 'dst' is a directory, the file - will be moved into it with the same name; otherwise, 'src' is - just renamed to 'dst'. Return the new full name of the file. - - Handles cross-device moves on Unix using - 'copy_file()'. What about other systems???""" - - from os.path import exists, isfile, isdir, basename, dirname - - if verbose: - print "moving %s -> %s" % (src, dst) - - if dry_run: - return dst - - if not isfile (src): - raise DistutilsFileError, \ - "can't move '%s': not a regular file" % src - - if isdir (dst): - dst = os.path.join (dst, basename (src)) - elif exists (dst): - raise DistutilsFileError, \ - "can't move '%s': destination '%s' already exists" % \ - (src, dst) - - if not isdir (dirname (dst)): - raise DistutilsFileError, \ - "can't move '%s': destination '%s' not a valid path" % \ - (src, dst) - - copy_it = 0 - try: - os.rename (src, dst) - except os.error, (num, msg): - if num == errno.EXDEV: - copy_it = 1 - else: - raise DistutilsFileError, \ - "couldn't move '%s' to '%s': %s" % (src, dst, msg) - - if copy_it: - copy_file (src, dst) - try: - os.unlink (src) - except os.error, (num, msg): - try: - os.unlink (dst) - except os.error: - pass - raise DistutilsFileError, \ - ("couldn't move '%s' to '%s' by copy/delete: " + - "delete '%s' failed: %s") % \ - (src, dst, src, msg) - - return dst - -# move_file () - - -def write_file (filename, contents): - """Create a file with the specified name and write 'contents' (a - sequence of strings without line terminators) to it.""" - - f = open (filename, "w") - for line in contents: - f.write (line + "\n") - f.close () +# for backwards compatibility: +from distutils.file_util import * +from distutils.dir_util import * +from distutils.dep_util import * +from distutils.archive_util import * def get_platform (): @@ -620,141 +101,3 @@ def subst_vars (str, local_vars): # subst_vars () -def make_tarball (base_name, base_dir, compress="gzip", - verbose=0, dry_run=0): - """Create a (possibly compressed) tar file from all the files under - 'base_dir'. 'compress' must be "gzip" (the default), "compress", or - None. Both "tar" and the compression utility named by 'compress' - must be on the default program search path, so this is probably - Unix-specific. The output tar file will be named 'base_dir' + - ".tar", possibly plus the appropriate compression extension - (".gz" or ".Z"). Return the output filename.""" - - # XXX GNU tar 1.13 has a nifty option to add a prefix directory. - # It's pretty new, though, so we certainly can't require it -- - # but it would be nice to take advantage of it to skip the - # "create a tree of hardlinks" step! (Would also be nice to - # detect GNU tar to use its 'z' option and save a step.) - - compress_ext = { 'gzip': ".gz", - 'compress': ".Z" } - - if compress is not None and compress not in ('gzip', 'compress'): - raise ValueError, \ - "bad value for 'compress': must be None, 'gzip', or 'compress'" - - archive_name = base_name + ".tar" - cmd = ["tar", "-cf", archive_name, base_dir] - spawn (cmd, verbose=verbose, dry_run=dry_run) - - if compress: - spawn ([compress, archive_name], verbose=verbose, dry_run=dry_run) - return archive_name + compress_ext[compress] - else: - return archive_name - -# make_tarball () - - -def make_zipfile (base_name, base_dir, verbose=0, dry_run=0): - """Create a zip file from all the files under 'base_dir'. The - output zip file will be named 'base_dir' + ".zip". Uses either the - InfoZIP "zip" utility (if installed and found on the default search - path) or the "zipfile" Python module (if available). If neither - tool is available, raises DistutilsExecError. Returns the name - of the output zip file.""" - - # This initially assumed the Unix 'zip' utility -- but - # apparently InfoZIP's zip.exe works the same under Windows, so - # no changes needed! - - zip_filename = base_name + ".zip" - try: - spawn (["zip", "-rq", zip_filename, base_dir], - verbose=verbose, dry_run=dry_run) - except DistutilsExecError: - - # XXX really should distinguish between "couldn't find - # external 'zip' command" and "zip failed" -- shouldn't try - # again in the latter case. (I think fixing this will - # require some cooperation from the spawn module -- perhaps - # a utility function to search the path, so we can fallback - # on zipfile.py without the failed spawn.) - try: - import zipfile - except ImportError: - raise DistutilsExecError, \ - ("unable to create zip file '%s': " + - "could neither find a standalone zip utility nor " + - "import the 'zipfile' module") % zip_filename - - if verbose: - print "creating '%s' and adding '%s' to it" % \ - (zip_filename, base_dir) - - def visit (z, dirname, names): - for name in names: - path = os.path.join (dirname, name) - if os.path.isfile (path): - z.write (path, path) - - if not dry_run: - z = zipfile.ZipFile (zip_filename, "wb", - compression=zipfile.ZIP_DEFLATED) - - os.path.walk (base_dir, visit, z) - z.close() - - return zip_filename - -# make_zipfile () - - -def make_archive (base_name, format, - root_dir=None, base_dir=None, - verbose=0, dry_run=0): - - """Create an archive file (eg. zip or tar). 'base_name' is the name - of the file to create, minus any format-specific extension; 'format' - is the archive format: one of "zip", "tar", "ztar", or "gztar". - 'root_dir' is a directory that will be the root directory of the - archive; ie. we typically chdir into 'root_dir' before creating the - archive. 'base_dir' is the directory where we start archiving from; - ie. 'base_dir' will be the common prefix of all files and - directories in the archive. 'root_dir' and 'base_dir' both default - to the current directory.""" - - save_cwd = os.getcwd() - if root_dir is not None: - if verbose: - print "changing into '%s'" % root_dir - base_name = os.path.abspath (base_name) - if not dry_run: - os.chdir (root_dir) - - if base_dir is None: - base_dir = os.curdir - - kwargs = { 'verbose': verbose, - 'dry_run': dry_run } - - if format == 'gztar': - func = make_tarball - kwargs['compress'] = 'gzip' - elif format == 'ztar': - func = make_tarball - kwargs['compress'] = 'compress' - elif format == 'tar': - func = make_tarball - kwargs['compress'] = None - elif format == 'zip': - func = make_zipfile - - apply (func, (base_name, base_dir), kwargs) - - if root_dir is not None: - if verbose: - print "changing back to '%s'" % save_cwd - os.chdir (save_cwd) - -# make_archive () |