|
1 """distutils.file_util |
|
2 |
|
3 Utility functions for operating on single files. |
|
4 """ |
|
5 |
|
6 # This module should be kept compatible with Python 2.1. |
|
7 |
|
8 __revision__ = "$Id: file_util.py 37828 2004-11-10 22:23:15Z loewis $" |
|
9 |
|
10 import os |
|
11 from distutils.errors import DistutilsFileError |
|
12 from distutils import log |
|
13 |
|
14 # for generating verbose output in 'copy_file()' |
|
15 _copy_action = { None: 'copying', |
|
16 'hard': 'hard linking', |
|
17 'sym': 'symbolically linking' } |
|
18 |
|
19 |
|
20 def _copy_file_contents (src, dst, buffer_size=16*1024): |
|
21 """Copy the file 'src' to 'dst'; both must be filenames. Any error |
|
22 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 """ |
|
27 # 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 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 |
|
46 try: |
|
47 fdst = open(dst, 'wb') |
|
48 except os.error, (errno, errstr): |
|
49 raise DistutilsFileError, \ |
|
50 "could not create '%s': %s" % (dst, errstr) |
|
51 |
|
52 while 1: |
|
53 try: |
|
54 buf = fsrc.read(buffer_size) |
|
55 except os.error, (errno, errstr): |
|
56 raise DistutilsFileError, \ |
|
57 "could not read from '%s': %s" % (src, errstr) |
|
58 |
|
59 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) |
|
67 |
|
68 finally: |
|
69 if fdst: |
|
70 fdst.close() |
|
71 if fsrc: |
|
72 fsrc.close() |
|
73 |
|
74 # _copy_file_contents() |
|
75 |
|
76 def copy_file (src, dst, |
|
77 preserve_mode=1, |
|
78 preserve_times=1, |
|
79 update=0, |
|
80 link=None, |
|
81 verbose=0, |
|
82 dry_run=0): |
|
83 |
|
84 """Copy a file 'src' to 'dst'. If 'dst' is a directory, then 'src' is |
|
85 copied there with the same name; otherwise, it must be a filename. (If |
|
86 the file exists, it will be ruthlessly clobbered.) If 'preserve_mode' |
|
87 is true (the default), the file's mode (type and permission bits, or |
|
88 whatever is analogous on the current platform) is copied. If |
|
89 'preserve_times' is true (the default), the last-modified and |
|
90 last-access times are copied as well. If 'update' is true, 'src' will |
|
91 only be copied if 'dst' does not exist, or if 'dst' does exist but is |
|
92 older than 'src'. |
|
93 |
|
94 'link' allows you to make hard links (os.link) or symbolic links |
|
95 (os.symlink) instead of copying: set it to "hard" or "sym"; if it is |
|
96 None (the default), files are copied. Don't set 'link' on systems that |
|
97 don't support it: 'copy_file()' doesn't check if hard or symbolic |
|
98 linking is available. |
|
99 |
|
100 Under Mac OS, uses the native file copy function in macostools; on |
|
101 other systems, uses '_copy_file_contents()' to copy file contents. |
|
102 |
|
103 Return a tuple (dest_name, copied): 'dest_name' is the actual name of |
|
104 the output file, and 'copied' is true if the file was copied (or would |
|
105 have been copied, if 'dry_run' true). |
|
106 """ |
|
107 # XXX if the destination file already exists, we clobber it if |
|
108 # copying, but blow up if linking. Hmmm. And I don't know what |
|
109 # macostools.copyfile() does. Should definitely be consistent, and |
|
110 # should probably blow up if destination exists and we would be |
|
111 # changing it (ie. it's not already a hard/soft link to src OR |
|
112 # (not update) and (src newer than dst). |
|
113 |
|
114 from distutils.dep_util import newer |
|
115 from stat import ST_ATIME, ST_MTIME, ST_MODE, S_IMODE |
|
116 |
|
117 if not os.path.isfile(src): |
|
118 raise DistutilsFileError, \ |
|
119 "can't copy '%s': doesn't exist or not a regular file" % src |
|
120 |
|
121 if os.path.isdir(dst): |
|
122 dir = dst |
|
123 dst = os.path.join(dst, os.path.basename(src)) |
|
124 else: |
|
125 dir = os.path.dirname(dst) |
|
126 |
|
127 if update and not newer(src, dst): |
|
128 log.debug("not copying %s (output up-to-date)", src) |
|
129 return dst, 0 |
|
130 |
|
131 try: |
|
132 action = _copy_action[link] |
|
133 except KeyError: |
|
134 raise ValueError, \ |
|
135 "invalid value '%s' for 'link' argument" % link |
|
136 if os.path.basename(dst) == os.path.basename(src): |
|
137 log.info("%s %s -> %s", action, src, dir) |
|
138 else: |
|
139 log.info("%s %s -> %s", action, src, dst) |
|
140 |
|
141 if dry_run: |
|
142 return (dst, 1) |
|
143 |
|
144 # On Mac OS, use the native file copy routine |
|
145 if os.name == 'mac': |
|
146 import macostools |
|
147 try: |
|
148 macostools.copy(src, dst, 0, preserve_times) |
|
149 except os.error, exc: |
|
150 raise DistutilsFileError, \ |
|
151 "could not copy '%s' to '%s': %s" % (src, dst, exc[-1]) |
|
152 |
|
153 # If linking (hard or symbolic), use the appropriate system call |
|
154 # (Unix only, of course, but that's the caller's responsibility) |
|
155 elif link == 'hard': |
|
156 if not (os.path.exists(dst) and os.path.samefile(src, dst)): |
|
157 os.link(src, dst) |
|
158 elif link == 'sym': |
|
159 if not (os.path.exists(dst) and os.path.samefile(src, dst)): |
|
160 os.symlink(src, dst) |
|
161 |
|
162 # Otherwise (non-Mac, not linking), copy the file contents and |
|
163 # (optionally) copy the times and mode. |
|
164 else: |
|
165 _copy_file_contents(src, dst) |
|
166 if preserve_mode or preserve_times: |
|
167 st = os.stat(src) |
|
168 |
|
169 # According to David Ascher <da@ski.org>, utime() should be done |
|
170 # before chmod() (at least under NT). |
|
171 if preserve_times: |
|
172 os.utime(dst, (st[ST_ATIME], st[ST_MTIME])) |
|
173 if preserve_mode: |
|
174 os.chmod(dst, S_IMODE(st[ST_MODE])) |
|
175 |
|
176 return (dst, 1) |
|
177 |
|
178 # copy_file () |
|
179 |
|
180 |
|
181 # XXX I suspect this is Unix-specific -- need porting help! |
|
182 def move_file (src, dst, |
|
183 verbose=0, |
|
184 dry_run=0): |
|
185 |
|
186 """Move a file 'src' to 'dst'. If 'dst' is a directory, the file will |
|
187 be moved into it with the same name; otherwise, 'src' is just renamed |
|
188 to 'dst'. Return the new full name of the file. |
|
189 |
|
190 Handles cross-device moves on Unix using 'copy_file()'. What about |
|
191 other systems??? |
|
192 """ |
|
193 from os.path import exists, isfile, isdir, basename, dirname |
|
194 import errno |
|
195 |
|
196 log.info("moving %s -> %s", src, dst) |
|
197 |
|
198 if dry_run: |
|
199 return dst |
|
200 |
|
201 if not isfile(src): |
|
202 raise DistutilsFileError, \ |
|
203 "can't move '%s': not a regular file" % src |
|
204 |
|
205 if isdir(dst): |
|
206 dst = os.path.join(dst, basename(src)) |
|
207 elif exists(dst): |
|
208 raise DistutilsFileError, \ |
|
209 "can't move '%s': destination '%s' already exists" % \ |
|
210 (src, dst) |
|
211 |
|
212 if not isdir(dirname(dst)): |
|
213 raise DistutilsFileError, \ |
|
214 "can't move '%s': destination '%s' not a valid path" % \ |
|
215 (src, dst) |
|
216 |
|
217 copy_it = 0 |
|
218 try: |
|
219 os.rename(src, dst) |
|
220 except os.error, (num, msg): |
|
221 if num == errno.EXDEV: |
|
222 copy_it = 1 |
|
223 else: |
|
224 raise DistutilsFileError, \ |
|
225 "couldn't move '%s' to '%s': %s" % (src, dst, msg) |
|
226 |
|
227 if copy_it: |
|
228 copy_file(src, dst) |
|
229 try: |
|
230 os.unlink(src) |
|
231 except os.error, (num, msg): |
|
232 try: |
|
233 os.unlink(dst) |
|
234 except os.error: |
|
235 pass |
|
236 raise DistutilsFileError, \ |
|
237 ("couldn't move '%s' to '%s' by copy/delete: " + |
|
238 "delete '%s' failed: %s") % \ |
|
239 (src, dst, src, msg) |
|
240 |
|
241 return dst |
|
242 |
|
243 # move_file () |
|
244 |
|
245 |
|
246 def write_file (filename, contents): |
|
247 """Create a file with the specified name and write 'contents' (a |
|
248 sequence of strings without line terminators) to it. |
|
249 """ |
|
250 f = open(filename, "w") |
|
251 for line in contents: |
|
252 f.write(line + "\n") |
|
253 f.close() |