blob: a73db42b54d0d135a29cc607e0918e0783bed5df [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
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:
Greg Ward43550932000-05-20 16:05:34 +0000134 if os.path.basename(dst) == os.path.basename(src):
135 print "%s %s -> %s" % (action, src, dir)
136 else:
137 print "%s %s -> %s" % (action, src, dst)
138
Greg Wardaebf7062000-04-04 02:05:59 +0000139 if dry_run:
140 return 1
141
142 # On a Mac, use the native file copy routine
143 if os.name == 'mac':
144 import macostools
145 try:
146 macostools.copy (src, dst, 0, preserve_times)
Greg Ward3af07e92000-04-22 15:17:14 +0000147 except os.error, exc:
Greg Wardaebf7062000-04-04 02:05:59 +0000148 raise DistutilsFileError, \
149 "could not copy '%s' to '%s': %s" % (src, dst, exc[-1])
150
151 # If linking (hard or symbolic), use the appropriate system call
152 # (Unix only, of course, but that's the caller's responsibility)
153 elif link == 'hard':
154 if not (os.path.exists (dst) and os.path.samefile (src, dst)):
155 os.link (src, dst)
156 elif link == 'sym':
157 if not (os.path.exists (dst) and os.path.samefile (src, dst)):
158 os.symlink (src, dst)
159
160 # Otherwise (non-Mac, not linking), copy the file contents and
161 # (optionally) copy the times and mode.
162 else:
163 _copy_file_contents (src, dst)
164 if preserve_mode or preserve_times:
165 st = os.stat (src)
166
167 # According to David Ascher <da@ski.org>, utime() should be done
168 # before chmod() (at least under NT).
169 if preserve_times:
170 os.utime (dst, (st[ST_ATIME], st[ST_MTIME]))
171 if preserve_mode:
172 os.chmod (dst, S_IMODE (st[ST_MODE]))
173
174 return 1
175
176# copy_file ()
177
178
179# XXX I suspect this is Unix-specific -- need porting help!
180def move_file (src, dst,
181 verbose=0,
182 dry_run=0):
183
184 """Move a file 'src' to 'dst'. If 'dst' is a directory, the file
185 will be moved into it with the same name; otherwise, 'src' is
186 just renamed to 'dst'. Return the new full name of the file.
187
188 Handles cross-device moves on Unix using
189 'copy_file()'. What about other systems???"""
190
191 from os.path import exists, isfile, isdir, basename, dirname
192
193 if verbose:
194 print "moving %s -> %s" % (src, dst)
195
196 if dry_run:
197 return dst
198
199 if not isfile (src):
200 raise DistutilsFileError, \
201 "can't move '%s': not a regular file" % src
202
203 if isdir (dst):
204 dst = os.path.join (dst, basename (src))
205 elif exists (dst):
206 raise DistutilsFileError, \
207 "can't move '%s': destination '%s' already exists" % \
208 (src, dst)
209
210 if not isdir (dirname (dst)):
211 raise DistutilsFileError, \
212 "can't move '%s': destination '%s' not a valid path" % \
213 (src, dst)
214
215 copy_it = 0
216 try:
217 os.rename (src, dst)
218 except os.error, (num, msg):
219 if num == errno.EXDEV:
220 copy_it = 1
221 else:
222 raise DistutilsFileError, \
223 "couldn't move '%s' to '%s': %s" % (src, dst, msg)
224
225 if copy_it:
226 copy_file (src, dst)
227 try:
228 os.unlink (src)
229 except os.error, (num, msg):
230 try:
231 os.unlink (dst)
232 except os.error:
233 pass
234 raise DistutilsFileError, \
235 ("couldn't move '%s' to '%s' by copy/delete: " +
236 "delete '%s' failed: %s") % \
237 (src, dst, src, msg)
238
239 return dst
240
241# move_file ()
242
243
244def write_file (filename, contents):
245 """Create a file with the specified name and write 'contents' (a
246 sequence of strings without line terminators) to it."""
247
248 f = open (filename, "w")
249 for line in contents:
250 f.write (line + "\n")
251 f.close ()