| #!/usr/bin/env python3.8 |
| # Copyright 2016 The Fuchsia Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Script to check C and C++ file header guards. |
| |
| This script accepts a list of file or directory arguments. If a given |
| path is a file, it runs the checker on it. If the path is a directory, |
| it runs the checker on all files in that directory. |
| |
| In addition, this script checks for potential header guard |
| collisions. This is useful since we munge / to _, and so |
| lib/abc/xyz/xyz.h |
| and |
| lib/abc_xyz/xyz.h |
| both want to use LIB_ABC_XYZ_XYZ_H_ as a header guard. |
| |
| """ |
| |
| |
| import argparse |
| import collections |
| import fileinput |
| import os.path |
| import re |
| import string |
| import sys |
| |
| FUCHSIA_ROOT = os.path.dirname( # $root |
| os.path.dirname( # scripts |
| os.path.dirname( # style |
| os.path.realpath( |
| os.path.abspath(__file__))))) |
| |
| SYSROOT_PREFIXES = [ |
| 'ZIRCON_SYSTEM_PUBLIC', |
| 'ZIRCON_THIRD_PARTY_ULIB_MUSL_INCLUDE', |
| ] |
| sysroot_prefix = re.compile('^(' + '|'.join(SYSROOT_PREFIXES) + ')_') |
| |
| PUBLIC_PREFIXES = [ |
| 'ZIRCON_SYSTEM_ULIB_.*_INCLUDE', |
| 'GARNET_PUBLIC', |
| 'PERIDOT_PUBLIC', |
| 'TOPAZ_PUBLIC', |
| 'SDK' |
| ] |
| public_prefix = re.compile('^(' + '|'.join(PUBLIC_PREFIXES) + ')_') |
| |
| all_header_guards = collections.defaultdict(list) |
| |
| pragma_once = re.compile('^#pragma once$') |
| disallowed_header_characters = re.compile('[^a-zA-Z0-9_]') |
| |
| def adjust_for_location(header_guard): |
| """Remove internal location prefix from public headers if applicable.""" |
| # Remove public prefixes |
| header_guard = public_prefix.sub('', header_guard, 1) |
| # Replace sysroot prefixes |
| header_guard = sysroot_prefix.sub('SYSROOT_', header_guard, 1) |
| |
| return header_guard |
| |
| |
| def header_guard_from_path(path): |
| """Compute the header guard from the path""" |
| assert(path.startswith(FUCHSIA_ROOT)) |
| relative_path = path[len(FUCHSIA_ROOT):].strip('/') |
| upper_path = relative_path.upper() |
| header_guard = re.sub(disallowed_header_characters, '_', upper_path) + '_' |
| header_guard = adjust_for_location(header_guard) |
| return header_guard |
| |
| |
| def check_file(path, fix_guards=False): |
| """Check whether the file has a correct header guard. |
| |
| A header guard can either be a #pragma once, or else a matching set of |
| #ifndef PATH_TO_FILE_ |
| #define PATH_TO_FILE_ |
| ... |
| #endif // PATH_TO_FILE_ |
| preprocessor directives, where both '.' and '/' in the path are |
| mapped to '_', and a trailing '_' is appended. |
| |
| In either the #pragma once case or the header guard case, it is |
| assumed that there is no trailing or leading whitespace. |
| |
| """ |
| |
| # Only check .h files |
| if path[-2:] != '.h': |
| return True |
| |
| header_guard = header_guard_from_path(path) |
| all_header_guards[header_guard].append(path) |
| |
| ifndef = re.compile('^#ifndef %s$' % header_guard) |
| define = re.compile('^#define %s$' % header_guard) |
| endif = re.compile('^#endif +// *%s$' % header_guard) |
| |
| found_pragma_once = False |
| found_ifndef = False |
| found_define = False |
| found_endif = False |
| |
| with open(path, 'r') as f: |
| for line in f.readlines(): |
| match = pragma_once.match(line) |
| if match: |
| if found_pragma_once: |
| print('%s contains multiple #pragma once' % path) |
| return False |
| found_pragma_once = True |
| |
| match = ifndef.match(line) |
| if match: |
| if found_ifndef: |
| print('%s contains multiple ifndef header guards' % path) |
| return False |
| found_ifndef = True |
| |
| match = define.match(line) |
| if match: |
| if found_define: |
| print('%s contains multiple define header guards' % path) |
| return False |
| found_define = True |
| |
| match = endif.match(line) |
| if match: |
| if found_endif: |
| print('%s contains multiple endif header guards' % path) |
| return False |
| found_endif = True |
| |
| if found_pragma_once: |
| if found_ifndef or found_define or found_endif: |
| print('%s contains both #pragma once and header guards' % path) |
| return False |
| if not fix_guards: |
| return True |
| |
| if found_ifndef and found_define and found_endif: |
| return True |
| |
| if not found_ifndef: |
| print('%s did not contain ifndef part of its header guard' % path) |
| elif not found_define: |
| print('%s did not contain define part of its header guard' % path) |
| elif not found_endif: |
| print('%s did not contain endif part of its header guard' % path) |
| elif fix_guards: |
| if found_pragma_once: |
| print('%s contained #pragma once instead of a header guard' % path) |
| else: |
| print('%s did not contain a header guard or the header guard did ' |
| 'not match the file path' % path) |
| else: |
| print('%s contained neither a proper header guard nor #pragma once' % |
| path) |
| |
| header_guards_fixed = False |
| if fix_guards: |
| header_guards_fixed = fix_header_guard(path, header_guard) |
| |
| if not header_guards_fixed: |
| print('Allowable header guard values are %s' % list(all_header_guards.keys())) |
| |
| return False |
| |
| |
| def fix_header_guard(path, header_guard): |
| """Attempt to fix the header guard in the given file.""" |
| ifndef = re.compile('^#ifndef [^\s]+_H_$') |
| define = re.compile('^#define [^\s]+_H_$') |
| endif = re.compile('^#endif +// *[^\s]+_H_$') |
| fixed_ifndef = False |
| fixed_define = False |
| fixed_endif = False |
| fixed_pragma_once = False |
| |
| for line in fileinput.input(path, inplace=1): |
| (new_line, changes) = re.subn(ifndef, |
| '#ifndef %s' % header_guard, |
| line) |
| if changes: |
| fixed_ifndef = True |
| sys.stdout.write(new_line) |
| continue |
| (new_line, changes) = re.subn(define, |
| '#define %s' % header_guard, |
| line) |
| if changes: |
| fixed_define = True |
| sys.stdout.write(new_line) |
| continue |
| (new_line, changes) = re.subn(endif, |
| '#endif // %s' % header_guard, |
| line) |
| if changes: |
| fixed_endif = True |
| sys.stdout.write(new_line) |
| continue |
| if pragma_once.match(line): |
| fixed_pragma_once = True |
| sys.stdout.write('#ifndef %s\n' % header_guard) |
| sys.stdout.write('#define %s\n' % header_guard) |
| continue |
| sys.stdout.write(line) |
| |
| if fixed_pragma_once: |
| with open(path, 'a') as file: |
| file.write('\n') |
| file.write('#endif // %s\n' % header_guard) |
| |
| if (fixed_ifndef and fixed_define and fixed_endif) or fixed_pragma_once: |
| print('Fixed!') |
| return True |
| |
| print('Not fixed...') |
| return False |
| |
| |
| def check_dir(p, fix_guards=False): |
| """Walk recursively over a directory checking .h files""" |
| |
| def prune(d): |
| if d[0] == '.' or d == 'third_party': |
| return True |
| return False |
| |
| for root, dirs, paths in os.walk(p): |
| # Prune dot directories like .git |
| [dirs.remove(d) for d in list(dirs) if prune(d)] |
| for path in paths: |
| check_file(os.path.join(root, path), fix_guards=fix_guards) |
| |
| |
| def check_collisions(): |
| for header_guard, paths in all_header_guards.items(): |
| if len(paths) == 1: |
| continue |
| print('Multiple files could use %s as a header guard:' % header_guard) |
| for path in paths: |
| print(' %s' % path) |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser() |
| parser.add_argument('--fix', |
| help='Correct wrong header guards', |
| action='store_true') |
| (arg_results, other_args) = parser.parse_known_args() |
| fix_guards = arg_results.fix |
| for p in other_args: |
| p = os.path.realpath(os.path.abspath(p)) |
| if os.path.isdir(p): |
| check_dir(p, fix_guards=fix_guards) |
| else: |
| check_file(p, fix_guards=fix_guards) |
| check_collisions() |
| |
| |
| if __name__ == "__main__": |
| sys.exit(main()) |