Skip to content

Commit 1800e86

Browse files
Scripts 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 1800e86

File tree

2 files changed

+218
-0
lines changed

2 files changed

+218
-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()

tools/bin/mbedtls-trace-files.sh

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#!/bin/sh
2+
3+
set -eu
4+
5+
help () {
6+
cat <<EOF
7+
Usage: $0 [OPTION]... REVISIONS [FILE]...
8+
Log the values of the specified files for the specified Git revisions.
9+
10+
Run this script from a clean Git worktree.
11+
This script runs \`make FILE\` to generate the desired files.
12+
The outputs are stored in a subdirectory named for each commit hash.
13+
REVISIONS is a comma-separated of Git revisions (see gitrevisions(7)).
14+
15+
Options:
16+
-U Run even if the worktree is not clean
17+
-b DIR Directory where to run \`make\` (default: .)
18+
-o DIR Directory where the outputs will be stored (default: .)
19+
-r CMD Shell command to run before each build
20+
EOF
21+
}
22+
23+
if [ $# -eq 0 ] || [ "$1" = "--help" ]; then
24+
help
25+
exit
26+
fi
27+
28+
29+
force_unclean=
30+
build_dir=.
31+
output_root=.
32+
pre_command=
33+
34+
while getopts : OPTLET; do
35+
case $OPTLET in
36+
U) force_unclean=1;;
37+
b) build_dir=$OPTARG;;
38+
o) output_root=$OPTARG;;
39+
r) pre_command=$OPTARG;;
40+
\?) exit 120;;
41+
esac
42+
done
43+
shift $((OPTIND - 1))
44+
all_revisions=$1
45+
46+
## trace_revision REVISION [FILE]...
47+
trace_revision () {
48+
git checkout "$1"
49+
shift
50+
(cd -- "$output_root" && exec make -C "$@")
51+
for file
52+
do
53+
mkdir -p -- "$output_root/$file"
54+
cp -p -- "$build_dir/$file" "$output_root/$file"
55+
done
56+
}
57+
58+
## trace_revisions REVISION_RANGE [FILE]...
59+
trace_revisions () {
60+
revisions=$(git log --format=%H "$1")
61+
shift
62+
for revision in $revisions; do
63+
trace_revision "$revision" "$@"
64+
done
65+
}
66+
67+
if [ -z "$force_unclean" ]; then
68+
if ! git diff -q; then
69+
echo >&2 "$0: You have uncommitted changes. Please stash or commit them."
70+
exit 3
71+
fi
72+
fi
73+
74+
initial_revision=$(git rev-parse --abbrev-ref HEAD)
75+
76+
while case $all_revisions in *,*) true;; *) false;; esac do
77+
trace_revisions "${all_revisions%%,*}" "$@"
78+
all_revisions=${all_revisions#*,}
79+
done
80+
trace_revisions "$all_revisions" "$@"
81+
82+
git checkout $initial_revision

0 commit comments

Comments
 (0)