blob: 0708b286f2eede3d619fe161ea6768baef98e1f6 [file] [log] [blame]
#!/usr/bin/env python2.7
# 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('^(' + string.join(SYSROOT_PREFIXES, '|') + ')_')
PUBLIC_PREFIXES = [
'ZIRCON_SYSTEM_ULIB_.*_INCLUDE',
'GARNET_PUBLIC',
'PERIDOT_PUBLIC',
'TOPAZ_PUBLIC',
'SDK'
]
public_prefix = re.compile('^(' + string.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' % 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.iteritems():
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())