blob: d8e8fd5f8d9e9dd86a125566b70645edf3ac6c0d [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
Greg Wardaebf7062000-04-04 02:05:59 +00006__revision__ = "$Id$"
7
8import os
9from distutils.errors import DistutilsFileError
Jeremy Hyltoncd8a1142002-06-04 20:14:43 +000010from distutils import log
Greg Wardaebf7062000-04-04 02:05:59 +000011
12# for generating verbose output in 'copy_file()'
Tarek Ziadé9ad7bbc2009-07-03 19:14:49 +000013_copy_action = {None: 'copying',
14 'hard': 'hard linking',
15 'sym': 'symbolically linking'}
Greg Wardaebf7062000-04-04 02:05:59 +000016
17
Tarek Ziadé9ad7bbc2009-07-03 19:14:49 +000018def _copy_file_contents(src, dst, buffer_size=16*1024):
19 """Copy the file 'src' to 'dst'.
20
21 Both must be filenames. Any error opening either file, reading from
22 'src', or writing to 'dst', raises DistutilsFileError. Data is
23 read/written in chunks of 'buffer_size' bytes (default 16k). No attempt
24 is made to handle anything apart from regular files.
Greg Ward9e3dc4e2000-09-23 00:59:34 +000025 """
Greg Wardaebf7062000-04-04 02:05:59 +000026 # Stolen from shutil module in the standard library, but with
27 # custom error-handling added.
Greg Wardaebf7062000-04-04 02:05:59 +000028 fsrc = None
29 fdst = None
30 try:
31 try:
32 fsrc = open(src, 'rb')
33 except os.error, (errno, errstr):
Tarek Ziadé9ad7bbc2009-07-03 19:14:49 +000034 raise DistutilsFileError("could not open '%s': %s" % (src, errstr))
Fred Drakeb94b8492001-12-06 20:51:35 +000035
Andrew M. Kuchling3b388ec2002-02-01 18:29:34 +000036 if os.path.exists(dst):
37 try:
38 os.unlink(dst)
39 except os.error, (errno, errstr):
Tarek Ziadé9ad7bbc2009-07-03 19:14:49 +000040 raise DistutilsFileError(
41 "could not delete '%s': %s" % (dst, errstr))
Tim Peters182b5ac2004-07-18 06:16:08 +000042
Greg Wardaebf7062000-04-04 02:05:59 +000043 try:
44 fdst = open(dst, 'wb')
45 except os.error, (errno, errstr):
Tarek Ziadé9ad7bbc2009-07-03 19:14:49 +000046 raise DistutilsFileError(
47 "could not create '%s': %s" % (dst, errstr))
Fred Drakeb94b8492001-12-06 20:51:35 +000048
Greg Wardaebf7062000-04-04 02:05:59 +000049 while 1:
50 try:
Greg Ward9e3dc4e2000-09-23 00:59:34 +000051 buf = fsrc.read(buffer_size)
Greg Wardaebf7062000-04-04 02:05:59 +000052 except os.error, (errno, errstr):
Tarek Ziadé9ad7bbc2009-07-03 19:14:49 +000053 raise DistutilsFileError(
54 "could not read from '%s': %s" % (src, errstr))
Fred Drakeb94b8492001-12-06 20:51:35 +000055
Greg Wardaebf7062000-04-04 02:05:59 +000056 if not buf:
57 break
58
59 try:
60 fdst.write(buf)
61 except os.error, (errno, errstr):
Tarek Ziadé9ad7bbc2009-07-03 19:14:49 +000062 raise DistutilsFileError(
63 "could not write to '%s': %s" % (dst, errstr))
Fred Drakeb94b8492001-12-06 20:51:35 +000064
Greg Wardaebf7062000-04-04 02:05:59 +000065 finally:
66 if fdst:
67 fdst.close()
68 if fsrc:
69 fsrc.close()
70
Tarek Ziadé9ad7bbc2009-07-03 19:14:49 +000071def copy_file(src, dst, preserve_mode=1, preserve_times=1, update=0,
72 link=None, verbose=1, dry_run=0):
73 """Copy a file 'src' to 'dst'.
Greg Wardaebf7062000-04-04 02:05:59 +000074
Tarek Ziadé9ad7bbc2009-07-03 19:14:49 +000075 If 'dst' is a directory, then 'src' is copied there with the same name;
76 otherwise, it must be a filename. (If the file exists, it will be
77 ruthlessly clobbered.) If 'preserve_mode' is true (the default),
78 the file's mode (type and permission bits, or whatever is analogous on
79 the current platform) is copied. If 'preserve_times' is true (the
80 default), the last-modified and last-access times are copied as well.
81 If 'update' is true, 'src' will only be copied if 'dst' does not exist,
82 or if 'dst' does exist but is older than 'src'.
Greg Wardaebf7062000-04-04 02:05:59 +000083
Greg Ward9e3dc4e2000-09-23 00:59:34 +000084 'link' allows you to make hard links (os.link) or symbolic links
85 (os.symlink) instead of copying: set it to "hard" or "sym"; if it is
86 None (the default), files are copied. Don't set 'link' on systems that
87 don't support it: 'copy_file()' doesn't check if hard or symbolic
88 linking is available.
Greg Wardaebf7062000-04-04 02:05:59 +000089
Greg Ward9e3dc4e2000-09-23 00:59:34 +000090 Under Mac OS, uses the native file copy function in macostools; on
91 other systems, uses '_copy_file_contents()' to copy file contents.
Greg Wardaebf7062000-04-04 02:05:59 +000092
Greg Ward0d4a8532000-09-30 17:29:35 +000093 Return a tuple (dest_name, copied): 'dest_name' is the actual name of
94 the output file, and 'copied' is true if the file was copied (or would
95 have been copied, if 'dry_run' true).
Greg Ward9e3dc4e2000-09-23 00:59:34 +000096 """
Greg Wardaebf7062000-04-04 02:05:59 +000097 # XXX if the destination file already exists, we clobber it if
98 # copying, but blow up if linking. Hmmm. And I don't know what
99 # macostools.copyfile() does. Should definitely be consistent, and
100 # should probably blow up if destination exists and we would be
101 # changing it (ie. it's not already a hard/soft link to src OR
102 # (not update) and (src newer than dst).
103
Greg Wardaebf7062000-04-04 02:05:59 +0000104 from distutils.dep_util import newer
Greg Warde628a2f2001-07-25 19:48:03 +0000105 from stat import ST_ATIME, ST_MTIME, ST_MODE, S_IMODE
Greg Wardaebf7062000-04-04 02:05:59 +0000106
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000107 if not os.path.isfile(src):
Tarek Ziadé9ad7bbc2009-07-03 19:14:49 +0000108 raise DistutilsFileError(
109 "can't copy '%s': doesn't exist or not a regular file" % src)
Greg Wardaebf7062000-04-04 02:05:59 +0000110
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000111 if os.path.isdir(dst):
Greg Wardaebf7062000-04-04 02:05:59 +0000112 dir = dst
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000113 dst = os.path.join(dst, os.path.basename(src))
Greg Wardaebf7062000-04-04 02:05:59 +0000114 else:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000115 dir = os.path.dirname(dst)
Greg Wardaebf7062000-04-04 02:05:59 +0000116
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000117 if update and not newer(src, dst):
Tarek Ziadéaaf2e182009-02-06 00:49:45 +0000118 if verbose >= 1:
Tarek Ziadéd5eb9852009-02-06 00:31:59 +0000119 log.debug("not copying %s (output up-to-date)", src)
Jeremy Hyltoncd8a1142002-06-04 20:14:43 +0000120 return dst, 0
Greg Wardaebf7062000-04-04 02:05:59 +0000121
122 try:
123 action = _copy_action[link]
124 except KeyError:
Tarek Ziadé9ad7bbc2009-07-03 19:14:49 +0000125 raise ValueError("invalid value '%s' for 'link' argument" % link)
Tarek Ziadéd5eb9852009-02-06 00:31:59 +0000126
Tarek Ziadéaaf2e182009-02-06 00:49:45 +0000127 if verbose >= 1:
Tarek Ziadéd5eb9852009-02-06 00:31:59 +0000128 if os.path.basename(dst) == os.path.basename(src):
129 log.info("%s %s -> %s", action, src, dir)
130 else:
131 log.info("%s %s -> %s", action, src, dst)
Fred Drakeb94b8492001-12-06 20:51:35 +0000132
Greg Wardaebf7062000-04-04 02:05:59 +0000133 if dry_run:
Greg Ward0d4a8532000-09-30 17:29:35 +0000134 return (dst, 1)
Greg Wardaebf7062000-04-04 02:05:59 +0000135
Greg Ward0d4a8532000-09-30 17:29:35 +0000136 # On Mac OS, use the native file copy routine
Greg Wardaebf7062000-04-04 02:05:59 +0000137 if os.name == 'mac':
138 import macostools
139 try:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000140 macostools.copy(src, dst, 0, preserve_times)
Greg Ward3af07e92000-04-22 15:17:14 +0000141 except os.error, exc:
Tarek Ziadé9ad7bbc2009-07-03 19:14:49 +0000142 raise DistutilsFileError(
143 "could not copy '%s' to '%s': %s" % (src, dst, exc[-1]))
Fred Drakeb94b8492001-12-06 20:51:35 +0000144
Greg Wardaebf7062000-04-04 02:05:59 +0000145 # If linking (hard or symbolic), use the appropriate system call
146 # (Unix only, of course, but that's the caller's responsibility)
147 elif link == 'hard':
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000148 if not (os.path.exists(dst) and os.path.samefile(src, dst)):
149 os.link(src, dst)
Greg Wardaebf7062000-04-04 02:05:59 +0000150 elif link == 'sym':
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000151 if not (os.path.exists(dst) and os.path.samefile(src, dst)):
152 os.symlink(src, dst)
Greg Wardaebf7062000-04-04 02:05:59 +0000153
154 # Otherwise (non-Mac, not linking), copy the file contents and
155 # (optionally) copy the times and mode.
156 else:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000157 _copy_file_contents(src, dst)
Greg Wardaebf7062000-04-04 02:05:59 +0000158 if preserve_mode or preserve_times:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000159 st = os.stat(src)
Greg Wardaebf7062000-04-04 02:05:59 +0000160
161 # According to David Ascher <da@ski.org>, utime() should be done
162 # before chmod() (at least under NT).
163 if preserve_times:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000164 os.utime(dst, (st[ST_ATIME], st[ST_MTIME]))
Greg Wardaebf7062000-04-04 02:05:59 +0000165 if preserve_mode:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000166 os.chmod(dst, S_IMODE(st[ST_MODE]))
Greg Wardaebf7062000-04-04 02:05:59 +0000167
Greg Ward0d4a8532000-09-30 17:29:35 +0000168 return (dst, 1)
Greg Wardaebf7062000-04-04 02:05:59 +0000169
Greg Wardaebf7062000-04-04 02:05:59 +0000170# XXX I suspect this is Unix-specific -- need porting help!
Tarek Ziadé9ad7bbc2009-07-03 19:14:49 +0000171def move_file (src, dst, verbose=1, dry_run=0):
172 """Move a file 'src' to 'dst'.
Greg Wardaebf7062000-04-04 02:05:59 +0000173
Tarek Ziadé9ad7bbc2009-07-03 19:14:49 +0000174 If 'dst' is a directory, the file will be moved into it with the same
175 name; otherwise, 'src' is just renamed to 'dst'. Return the new
176 full name of the file.
Greg Wardaebf7062000-04-04 02:05:59 +0000177
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000178 Handles cross-device moves on Unix using 'copy_file()'. What about
179 other systems???
180 """
Greg Wardaebf7062000-04-04 02:05:59 +0000181 from os.path import exists, isfile, isdir, basename, dirname
Andrew M. Kuchling106ffdb2001-08-09 20:59:53 +0000182 import errno
Fred Drakeb94b8492001-12-06 20:51:35 +0000183
Tarek Ziadéaaf2e182009-02-06 00:49:45 +0000184 if verbose >= 1:
Tarek Ziadéd5eb9852009-02-06 00:31:59 +0000185 log.info("moving %s -> %s", src, dst)
Greg Wardaebf7062000-04-04 02:05:59 +0000186
187 if dry_run:
188 return dst
189
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000190 if not isfile(src):
Tarek Ziadé9ad7bbc2009-07-03 19:14:49 +0000191 raise DistutilsFileError("can't move '%s': not a regular file" % src)
Greg Wardaebf7062000-04-04 02:05:59 +0000192
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000193 if isdir(dst):
194 dst = os.path.join(dst, basename(src))
195 elif exists(dst):
Tarek Ziadé9ad7bbc2009-07-03 19:14:49 +0000196 raise DistutilsFileError(
197 "can't move '%s': destination '%s' already exists" %
198 (src, dst))
Greg Wardaebf7062000-04-04 02:05:59 +0000199
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000200 if not isdir(dirname(dst)):
Tarek Ziadé9ad7bbc2009-07-03 19:14:49 +0000201 raise DistutilsFileError(
Greg Wardaebf7062000-04-04 02:05:59 +0000202 "can't move '%s': destination '%s' not a valid path" % \
Tarek Ziadé9ad7bbc2009-07-03 19:14:49 +0000203 (src, dst))
Greg Wardaebf7062000-04-04 02:05:59 +0000204
205 copy_it = 0
206 try:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000207 os.rename(src, dst)
Greg Wardaebf7062000-04-04 02:05:59 +0000208 except os.error, (num, msg):
209 if num == errno.EXDEV:
210 copy_it = 1
211 else:
Tarek Ziadé9ad7bbc2009-07-03 19:14:49 +0000212 raise DistutilsFileError(
213 "couldn't move '%s' to '%s': %s" % (src, dst, msg))
Greg Wardaebf7062000-04-04 02:05:59 +0000214
215 if copy_it:
Tarek Ziadéd5eb9852009-02-06 00:31:59 +0000216 copy_file(src, dst, verbose=verbose)
Greg Wardaebf7062000-04-04 02:05:59 +0000217 try:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000218 os.unlink(src)
Greg Wardaebf7062000-04-04 02:05:59 +0000219 except os.error, (num, msg):
220 try:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000221 os.unlink(dst)
Greg Wardaebf7062000-04-04 02:05:59 +0000222 except os.error:
223 pass
Tarek Ziadé9ad7bbc2009-07-03 19:14:49 +0000224 raise DistutilsFileError(
Fred Drakeb94b8492001-12-06 20:51:35 +0000225 ("couldn't move '%s' to '%s' by copy/delete: " +
Tarek Ziadé9ad7bbc2009-07-03 19:14:49 +0000226 "delete '%s' failed: %s") %
227 (src, dst, src, msg))
Greg Wardaebf7062000-04-04 02:05:59 +0000228 return dst
229
Greg Wardaebf7062000-04-04 02:05:59 +0000230
231def write_file (filename, contents):
232 """Create a file with the specified name and write 'contents' (a
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000233 sequence of strings without line terminators) to it.
234 """
235 f = open(filename, "w")
Greg Wardaebf7062000-04-04 02:05:59 +0000236 for line in contents:
Greg Ward9e3dc4e2000-09-23 00:59:34 +0000237 f.write(line + "\n")
238 f.close()