blob: 2bddda88e9058d6f4176da0a98f229abfd608d75 [file] [log] [blame]
Miguel Young de la Sotafc174e82019-12-17 13:12:15 -06001#!/usr/bin/env python3
2# Copyright lowRISC contributors.
3# Licensed under the Apache License, Version 2.0, see LICENSE for details.
4# SPDX-License-Identifier: Apache-2.0
5
6# fix_include_guard.py script takes any number of C or C++ header files as
7# input, and formats their include guards to conform to the style mandated
8# by Google C++. See
9# https://google.github.io/styleguide/cppguide.html#The__define_Guard
10#
11# clang-format does not, unfortunately, have support for automatically
12# generating the correct include guards; hence the existence of this script.
13#
14# This script will write the names of all affected files to stdout; if no such
15# output is present, all files have the correct guards. This script is
Rupert Swarbrick55a311a2021-03-17 15:34:51 +000016# idempotent. If it cannot fix a file, the script will write the file name to
17# stderr and will exit at the end with a nonzero status code.
Miguel Young de la Sotafc174e82019-12-17 13:12:15 -060018
19import argparse
Miguel Young de la Sotafc174e82019-12-17 13:12:15 -060020import sys
21import re
22
23from pathlib import Path
24
25PROJECT_NAME = "OPENTITAN"
26
27# This file is $REPO_TOP/util/fix_include_guard.py, so it takes two parent()
28# calls to get back to the top.
29REPO_TOP = Path(__file__).resolve().parent.parent
30
31
32def main():
33 parser = argparse.ArgumentParser()
34 parser.add_argument(
35 '--dry-run',
36 action='store_true',
37 help='report writes which would have happened')
38 parser.add_argument(
39 'headers',
40 type=str,
41 nargs='+',
42 help='headers to fix guards for')
43 args = parser.parse_args()
44
Rupert Swarbrick55a311a2021-03-17 15:34:51 +000045 total_unfixable = 0
46 total_fixable = 0
47
Miguel Young de la Sotafc174e82019-12-17 13:12:15 -060048 for header_path in args.headers:
49 header = Path(header_path).resolve().relative_to(REPO_TOP)
50 if header.suffix != '.h' or 'vendor' in header.parts:
51 continue
52
53 uppercase_dir = re.sub(r'[^\w]', '_', str(header.parent)).upper()
54 uppercase_stem = re.sub(r'[^\w]', '_', str(header.stem)).upper()
55 guard = '%s_%s_%s_H_' % (PROJECT_NAME, uppercase_dir, uppercase_stem)
56
57 header_text = header.read_text()
58 header_original = header_text
59
Rupert Swarbrick55a311a2021-03-17 15:34:51 +000060 # Find the first non-comment, non-whitespace line in the file
61 first_line_match = re.search(r'^\s*([^/].*)', header_text, re.MULTILINE)
62 if first_line_match is None:
63 total_unfixable += 1
64 print('No non-comment line found in `{}\'.'.format(header),
65 file=sys.stderr)
66 continue
67
68 first_line = first_line_match.group(1).rstrip()
69
Miguel Young de la Sotafc174e82019-12-17 13:12:15 -060070 # Find the old guard name, which will be the first #ifndef in the file.
Rupert Swarbrick55a311a2021-03-17 15:34:51 +000071 # This should be an #ifndef line. If it isn't, we can't fix things
72 ifndef_match = re.match(r'#ifndef\s+(\w+)$', first_line)
73 if ifndef_match is None:
74 total_unfixable += 1
75 print('Unsupported first non-comment line in `{}\': {!r}.'
76 .format(header, first_line),
77 file=sys.stderr)
78 continue
79
80 old_guard = ifndef_match.group(1)
Miguel Young de la Sotafc174e82019-12-17 13:12:15 -060081
82 # Fix the guards at the top, which are guaranteed to be there.
83 header_text = re.sub('#(ifndef|define) +%s' % (old_guard, ),
84 r'#\1 %s' % (guard, ), header_text)
85
86 # Fix up the endif. Since this is the last thing in the file, and it
87 # might be missing the comment, we just truncate the file, and add on
88 # the required guard end.
89 header_text = header_text[:header_text.rindex('#endif')]
90 header_text += "#endif // %s\n" % (guard, )
91
92 if header_text != header_original:
93 print('Fixing header: "%s"' % (header, ), file=sys.stdout)
Rupert Swarbrick55a311a2021-03-17 15:34:51 +000094 total_fixable += 1
Miguel Young de la Sotafc174e82019-12-17 13:12:15 -060095 if not args.dry_run:
96 header.write_text(header_text)
97
Rupert Swarbrick55a311a2021-03-17 15:34:51 +000098 if total_fixable:
99 verb = 'Would have fixed' if args.dry_run else 'Fixed'
100 print('{} {} files.'.format(verb, total_fixable), file=sys.stderr)
101 if total_unfixable:
102 print('Failed to fix {} files.'.format(total_unfixable),
103 file=sys.stderr)
104
105 # Pass if we fixed everything or there was nothing to fix.
106 unfixed = total_unfixable + (total_fixable if args.dry_run else 0)
107 return 1 if unfixed else 0
Miguel Young de la Sotafc174e82019-12-17 13:12:15 -0600108
109
110if __name__ == '__main__':
Rupert Swarbrick55a311a2021-03-17 15:34:51 +0000111 sys.exit(main())