blob: d05b456161ed093dbae6dc52da586c1dbcb603b1 [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)
38
39 try:
40 fdst = open(dst, 'wb')
41 except os.error, (errno, errstr):
42 raise DistutilsFileError, \
43 "could not create '%s': %s" % (dst, errstr)
44
45 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)
51
52 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)
60
61 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 Ward9e3dc4e2000-09-23 00:59:34 +000098 Return the name of the destination file, whether it was actually copied
99 or not.
100 """
Greg Wardaebf7062000-04-04 02:05:59 +0000101 # XXX if the destination file already exists, we clobber it if
102 # copying, but blow up if linking. Hmmm. And I don't know what
103 # macostools.copyfile() does. Should definitely be consistent, and
104 # should probably blow up if destination exists and we would be
105 # changing it (ie. it's not already a hard/soft link to src OR
106 # (not update) and (src newer than dst).
107
108 from stat import *
109 from distutils.dep_util import newer
110
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000111 if not os.path.isfile(src):
Greg Wardaebf7062000-04-04 02:05:59 +0000112 raise DistutilsFileError, \
113 "can't copy '%s': doesn't exist or not a regular file" % src
114
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000115 if os.path.isdir(dst):
Greg Wardaebf7062000-04-04 02:05:59 +0000116 dir = dst
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000117 dst = os.path.join(dst, os.path.basename(src))
Greg Wardaebf7062000-04-04 02:05:59 +0000118 else:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000119 dir = os.path.dirname(dst)
Greg Wardaebf7062000-04-04 02:05:59 +0000120
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000121 if update and not newer(src, dst):
Greg Wardaebf7062000-04-04 02:05:59 +0000122 if verbose:
123 print "not copying %s (output up-to-date)" % src
Greg Warda392dcb2000-06-23 01:42:40 +0000124 return dst
Greg Wardaebf7062000-04-04 02:05:59 +0000125
126 try:
127 action = _copy_action[link]
128 except KeyError:
129 raise ValueError, \
130 "invalid value '%s' for 'link' argument" % link
131 if verbose:
Greg Ward43550932000-05-20 16:05:34 +0000132 if os.path.basename(dst) == os.path.basename(src):
133 print "%s %s -> %s" % (action, src, dir)
134 else:
135 print "%s %s -> %s" % (action, src, dst)
136
Greg Wardaebf7062000-04-04 02:05:59 +0000137 if dry_run:
Greg Warda392dcb2000-06-23 01:42:40 +0000138 return dst
Greg Wardaebf7062000-04-04 02:05:59 +0000139
140 # On a Mac, use the native file copy routine
141 if os.name == 'mac':
142 import macostools
143 try:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000144 macostools.copy(src, dst, 0, preserve_times)
Greg Ward3af07e92000-04-22 15:17:14 +0000145 except os.error, exc:
Greg Wardaebf7062000-04-04 02:05:59 +0000146 raise DistutilsFileError, \
147 "could not copy '%s' to '%s': %s" % (src, dst, exc[-1])
148
149 # If linking (hard or symbolic), use the appropriate system call
150 # (Unix only, of course, but that's the caller's responsibility)
151 elif link == 'hard':
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000152 if not (os.path.exists(dst) and os.path.samefile(src, dst)):
153 os.link(src, dst)
Greg Wardaebf7062000-04-04 02:05:59 +0000154 elif link == 'sym':
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000155 if not (os.path.exists(dst) and os.path.samefile(src, dst)):
156 os.symlink(src, dst)
Greg Wardaebf7062000-04-04 02:05:59 +0000157
158 # Otherwise (non-Mac, not linking), copy the file contents and
159 # (optionally) copy the times and mode.
160 else:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000161 _copy_file_contents(src, dst)
Greg Wardaebf7062000-04-04 02:05:59 +0000162 if preserve_mode or preserve_times:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000163 st = os.stat(src)
Greg Wardaebf7062000-04-04 02:05:59 +0000164
165 # According to David Ascher <da@ski.org>, utime() should be done
166 # before chmod() (at least under NT).
167 if preserve_times:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000168 os.utime(dst, (st[ST_ATIME], st[ST_MTIME]))
Greg Wardaebf7062000-04-04 02:05:59 +0000169 if preserve_mode:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000170 os.chmod(dst, S_IMODE(st[ST_MODE]))
Greg Wardaebf7062000-04-04 02:05:59 +0000171
Greg Warda392dcb2000-06-23 01:42:40 +0000172 return dst
Greg Wardaebf7062000-04-04 02:05:59 +0000173
174# copy_file ()
175
176
177# XXX I suspect this is Unix-specific -- need porting help!
178def move_file (src, dst,
179 verbose=0,
180 dry_run=0):
181
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000182 """Move a file 'src' to 'dst'. If 'dst' is a directory, the file will
183 be moved into it with the same name; otherwise, 'src' is just renamed
184 to 'dst'. Return the new full name of the file.
Greg Wardaebf7062000-04-04 02:05:59 +0000185
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000186 Handles cross-device moves on Unix using 'copy_file()'. What about
187 other systems???
188 """
Greg Wardaebf7062000-04-04 02:05:59 +0000189 from os.path import exists, isfile, isdir, basename, dirname
190
191 if verbose:
192 print "moving %s -> %s" % (src, dst)
193
194 if dry_run:
195 return dst
196
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000197 if not isfile(src):
Greg Wardaebf7062000-04-04 02:05:59 +0000198 raise DistutilsFileError, \
199 "can't move '%s': not a regular file" % src
200
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000201 if isdir(dst):
202 dst = os.path.join(dst, basename(src))
203 elif exists(dst):
Greg Wardaebf7062000-04-04 02:05:59 +0000204 raise DistutilsFileError, \
205 "can't move '%s': destination '%s' already exists" % \
206 (src, dst)
207
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000208 if not isdir(dirname(dst)):
Greg Wardaebf7062000-04-04 02:05:59 +0000209 raise DistutilsFileError, \
210 "can't move '%s': destination '%s' not a valid path" % \
211 (src, dst)
212
213 copy_it = 0
214 try:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000215 os.rename(src, dst)
Greg Wardaebf7062000-04-04 02:05:59 +0000216 except os.error, (num, msg):
217 if num == errno.EXDEV:
218 copy_it = 1
219 else:
220 raise DistutilsFileError, \
221 "couldn't move '%s' to '%s': %s" % (src, dst, msg)
222
223 if copy_it:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000224 copy_file(src, dst)
Greg Wardaebf7062000-04-04 02:05:59 +0000225 try:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000226 os.unlink(src)
Greg Wardaebf7062000-04-04 02:05:59 +0000227 except os.error, (num, msg):
228 try:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000229 os.unlink(dst)
Greg Wardaebf7062000-04-04 02:05:59 +0000230 except os.error:
231 pass
232 raise DistutilsFileError, \
233 ("couldn't move '%s' to '%s' by copy/delete: " +
234 "delete '%s' failed: %s") % \
235 (src, dst, src, msg)
236
237 return dst
238
239# move_file ()
240
241
242def write_file (filename, contents):
243 """Create a file with the specified name and write 'contents' (a
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000244 sequence of strings without line terminators) to it.
245 """
246 f = open(filename, "w")
Greg Wardaebf7062000-04-04 02:05:59 +0000247 for line in contents:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000248 f.write(line + "\n")
249 f.close()