File indexing completed on 2025-08-03 08:09:26
0001
0002 import argparse
0003 import os
0004 from glob import glob
0005 import re
0006 from fnmatch import fnmatch
0007 import sys
0008
0009
0010 def line_fmt(line):
0011 return "{: >4d} ".format(line)
0012
0013
0014 def code_print(code, start, maxlines=15):
0015 lines = code.split("\n")
0016 nlines = len(lines)
0017
0018 lines = [line_fmt(i + start) + l for i, l in enumerate(lines)]
0019
0020 nlup = int(maxlines / 2)
0021 nllo = maxlines - nlup - 1
0022
0023 if nlines > maxlines:
0024 lines = lines[:nlup] + [" " * 5 + "// ..."] + lines[-nllo:]
0025
0026 return "\n".join(lines)
0027
0028
0029 def check_include_guards(file):
0030 with open(file) as f:
0031 text = f.read()
0032
0033 match_local = list(
0034 re.finditer(
0035 r"(#ifndef [A-Za-z0-9_]*\n#define [A-Za-z0-9_]*.*)\n((:?.|\n)+?)#endif",
0036 text,
0037 )
0038 )
0039 match_global = re.search(
0040 r"#ifndef (.*)\n#define \1.*\n[\s\S]+#endif[A-Za-z0-9\-_/* ]*$", text
0041 )
0042
0043 valid_global = True
0044 valid_local = True
0045 errbuf = ""
0046
0047 if match_global is not None and len(match_local) <= 1:
0048 valid_global = False
0049
0050 errbuf += "This looks like a file-spanning include guard\n"
0051 errbuf += "This is discouraged as per [ACTS-450]"
0052 errbuf += "(https://its.cern.ch/jira/browse/ACTS-450)" + "\n" * 2
0053
0054 start = text[: match_global.start()].count("\n") + 1
0055 errbuf += code_print(match_global.group(0), start)
0056 errbuf += "\n" * 2
0057
0058 if valid_global or len(match_local) > 1:
0059 for m in match_local:
0060 lineno = text[: m.start()].count("\n") + 1
0061
0062 valid_local = False
0063 errbuf += "This looks like a local #ifndef / include-guard\n"
0064 errbuf += "This is discouraged as per [ACTS-450]"
0065 errbuf += "(https://its.cern.ch/jira/browse/ACTS-450)" + "\n" * 2
0066 errbuf += code_print(m.group(0), lineno)
0067 errbuf += "\n" * 2
0068
0069 return valid_local, valid_global, errbuf
0070
0071
0072 def main():
0073 p = argparse.ArgumentParser()
0074
0075 input_help = """
0076 Input files: either file path, dir path (will glob for headers) or custom glob pattern
0077 """
0078 p.add_argument("input", help=input_help.strip())
0079 p.add_argument(
0080 "--fail-local", "-l", action="store_true", help="Fail on local include guards"
0081 )
0082 p.add_argument(
0083 "--fail-global", "-g", action="store_true", help="Fail on global include guards"
0084 )
0085 p.add_argument("--quiet-local", "-ql", action="store_true")
0086 p.add_argument("--quiet-global", "-qg", action="store_true")
0087 p.add_argument("--exclude", "-e", action="append", default=[])
0088
0089 args = p.parse_args()
0090
0091 headers = []
0092
0093 if os.path.isfile(args.input):
0094 headers = [args.input]
0095 elif os.path.isdir(args.input):
0096 patterns = ["**/*.hpp", "**/*.h"]
0097 headers = sum(
0098 [glob(os.path.join(args.input, p), recursive=True) for p in patterns], []
0099 )
0100 else:
0101 headers = glob(args.input, recursive=True)
0102
0103 valid = True
0104 nlocal = 0
0105 nglobal = 0
0106
0107 for h in headers:
0108 if any([fnmatch(h, e) for e in args.exclude]):
0109 continue
0110 valid_local, valid_global, errbuf = check_include_guards(h)
0111
0112 if not valid_local:
0113 nlocal += 1
0114 if args.fail_local:
0115 valid = False
0116 if not valid_global:
0117 nglobal += 1
0118 if args.fail_global:
0119 valid = False
0120
0121 if not valid_local or not valid_global:
0122 head = "Issue(s) in file {}:\n".format(h)
0123 print("-" * len(head))
0124 print(head)
0125 print(errbuf)
0126 print("\n")
0127
0128 print("=" * 40)
0129 print("Checked {} files".format(len(headers)))
0130 print("Issues found in {} files".format(nlocal + nglobal))
0131 print("{} files have local include guards".format(nlocal))
0132 print("{} files have global include guards".format(nglobal))
0133
0134 if valid:
0135 sys.exit(0)
0136 else:
0137 sys.exit(1)
0138
0139
0140 if "__main__" == __name__:
0141 main()