summaryrefslogtreecommitdiffstats
path: root/Lib/distutils
diff options
context:
space:
mode:
authorGreg Ward <gward@python.net>2000-03-31 03:02:22 (GMT)
committerGreg Ward <gward@python.net>2000-03-31 03:02:22 (GMT)
commit32ce329ce4c8ac8a958a624f060274bd50a8f5b9 (patch)
treef410a77c9dc368188ef4312b610fe70846456cf2 /Lib/distutils
parent318a9d7aa2163031eac19291592b1f396e11a879 (diff)
downloadcpython-32ce329ce4c8ac8a958a624f060274bd50a8f5b9.zip
cpython-32ce329ce4c8ac8a958a624f060274bd50a8f5b9.tar.gz
cpython-32ce329ce4c8ac8a958a624f060274bd50a8f5b9.tar.bz2
Added 'create_tree()'.
Changes to 'copy_file()': * added support for making hard links and symlinks * noted that it silently clobbers existing files when copying, but blows up if destination exists when linking -- hmmm... * error message tweak Added 'base_name' parameter to 'make_tarball()' and 'make_zipfile()'. Added 'make_archive()' -- wrapper around 'make_tarball()' or 'make_zipfile()' to take care of the archive "root directory".
Diffstat (limited to 'Lib/distutils')
-rw-r--r--Lib/distutils/util.py160
1 files changed, 135 insertions, 25 deletions
diff --git a/Lib/distutils/util.py b/Lib/distutils/util.py
index 2729619..6674d0a 100644
--- a/Lib/distutils/util.py
+++ b/Lib/distutils/util.py
@@ -19,6 +19,11 @@ from distutils.spawn import spawn
# 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).
@@ -83,6 +88,30 @@ def mkpath (name, mode=0777, verbose=0, dry_run=0):
# 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
@@ -241,6 +270,7 @@ def copy_file (src, dst,
preserve_mode=1,
preserve_times=1,
update=0,
+ link=None,
verbose=0,
dry_run=0):
@@ -256,17 +286,32 @@ def copy_file (src, dst,
'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 doesn't copy Mac-specific metadata
-
+ # 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': not a regular file" % src
+ "can't copy '%s': doesn't exist or not a regular file" % src
if os.path.isdir (dst):
dir = dst
@@ -279,8 +324,13 @@ def copy_file (src, dst,
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 "copying %s -> %s" % (src, dir)
+ print "%s %s -> %s" % (action, src, dir)
if dry_run:
return 1
@@ -293,19 +343,29 @@ def copy_file (src, dst,
except OSError, exc:
raise DistutilsFileError, \
"could not copy '%s' to '%s': %s" % (src, dst, exc[-1])
- return 1
- # Otherwise use custom routine
- _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]))
+ # 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
@@ -375,7 +435,7 @@ def copy_tree (src, dst,
else:
copy_file (src_name, dst_name,
preserve_mode, preserve_times,
- update, verbose, dry_run)
+ update, None, verbose, dry_run)
outputs.append (dst_name)
return outputs
@@ -562,7 +622,8 @@ def subst_vars (str, local_vars):
# subst_vars ()
-def make_tarball (base_dir, compress="gzip", verbose=0, dry_run=0):
+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'
@@ -584,7 +645,7 @@ def make_tarball (base_dir, compress="gzip", verbose=0, dry_run=0):
raise ValueError, \
"bad value for 'compress': must be None, 'gzip', or 'compress'"
- archive_name = base_dir + ".tar"
+ archive_name = base_name + ".tar"
cmd = ["tar", "-cf", archive_name, base_dir]
spawn (cmd, verbose=verbose, dry_run=dry_run)
@@ -597,21 +658,21 @@ def make_tarball (base_dir, compress="gzip", verbose=0, dry_run=0):
# make_tarball ()
-def make_zipfile (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
+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."""
+ 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_dir + ".zip"
+ zip_filename = base_name + ".zip"
try:
- spawn (["zip", "-r", zip_filename, base_dir],
+ spawn (["zip", "-rq", zip_filename, base_dir],
verbose=verbose, dry_run=dry_run)
except DistutilsExecError:
@@ -649,3 +710,52 @@ def make_zipfile (base_dir, verbose=0, dry_run=0):
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
+ 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 ()