blob: 14772fb74aadadd36d76f42f80dea3ad6e0fe650 [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
Andrew M. Kuchling3b388ec2002-02-01 18:29:34 +000039 if os.path.exists(dst):
40 try:
41 os.unlink(dst)
42 except os.error, (errno, errstr):
43 raise DistutilsFileError, \
44 "could not delete '%s': %s" % (dst, errstr)
45
Greg Wardaebf7062000-04-04 02:05:59 +000046 try:
47 fdst = open(dst, 'wb')
48 except os.error, (errno, errstr):
49 raise DistutilsFileError, \
50 "could not create '%s': %s" % (dst, errstr)
Fred Drakeb94b8492001-12-06 20:51:35 +000051
Greg Wardaebf7062000-04-04 02:05:59 +000052 while 1:
53 try:
Greg Ward9e3dc4e2000-09-23 00:59:34 +000054 buf = fsrc.read(buffer_size)
Greg Wardaebf7062000-04-04 02:05:59 +000055 except os.error, (errno, errstr):
56 raise DistutilsFileError, \
57 "could not read from '%s': %s" % (src, errstr)
Fred Drakeb94b8492001-12-06 20:51:35 +000058
Greg Wardaebf7062000-04-04 02:05:59 +000059 if not buf:
60 break
61
62 try:
63 fdst.write(buf)
64 except os.error, (errno, errstr):
65 raise DistutilsFileError, \
66 "could not write to '%s': %s" % (dst, errstr)
Fred Drakeb94b8492001-12-06 20:51:35 +000067
Greg Wardaebf7062000-04-04 02:05:59 +000068 finally:
69 if fdst:
70 fdst.close()
71 if fsrc:
72 fsrc.close()
73
74# _copy_file_contents()
75
76
77def copy_file (src, dst,
78 preserve_mode=1,
79 preserve_times=1,
80 update=0,
81 link=None,
82 verbose=0,
83 dry_run=0):
84
Greg Ward9e3dc4e2000-09-23 00:59:34 +000085 """Copy a file 'src' to 'dst'. If 'dst' is a directory, then 'src' is
86 copied there with the same name; otherwise, it must be a filename. (If
87 the file exists, it will be ruthlessly clobbered.) If 'preserve_mode'
88 is true (the default), the file's mode (type and permission bits, or
89 whatever is analogous on the current platform) is copied. If
90 'preserve_times' is true (the default), the last-modified and
91 last-access times are copied as well. If 'update' is true, 'src' will
92 only be copied if 'dst' does not exist, or if 'dst' does exist but is
93 older than 'src'. If 'verbose' is true, then a one-line summary of the
94 copy will be printed to stdout.
Greg Wardaebf7062000-04-04 02:05:59 +000095
Greg Ward9e3dc4e2000-09-23 00:59:34 +000096 'link' allows you to make hard links (os.link) or symbolic links
97 (os.symlink) instead of copying: set it to "hard" or "sym"; if it is
98 None (the default), files are copied. Don't set 'link' on systems that
99 don't support it: 'copy_file()' doesn't check if hard or symbolic
100 linking is available.
Greg Wardaebf7062000-04-04 02:05:59 +0000101
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000102 Under Mac OS, uses the native file copy function in macostools; on
103 other systems, uses '_copy_file_contents()' to copy file contents.
Greg Wardaebf7062000-04-04 02:05:59 +0000104
Greg Ward0d4a8532000-09-30 17:29:35 +0000105 Return a tuple (dest_name, copied): 'dest_name' is the actual name of
106 the output file, and 'copied' is true if the file was copied (or would
107 have been copied, if 'dry_run' true).
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000108 """
Greg Wardaebf7062000-04-04 02:05:59 +0000109 # XXX if the destination file already exists, we clobber it if
110 # copying, but blow up if linking. Hmmm. And I don't know what
111 # macostools.copyfile() does. Should definitely be consistent, and
112 # should probably blow up if destination exists and we would be
113 # changing it (ie. it's not already a hard/soft link to src OR
114 # (not update) and (src newer than dst).
115
Greg Wardaebf7062000-04-04 02:05:59 +0000116 from distutils.dep_util import newer
Greg Warde628a2f2001-07-25 19:48:03 +0000117 from stat import ST_ATIME, ST_MTIME, ST_MODE, S_IMODE
Greg Wardaebf7062000-04-04 02:05:59 +0000118
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000119 if not os.path.isfile(src):
Greg Wardaebf7062000-04-04 02:05:59 +0000120 raise DistutilsFileError, \
121 "can't copy '%s': doesn't exist or not a regular file" % src
122
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000123 if os.path.isdir(dst):
Greg Wardaebf7062000-04-04 02:05:59 +0000124 dir = dst
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000125 dst = os.path.join(dst, os.path.basename(src))
Greg Wardaebf7062000-04-04 02:05:59 +0000126 else:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000127 dir = os.path.dirname(dst)
Greg Wardaebf7062000-04-04 02:05:59 +0000128
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000129 if update and not newer(src, dst):
Greg Wardaebf7062000-04-04 02:05:59 +0000130 if verbose:
131 print "not copying %s (output up-to-date)" % src
Greg Ward0d4a8532000-09-30 17:29:35 +0000132 return (dst, 0)
Greg Wardaebf7062000-04-04 02:05:59 +0000133
134 try:
135 action = _copy_action[link]
136 except KeyError:
137 raise ValueError, \
138 "invalid value '%s' for 'link' argument" % link
139 if verbose:
Greg Ward43550932000-05-20 16:05:34 +0000140 if os.path.basename(dst) == os.path.basename(src):
141 print "%s %s -> %s" % (action, src, dir)
142 else:
143 print "%s %s -> %s" % (action, src, dst)
Fred Drakeb94b8492001-12-06 20:51:35 +0000144
Greg Wardaebf7062000-04-04 02:05:59 +0000145 if dry_run:
Greg Ward0d4a8532000-09-30 17:29:35 +0000146 return (dst, 1)
Greg Wardaebf7062000-04-04 02:05:59 +0000147
Greg Ward0d4a8532000-09-30 17:29:35 +0000148 # On Mac OS, use the native file copy routine
Greg Wardaebf7062000-04-04 02:05:59 +0000149 if os.name == 'mac':
150 import macostools
151 try:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000152 macostools.copy(src, dst, 0, preserve_times)
Greg Ward3af07e92000-04-22 15:17:14 +0000153 except os.error, exc:
Greg Wardaebf7062000-04-04 02:05:59 +0000154 raise DistutilsFileError, \
155 "could not copy '%s' to '%s': %s" % (src, dst, exc[-1])
Fred Drakeb94b8492001-12-06 20:51:35 +0000156
Greg Wardaebf7062000-04-04 02:05:59 +0000157 # If linking (hard or symbolic), use the appropriate system call
158 # (Unix only, of course, but that's the caller's responsibility)
159 elif link == 'hard':
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000160 if not (os.path.exists(dst) and os.path.samefile(src, dst)):
161 os.link(src, dst)
Greg Wardaebf7062000-04-04 02:05:59 +0000162 elif link == 'sym':
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000163 if not (os.path.exists(dst) and os.path.samefile(src, dst)):
164 os.symlink(src, dst)
Greg Wardaebf7062000-04-04 02:05:59 +0000165
166 # Otherwise (non-Mac, not linking), copy the file contents and
167 # (optionally) copy the times and mode.
168 else:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000169 _copy_file_contents(src, dst)
Greg Wardaebf7062000-04-04 02:05:59 +0000170 if preserve_mode or preserve_times:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000171 st = os.stat(src)
Greg Wardaebf7062000-04-04 02:05:59 +0000172
173 # According to David Ascher <da@ski.org>, utime() should be done
174 # before chmod() (at least under NT).
175 if preserve_times:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000176 os.utime(dst, (st[ST_ATIME], st[ST_MTIME]))
Greg Wardaebf7062000-04-04 02:05:59 +0000177 if preserve_mode:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000178 os.chmod(dst, S_IMODE(st[ST_MODE]))
Greg Wardaebf7062000-04-04 02:05:59 +0000179
Greg Ward0d4a8532000-09-30 17:29:35 +0000180 return (dst, 1)
Greg Wardaebf7062000-04-04 02:05:59 +0000181
182# copy_file ()
183
184
185# XXX I suspect this is Unix-specific -- need porting help!
186def move_file (src, dst,
187 verbose=0,
188 dry_run=0):
189
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000190 """Move a file 'src' to 'dst'. If 'dst' is a directory, the file will
191 be moved into it with the same name; otherwise, 'src' is just renamed
192 to 'dst'. Return the new full name of the file.
Greg Wardaebf7062000-04-04 02:05:59 +0000193
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000194 Handles cross-device moves on Unix using 'copy_file()'. What about
195 other systems???
196 """
Greg Wardaebf7062000-04-04 02:05:59 +0000197 from os.path import exists, isfile, isdir, basename, dirname
Andrew M. Kuchling106ffdb2001-08-09 20:59:53 +0000198 import errno
Fred Drakeb94b8492001-12-06 20:51:35 +0000199
Greg Wardaebf7062000-04-04 02:05:59 +0000200 if verbose:
201 print "moving %s -> %s" % (src, dst)
202
203 if dry_run:
204 return dst
205
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000206 if not isfile(src):
Greg Wardaebf7062000-04-04 02:05:59 +0000207 raise DistutilsFileError, \
208 "can't move '%s': not a regular file" % src
209
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000210 if isdir(dst):
211 dst = os.path.join(dst, basename(src))
212 elif exists(dst):
Greg Wardaebf7062000-04-04 02:05:59 +0000213 raise DistutilsFileError, \
214 "can't move '%s': destination '%s' already exists" % \
215 (src, dst)
216
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000217 if not isdir(dirname(dst)):
Greg Wardaebf7062000-04-04 02:05:59 +0000218 raise DistutilsFileError, \
219 "can't move '%s': destination '%s' not a valid path" % \
220 (src, dst)
221
222 copy_it = 0
223 try:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000224 os.rename(src, dst)
Greg Wardaebf7062000-04-04 02:05:59 +0000225 except os.error, (num, msg):
226 if num == errno.EXDEV:
227 copy_it = 1
228 else:
229 raise DistutilsFileError, \
230 "couldn't move '%s' to '%s': %s" % (src, dst, msg)
231
232 if copy_it:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000233 copy_file(src, dst)
Greg Wardaebf7062000-04-04 02:05:59 +0000234 try:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000235 os.unlink(src)
Greg Wardaebf7062000-04-04 02:05:59 +0000236 except os.error, (num, msg):
237 try:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000238 os.unlink(dst)
Greg Wardaebf7062000-04-04 02:05:59 +0000239 except os.error:
240 pass
241 raise DistutilsFileError, \
Fred Drakeb94b8492001-12-06 20:51:35 +0000242 ("couldn't move '%s' to '%s' by copy/delete: " +
Greg Wardaebf7062000-04-04 02:05:59 +0000243 "delete '%s' failed: %s") % \
244 (src, dst, src, msg)
245
246 return dst
247
248# move_file ()
249
250
251def write_file (filename, contents):
252 """Create a file with the specified name and write 'contents' (a
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000253 sequence of strings without line terminators) to it.
254 """
255 f = open(filename, "w")
Greg Wardaebf7062000-04-04 02:05:59 +0000256 for line in contents:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000257 f.write(line + "\n")
258 f.close()