blob: 2d0148f3f1dd260f2d07e95c3698956b600aaa37 [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
Greg Ward43550932000-05-20 16:05:34 +000093 hard or symbolic linking is available.
Greg Wardaebf7062000-04-04 02:05:59 +000094
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
Greg Warda392dcb2000-06-23 01:42:40 +000099 Return the name of the destination file, whether it was actually
100 copied or not."""
Greg Wardaebf7062000-04-04 02:05:59 +0000101
102 # XXX if the destination file already exists, we clobber it if
103 # copying, but blow up if linking. Hmmm. And I don't know what
104 # macostools.copyfile() does. Should definitely be consistent, and
105 # should probably blow up if destination exists and we would be
106 # changing it (ie. it's not already a hard/soft link to src OR
107 # (not update) and (src newer than dst).
108
109 from stat import *
110 from distutils.dep_util import newer
111
112 if not os.path.isfile (src):
113 raise DistutilsFileError, \
114 "can't copy '%s': doesn't exist or not a regular file" % src
115
116 if os.path.isdir (dst):
117 dir = dst
118 dst = os.path.join (dst, os.path.basename (src))
119 else:
120 dir = os.path.dirname (dst)
121
122 if update and not newer (src, dst):
123 if verbose:
124 print "not copying %s (output up-to-date)" % src
Greg Warda392dcb2000-06-23 01:42:40 +0000125 return dst
Greg Wardaebf7062000-04-04 02:05:59 +0000126
127 try:
128 action = _copy_action[link]
129 except KeyError:
130 raise ValueError, \
131 "invalid value '%s' for 'link' argument" % link
132 if verbose:
Greg Ward43550932000-05-20 16:05:34 +0000133 if os.path.basename(dst) == os.path.basename(src):
134 print "%s %s -> %s" % (action, src, dir)
135 else:
136 print "%s %s -> %s" % (action, src, dst)
137
Greg Wardaebf7062000-04-04 02:05:59 +0000138 if dry_run:
Greg Warda392dcb2000-06-23 01:42:40 +0000139 return dst
Greg Wardaebf7062000-04-04 02:05:59 +0000140
141 # On a Mac, use the native file copy routine
142 if os.name == 'mac':
143 import macostools
144 try:
145 macostools.copy (src, dst, 0, preserve_times)
Greg Ward3af07e92000-04-22 15:17:14 +0000146 except os.error, exc:
Greg Wardaebf7062000-04-04 02:05:59 +0000147 raise DistutilsFileError, \
148 "could not copy '%s' to '%s': %s" % (src, dst, exc[-1])
149
150 # If linking (hard or symbolic), use the appropriate system call
151 # (Unix only, of course, but that's the caller's responsibility)
152 elif link == 'hard':
153 if not (os.path.exists (dst) and os.path.samefile (src, dst)):
154 os.link (src, dst)
155 elif link == 'sym':
156 if not (os.path.exists (dst) and os.path.samefile (src, dst)):
157 os.symlink (src, dst)
158
159 # Otherwise (non-Mac, not linking), copy the file contents and
160 # (optionally) copy the times and mode.
161 else:
162 _copy_file_contents (src, dst)
163 if preserve_mode or preserve_times:
164 st = os.stat (src)
165
166 # According to David Ascher <da@ski.org>, utime() should be done
167 # before chmod() (at least under NT).
168 if preserve_times:
169 os.utime (dst, (st[ST_ATIME], st[ST_MTIME]))
170 if preserve_mode:
171 os.chmod (dst, S_IMODE (st[ST_MODE]))
172
Greg Warda392dcb2000-06-23 01:42:40 +0000173 return dst
Greg Wardaebf7062000-04-04 02:05:59 +0000174
175# copy_file ()
176
177
178# XXX I suspect this is Unix-specific -- need porting help!
179def move_file (src, dst,
180 verbose=0,
181 dry_run=0):
182
183 """Move a file 'src' to 'dst'. If 'dst' is a directory, the file
184 will be moved into it with the same name; otherwise, 'src' is
185 just renamed to 'dst'. Return the new full name of the file.
186
187 Handles cross-device moves on Unix using
188 'copy_file()'. What about other systems???"""
189
190 from os.path import exists, isfile, isdir, basename, dirname
191
192 if verbose:
193 print "moving %s -> %s" % (src, dst)
194
195 if dry_run:
196 return dst
197
198 if not isfile (src):
199 raise DistutilsFileError, \
200 "can't move '%s': not a regular file" % src
201
202 if isdir (dst):
203 dst = os.path.join (dst, basename (src))
204 elif exists (dst):
205 raise DistutilsFileError, \
206 "can't move '%s': destination '%s' already exists" % \
207 (src, dst)
208
209 if not isdir (dirname (dst)):
210 raise DistutilsFileError, \
211 "can't move '%s': destination '%s' not a valid path" % \
212 (src, dst)
213
214 copy_it = 0
215 try:
216 os.rename (src, dst)
217 except os.error, (num, msg):
218 if num == errno.EXDEV:
219 copy_it = 1
220 else:
221 raise DistutilsFileError, \
222 "couldn't move '%s' to '%s': %s" % (src, dst, msg)
223
224 if copy_it:
225 copy_file (src, dst)
226 try:
227 os.unlink (src)
228 except os.error, (num, msg):
229 try:
230 os.unlink (dst)
231 except os.error:
232 pass
233 raise DistutilsFileError, \
234 ("couldn't move '%s' to '%s' by copy/delete: " +
235 "delete '%s' failed: %s") % \
236 (src, dst, src, msg)
237
238 return dst
239
240# move_file ()
241
242
243def write_file (filename, contents):
244 """Create a file with the specified name and write 'contents' (a
245 sequence of strings without line terminators) to it."""
246
247 f = open (filename, "w")
248 for line in contents:
249 f.write (line + "\n")
250 f.close ()