3 Utility functions for operating on single files.
7 from .errors
import DistutilsFileError
10 # for generating verbose output in 'copy_file()'
11 _copy_action
= {None: 'copying', 'hard': 'hard linking', 'sym': 'symbolically linking'}
14 def _copy_file_contents(src
, dst
, buffer_size
=16 * 1024): # noqa: C901
15 """Copy the file 'src' to 'dst'; both must be filenames. Any error
16 opening either file, reading from 'src', or writing to 'dst', raises
17 DistutilsFileError. Data is read/written in chunks of 'buffer_size'
18 bytes (default 16k). No attempt is made to handle anything apart from
21 # Stolen from shutil module in the standard library, but with
22 # custom error-handling added.
27 fsrc
= open(src
, 'rb')
29 raise DistutilsFileError("could not open '{}': {}".format(src
, e
.strerror
))
31 if os
.path
.exists(dst
):
35 raise DistutilsFileError(
36 "could not delete '{}': {}".format(dst
, e
.strerror
)
40 fdst
= open(dst
, 'wb')
42 raise DistutilsFileError(
43 "could not create '{}': {}".format(dst
, e
.strerror
)
48 buf
= fsrc
.read(buffer_size
)
50 raise DistutilsFileError(
51 "could not read from '{}': {}".format(src
, e
.strerror
)
60 raise DistutilsFileError(
61 "could not write to '{}': {}".format(dst
, e
.strerror
)
70 def copy_file( # noqa: C901
80 """Copy a file 'src' to 'dst'. If 'dst' is a directory, then 'src' is
81 copied there with the same name; otherwise, it must be a filename. (If
82 the file exists, it will be ruthlessly clobbered.) If 'preserve_mode'
83 is true (the default), the file's mode (type and permission bits, or
84 whatever is analogous on the current platform) is copied. If
85 'preserve_times' is true (the default), the last-modified and
86 last-access times are copied as well. If 'update' is true, 'src' will
87 only be copied if 'dst' does not exist, or if 'dst' does exist but is
90 'link' allows you to make hard links (os.link) or symbolic links
91 (os.symlink) instead of copying: set it to "hard" or "sym"; if it is
92 None (the default), files are copied. Don't set 'link' on systems that
93 don't support it: 'copy_file()' doesn't check if hard or symbolic
94 linking is available. If hardlink fails, falls back to
95 _copy_file_contents().
97 Under Mac OS, uses the native file copy function in macostools; on
98 other systems, uses '_copy_file_contents()' to copy file contents.
100 Return a tuple (dest_name, copied): 'dest_name' is the actual name of
101 the output file, and 'copied' is true if the file was copied (or would
102 have been copied, if 'dry_run' true).
104 # XXX if the destination file already exists, we clobber it if
105 # copying, but blow up if linking. Hmmm. And I don't know what
106 # macostools.copyfile() does. Should definitely be consistent, and
107 # should probably blow up if destination exists and we would be
108 # changing it (ie. it's not already a hard/soft link to src OR
109 # (not update) and (src newer than dst).
111 from distutils
.dep_util
import newer
112 from stat
import ST_ATIME
, ST_MTIME
, ST_MODE
, S_IMODE
114 if not os
.path
.isfile(src
):
115 raise DistutilsFileError(
116 "can't copy '%s': doesn't exist or not a regular file" % src
119 if os
.path
.isdir(dst
):
121 dst
= os
.path
.join(dst
, os
.path
.basename(src
))
123 dir = os
.path
.dirname(dst
)
125 if update
and not newer(src
, dst
):
127 log
.debug("not copying %s (output up-to-date)", src
)
131 action
= _copy_action
[link
]
133 raise ValueError("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)
139 log
.info("%s %s -> %s", action
, src
, dst
)
144 # If linking (hard or symbolic), use the appropriate system call
145 # (Unix only, of course, but that's the caller's responsibility)
147 if not (os
.path
.exists(dst
) and os
.path
.samefile(src
, dst
)):
152 # If hard linking fails, fall back on copying file
153 # (some special filesystems don't support hard linking
154 # even under Unix, see issue #8876).
157 if not (os
.path
.exists(dst
) and os
.path
.samefile(src
, dst
)):
161 # Otherwise (non-Mac, not linking), copy the file contents and
162 # (optionally) copy the times and mode.
163 _copy_file_contents(src
, dst
)
164 if preserve_mode
or preserve_times
:
167 # According to David Ascher <da@ski.org>, utime() should be done
168 # before chmod() (at least under NT).
170 os
.utime(dst
, (st
[ST_ATIME
], st
[ST_MTIME
]))
172 os
.chmod(dst
, S_IMODE(st
[ST_MODE
]))
177 # XXX I suspect this is Unix-specific -- need porting help!
178 def move_file(src
, dst
, verbose
=1, dry_run
=0): # noqa: C901
179 """Move a file 'src' to 'dst'. If 'dst' is a directory, the file will
180 be moved into it with the same name; otherwise, 'src' is just renamed
181 to 'dst'. Return the new full name of the file.
183 Handles cross-device moves on Unix using 'copy_file()'. What about
186 from os
.path
import exists
, isfile
, isdir
, basename
, dirname
190 log
.info("moving %s -> %s", src
, dst
)
196 raise DistutilsFileError("can't move '%s': not a regular file" % src
)
199 dst
= os
.path
.join(dst
, basename(src
))
201 raise DistutilsFileError(
202 "can't move '{}': destination '{}' already exists".format(src
, dst
)
205 if not isdir(dirname(dst
)):
206 raise DistutilsFileError(
207 "can't move '{}': destination '{}' not a valid path".format(src
, dst
)
215 if num
== errno
.EXDEV
:
218 raise DistutilsFileError(
219 "couldn't move '{}' to '{}': {}".format(src
, dst
, msg
)
223 copy_file(src
, dst
, verbose
=verbose
)
232 raise DistutilsFileError(
233 "couldn't move '%s' to '%s' by copy/delete: "
234 "delete '%s' failed: %s" % (src
, dst
, src
, msg
)
239 def write_file(filename
, contents
):
240 """Create a file with the specified name and write 'contents' (a
241 sequence of strings without line terminators) to it.
243 f
= open(filename
, "w")
245 for line
in contents
: