blob: 07a1f912e391d17d2e2a91bd1432de909829b050 [file] [log] [blame]
#!/usr/bin/env python
# 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 os.path
import paths
import re
import sys
all_header_guards = collections.defaultdict(list)
pragma_once = re.compile('^#pragma once$')
def check_file(path):
"""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
assert(path.startswith(paths.FUCHSIA_ROOT))
relative_path = path[len(paths.FUCHSIA_ROOT):].strip('/')
upper_path = relative_path.upper()
header_guard = upper_path.replace('.', '_').replace('/', '_') + '_'
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
return True
if found_ifndef and found_define and found_endif:
return True
if found_ifndef or found_define or found_endif:
print('%s contained only part of a header guard' % path)
return False
print('%s contained neither a header guard nor #pragma once' % path)
return False
def check_dir(p):
""" Walk recursively over a directory checking .h files"""
def prune(d):
if d[0] == '.':
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))
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():
for p in sys.argv[1:]:
p = os.path.abspath(p)
if os.path.isdir(p):
check_dir(p)
else:
check_file(p)
check_collisions()
if __name__ == "__main__":
sys.exit(main())