blob: 0e85a74b698f692a2f1fba699a98357a2af8ac7c [file] [log] [blame]
Greg Wardaebf7062000-04-04 02:05:59 +00001"""distutils.file_util
2
3Utility functions for operating on single files."""
4
5# created 2000/04/03, Greg Ward (extracted from util.py)
6
7__revision__ = "$Id$"
8
9import os
10from distutils.errors import DistutilsFileError
11
12
13# for generating verbose output in 'copy_file()'
14_copy_action = { None: 'copying',
15 'hard': 'hard linking',
16 'sym': 'symbolically linking' }
17
18
19def _copy_file_contents (src, dst, buffer_size=16*1024):
20 """Copy the file 'src' to 'dst'; both must be filenames. Any error
Greg Ward9e3dc4e2000-09-23 00:59:34 +000021 opening either file, reading from 'src', or writing to 'dst', raises
22 DistutilsFileError. Data is read/written in chunks of 'buffer_size'
23 bytes (default 16k). No attempt is made to handle anything apart from
24 regular files.
25 """
Greg Wardaebf7062000-04-04 02:05:59 +000026 # Stolen from shutil module in the standard library, but with
27 # custom error-handling added.
28
29 fsrc = None
30 fdst = None
31 try:
32 try:
33 fsrc = open(src, 'rb')
34 except os.error, (errno, errstr):
35 raise DistutilsFileError, \
36 "could not open '%s': %s" % (src, errstr)
37
38 try:
39 fdst = open(dst, 'wb')
40 except os.error, (errno, errstr):
41 raise DistutilsFileError, \
42 "could not create '%s': %s" % (dst, errstr)
43
44 while 1:
45 try:
Greg Ward9e3dc4e2000-09-23 00:59:34 +000046 buf = fsrc.read(buffer_size)
Greg Wardaebf7062000-04-04 02:05:59 +000047 except os.error, (errno, errstr):
48 raise DistutilsFileError, \
49 "could not read from '%s': %s" % (src, errstr)
50
51 if not buf:
52 break
53
54 try:
55 fdst.write(buf)
56 except os.error, (errno, errstr):
57 raise DistutilsFileError, \
58 "could not write to '%s': %s" % (dst, errstr)
59
60 finally:
61 if fdst:
62 fdst.close()
63 if fsrc:
64 fsrc.close()
65
66# _copy_file_contents()
67
68
69def copy_file (src, dst,
70 preserve_mode=1,
71 preserve_times=1,
72 update=0,
73 link=None,
74 verbose=0,
75 dry_run=0):
76
Greg Ward9e3dc4e2000-09-23 00:59:34 +000077 """Copy a file 'src' to 'dst'. If 'dst' is a directory, then 'src' is
78 copied there with the same name; otherwise, it must be a filename. (If
79 the file exists, it will be ruthlessly clobbered.) If 'preserve_mode'
80 is true (the default), the file's mode (type and permission bits, or
81 whatever is analogous on the current platform) is copied. If
82 'preserve_times' is true (the default), the last-modified and
83 last-access times are copied as well. If 'update' is true, 'src' will
84 only be copied if 'dst' does not exist, or if 'dst' does exist but is
85 older than 'src'. If 'verbose' is true, then a one-line summary of the
86 copy will be printed to stdout.
Greg Wardaebf7062000-04-04 02:05:59 +000087
Greg Ward9e3dc4e2000-09-23 00:59:34 +000088 'link' allows you to make hard links (os.link) or symbolic links
89 (os.symlink) instead of copying: set it to "hard" or "sym"; if it is
90 None (the default), files are copied. Don't set 'link' on systems that
91 don't support it: 'copy_file()' doesn't check if hard or symbolic
92 linking is available.
Greg Wardaebf7062000-04-04 02:05:59 +000093
Greg Ward9e3dc4e2000-09-23 00:59:34 +000094 Under Mac OS, uses the native file copy function in macostools; on
95 other systems, uses '_copy_file_contents()' to copy file contents.
Greg Wardaebf7062000-04-04 02:05:59 +000096
Greg Ward9e3dc4e2000-09-23 00:59:34 +000097 Return the name of the destination file, whether it was actually copied
98 or not.
99 """
Greg Wardaebf7062000-04-04 02:05:59 +0000100 # XXX if the destination file already exists, we clobber it if
101 # copying, but blow up if linking. Hmmm. And I don't know what
102 # macostools.copyfile() does. Should definitely be consistent, and
103 # should probably blow up if destination exists and we would be
104 # changing it (ie. it's not already a hard/soft link to src OR
105 # (not update) and (src newer than dst).
106
107 from stat import *
108 from distutils.dep_util import newer
109
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000110 if not os.path.isfile(src):
Greg Wardaebf7062000-04-04 02:05:59 +0000111 raise DistutilsFileError, \
112 "can't copy '%s': doesn't exist or not a regular file" % src
113
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000114 if os.path.isdir(dst):
Greg Wardaebf7062000-04-04 02:05:59 +0000115 dir = dst
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000116 dst = os.path.join(dst, os.path.basename(src))
Greg Wardaebf7062000-04-04 02:05:59 +0000117 else:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000118 dir = os.path.dirname(dst)
Greg Wardaebf7062000-04-04 02:05:59 +0000119
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000120 if update and not newer(src, dst):
Greg Wardaebf7062000-04-04 02:05:59 +0000121 if verbose:
122 print "not copying %s (output up-to-date)" % src
Greg Warda392dcb2000-06-23 01:42:40 +0000123 return dst
Greg Wardaebf7062000-04-04 02:05:59 +0000124
125 try:
126 action = _copy_action[link]
127 except KeyError:
128 raise ValueError, \
129 "invalid value '%s' for 'link' argument" % link
130 if verbose:
Greg Ward43550932000-05-20 16:05:34 +0000131 if os.path.basename(dst) == os.path.basename(src):
132 print "%s %s -> %s" % (action, src, dir)
133 else:
134 print "%s %s -> %s" % (action, src, dst)
135
Greg Wardaebf7062000-04-04 02:05:59 +0000136 if dry_run:
Greg Warda392dcb2000-06-23 01:42:40 +0000137 return dst
Greg Wardaebf7062000-04-04 02:05:59 +0000138
139 # On a Mac, use the native file copy routine
140 if os.name == 'mac':
141 import macostools
142 try:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000143 macostools.copy(src, dst, 0, preserve_times)
Greg Ward3af07e92000-04-22 15:17:14 +0000144 except os.error, exc:
Greg Wardaebf7062000-04-04 02:05:59 +0000145 raise DistutilsFileError, \
146 "could not copy '%s' to '%s': %s" % (src, dst, exc[-1])
147
148 # If linking (hard or symbolic), use the appropriate system call
149 # (Unix only, of course, but that's the caller's responsibility)
150 elif link == 'hard':
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000151 if not (os.path.exists(dst) and os.path.samefile(src, dst)):
152 os.link(src, dst)
Greg Wardaebf7062000-04-04 02:05:59 +0000153 elif link == 'sym':
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000154 if not (os.path.exists(dst) and os.path.samefile(src, dst)):
155 os.symlink(src, dst)
Greg Wardaebf7062000-04-04 02:05:59 +0000156
157 # Otherwise (non-Mac, not linking), copy the file contents and
158 # (optionally) copy the times and mode.
159 else:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000160 _copy_file_contents(src, dst)
Greg Wardaebf7062000-04-04 02:05:59 +0000161 if preserve_mode or preserve_times:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000162 st = os.stat(src)
Greg Wardaebf7062000-04-04 02:05:59 +0000163
164 # According to David Ascher <da@ski.org>, utime() should be done
165 # before chmod() (at least under NT).
166 if preserve_times:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000167 os.utime(dst, (st[ST_ATIME], st[ST_MTIME]))
Greg Wardaebf7062000-04-04 02:05:59 +0000168 if preserve_mode:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000169 os.chmod(dst, S_IMODE(st[ST_MODE]))
Greg Wardaebf7062000-04-04 02:05:59 +0000170
Greg Warda392dcb2000-06-23 01:42:40 +0000171 return dst
Greg Wardaebf7062000-04-04 02:05:59 +0000172
173# copy_file ()
174
175
176# XXX I suspect this is Unix-specific -- need porting help!
177def move_file (src, dst,
178 verbose=0,
179 dry_run=0):
180
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000181 """Move a file 'src' to 'dst'. If 'dst' is a directory, the file will
182 be moved into it with the same name; otherwise, 'src' is just renamed
183 to 'dst'. Return the new full name of the file.
Greg Wardaebf7062000-04-04 02:05:59 +0000184
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000185 Handles cross-device moves on Unix using 'copy_file()'. What about
186 other systems???
187 """
Greg Wardaebf7062000-04-04 02:05:59 +0000188 from os.path import exists, isfile, isdir, basename, dirname
189
190 if verbose:
191 print "moving %s -> %s" % (src, dst)
192
193 if dry_run:
194 return dst
195
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000196 if not isfile(src):
Greg Wardaebf7062000-04-04 02:05:59 +0000197 raise DistutilsFileError, \
198 "can't move '%s': not a regular file" % src
199
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000200 if isdir(dst):
201 dst = os.path.join(dst, basename(src))
202 elif exists(dst):
Greg Wardaebf7062000-04-04 02:05:59 +0000203 raise DistutilsFileError, \
204 "can't move '%s': destination '%s' already exists" % \
205 (src, dst)
206
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000207 if not isdir(dirname(dst)):
Greg Wardaebf7062000-04-04 02:05:59 +0000208 raise DistutilsFileError, \
209 "can't move '%s': destination '%s' not a valid path" % \
210 (src, dst)
211
212 copy_it = 0
213 try:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000214 os.rename(src, dst)
Greg Wardaebf7062000-04-04 02:05:59 +0000215 except os.error, (num, msg):
216 if num == errno.EXDEV:
217 copy_it = 1
218 else:
219 raise DistutilsFileError, \
220 "couldn't move '%s' to '%s': %s" % (src, dst, msg)
221
222 if copy_it:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000223 copy_file(src, dst)
Greg Wardaebf7062000-04-04 02:05:59 +0000224 try:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000225 os.unlink(src)
Greg Wardaebf7062000-04-04 02:05:59 +0000226 except os.error, (num, msg):
227 try:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000228 os.unlink(dst)
Greg Wardaebf7062000-04-04 02:05:59 +0000229 except os.error:
230 pass
231 raise DistutilsFileError, \
232 ("couldn't move '%s' to '%s' by copy/delete: " +
233 "delete '%s' failed: %s") % \
234 (src, dst, src, msg)
235
236 return dst
237
238# move_file ()
239
240
241def write_file (filename, contents):
242 """Create a file with the specified name and write 'contents' (a
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000243 sequence of strings without line terminators) to it.
244 """
245 f = open(filename, "w")
Greg Wardaebf7062000-04-04 02:05:59 +0000246 for line in contents:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000247 f.write(line + "\n")
248 f.close()