Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
allendowney
GitHub Repository: allendowney/cpython
Path: blob/main/Tools/build/update_file.py
12 views
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
93
94