Skip to content

Commit 1434063

Browse files
committed
Add helper script for flattening out output from clang-cpp
This change introduces a python script whose sole purpose is to parse output from clang-cpp and provide meaningful information about what is contained within the file itself. It is particularly helpful with header files. The impetus for this work was to avoid the tedious task of having to manually parse OpenSSL headers so it would be easier to determine what APIs/constants may have changed from an interface perspective between versions and what things have remained the same.
1 parent 48bf337 commit 1434063

File tree

1 file changed

+99
-0
lines changed

1 file changed

+99
-0
lines changed
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
#!/usr/bin/env python3
2+
"""Flatten output from the clang preprocessor.
3+
4+
This script takes the output from `clang -dD -E` and flattens it so it can be more
5+
easily analyzed/compared by rudimentary tools like awk, diff, and grep.
6+
"""
7+
import argparse
8+
import atexit
9+
import logging
10+
import os
11+
import re
12+
import sys
13+
import tempfile
14+
from typing import Optional
15+
16+
17+
INCLUDE_START_RE = re.compile(r'#\s+\d+\s+"(.+)"')
18+
UNCLOSED_FUNC_DECL_RE = re.compile(r",$")
19+
UNDEF_DIRECTIVE_RE = re.compile(r"^#undef\s+")
20+
21+
22+
def main(argv: Optional[list[str]] = None) -> int:
23+
"""Eponymous main(..)."""
24+
argparser = argparse.ArgumentParser()
25+
26+
output_options = argparser.add_mutually_exclusive_group(required=True)
27+
output_options.add_argument("-i", "--in-place", action="store_true")
28+
output_options.add_argument(
29+
"-o", "--output", dest="output_file", type=argparse.FileType("w")
30+
)
31+
32+
# argparser.add_argument("--keep-all-scope", action="store_true")
33+
argparser.add_argument("--keep-blank-lines", action="store_true")
34+
argparser.add_argument("--skip-undef", action="store_true")
35+
argparser.add_argument("input_file", type=argparse.FileType("r"))
36+
37+
def remove_tempfile(temp_filename: str) -> None:
38+
os.unlink(temp_filename)
39+
40+
def rename_tempfile(temp_filename: str, input_filename: str) -> None:
41+
os.rename(temp_filename, input_filename)
42+
43+
args = argparser.parse_args(args=argv)
44+
if args.in_place:
45+
output_fileobj = tempfile.NamedTemporaryFile(
46+
mode="w+t",
47+
delete=False,
48+
dir=os.path.basename(args.input_file),
49+
)
50+
atexit.register(remove_tempfile, output_fileobj.name)
51+
atexit.register(rename_tempfile, output_fileobj.name, args.temp_fileobj.name)
52+
else:
53+
output_fileobj = args.output_file
54+
55+
try:
56+
output_lines = []
57+
with args.input_file as input_fileobj:
58+
59+
keep = False
60+
original_include_path = None
61+
62+
for line in input_fileobj:
63+
64+
if line == "\n":
65+
if args.keep_blank_lines:
66+
output_lines.append(line)
67+
continue
68+
69+
if args.skip_undef and UNDEF_DIRECTIVE_RE.match(line):
70+
continue
71+
72+
matches = INCLUDE_START_RE.match(line)
73+
if matches is not None:
74+
path_match = matches.group(1)
75+
if original_include_path is None:
76+
original_include_path = path_match
77+
keep = path_match == original_include_path
78+
continue
79+
if not keep:
80+
continue
81+
82+
if UNCLOSED_FUNC_DECL_RE.match(line):
83+
line = line.rstrip("\n")
84+
85+
output_lines.append(line)
86+
87+
with output_fileobj:
88+
output_fileobj.writelines(output_lines)
89+
except Exception: # pylint: disable=broad-except
90+
logging.exception("Script failed abnormally.")
91+
atexit.unregister(rename_tempfile)
92+
return 1
93+
94+
atexit.unregister(remove_tempfile)
95+
return 0
96+
97+
98+
if __name__ == "__main__":
99+
sys.exit(main())

0 commit comments

Comments
 (0)