File indexing completed on 2025-01-18 09:10:43
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", nargs="+", 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 len(args.input) == 1:
0094 if os.path.isdir(args.input[0]):
0095 patterns = ["**/*.hpp", "**/*.h"]
0096 headers = sum(
0097 [
0098 glob(os.path.join(args.input[0], p), recursive=True)
0099 for p in patterns
0100 ],
0101 [],
0102 )
0103 else:
0104 headers = glob(args.input[0], recursive=True)
0105 elif all([os.path.isfile(i) for i in args.input]):
0106 headers = args.input
0107
0108 valid = True
0109 nlocal = 0
0110 nglobal = 0
0111
0112 for h in headers:
0113 if any([fnmatch(h, e) for e in args.exclude]):
0114 continue
0115 valid_local, valid_global, errbuf = check_include_guards(h)
0116
0117 if not valid_local:
0118 nlocal += 1
0119 if args.fail_local:
0120 valid = False
0121 if not valid_global:
0122 nglobal += 1
0123 if args.fail_global:
0124 valid = False
0125
0126 if not valid_local or not valid_global:
0127 head = "Issue(s) in file {}:\n".format(h)
0128 print("-" * len(head))
0129 print(head)
0130 print(errbuf)
0131 print("\n")
0132
0133 print("=" * 40)
0134 print("Checked {} files".format(len(headers)))
0135 print("Issues found in {} files".format(nlocal + nglobal))
0136 print("{} files have local include guards".format(nlocal))
0137 print("{} files have global include guards".format(nglobal))
0138
0139 if valid:
0140 sys.exit(0)
0141 else:
0142 sys.exit(1)
0143
0144
0145 if "__main__" == __name__:
0146 main()