blob: 39f6eea28964ae07a2e35b77ab7d7d7fa97bd59a [file] [log] [blame]
Greg Wardaebf7062000-04-04 02:05:59 +00001"""distutils.file_util
2
Greg Ward449f5562000-09-26 02:03:34 +00003Utility functions for operating on single files.
4"""
Greg Wardaebf7062000-04-04 02:05:59 +00005
6# created 2000/04/03, Greg Ward (extracted from util.py)
7
8__revision__ = "$Id$"
9
10import os
Jeremy Hylton2fa699e2001-01-31 20:07:17 +000011from stat import *
Greg Wardaebf7062000-04-04 02:05:59 +000012from distutils.errors import DistutilsFileError
13
14
15# for generating verbose output in 'copy_file()'
16_copy_action = { None: 'copying',
17 'hard': 'hard linking',
18 'sym': 'symbolically linking' }
19
20
21def _copy_file_contents (src, dst, buffer_size=16*1024):
22 """Copy the file 'src' to 'dst'; both must be filenames. Any error
Greg Ward9e3dc4e2000-09-23 00:59:34 +000023 opening either file, reading from 'src', or writing to 'dst', raises
24 DistutilsFileError. Data is read/written in chunks of 'buffer_size'
25 bytes (default 16k). No attempt is made to handle anything apart from
26 regular files.
27 """
Greg Wardaebf7062000-04-04 02:05:59 +000028 # Stolen from shutil module in the standard library, but with
29 # custom error-handling added.
30
31 fsrc = None
32 fdst = None
33 try:
34 try:
35 fsrc = open(src, 'rb')
36 except os.error, (errno, errstr):
37 raise DistutilsFileError, \
38 "could not open '%s': %s" % (src, errstr)
39
40 try:
41 fdst = open(dst, 'wb')
42 except os.error, (errno, errstr):
43 raise DistutilsFileError, \
44 "could not create '%s': %s" % (dst, errstr)
45
46 while 1:
47 try:
Greg Ward9e3dc4e2000-09-23 00:59:34 +000048 buf = fsrc.read(buffer_size)
Greg Wardaebf7062000-04-04 02:05:59 +000049 except os.error, (errno, errstr):
50 raise DistutilsFileError, \
51 "could not read from '%s': %s" % (src, errstr)
52
53 if not buf:
54 break
55
56 try:
57 fdst.write(buf)
58 except os.error, (errno, errstr):
59 raise DistutilsFileError, \
60 "could not write to '%s': %s" % (dst, errstr)
61
62 finally:
63 if fdst:
64 fdst.close()
65 if fsrc:
66 fsrc.close()
67
68# _copy_file_contents()
69
70
71def copy_file (src, dst,
72 preserve_mode=1,
73 preserve_times=1,
74 update=0,
75 link=None,
76 verbose=0,
77 dry_run=0):
78
Greg Ward9e3dc4e2000-09-23 00:59:34 +000079 """Copy a file 'src' to 'dst'. If 'dst' is a directory, then 'src' is
80 copied there with the same name; otherwise, it must be a filename. (If
81 the file exists, it will be ruthlessly clobbered.) If 'preserve_mode'
82 is true (the default), the file's mode (type and permission bits, or
83 whatever is analogous on the current platform) is copied. If
84 'preserve_times' is true (the default), the last-modified and
85 last-access times are copied as well. If 'update' is true, 'src' will
86 only be copied if 'dst' does not exist, or if 'dst' does exist but is
87 older than 'src'. If 'verbose' is true, then a one-line summary of the
88 copy will be printed to stdout.
Greg Wardaebf7062000-04-04 02:05:59 +000089
Greg Ward9e3dc4e2000-09-23 00:59:34 +000090 'link' allows you to make hard links (os.link) or symbolic links
91 (os.symlink) instead of copying: set it to "hard" or "sym"; if it is
92 None (the default), files are copied. Don't set 'link' on systems that
93 don't support it: 'copy_file()' doesn't check if hard or symbolic
94 linking is available.
Greg Wardaebf7062000-04-04 02:05:59 +000095
Greg Ward9e3dc4e2000-09-23 00:59:34 +000096 Under Mac OS, uses the native file copy function in macostools; on
97 other systems, uses '_copy_file_contents()' to copy file contents.
Greg Wardaebf7062000-04-04 02:05:59 +000098
Greg Ward0d4a8532000-09-30 17:29:35 +000099 Return a tuple (dest_name, copied): 'dest_name' is the actual name of
100 the output file, and 'copied' is true if the file was copied (or would
101 have been copied, if 'dry_run' true).
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000102 """
Greg Wardaebf7062000-04-04 02:05:59 +0000103 # XXX if the destination file already exists, we clobber it if
104 # copying, but blow up if linking. Hmmm. And I don't know what
105 # macostools.copyfile() does. Should definitely be consistent, and
106 # should probably blow up if destination exists and we would be
107 # changing it (ie. it's not already a hard/soft link to src OR
108 # (not update) and (src newer than dst).
109
Greg Wardaebf7062000-04-04 02:05:59 +0000110 from distutils.dep_util import newer
111
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000112 if not os.path.isfile(src):
Greg Wardaebf7062000-04-04 02:05:59 +0000113 raise DistutilsFileError, \
114 "can't copy '%s': doesn't exist or not a regular file" % src
115
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000116 if os.path.isdir(dst):
Greg Wardaebf7062000-04-04 02:05:59 +0000117 dir = dst
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000118 dst = os.path.join(dst, os.path.basename(src))
Greg Wardaebf7062000-04-04 02:05:59 +0000119 else:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000120 dir = os.path.dirname(dst)
Greg Wardaebf7062000-04-04 02:05:59 +0000121
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000122 if update and not newer(src, dst):
Greg Wardaebf7062000-04-04 02:05:59 +0000123 if verbose:
124 print "not copying %s (output up-to-date)" % src
Greg Ward0d4a8532000-09-30 17:29:35 +0000125 return (dst, 0)
Greg Wardaebf7062000-04-04 02:05:59 +0000126
127 try:
128 action = _copy_action[link]
129 except KeyError:
130 raise ValueError, \
131 "invalid value '%s' for 'link' argument" % link
132 if verbose:
Greg Ward43550932000-05-20 16:05:34 +0000133 if os.path.basename(dst) == os.path.basename(src):
134 print "%s %s -> %s" % (action, src, dir)
135 else:
136 print "%s %s -> %s" % (action, src, dst)
137
Greg Wardaebf7062000-04-04 02:05:59 +0000138 if dry_run:
Greg Ward0d4a8532000-09-30 17:29:35 +0000139 return (dst, 1)
Greg Wardaebf7062000-04-04 02:05:59 +0000140
Greg Ward0d4a8532000-09-30 17:29:35 +0000141 # On Mac OS, use the native file copy routine
Greg Wardaebf7062000-04-04 02:05:59 +0000142 if os.name == 'mac':
143 import macostools
144 try:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000145 macostools.copy(src, dst, 0, preserve_times)
Greg Ward3af07e92000-04-22 15:17:14 +0000146 except os.error, exc:
Greg Wardaebf7062000-04-04 02:05:59 +0000147 raise DistutilsFileError, \
148 "could not copy '%s' to '%s': %s" % (src, dst, exc[-1])
149
150 # If linking (hard or symbolic), use the appropriate system call
151 # (Unix only, of course, but that's the caller's responsibility)
152 elif link == 'hard':
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000153 if not (os.path.exists(dst) and os.path.samefile(src, dst)):
154 os.link(src, dst)
Greg Wardaebf7062000-04-04 02:05:59 +0000155 elif link == 'sym':
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000156 if not (os.path.exists(dst) and os.path.samefile(src, dst)):
157 os.symlink(src, dst)
Greg Wardaebf7062000-04-04 02:05:59 +0000158
159 # Otherwise (non-Mac, not linking), copy the file contents and
160 # (optionally) copy the times and mode.
161 else:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000162 _copy_file_contents(src, dst)
Greg Wardaebf7062000-04-04 02:05:59 +0000163 if preserve_mode or preserve_times:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000164 st = os.stat(src)
Greg Wardaebf7062000-04-04 02:05:59 +0000165
166 # According to David Ascher <da@ski.org>, utime() should be done
167 # before chmod() (at least under NT).
168 if preserve_times:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000169 os.utime(dst, (st[ST_ATIME], st[ST_MTIME]))
Greg Wardaebf7062000-04-04 02:05:59 +0000170 if preserve_mode:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000171 os.chmod(dst, S_IMODE(st[ST_MODE]))
Greg Wardaebf7062000-04-04 02:05:59 +0000172
Greg Ward0d4a8532000-09-30 17:29:35 +0000173 return (dst, 1)
Greg Wardaebf7062000-04-04 02:05:59 +0000174
175# copy_file ()
176
177
178# XXX I suspect this is Unix-specific -- need porting help!
179def move_file (src, dst,
180 verbose=0,
181 dry_run=0):
182
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000183 """Move a file 'src' to 'dst'. If 'dst' is a directory, the file will
184 be moved into it with the same name; otherwise, 'src' is just renamed
185 to 'dst'. Return the new full name of the file.
Greg Wardaebf7062000-04-04 02:05:59 +0000186
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000187 Handles cross-device moves on Unix using 'copy_file()'. What about
188 other systems???
189 """
Greg Wardaebf7062000-04-04 02:05:59 +0000190 from os.path import exists, isfile, isdir, basename, dirname
191
192 if verbose:
193 print "moving %s -> %s" % (src, dst)
194
195 if dry_run:
196 return dst
197
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000198 if not isfile(src):
Greg Wardaebf7062000-04-04 02:05:59 +0000199 raise DistutilsFileError, \
200 "can't move '%s': not a regular file" % src
201
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000202 if isdir(dst):
203 dst = os.path.join(dst, basename(src))
204 elif exists(dst):
Greg Wardaebf7062000-04-04 02:05:59 +0000205 raise DistutilsFileError, \
206 "can't move '%s': destination '%s' already exists" % \
207 (src, dst)
208
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000209 if not isdir(dirname(dst)):
Greg Wardaebf7062000-04-04 02:05:59 +0000210 raise DistutilsFileError, \
211 "can't move '%s': destination '%s' not a valid path" % \
212 (src, dst)
213
214 copy_it = 0
215 try:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000216 os.rename(src, dst)
Greg Wardaebf7062000-04-04 02:05:59 +0000217 except os.error, (num, msg):
218 if num == errno.EXDEV:
219 copy_it = 1
220 else:
221 raise DistutilsFileError, \
222 "couldn't move '%s' to '%s': %s" % (src, dst, msg)
223
224 if copy_it:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000225 copy_file(src, dst)
Greg Wardaebf7062000-04-04 02:05:59 +0000226 try:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000227 os.unlink(src)
Greg Wardaebf7062000-04-04 02:05:59 +0000228 except os.error, (num, msg):
229 try:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000230 os.unlink(dst)
Greg Wardaebf7062000-04-04 02:05:59 +0000231 except os.error:
232 pass
233 raise DistutilsFileError, \
234 ("couldn't move '%s' to '%s' by copy/delete: " +
235 "delete '%s' failed: %s") % \
236 (src, dst, src, msg)
237
238 return dst
239
240# move_file ()
241
242
243def write_file (filename, contents):
244 """Create a file with the specified name and write 'contents' (a
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000245 sequence of strings without line terminators) to it.
246 """
247 f = open(filename, "w")
Greg Wardaebf7062000-04-04 02:05:59 +0000248 for line in contents:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000249 f.write(line + "\n")
250 f.close()