Home | History | Annotate | Download | only in distutils
      1 """distutils.file_util
      2 
      3 Utility functions for operating on single files.
      4 """
      5 
      6 __revision__ = "$Id$"
      7 
      8 import os
      9 from distutils.errors import DistutilsFileError
     10 from 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 
     18 def _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 
     71 def 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.
     89 
     90     Under Mac OS, uses the native file copy function in macostools; on
     91     other systems, uses '_copy_file_contents()' to copy file contents.
     92 
     93     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).
     96     """
     97     # 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 
    104     from distutils.dep_util import newer
    105     from stat import ST_ATIME, ST_MTIME, ST_MODE, S_IMODE
    106 
    107     if not os.path.isfile(src):
    108         raise DistutilsFileError(
    109               "can't copy '%s': doesn't exist or not a regular file" % src)
    110 
    111     if os.path.isdir(dst):
    112         dir = dst
    113         dst = os.path.join(dst, os.path.basename(src))
    114     else:
    115         dir = os.path.dirname(dst)
    116 
    117     if update and not newer(src, dst):
    118         if verbose >= 1:
    119             log.debug("not copying %s (output up-to-date)", src)
    120         return dst, 0
    121 
    122     try:
    123         action = _copy_action[link]
    124     except KeyError:
    125         raise ValueError("invalid value '%s' for 'link' argument" % link)
    126 
    127     if verbose >= 1:
    128         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)
    132 
    133     if dry_run:
    134         return (dst, 1)
    135 
    136     # If linking (hard or symbolic), use the appropriate system call
    137     # (Unix only, of course, but that's the caller's responsibility)
    138     if link == 'hard':
    139         if not (os.path.exists(dst) and os.path.samefile(src, dst)):
    140             os.link(src, dst)
    141     elif link == 'sym':
    142         if not (os.path.exists(dst) and os.path.samefile(src, dst)):
    143             os.symlink(src, dst)
    144 
    145     # Otherwise (non-Mac, not linking), copy the file contents and
    146     # (optionally) copy the times and mode.
    147     else:
    148         _copy_file_contents(src, dst)
    149         if preserve_mode or preserve_times:
    150             st = os.stat(src)
    151 
    152             # According to David Ascher <da (at] ski.org>, utime() should be done
    153             # before chmod() (at least under NT).
    154             if preserve_times:
    155                 os.utime(dst, (st[ST_ATIME], st[ST_MTIME]))
    156             if preserve_mode:
    157                 os.chmod(dst, S_IMODE(st[ST_MODE]))
    158 
    159     return (dst, 1)
    160 
    161 # XXX I suspect this is Unix-specific -- need porting help!
    162 def move_file (src, dst, verbose=1, dry_run=0):
    163     """Move a file 'src' to 'dst'.
    164 
    165     If 'dst' is a directory, the file will be moved into it with the same
    166     name; otherwise, 'src' is just renamed to 'dst'.  Return the new
    167     full name of the file.
    168 
    169     Handles cross-device moves on Unix using 'copy_file()'.  What about
    170     other systems???
    171     """
    172     from os.path import exists, isfile, isdir, basename, dirname
    173     import errno
    174 
    175     if verbose >= 1:
    176         log.info("moving %s -> %s", src, dst)
    177 
    178     if dry_run:
    179         return dst
    180 
    181     if not isfile(src):
    182         raise DistutilsFileError("can't move '%s': not a regular file" % src)
    183 
    184     if isdir(dst):
    185         dst = os.path.join(dst, basename(src))
    186     elif exists(dst):
    187         raise DistutilsFileError(
    188               "can't move '%s': destination '%s' already exists" %
    189               (src, dst))
    190 
    191     if not isdir(dirname(dst)):
    192         raise DistutilsFileError(
    193               "can't move '%s': destination '%s' not a valid path" % \
    194               (src, dst))
    195 
    196     copy_it = 0
    197     try:
    198         os.rename(src, dst)
    199     except os.error, (num, msg):
    200         if num == errno.EXDEV:
    201             copy_it = 1
    202         else:
    203             raise DistutilsFileError(
    204                   "couldn't move '%s' to '%s': %s" % (src, dst, msg))
    205 
    206     if copy_it:
    207         copy_file(src, dst, verbose=verbose)
    208         try:
    209             os.unlink(src)
    210         except os.error, (num, msg):
    211             try:
    212                 os.unlink(dst)
    213             except os.error:
    214                 pass
    215             raise DistutilsFileError(
    216                   ("couldn't move '%s' to '%s' by copy/delete: " +
    217                    "delete '%s' failed: %s") %
    218                   (src, dst, src, msg))
    219     return dst
    220 
    221 
    222 def write_file (filename, contents):
    223     """Create a file with the specified name and write 'contents' (a
    224     sequence of strings without line terminators) to it.
    225     """
    226     f = open(filename, "w")
    227     try:
    228         for line in contents:
    229             f.write(line + "\n")
    230     finally:
    231         f.close()
    232