cpython/Tools/build/update_file.py
Victor Stinner 1863302d61
gh-97669: Create Tools/build/ directory (#97963)
Create Tools/build/ directory. Move the following scripts from
Tools/scripts/ to Tools/build/:

* check_extension_modules.py
* deepfreeze.py
* freeze_modules.py
* generate_global_objects.py
* generate_levenshtein_examples.py
* generate_opcode_h.py
* generate_re_casefix.py
* generate_sre_constants.py
* generate_stdlib_module_names.py
* generate_token.py
* parse_html5_entities.py
* smelly.py
* stable_abi.py
* umarshal.py
* update_file.py
* verify_ensurepip_wheels.py

Update references to these scripts.
2022-10-17 12:01:00 +02:00

93 lines
2.7 KiB
Python

"""
A script that replaces an old file with a new one, only if the contents
actually changed. If not, the new file is simply deleted.
This avoids wholesale rebuilds when a code (re)generation phase does not
actually change the in-tree generated code.
"""
import contextlib
import os
import os.path
import sys
@contextlib.contextmanager
def updating_file_with_tmpfile(filename, tmpfile=None):
"""A context manager for updating a file via a temp file.
The context manager provides two open files: the source file open
for reading, and the temp file, open for writing.
Upon exiting: both files are closed, and the source file is replaced
with the temp file.
"""
# XXX Optionally use tempfile.TemporaryFile?
if not tmpfile:
tmpfile = filename + '.tmp'
elif os.path.isdir(tmpfile):
tmpfile = os.path.join(tmpfile, filename + '.tmp')
with open(filename, 'rb') as infile:
line = infile.readline()
if line.endswith(b'\r\n'):
newline = "\r\n"
elif line.endswith(b'\r'):
newline = "\r"
elif line.endswith(b'\n'):
newline = "\n"
else:
raise ValueError(f"unknown end of line: {filename}: {line!a}")
with open(tmpfile, 'w', newline=newline) as outfile:
with open(filename) as infile:
yield infile, outfile
update_file_with_tmpfile(filename, tmpfile)
def update_file_with_tmpfile(filename, tmpfile, *, create=False):
try:
targetfile = open(filename, 'rb')
except FileNotFoundError:
if not create:
raise # re-raise
outcome = 'created'
os.replace(tmpfile, filename)
else:
with targetfile:
old_contents = targetfile.read()
with open(tmpfile, 'rb') as f:
new_contents = f.read()
# Now compare!
if old_contents != new_contents:
outcome = 'updated'
os.replace(tmpfile, filename)
else:
outcome = 'same'
os.unlink(tmpfile)
return outcome
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--create', action='store_true')
parser.add_argument('--exitcode', action='store_true')
parser.add_argument('filename', help='path to be updated')
parser.add_argument('tmpfile', help='path with new contents')
args = parser.parse_args()
kwargs = vars(args)
setexitcode = kwargs.pop('exitcode')
outcome = update_file_with_tmpfile(**kwargs)
if setexitcode:
if outcome == 'same':
sys.exit(0)
elif outcome == 'updated':
sys.exit(1)
elif outcome == 'created':
sys.exit(2)
else:
raise NotImplementedError