python (3.11.7)

(root)/
lib/
python3.11/
site-packages/
setuptools/
_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', 'hard': 'hard linking', 'sym': 'symbolically linking'}
      12  
      13  
      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
      19      regular files.
      20      """
      21      # Stolen from shutil module in the standard library, but with
      22      # custom error-handling added.
      23      fsrc = None
      24      fdst = None
      25      try:
      26          try:
      27              fsrc = open(src, 'rb')
      28          except OSError as e:
      29              raise DistutilsFileError("could not open '{}': {}".format(src, e.strerror))
      30  
      31          if os.path.exists(dst):
      32              try:
      33                  os.unlink(dst)
      34              except OSError as e:
      35                  raise DistutilsFileError(
      36                      "could not delete '{}': {}".format(dst, e.strerror)
      37                  )
      38  
      39          try:
      40              fdst = open(dst, 'wb')
      41          except OSError as e:
      42              raise DistutilsFileError(
      43                  "could not create '{}': {}".format(dst, e.strerror)
      44              )
      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 '{}': {}".format(src, e.strerror)
      52                  )
      53  
      54              if not buf:
      55                  break
      56  
      57              try:
      58                  fdst.write(buf)
      59              except OSError as e:
      60                  raise DistutilsFileError(
      61                      "could not write to '{}': {}".format(dst, e.strerror)
      62                  )
      63      finally:
      64          if fdst:
      65              fdst.close()
      66          if fsrc:
      67              fsrc.close()
      68  
      69  
      70  def copy_file(  # noqa: C901
      71      src,
      72      dst,
      73      preserve_mode=1,
      74      preserve_times=1,
      75      update=0,
      76      link=None,
      77      verbose=1,
      78      dry_run=0,
      79  ):
      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
      88      older than 'src'.
      89  
      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().
      96  
      97      Under Mac OS, uses the native file copy function in macostools; on
      98      other systems, uses '_copy_file_contents()' to copy file contents.
      99  
     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).
     103      """
     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).
     110  
     111      from distutils.dep_util import newer
     112      from stat import ST_ATIME, ST_MTIME, ST_MODE, S_IMODE
     113  
     114      if not os.path.isfile(src):
     115          raise DistutilsFileError(
     116              "can't copy '%s': doesn't exist or not a regular file" % src
     117          )
     118  
     119      if os.path.isdir(dst):
     120          dir = dst
     121          dst = os.path.join(dst, os.path.basename(src))
     122      else:
     123          dir = os.path.dirname(dst)
     124  
     125      if update and not newer(src, dst):
     126          if verbose >= 1:
     127              log.debug("not copying %s (output up-to-date)", src)
     128          return (dst, 0)
     129  
     130      try:
     131          action = _copy_action[link]
     132      except KeyError:
     133          raise ValueError("invalid value '%s' for 'link' argument" % link)
     134  
     135      if verbose >= 1:
     136          if os.path.basename(dst) == os.path.basename(src):
     137              log.info("%s %s -> %s", action, src, dir)
     138          else:
     139              log.info("%s %s -> %s", action, src, dst)
     140  
     141      if dry_run:
     142          return (dst, 1)
     143  
     144      # If linking (hard or symbolic), use the appropriate system call
     145      # (Unix only, of course, but that's the caller's responsibility)
     146      elif link == 'hard':
     147          if not (os.path.exists(dst) and os.path.samefile(src, dst)):
     148              try:
     149                  os.link(src, dst)
     150                  return (dst, 1)
     151              except OSError:
     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).
     155                  pass
     156      elif link == 'sym':
     157          if not (os.path.exists(dst) and os.path.samefile(src, dst)):
     158              os.symlink(src, dst)
     159              return (dst, 1)
     160  
     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:
     165          st = os.stat(src)
     166  
     167          # According to David Ascher <da@ski.org>, utime() should be done
     168          # before chmod() (at least under NT).
     169          if preserve_times:
     170              os.utime(dst, (st[ST_ATIME], st[ST_MTIME]))
     171          if preserve_mode:
     172              os.chmod(dst, S_IMODE(st[ST_MODE]))
     173  
     174      return (dst, 1)
     175  
     176  
     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  
     180      """Move a file 'src' to 'dst'.  If 'dst' is a directory, the file will
     181      be moved into it with the same name; otherwise, 'src' is just renamed
     182      to 'dst'.  Return the new full name of the file.
     183  
     184      Handles cross-device moves on Unix using 'copy_file()'.  What about
     185      other systems???
     186      """
     187      from os.path import exists, isfile, isdir, basename, dirname
     188      import errno
     189  
     190      if verbose >= 1:
     191          log.info("moving %s -> %s", src, dst)
     192  
     193      if dry_run:
     194          return dst
     195  
     196      if not isfile(src):
     197          raise DistutilsFileError("can't move '%s': not a regular file" % src)
     198  
     199      if isdir(dst):
     200          dst = os.path.join(dst, basename(src))
     201      elif exists(dst):
     202          raise DistutilsFileError(
     203              "can't move '{}': destination '{}' already exists".format(src, dst)
     204          )
     205  
     206      if not isdir(dirname(dst)):
     207          raise DistutilsFileError(
     208              "can't move '{}': destination '{}' not a valid path".format(src, dst)
     209          )
     210  
     211      copy_it = False
     212      try:
     213          os.rename(src, dst)
     214      except OSError as e:
     215          (num, msg) = e.args
     216          if num == errno.EXDEV:
     217              copy_it = True
     218          else:
     219              raise DistutilsFileError(
     220                  "couldn't move '{}' to '{}': {}".format(src, dst, msg)
     221              )
     222  
     223      if copy_it:
     224          copy_file(src, dst, verbose=verbose)
     225          try:
     226              os.unlink(src)
     227          except OSError as e:
     228              (num, msg) = e.args
     229              try:
     230                  os.unlink(dst)
     231              except OSError:
     232                  pass
     233              raise DistutilsFileError(
     234                  "couldn't move '%s' to '%s' by copy/delete: "
     235                  "delete '%s' failed: %s" % (src, dst, src, msg)
     236              )
     237      return dst
     238  
     239  
     240  def write_file(filename, contents):
     241      """Create a file with the specified name and write 'contents' (a
     242      sequence of strings without line terminators) to it.
     243      """
     244      f = open(filename, "w")
     245      try:
     246          for line in contents:
     247              f.write(line + "\n")
     248      finally:
     249          f.close()