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