(root)/
Python-3.11.7/
Tools/
scripts/
update_file.py
       1  """
       2  A script that replaces an old file with a new one, only if the contents
       3  actually changed.  If not, the new file is simply deleted.
       4  
       5  This avoids wholesale rebuilds when a code (re)generation phase does not
       6  actually change the in-tree generated code.
       7  """
       8  
       9  import contextlib
      10  import os
      11  import os.path
      12  import sys
      13  
      14  
      15  @contextlib.contextmanager
      16  def updating_file_with_tmpfile(filename, tmpfile=None):
      17      """A context manager for updating a file via a temp file.
      18  
      19      The context manager provides two open files: the source file open
      20      for reading, and the temp file, open for writing.
      21  
      22      Upon exiting: both files are closed, and the source file is replaced
      23      with the temp file.
      24      """
      25      # XXX Optionally use tempfile.TemporaryFile?
      26      if not tmpfile:
      27          tmpfile = filename + '.tmp'
      28      elif os.path.isdir(tmpfile):
      29          tmpfile = os.path.join(tmpfile, filename + '.tmp')
      30  
      31      with open(filename, 'rb') as infile:
      32          line = infile.readline()
      33  
      34      if line.endswith(b'\r\n'):
      35          newline = "\r\n"
      36      elif line.endswith(b'\r'):
      37          newline = "\r"
      38      elif line.endswith(b'\n'):
      39          newline = "\n"
      40      else:
      41          raise ValueError(f"unknown end of line: {filename}: {line!a}")
      42  
      43      with open(tmpfile, 'w', newline=newline) as outfile:
      44          with open(filename) as infile:
      45              yield infile, outfile
      46      update_file_with_tmpfile(filename, tmpfile)
      47  
      48  
      49  def update_file_with_tmpfile(filename, tmpfile, *, create=False):
      50      try:
      51          targetfile = open(filename, 'rb')
      52      except FileNotFoundError:
      53          if not create:
      54              raise  # re-raise
      55          outcome = 'created'
      56          os.replace(tmpfile, filename)
      57      else:
      58          with targetfile:
      59              old_contents = targetfile.read()
      60          with open(tmpfile, 'rb') as f:
      61              new_contents = f.read()
      62          # Now compare!
      63          if old_contents != new_contents:
      64              outcome = 'updated'
      65              os.replace(tmpfile, filename)
      66          else:
      67              outcome = 'same'
      68              os.unlink(tmpfile)
      69      return outcome
      70  
      71  
      72  if __name__ == '__main__':
      73      import argparse
      74      parser = argparse.ArgumentParser()
      75      parser.add_argument('--create', action='store_true')
      76      parser.add_argument('--exitcode', action='store_true')
      77      parser.add_argument('filename', help='path to be updated')
      78      parser.add_argument('tmpfile', help='path with new contents')
      79      args = parser.parse_args()
      80      kwargs = vars(args)
      81      setexitcode = kwargs.pop('exitcode')
      82  
      83      outcome = update_file_with_tmpfile(**kwargs)
      84      if setexitcode:
      85          if outcome == 'same':
      86              sys.exit(0)
      87          elif outcome == 'updated':
      88              sys.exit(1)
      89          elif outcome == 'created':
      90              sys.exit(2)
      91          else:
      92              raise NotImplementedError