Skip to content

Commit aebf706

Browse files
committed
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.
1 parent fe6462c commit aebf706

File tree

5 files changed

+719
-664
lines changed

5 files changed

+719
-664
lines changed

Lib/distutils/archive_util.py

+152
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
"""distutils.archive_util
2+
3+
Utility functions for creating archive files (tarballs, zip files,
4+
that sort of thing)."""
5+
6+
# created 2000/04/03, Greg Ward (extracted from util.py)
7+
8+
__revision__ = "$Id$"
9+
10+
import os
11+
from distutils.errors import DistutilsExecError
12+
from distutils.spawn import spawn
13+
14+
15+
def make_tarball (base_name, base_dir, compress="gzip",
16+
verbose=0, dry_run=0):
17+
"""Create a (possibly compressed) tar file from all the files under
18+
'base_dir'. 'compress' must be "gzip" (the default), "compress", or
19+
None. Both "tar" and the compression utility named by 'compress'
20+
must be on the default program search path, so this is probably
21+
Unix-specific. The output tar file will be named 'base_dir' +
22+
".tar", possibly plus the appropriate compression extension
23+
(".gz" or ".Z"). Return the output filename."""
24+
25+
# XXX GNU tar 1.13 has a nifty option to add a prefix directory.
26+
# It's pretty new, though, so we certainly can't require it --
27+
# but it would be nice to take advantage of it to skip the
28+
# "create a tree of hardlinks" step! (Would also be nice to
29+
# detect GNU tar to use its 'z' option and save a step.)
30+
31+
compress_ext = { 'gzip': ".gz",
32+
'compress': ".Z" }
33+
34+
if compress is not None and compress not in ('gzip', 'compress'):
35+
raise ValueError, \
36+
"bad value for 'compress': must be None, 'gzip', or 'compress'"
37+
38+
archive_name = base_name + ".tar"
39+
cmd = ["tar", "-cf", archive_name, base_dir]
40+
spawn (cmd, verbose=verbose, dry_run=dry_run)
41+
42+
if compress:
43+
spawn ([compress, archive_name], verbose=verbose, dry_run=dry_run)
44+
return archive_name + compress_ext[compress]
45+
else:
46+
return archive_name
47+
48+
# make_tarball ()
49+
50+
51+
def make_zipfile (base_name, base_dir, verbose=0, dry_run=0):
52+
"""Create a zip file from all the files under 'base_dir'. The
53+
output zip file will be named 'base_dir' + ".zip". Uses either the
54+
InfoZIP "zip" utility (if installed and found on the default search
55+
path) or the "zipfile" Python module (if available). If neither
56+
tool is available, raises DistutilsExecError. Returns the name
57+
of the output zip file."""
58+
59+
# This initially assumed the Unix 'zip' utility -- but
60+
# apparently InfoZIP's zip.exe works the same under Windows, so
61+
# no changes needed!
62+
63+
zip_filename = base_name + ".zip"
64+
try:
65+
spawn (["zip", "-rq", zip_filename, base_dir],
66+
verbose=verbose, dry_run=dry_run)
67+
except DistutilsExecError:
68+
69+
# XXX really should distinguish between "couldn't find
70+
# external 'zip' command" and "zip failed" -- shouldn't try
71+
# again in the latter case. (I think fixing this will
72+
# require some cooperation from the spawn module -- perhaps
73+
# a utility function to search the path, so we can fallback
74+
# on zipfile.py without the failed spawn.)
75+
try:
76+
import zipfile
77+
except ImportError:
78+
raise DistutilsExecError, \
79+
("unable to create zip file '%s': " +
80+
"could neither find a standalone zip utility nor " +
81+
"import the 'zipfile' module") % zip_filename
82+
83+
if verbose:
84+
print "creating '%s' and adding '%s' to it" % \
85+
(zip_filename, base_dir)
86+
87+
def visit (z, dirname, names):
88+
for name in names:
89+
path = os.path.join (dirname, name)
90+
if os.path.isfile (path):
91+
z.write (path, path)
92+
93+
if not dry_run:
94+
z = zipfile.ZipFile (zip_filename, "wb",
95+
compression=zipfile.ZIP_DEFLATED)
96+
97+
os.path.walk (base_dir, visit, z)
98+
z.close()
99+
100+
return zip_filename
101+
102+
# make_zipfile ()
103+
104+
105+
def make_archive (base_name, format,
106+
root_dir=None, base_dir=None,
107+
verbose=0, dry_run=0):
108+
109+
"""Create an archive file (eg. zip or tar). 'base_name' is the name
110+
of the file to create, minus any format-specific extension; 'format'
111+
is the archive format: one of "zip", "tar", "ztar", or "gztar".
112+
'root_dir' is a directory that will be the root directory of the
113+
archive; ie. we typically chdir into 'root_dir' before creating the
114+
archive. 'base_dir' is the directory where we start archiving from;
115+
ie. 'base_dir' will be the common prefix of all files and
116+
directories in the archive. 'root_dir' and 'base_dir' both default
117+
to the current directory."""
118+
119+
save_cwd = os.getcwd()
120+
if root_dir is not None:
121+
if verbose:
122+
print "changing into '%s'" % root_dir
123+
base_name = os.path.abspath (base_name)
124+
if not dry_run:
125+
os.chdir (root_dir)
126+
127+
if base_dir is None:
128+
base_dir = os.curdir
129+
130+
kwargs = { 'verbose': verbose,
131+
'dry_run': dry_run }
132+
133+
if format == 'gztar':
134+
func = make_tarball
135+
kwargs['compress'] = 'gzip'
136+
elif format == 'ztar':
137+
func = make_tarball
138+
kwargs['compress'] = 'compress'
139+
elif format == 'tar':
140+
func = make_tarball
141+
kwargs['compress'] = None
142+
elif format == 'zip':
143+
func = make_zipfile
144+
145+
apply (func, (base_name, base_dir), kwargs)
146+
147+
if root_dir is not None:
148+
if verbose:
149+
print "changing back to '%s'" % save_cwd
150+
os.chdir (save_cwd)
151+
152+
# make_archive ()

Lib/distutils/dep_util.py

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
"""distutils.dep_util
2+
3+
Utility functions for simple, timestamp-based dependency of files
4+
and groups of files; also, function based entirely on such
5+
timestamp dependency analysis."""
6+
7+
# created 2000/04/03, Greg Ward (extracted from util.py)
8+
9+
__revision__ = "$Id$"
10+
11+
import os
12+
from distutils.errors import DistutilsFileError
13+
14+
15+
def newer (source, target):
16+
"""Return true if 'source' exists and is more recently modified than
17+
'target', or if 'source' exists and 'target' doesn't. Return
18+
false if both exist and 'target' is the same age or younger than
19+
'source'. Raise DistutilsFileError if 'source' does not
20+
exist."""
21+
22+
if not os.path.exists (source):
23+
raise DistutilsFileError, "file '%s' does not exist" % source
24+
if not os.path.exists (target):
25+
return 1
26+
27+
from stat import ST_MTIME
28+
mtime1 = os.stat(source)[ST_MTIME]
29+
mtime2 = os.stat(target)[ST_MTIME]
30+
31+
return mtime1 > mtime2
32+
33+
# newer ()
34+
35+
36+
def newer_pairwise (sources, targets):
37+
"""Walk two filename lists in parallel, testing if each source is newer
38+
than its corresponding target. Return a pair of lists (sources,
39+
targets) where source is newer than target, according to the
40+
semantics of 'newer()'."""
41+
42+
if len (sources) != len (targets):
43+
raise ValueError, "'sources' and 'targets' must be same length"
44+
45+
# build a pair of lists (sources, targets) where source is newer
46+
n_sources = []
47+
n_targets = []
48+
for i in range (len (sources)):
49+
if newer (sources[i], targets[i]):
50+
n_sources.append (sources[i])
51+
n_targets.append (targets[i])
52+
53+
return (n_sources, n_targets)
54+
55+
# newer_pairwise ()
56+
57+
58+
def newer_group (sources, target, missing='error'):
59+
"""Return true if 'target' is out-of-date with respect to any
60+
file listed in 'sources'. In other words, if 'target' exists and
61+
is newer than every file in 'sources', return false; otherwise
62+
return true. 'missing' controls what we do when a source file is
63+
missing; the default ("error") is to blow up with an OSError from
64+
inside 'stat()'; if it is "ignore", we silently drop any missing
65+
source files; if it is "newer", any missing source files make us
66+
assume that 'target' is out-of-date (this is handy in "dry-run"
67+
mode: it'll make you pretend to carry out commands that wouldn't
68+
work because inputs are missing, but that doesn't matter because
69+
you're not actually going to run the commands)."""
70+
71+
# If the target doesn't even exist, then it's definitely out-of-date.
72+
if not os.path.exists (target):
73+
return 1
74+
75+
# Otherwise we have to find out the hard way: if *any* source file
76+
# is more recent than 'target', then 'target' is out-of-date and
77+
# we can immediately return true. If we fall through to the end
78+
# of the loop, then 'target' is up-to-date and we return false.
79+
from stat import ST_MTIME
80+
target_mtime = os.stat (target)[ST_MTIME]
81+
for source in sources:
82+
if not os.path.exists (source):
83+
if missing == 'error': # blow up when we stat() the file
84+
pass
85+
elif missing == 'ignore': # missing source dropped from
86+
continue # target's dependency list
87+
elif missing == 'newer': # missing source means target is
88+
return 1 # out-of-date
89+
90+
source_mtime = os.stat(source)[ST_MTIME]
91+
if source_mtime > target_mtime:
92+
return 1
93+
else:
94+
return 0
95+
96+
# newer_group ()
97+
98+
99+
# XXX this isn't used anywhere, and worse, it has the same name as a method
100+
# in Command with subtly different semantics. (This one just has one
101+
# source -> one dest; that one has many sources -> one dest.) Nuke it?
102+
def make_file (src, dst, func, args,
103+
verbose=0, update_message=None, noupdate_message=None):
104+
"""Makes 'dst' from 'src' (both filenames) by calling 'func' with
105+
'args', but only if it needs to: i.e. if 'dst' does not exist or
106+
'src' is newer than 'dst'."""
107+
108+
if newer (src, dst):
109+
if verbose and update_message:
110+
print update_message
111+
apply (func, args)
112+
else:
113+
if verbose and noupdate_message:
114+
print noupdate_message
115+
116+
# make_file ()

0 commit comments

Comments
 (0)