ECCE @ EIC Software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
check_include_guards.py
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file check_include_guards.py
1 #!/usr/bin/env python3
2 from __future__ import print_function
3 
4 import argparse
5 import os
6 from glob import glob
7 from concurrent.futures import ProcessPoolExecutor
8 import re
9 from fnmatch import fnmatch
10 import sys
11 
12 
13 def line_fmt(line):
14  return "{: >4d} ".format(line)
15 
16 def code_print(code, start, maxlines=15):
17  lines = code.split("\n")
18  nlines = len(lines)
19 
20  lines = [line_fmt(i+start) + l for i, l in enumerate(lines)]
21 
22  nlup = int(maxlines/2)
23  nllo = maxlines - nlup - 1
24 
25  if nlines > maxlines:
26  lines = lines[:nlup] + [" "*5 + "// ..."] + lines[-nllo:]
27 
28  return "\n".join(lines)
29 
31  with open(file) as f:
32  text = f.read()
33 
34  match_local = list(re.finditer(r"(#ifndef [A-Za-z0-9_]*\n#define [A-Za-z0-9_]*.*)\n((:?.|\n)+?)#endif", text))
35  match_global = re.search(r"#ifndef (.*)\n#define \1.*\n[\s\S]+#endif[A-Za-z0-9\-_/* ]*$", text)
36 
37  valid_global = True
38  valid_local = True
39  errbuf = ""
40 
41  if match_global is not None and len(match_local) <= 1:
42  valid_global = False
43 
44  errbuf += "This looks like a file-spanning include guard\n"
45  errbuf += "This is discouraged as per [ACTS-450]"
46  errbuf += "(https://its.cern.ch/jira/browse/ACTS-450)" + "\n"*2
47 
48  start = text[:match_global.start()].count("\n")+1
49  errbuf += code_print(match_global.group(0), start)
50  errbuf += "\n"*2
51 
52  if valid_global or len(match_local) > 1:
53  for m in match_local:
54 
55  lineno = text[:m.start()].count("\n") + 1
56 
57  valid_local = False
58  errbuf += "This looks like a local #ifndef / include-guard\n"
59  errbuf += "This is discouraged as per [ACTS-450]"
60  errbuf += "(https://its.cern.ch/jira/browse/ACTS-450)" + "\n"*2
61  errbuf += code_print(m.group(0), lineno)
62  errbuf += "\n"*2
63 
64 
65 
66 
67  return valid_local, valid_global, errbuf
68 
69 def main():
70  p = argparse.ArgumentParser()
71 
72  input_help = """
73 Input files: either file path, dir path (will glob for headers) or custom glob pattern
74  """
75  p.add_argument("input", help=input_help.strip())
76  p.add_argument("--fail-local", "-l", action="store_true", help="Fail on local include guards")
77  p.add_argument("--fail-global", "-g", action="store_true", help="Fail on global include guards")
78  p.add_argument("--quiet-local", "-ql", action="store_true")
79  p.add_argument("--quiet-global", "-qg", action="store_true")
80  p.add_argument("--exclude", "-e", action="append", default=[])
81 
82  args = p.parse_args()
83 
84  headers = []
85 
86  if os.path.isfile(args.input):
87  headers = [args.input]
88  elif os.path.isdir(args.input):
89  patterns = ["**/*.hpp", "**/*.h"]
90  headers = sum([glob(os.path.join(args.input, p), recursive=True) for p in patterns], [])
91  else:
92  headers = glob(args.input, recursive=True)
93 
94  valid = True
95  nlocal = 0
96  nglobal = 0
97 
98  for h in headers:
99  if any([fnmatch(h, e) for e in args.exclude]):
100  continue
101  valid_local, valid_global, errbuf = check_include_guards(h)
102 
103  if not valid_local:
104  nlocal += 1
105  if args.fail_local:
106  valid = False
107  if not valid_global:
108  nglobal += 1
109  if args.fail_global:
110  valid = False
111 
112  if not valid_local or not valid_global:
113  head = "Issue(s) in file {}:\n".format(h)
114  print("-"*len(head))
115  print(head)
116  print(errbuf)
117  print("\n")
118 
119 
120  print("="*40)
121  print("Checked {} files".format(len(headers)))
122  print("Issues found in {} files".format(nlocal+nglobal))
123  print("{} files have local include guards".format(nlocal))
124  print("{} files have global include guards".format(nglobal))
125 
126  if valid:
127  sys.exit(0)
128  else:
129  sys.exit(1)
130 
131 
132 if "__main__" == __name__:
133  main()
134