blob: 526e4cf593f59404f67f6c0b28d850e513684575 [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
11from distutils.errors import DistutilsFileError
12
13
14# for generating verbose output in 'copy_file()'
15_copy_action = { None: 'copying',
16 'hard': 'hard linking',
17 'sym': 'symbolically linking' }
18
19
20def _copy_file_contents (src, dst, buffer_size=16*1024):
21 """Copy the file 'src' to 'dst'; both must be filenames. Any error
Greg Ward9e3dc4e2000-09-23 00:59:34 +000022 opening either file, reading from 'src', or writing to 'dst', raises
23 DistutilsFileError. Data is read/written in chunks of 'buffer_size'
24 bytes (default 16k). No attempt is made to handle anything apart from
25 regular files.
26 """
Greg Wardaebf7062000-04-04 02:05:59 +000027 # Stolen from shutil module in the standard library, but with
28 # custom error-handling added.
29
30 fsrc = None
31 fdst = None
32 try:
33 try:
34 fsrc = open(src, 'rb')
35 except os.error, (errno, errstr):
36 raise DistutilsFileError, \
37 "could not open '%s': %s" % (src, errstr)
Fred Drakeb94b8492001-12-06 20:51:35 +000038
Greg Wardaebf7062000-04-04 02:05:59 +000039 try:
40 fdst = open(dst, 'wb')
41 except os.error, (errno, errstr):
42 raise DistutilsFileError, \
43 "could not create '%s': %s" % (dst, errstr)
Fred Drakeb94b8492001-12-06 20:51:35 +000044
Greg Wardaebf7062000-04-04 02:05:59 +000045 while 1:
46 try:
Greg Ward9e3dc4e2000-09-23 00:59:34 +000047 buf = fsrc.read(buffer_size)
Greg Wardaebf7062000-04-04 02:05:59 +000048 except os.error, (errno, errstr):
49 raise DistutilsFileError, \
50 "could not read from '%s': %s" % (src, errstr)
Fred Drakeb94b8492001-12-06 20:51:35 +000051
Greg Wardaebf7062000-04-04 02:05:59 +000052 if not buf:
53 break
54
55 try:
56 fdst.write(buf)
57 except os.error, (errno, errstr):
58 raise DistutilsFileError, \
59 "could not write to '%s': %s" % (dst, errstr)
Fred Drakeb94b8492001-12-06 20:51:35 +000060
Greg Wardaebf7062000-04-04 02:05:59 +000061 finally:
62 if fdst:
63 fdst.close()
64 if fsrc:
65 fsrc.close()
66
67# _copy_file_contents()
68
69
70def copy_file (src, dst,
71 preserve_mode=1,
72 preserve_times=1,
73 update=0,
74 link=None,
75 verbose=0,
76 dry_run=0):
77
Greg Ward9e3dc4e2000-09-23 00:59:34 +000078 """Copy a file 'src' to 'dst'. If 'dst' is a directory, then 'src' is
79 copied there with the same name; otherwise, it must be a filename. (If
80 the file exists, it will be ruthlessly clobbered.) If 'preserve_mode'
81 is true (the default), the file's mode (type and permission bits, or
82 whatever is analogous on the current platform) is copied. If
83 'preserve_times' is true (the default), the last-modified and
84 last-access times are copied as well. If 'update' is true, 'src' will
85 only be copied if 'dst' does not exist, or if 'dst' does exist but is
86 older than 'src'. If 'verbose' is true, then a one-line summary of the
87 copy will be printed to stdout.
Greg Wardaebf7062000-04-04 02:05:59 +000088
Greg Ward9e3dc4e2000-09-23 00:59:34 +000089 'link' allows you to make hard links (os.link) or symbolic links
90 (os.symlink) instead of copying: set it to "hard" or "sym"; if it is
91 None (the default), files are copied. Don't set 'link' on systems that
92 don't support it: 'copy_file()' doesn't check if hard or symbolic
93 linking is available.
Greg Wardaebf7062000-04-04 02:05:59 +000094
Greg Ward9e3dc4e2000-09-23 00:59:34 +000095 Under Mac OS, uses the native file copy function in macostools; on
96 other systems, uses '_copy_file_contents()' to copy file contents.
Greg Wardaebf7062000-04-04 02:05:59 +000097
Greg Ward0d4a8532000-09-30 17:29:35 +000098 Return a tuple (dest_name, copied): 'dest_name' is the actual name of
99 the output file, and 'copied' is true if the file was copied (or would
100 have been copied, if 'dry_run' true).
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000101 """
Greg Wardaebf7062000-04-04 02:05:59 +0000102 # XXX if the destination file already exists, we clobber it if
103 # copying, but blow up if linking. Hmmm. And I don't know what
104 # macostools.copyfile() does. Should definitely be consistent, and
105 # should probably blow up if destination exists and we would be
106 # changing it (ie. it's not already a hard/soft link to src OR
107 # (not update) and (src newer than dst).
108
Greg Wardaebf7062000-04-04 02:05:59 +0000109 from distutils.dep_util import newer
Greg Warde628a2f2001-07-25 19:48:03 +0000110 from stat import ST_ATIME, ST_MTIME, ST_MODE, S_IMODE
Greg Wardaebf7062000-04-04 02:05:59 +0000111
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)
Fred Drakeb94b8492001-12-06 20:51:35 +0000137
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])
Fred Drakeb94b8492001-12-06 20:51:35 +0000149
Greg Wardaebf7062000-04-04 02:05:59 +0000150 # 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
Andrew M. Kuchling106ffdb2001-08-09 20:59:53 +0000191 import errno
Fred Drakeb94b8492001-12-06 20:51:35 +0000192
Greg Wardaebf7062000-04-04 02:05:59 +0000193 if verbose:
194 print "moving %s -> %s" % (src, dst)
195
196 if dry_run:
197 return dst
198
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000199 if not isfile(src):
Greg Wardaebf7062000-04-04 02:05:59 +0000200 raise DistutilsFileError, \
201 "can't move '%s': not a regular file" % src
202
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000203 if isdir(dst):
204 dst = os.path.join(dst, basename(src))
205 elif exists(dst):
Greg Wardaebf7062000-04-04 02:05:59 +0000206 raise DistutilsFileError, \
207 "can't move '%s': destination '%s' already exists" % \
208 (src, dst)
209
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000210 if not isdir(dirname(dst)):
Greg Wardaebf7062000-04-04 02:05:59 +0000211 raise DistutilsFileError, \
212 "can't move '%s': destination '%s' not a valid path" % \
213 (src, dst)
214
215 copy_it = 0
216 try:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000217 os.rename(src, dst)
Greg Wardaebf7062000-04-04 02:05:59 +0000218 except os.error, (num, msg):
219 if num == errno.EXDEV:
220 copy_it = 1
221 else:
222 raise DistutilsFileError, \
223 "couldn't move '%s' to '%s': %s" % (src, dst, msg)
224
225 if copy_it:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000226 copy_file(src, dst)
Greg Wardaebf7062000-04-04 02:05:59 +0000227 try:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000228 os.unlink(src)
Greg Wardaebf7062000-04-04 02:05:59 +0000229 except os.error, (num, msg):
230 try:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000231 os.unlink(dst)
Greg Wardaebf7062000-04-04 02:05:59 +0000232 except os.error:
233 pass
234 raise DistutilsFileError, \
Fred Drakeb94b8492001-12-06 20:51:35 +0000235 ("couldn't move '%s' to '%s' by copy/delete: " +
Greg Wardaebf7062000-04-04 02:05:59 +0000236 "delete '%s' failed: %s") % \
237 (src, dst, src, msg)
238
239 return dst
240
241# move_file ()
242
243
244def write_file (filename, contents):
245 """Create a file with the specified name and write 'contents' (a
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000246 sequence of strings without line terminators) to it.
247 """
248 f = open(filename, "w")
Greg Wardaebf7062000-04-04 02:05:59 +0000249 for line in contents:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000250 f.write(line + "\n")
251 f.close()