| #!/usr/bin/env python3 | |
| # Copyright lowRISC contributors. | |
| # Licensed under the Apache License, Version 2.0, see LICENSE for details. | |
| # SPDX-License-Identifier: Apache-2.0 | |
| # fix_include_guard.py script takes any number of C or C++ header files as | |
| # input, and formats their include guards to conform to the style mandated | |
| # by Google C++. See | |
| # https://google.github.io/styleguide/cppguide.html#The__define_Guard | |
| # | |
| # clang-format does not, unfortunately, have support for automatically | |
| # generating the correct include guards; hence the existence of this script. | |
| # | |
| # This script will write the names of all affected files to stdout; if no such | |
| # output is present, all files have the correct guards. This script is | |
| # idempotent. If it cannot fix a file, the script will write the file name to | |
| # stderr and will exit at the end with a nonzero status code. | |
| import argparse | |
| import sys | |
| import re | |
| from pathlib import Path | |
| PROJECT_NAME = "OPENTITAN" | |
| # This file is $REPO_TOP/util/fix_include_guard.py, so it takes two parent() | |
| # calls to get back to the top. | |
| REPO_TOP = Path(__file__).resolve().parent.parent | |
| def main(): | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument( | |
| '--dry-run', | |
| action='store_true', | |
| help='report writes which would have happened') | |
| parser.add_argument( | |
| 'headers', | |
| type=str, | |
| nargs='+', | |
| help='headers to fix guards for') | |
| args = parser.parse_args() | |
| total_unfixable = 0 | |
| total_fixable = 0 | |
| for header_path in args.headers: | |
| header = Path(header_path).resolve().relative_to(REPO_TOP) | |
| if header.suffix != '.h' or 'vendor' in header.parts: | |
| continue | |
| uppercase_dir = re.sub(r'[^\w]', '_', str(header.parent)).upper() | |
| uppercase_stem = re.sub(r'[^\w]', '_', str(header.stem)).upper() | |
| guard = '%s_%s_%s_H_' % (PROJECT_NAME, uppercase_dir, uppercase_stem) | |
| header_text = header.read_text() | |
| header_original = header_text | |
| # Find the first non-comment, non-whitespace line in the file | |
| first_line_match = re.search(r'^\s*([^/].*)', header_text, re.MULTILINE) | |
| if first_line_match is None: | |
| total_unfixable += 1 | |
| print('No non-comment line found in `{}\'.'.format(header), | |
| file=sys.stderr) | |
| continue | |
| first_line = first_line_match.group(1).rstrip() | |
| # Find the old guard name, which will be the first #ifndef in the file. | |
| # This should be an #ifndef line. If it isn't, we can't fix things | |
| ifndef_match = re.match(r'#ifndef\s+(\w+)$', first_line) | |
| if ifndef_match is None: | |
| total_unfixable += 1 | |
| print('Unsupported first non-comment line in `{}\': {!r}.' | |
| .format(header, first_line), | |
| file=sys.stderr) | |
| continue | |
| old_guard = ifndef_match.group(1) | |
| # Fix the guards at the top, which are guaranteed to be there. | |
| header_text = re.sub('#(ifndef|define) +%s' % (old_guard, ), | |
| r'#\1 %s' % (guard, ), header_text) | |
| # Fix up the endif. Since this is the last thing in the file, and it | |
| # might be missing the comment, we just truncate the file, and add on | |
| # the required guard end. | |
| header_text = header_text[:header_text.rindex('#endif')] | |
| header_text += "#endif // %s\n" % (guard, ) | |
| if header_text != header_original: | |
| print('Fixing header: "%s"' % (header, ), file=sys.stdout) | |
| total_fixable += 1 | |
| if not args.dry_run: | |
| header.write_text(header_text) | |
| if total_fixable: | |
| verb = 'Would have fixed' if args.dry_run else 'Fixed' | |
| print('{} {} files.'.format(verb, total_fixable), file=sys.stderr) | |
| if total_unfixable: | |
| print('Failed to fix {} files.'.format(total_unfixable), | |
| file=sys.stderr) | |
| # Pass if we fixed everything or there was nothing to fix. | |
| unfixed = total_unfixable + (total_fixable if args.dry_run else 0) | |
| return 1 if unfixed else 0 | |
| if __name__ == '__main__': | |
| sys.exit(main()) |