Analysis Software
Documentation for sPHENIX simulation software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
parse_cmake_options.py
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file parse_cmake_options.py
1 #!/usr/bin/env python3
2 """
3 This script accepts a cmake lists file as an argument extracts all
4 `option` and `set(... CACHE ...)` variables. It then writes a
5 markdown table to stdout
6 """
7 
8 import argparse
9 from pathlib import Path
10 import re
11 import textwrap
12 import sys
13 import difflib
14 
15 p = argparse.ArgumentParser(description=__doc__)
16 
17 p.add_argument("cmakefile", help="Input cmake lists file to parse")
18 p.add_argument(
19  "--prefix", default="ACTS_", help="Prefix to identify relevant variables to extract"
20 )
21 p.add_argument(
22  "--width",
23  type=int,
24  default=40,
25  help="Width of second column generated from cmake doc strings",
26 )
27 p.add_argument(
28  "--write",
29  "-w",
30  help="Write table to this file, expects delimiters CMAKE_OPTS_{BEGIN,END}",
31  type=Path,
32 )
33 p.add_argument(
34  "--verify",
35  "-v",
36  help="Only verify the target file contains the right table, don't write",
37  action="store_true",
38 )
39 
40 
41 args = p.parse_args()
42 
43 cmakefile = Path(args.cmakefile)
44 
45 with cmakefile.open() as fh:
46  opts = {}
47  rows = []
48  for line in fh:
49  if m := re.match(
50  rf"option\( *({args.prefix}\w*) \"(.*)\" (ON|OFF|\${{\w+}})\ *\)", line
51  ):
52  name, doc, default = m.groups()
53  type = "bool"
54  if m := re.match(r"\${(\w+)}", default):
55  lookup = m.group(1)
56  if lookup in opts:
57  default = f"{lookup} -> {opts[lookup]}"
58  elif m := re.match(
59  rf"set\( *({args.prefix}\w*) \"(.*)\" CACHE (\w+) \"(.*)\"( FORCE)? *\)",
60  line,
61  ):
62  name, default, type, doc, _ = m.groups()
63  type = type.lower()
64  if default == "":
65  default = '""'
66  else:
67  continue
68 
69  opts[name] = default
70  doc = "<br>".join(textwrap.wrap(doc, width=args.width))
71  rows.append((name, f"{doc}<br> type: `{type}`, default: `{default}`"))
72 
73 output = ""
74 
75 headers = ("Option", "Description")
76 column_lengths = [0] * len(rows[0])
77 
78 for row in rows:
79  for i, col in enumerate(row):
80  column_lengths[i] = max(column_lengths[i], len(col))
81 
82 output += "|"
83 for i, header in enumerate(headers):
84  output += " " + header.ljust(column_lengths[i]) + " |"
85 output += "\n"
86 
87 output += "|"
88 for i in range(len(column_lengths)):
89  output += "-" + ("-" * column_lengths[i]) + "-|"
90 output += "\n"
91 
92 
93 for row in rows:
94  output += "|"
95  for i, col in enumerate(row):
96  output += " " + col.ljust(column_lengths[i]) + " |"
97  output += "\n"
98 
99 output = output.strip()
100 
101 if args.write and args.write.exists():
102  source = args.write.read_text().split("\n")
103  try:
104  begin = source.index("<!-- CMAKE_OPTS_BEGIN -->")
105  end = source.index("<!-- CMAKE_OPTS_END -->")
106  except ValueError:
107  print("Markers not found in output file")
108  sys.exit(1)
109 
110  if args.verify:
111  actual = "\n".join(source[begin + 1 : end])
112  if output != actual:
113  print("MISMATCH:\n" + "-" * 9 + "\n")
114  print(
115  "\n".join(
116  difflib.unified_diff(
117  actual.split("\n"),
118  output.split("\n"),
119  fromfile="actual",
120  tofile="output",
121  )
122  )
123  )
124  sys.exit(1)
125  elif args.write:
126  out = source[: begin + 1] + output.split("\n") + source[end:]
127  args.write.write_text("\n".join(out))
128 else:
129  print(output)