summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGreg Ward <gward@python.net>2000-04-04 02:05:59 (GMT)
committerGreg Ward <gward@python.net>2000-04-04 02:05:59 (GMT)
commitaebf706b4e845d7525d2d8e792f0e23fcfb3e6af (patch)
tree97c2dc7d622118a518c84934707c0c4f1dc1c1d1
parentfe6462c1f3206dd69659d20b5babab515fc6d4c3 (diff)
downloadcpython-aebf706b4e845d7525d2d8e792f0e23fcfb3e6af.zip
cpython-aebf706b4e845d7525d2d8e792f0e23fcfb3e6af.tar.gz
cpython-aebf706b4e845d7525d2d8e792f0e23fcfb3e6af.tar.bz2
Reorganization: ripped util.py to shreds, creating in the process:
- file_util.py: operations on single files - dir_util.py: operations on whole directories or directory trees - dep_util.py: simple timestamp-based dependency analysis - archive_util.py: creation of archive (tar, zip, ...) files The functions left in util.py are miscellany that don't fit in any of the new files.
-rw-r--r--Lib/distutils/archive_util.py152
-rw-r--r--Lib/distutils/dep_util.py116
-rw-r--r--Lib/distutils/dir_util.py196
-rw-r--r--Lib/distutils/file_util.py248
-rw-r--r--Lib/distutils/util.py671
5 files changed, 719 insertions, 664 deletions
diff --git a/Lib/distutils/archive_util.py b/Lib/distutils/archive_util.py
new file mode 100644
index 0000000..a28eed1
--- /dev/null
+++ b/Lib/distutils/archive_util.py
@@ -0,0 +1,152 @@
+"""distutils.archive_util
+
+Utility functions for creating archive files (tarballs, zip files,
+that sort of thing)."""
+
+# created 2000/04/03, Greg Ward (extracted from util.py)
+
+__revision__ = "$Id$"
+
+import os
+from distutils.errors import DistutilsExecError
+from distutils.spawn import spawn
+
+
+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 ()
diff --git a/Lib/distutils/dep_util.py b/Lib/distutils/dep_util.py
new file mode 100644
index 0000000..97812ed
--- /dev/null
+++ b/Lib/distutils/dep_util.py
@@ -0,0 +1,116 @@
+"""distutils.dep_util
+
+Utility functions for simple, timestamp-based dependency of files
+and groups of files; also, function based entirely on such
+timestamp dependency analysis."""
+
+# created 2000/04/03, Greg Ward (extracted from util.py)
+
+__revision__ = "$Id$"
+
+import os
+from distutils.errors import DistutilsFileError
+
+
+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 ()
diff --git a/Lib/distutils/dir_util.py b/Lib/distutils/dir_util.py
new file mode 100644
index 0000000..c049bbd
--- /dev/null
+++ b/Lib/distutils/dir_util.py
@@ -0,0 +1,196 @@
+"""distutils.dir_util
+
+Utility functions for manipulating directories and directory trees."""
+
+# created 2000/04/03, Greg Ward (extracted from util.py)
+
+__revision__ = "$Id$"
+
+import os
+from distutils.errors import DistutilsFileError
+
+
+# cache for by mkpath() -- in addition to cheapening redundant calls,
+# eliminates redundant "creating /foo/bar/baz" messages in dry-run mode
+PATH_CREATED = {}
+
+# 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 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'."""
+
+ from distutils.file_util import 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)."""
+
+ from shutil import rmtree
+
+ if verbose:
+ print "removing '%s' (and everything under it)" % directory
+ if dry_run:
+ return
+ try:
+ 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)
diff --git a/Lib/distutils/file_util.py b/Lib/distutils/file_util.py
new file mode 100644
index 0000000..91545ab
--- /dev/null
+++ b/Lib/distutils/file_util.py
@@ -0,0 +1,248 @@
+"""distutils.file_util
+
+Utility functions for operating on single files."""
+
+# created 2000/04/03, Greg Ward (extracted from util.py)
+
+__revision__ = "$Id$"
+
+import os
+from distutils.errors import DistutilsFileError
+
+
+# for generating verbose output in 'copy_file()'
+_copy_action = { None: 'copying',
+ 'hard': 'hard linking',
+ 'sym': 'symbolically linking' }
+
+
+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 *
+ from distutils.dep_util import newer
+
+ 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 ()
+
+
+# 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 ()
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 ()