blob: 32245109d533eeb82d4c2958808bd6f954667dd9 [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
21 opening either file, reading from 'src', or writing to 'dst',
22 raises DistutilsFileError. Data is read/written in chunks of
23 'buffer_size' bytes (default 16k). No attempt is made to handle
24 anything apart from regular files."""
25
26 # 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:
46 buf = fsrc.read (buffer_size)
47 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
77 """Copy a file 'src' to 'dst'. If 'dst' is a directory, then 'src'
78 is copied there with the same name; otherwise, it must be a
79 filename. (If the file exists, it will be ruthlessly clobbered.)
80 If 'preserve_mode' is true (the default), the file's mode (type
81 and permission bits, or whatever is analogous on the current
82 platform) is copied. If 'preserve_times' is true (the default),
83 the last-modified and last-access times are copied as well. If
84 'update' is true, 'src' will only be copied if 'dst' does not
85 exist, or if 'dst' does exist but is older than 'src'. If
86 'verbose' is true, then a one-line summary of the copy will be
87 printed to stdout.
88
89 '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
91 is None (the default), files are copied. Don't set 'link' on
92 systems that don't support it: 'copy_file()' doesn't check if
93 hard or symbolic linking is availalble.
94
95 Under Mac OS, uses the native file copy function in macostools;
96 on other systems, uses '_copy_file_contents()' to copy file
97 contents.
98
99 Return true if the file was copied (or would have been copied),
100 false otherwise (ie. 'update' was true and the destination is
101 up-to-date)."""
102
103 # 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
110 from stat import *
111 from distutils.dep_util import newer
112
113 if not os.path.isfile (src):
114 raise DistutilsFileError, \
115 "can't copy '%s': doesn't exist or not a regular file" % src
116
117 if os.path.isdir (dst):
118 dir = dst
119 dst = os.path.join (dst, os.path.basename (src))
120 else:
121 dir = os.path.dirname (dst)
122
123 if update and not newer (src, dst):
124 if verbose:
125 print "not copying %s (output up-to-date)" % src
126 return 0
127
128 try:
129 action = _copy_action[link]
130 except KeyError:
131 raise ValueError, \
132 "invalid value '%s' for 'link' argument" % link
133 if verbose:
134 print "%s %s -> %s" % (action, src, dir)
135
136 if dry_run:
137 return 1
138
139 # On a Mac, use the native file copy routine
140 if os.name == 'mac':
141 import macostools
142 try:
143 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':
151 if not (os.path.exists (dst) and os.path.samefile (src, dst)):
152 os.link (src, dst)
153 elif link == 'sym':
154 if not (os.path.exists (dst) and os.path.samefile (src, dst)):
155 os.symlink (src, dst)
156
157 # Otherwise (non-Mac, not linking), copy the file contents and
158 # (optionally) copy the times and mode.
159 else:
160 _copy_file_contents (src, dst)
161 if preserve_mode or preserve_times:
162 st = os.stat (src)
163
164 # According to David Ascher <da@ski.org>, utime() should be done
165 # before chmod() (at least under NT).
166 if preserve_times:
167 os.utime (dst, (st[ST_ATIME], st[ST_MTIME]))
168 if preserve_mode:
169 os.chmod (dst, S_IMODE (st[ST_MODE]))
170
171 return 1
172
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
181 """Move a file 'src' to 'dst'. If 'dst' is a directory, the file
182 will be moved into it with the same name; otherwise, 'src' is
183 just renamed to 'dst'. Return the new full name of the file.
184
185 Handles cross-device moves on Unix using
186 'copy_file()'. What about other systems???"""
187
188 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
196 if not isfile (src):
197 raise DistutilsFileError, \
198 "can't move '%s': not a regular file" % src
199
200 if isdir (dst):
201 dst = os.path.join (dst, basename (src))
202 elif exists (dst):
203 raise DistutilsFileError, \
204 "can't move '%s': destination '%s' already exists" % \
205 (src, dst)
206
207 if not isdir (dirname (dst)):
208 raise DistutilsFileError, \
209 "can't move '%s': destination '%s' not a valid path" % \
210 (src, dst)
211
212 copy_it = 0
213 try:
214 os.rename (src, dst)
215 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:
223 copy_file (src, dst)
224 try:
225 os.unlink (src)
226 except os.error, (num, msg):
227 try:
228 os.unlink (dst)
229 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
243 sequence of strings without line terminators) to it."""
244
245 f = open (filename, "w")
246 for line in contents:
247 f.write (line + "\n")
248 f.close ()