Skip to content

Commit 37a0f24

Browse files
Script to watch the evolution of generated files
Archive the evolution of generated files over a range of revisions. Signed-off-by: Gilles Peskine <[email protected]>
1 parent 2c5098a commit 37a0f24

File tree

1 file changed

+136
-0
lines changed

1 file changed

+136
-0
lines changed

tools/bin/mbedtls-trace-files.py

+136
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
#!/usr/bin/env python3
2+
3+
"""Archive the contents of the specified files for the specified Git revisions.
4+
5+
Run this script from a clean Git worktree.
6+
This script runs `make FILE` to generate the desired files.
7+
The outputs are stored in a subdirectory named for each commit hash.
8+
"""
9+
10+
# Copyright The Mbed TLS Contributors
11+
# SPDX-License-Identifier: Apache-2.0
12+
#
13+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
14+
# not use this file except in compliance with the License.
15+
# You may obtain a copy of the License at
16+
#
17+
# http://www.apache.org/licenses/LICENSE-2.0
18+
#
19+
# Unless required by applicable law or agreed to in writing, software
20+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
21+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22+
# See the License for the specific language governing permissions and
23+
# limitations under the License.
24+
25+
import argparse
26+
import os
27+
import shutil
28+
import subprocess
29+
from typing import List, Optional
30+
31+
32+
class UncommittedChangesException(Exception):
33+
"You have uncommitted changes. Please stash or commit them."
34+
pass
35+
36+
37+
class Archiver:
38+
"""Archive the contents of some files for some Git revisions."""
39+
40+
def __init__(
41+
self,
42+
build_dir: Optional[str] = None,
43+
output_dir: Optional[str] = None,
44+
run_after: Optional[str] = None,
45+
run_before: Optional[str] = None,
46+
) -> None:
47+
"""Configure an archiver for generated files.
48+
49+
`build_dir`: directory where ``make`` will be run.
50+
`output_dir`: parent directory for the per-revision directories.
51+
`run_before`: shell command to run before ``make``.
52+
`run_after`: shell command to run after ``make``.
53+
"""
54+
self.build_dir = build_dir if build_dir is not None else os.curdir
55+
self.output_dir = output_dir if output_dir is not None else os.curdir
56+
self.run_before = run_before
57+
self.run_after = run_after
58+
self.prepare()
59+
60+
def prepare(self) -> None:
61+
"""Prepare the working directory."""
62+
try:
63+
subprocess.check_call(['git', 'diff', '--quiet'])
64+
except subprocess.CalledProcessError:
65+
raise UncommittedChangesException()
66+
self.initial_revision = subprocess.check_output(
67+
['git', 'rev-parse', '--abbrev-ref', 'HEAD']
68+
).decode('ascii').strip()
69+
70+
def done(self) -> None:
71+
"""Restore the working directory."""
72+
subprocess.check_call(['git', 'checkout', self.initial_revision])
73+
74+
def archive_revision(self, revision: str, files: List[str]) -> None:
75+
"""Archive generated files for a given revision.
76+
77+
`revision`: Git revision to check out.
78+
`files`: list of files to archive.
79+
"""
80+
subprocess.check_call(['git', 'checkout', revision])
81+
if self.run_before:
82+
subprocess.check_call(self.run_before, shell=True)
83+
subprocess.check_call(['make'] + files,
84+
cwd=self.build_dir)
85+
for filename in files:
86+
target_dir = os.path.join(self.output_dir,
87+
revision,
88+
os.path.dirname(filename))
89+
os.makedirs(target_dir, exist_ok=True)
90+
shutil.copy2(filename, target_dir)
91+
if self.run_after:
92+
subprocess.check_call(self.run_after, shell=True)
93+
94+
def archive_revisions(self, revision_range: str, files: List[str]) -> None:
95+
"""Archive generated files for a given revision range.
96+
97+
`revision`: Git revision range to check out.
98+
`files`: list of files to archive.
99+
"""
100+
self.prepare()
101+
try:
102+
revisions = subprocess.check_output(
103+
['git', 'log', '--format=%H', revision_range]
104+
).decode('ascii').split()
105+
for revision in revisions:
106+
self.archive_revision(revision, files)
107+
finally:
108+
self.done()
109+
110+
111+
def main() -> None:
112+
"""Command line entry point."""
113+
parser = argparse.ArgumentParser(description=__doc__)
114+
parser.add_argument('--build-dir', '-b', metavar='DIR',
115+
help='Run `make` in DIR')
116+
parser.add_argument('--output-dir', '-o', metavar='DIR',
117+
help='Put output directories under DIR')
118+
parser.add_argument('--run-after', '-R', metavar='CMD',
119+
help='Shell command to run after each build')
120+
parser.add_argument('--run-before', '-r', metavar='CMD',
121+
help='Shell command to run before each build')
122+
parser.add_argument('revisions', metavar='REVISIONS',
123+
help='Comma-separated of Git revisions (see gitrevisions(7))')
124+
parser.add_argument('files', metavar='FILE', nargs='*',
125+
help='File to archive')
126+
options = parser.parse_args()
127+
revision_ranges = options.revisions.split(',')
128+
del options.revisions
129+
files = options.files
130+
del options.files
131+
archiver = Archiver(**vars(options))
132+
for revision_range in revision_ranges:
133+
archiver.archive_revisions(revision_range, files)
134+
135+
if __name__ == '__main__':
136+
main()

0 commit comments

Comments
 (0)