• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""distutils.file_util
2
3Utility functions for operating on single files.
4"""
5
6__revision__ = "$Id$"
7
8import os
9from distutils.errors import DistutilsFileError
10from distutils import log
11
12# for generating verbose output in 'copy_file()'
13_copy_action = {None: 'copying',
14                'hard': 'hard linking',
15                'sym': 'symbolically linking'}
16
17
18def _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.
25    """
26    # Stolen from shutil module in the standard library, but with
27    # custom error-handling added.
28    fsrc = None
29    fdst = None
30    try:
31        try:
32            fsrc = open(src, 'rb')
33        except os.error, (errno, errstr):
34            raise DistutilsFileError("could not open '%s': %s" % (src, errstr))
35
36        if os.path.exists(dst):
37            try:
38                os.unlink(dst)
39            except os.error, (errno, errstr):
40                raise DistutilsFileError(
41                      "could not delete '%s': %s" % (dst, errstr))
42
43        try:
44            fdst = open(dst, 'wb')
45        except os.error, (errno, errstr):
46            raise DistutilsFileError(
47                  "could not create '%s': %s" % (dst, errstr))
48
49        while 1:
50            try:
51                buf = fsrc.read(buffer_size)
52            except os.error, (errno, errstr):
53                raise DistutilsFileError(
54                      "could not read from '%s': %s" % (src, errstr))
55
56            if not buf:
57                break
58
59            try:
60                fdst.write(buf)
61            except os.error, (errno, errstr):
62                raise DistutilsFileError(
63                      "could not write to '%s': %s" % (dst, errstr))
64
65    finally:
66        if fdst:
67            fdst.close()
68        if fsrc:
69            fsrc.close()
70
71def 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'.
74
75    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'.
83
84    '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. If hardlink fails, falls back to
89    _copy_file_contents().
90
91    Under Mac OS, uses the native file copy function in macostools; on
92    other systems, uses '_copy_file_contents()' to copy file contents.
93
94    Return a tuple (dest_name, copied): 'dest_name' is the actual name of
95    the output file, and 'copied' is true if the file was copied (or would
96    have been copied, if 'dry_run' true).
97    """
98    # XXX if the destination file already exists, we clobber it if
99    # copying, but blow up if linking.  Hmmm.  And I don't know what
100    # macostools.copyfile() does.  Should definitely be consistent, and
101    # should probably blow up if destination exists and we would be
102    # changing it (ie. it's not already a hard/soft link to src OR
103    # (not update) and (src newer than dst).
104
105    from distutils.dep_util import newer
106    from stat import ST_ATIME, ST_MTIME, ST_MODE, S_IMODE
107
108    if not os.path.isfile(src):
109        raise DistutilsFileError(
110              "can't copy '%s': doesn't exist or not a regular file" % src)
111
112    if os.path.isdir(dst):
113        dir = dst
114        dst = os.path.join(dst, os.path.basename(src))
115    else:
116        dir = os.path.dirname(dst)
117
118    if update and not newer(src, dst):
119        if verbose >= 1:
120            log.debug("not copying %s (output up-to-date)", src)
121        return dst, 0
122
123    try:
124        action = _copy_action[link]
125    except KeyError:
126        raise ValueError("invalid value '%s' for 'link' argument" % link)
127
128    if verbose >= 1:
129        if os.path.basename(dst) == os.path.basename(src):
130            log.info("%s %s -> %s", action, src, dir)
131        else:
132            log.info("%s %s -> %s", action, src, dst)
133
134    if dry_run:
135        return (dst, 1)
136
137    # If linking (hard or symbolic), use the appropriate system call
138    # (Unix only, of course, but that's the caller's responsibility)
139    if link == 'hard':
140        if not (os.path.exists(dst) and os.path.samefile(src, dst)):
141            try:
142                os.link(src, dst)
143                return (dst, 1)
144            except OSError:
145                # If hard linking fails, fall back on copying file
146                # (some special filesystems don't support hard linking
147                #  even under Unix, see issue #8876).
148                pass
149    elif link == 'sym':
150        if not (os.path.exists(dst) and os.path.samefile(src, dst)):
151            os.symlink(src, dst)
152            return (dst, 1)
153
154    # Otherwise (non-Mac, not linking), copy the file contents and
155    # (optionally) copy the times and mode.
156    _copy_file_contents(src, dst)
157    if preserve_mode or preserve_times:
158        st = os.stat(src)
159
160        # According to David Ascher <da@ski.org>, utime() should be done
161        # before chmod() (at least under NT).
162        if preserve_times:
163            os.utime(dst, (st[ST_ATIME], st[ST_MTIME]))
164        if preserve_mode:
165            os.chmod(dst, S_IMODE(st[ST_MODE]))
166
167    return (dst, 1)
168
169# XXX I suspect this is Unix-specific -- need porting help!
170def move_file (src, dst, verbose=1, dry_run=0):
171    """Move a file 'src' to 'dst'.
172
173    If 'dst' is a directory, the file will be moved into it with the same
174    name; otherwise, 'src' is just renamed to 'dst'.  Return the new
175    full name of the file.
176
177    Handles cross-device moves on Unix using 'copy_file()'.  What about
178    other systems???
179    """
180    from os.path import exists, isfile, isdir, basename, dirname
181    import errno
182
183    if verbose >= 1:
184        log.info("moving %s -> %s", src, dst)
185
186    if dry_run:
187        return dst
188
189    if not isfile(src):
190        raise DistutilsFileError("can't move '%s': not a regular file" % src)
191
192    if isdir(dst):
193        dst = os.path.join(dst, basename(src))
194    elif exists(dst):
195        raise DistutilsFileError(
196              "can't move '%s': destination '%s' already exists" %
197              (src, dst))
198
199    if not isdir(dirname(dst)):
200        raise DistutilsFileError(
201              "can't move '%s': destination '%s' not a valid path" % \
202              (src, dst))
203
204    copy_it = 0
205    try:
206        os.rename(src, dst)
207    except os.error, (num, msg):
208        if num == errno.EXDEV:
209            copy_it = 1
210        else:
211            raise DistutilsFileError(
212                  "couldn't move '%s' to '%s': %s" % (src, dst, msg))
213
214    if copy_it:
215        copy_file(src, dst, verbose=verbose)
216        try:
217            os.unlink(src)
218        except os.error, (num, msg):
219            try:
220                os.unlink(dst)
221            except os.error:
222                pass
223            raise DistutilsFileError(
224                  ("couldn't move '%s' to '%s' by copy/delete: " +
225                   "delete '%s' failed: %s") %
226                  (src, dst, src, msg))
227    return dst
228
229
230def write_file (filename, contents):
231    """Create a file with the specified name and write 'contents' (a
232    sequence of strings without line terminators) to it.
233    """
234    f = open(filename, "w")
235    try:
236        for line in contents:
237            f.write(line + "\n")
238    finally:
239        f.close()
240