write_cmake_config.py
3.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#!/usr/bin/env python
r"""Emulates the bits of CMake's configure_file() function needed in LLVM.
The CMake build uses configure_file() for several things. This emulates that
function for the GN build. In the GN build, this runs at build time instead
of at generator time.
Takes a list of KEY=VALUE pairs (where VALUE can be empty).
The sequence `\` `n` in each VALUE is replaced by a newline character.
On each line, replaces '${KEY}' or '@KEY@' with VALUE.
Then, handles these special cases (note that FOO= sets the value of FOO to the
empty string, which is falsy, but FOO=0 sets it to '0' which is truthy):
1.) #cmakedefine01 FOO
Checks if key FOO is set to a truthy value, and depending on that prints
one of the following two lines:
#define FOO 1
#define FOO 0
2.) #cmakedefine FOO [...]
Checks if key FOO is set to a truthy value, and depending on that prints
one of the following two lines:
#define FOO [...]
/* #undef FOO */
Fails if any of the KEY=VALUE arguments aren't needed for processing the
input file, or if the input file references keys that weren't passed in.
"""
from __future__ import print_function
import argparse
import os
import re
import sys
def main():
parser = argparse.ArgumentParser(
epilog=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument('input', help='input file')
parser.add_argument('values', nargs='*', help='several KEY=VALUE pairs')
parser.add_argument('-o', '--output', required=True,
help='output file')
args = parser.parse_args()
values = {}
for value in args.values:
key, val = value.split('=', 1)
if key in values:
print('duplicate key "%s" in args' % key, file=sys.stderr)
return 1
values[key] = val.replace('\\n', '\n')
unused_values = set(values.keys())
# Matches e.g. '${FOO}' or '@FOO@' and captures FOO in group 1 or 2.
var_re = re.compile(r'\$\{([^}]*)\}|@([^@]*)@')
with open(args.input) as f:
in_lines = f.readlines()
out_lines = []
for in_line in in_lines:
def repl(m):
key = m.group(1) or m.group(2)
unused_values.discard(key)
return values[key]
in_line = var_re.sub(repl, in_line)
if in_line.startswith('#cmakedefine01 '):
_, var = in_line.split()
if values[var] == '0':
print('error: "%s=0" used with #cmakedefine01 %s' % (var, var))
print(" '0' evaluates as truthy with #cmakedefine01")
print(' use "%s=" instead' % var)
return 1
in_line = '#define %s %d\n' % (var, 1 if values[var] else 0)
unused_values.discard(var)
elif in_line.startswith('#cmakedefine '):
_, var = in_line.split(None, 1)
try:
var, val = var.split(None, 1)
in_line = '#define %s %s' % (var, val) # val ends in \n.
except:
var = var.rstrip()
in_line = '#define %s\n' % var
if not values[var]:
in_line = '/* #undef %s */\n' % var
unused_values.discard(var)
out_lines.append(in_line)
if unused_values:
print('unused values args:', file=sys.stderr)
print(' ' + '\n '.join(unused_values), file=sys.stderr)
return 1
output = ''.join(out_lines)
leftovers = var_re.findall(output)
if leftovers:
print(
'unprocessed values:\n',
'\n'.join([x[0] or x[1] for x in leftovers]),
file=sys.stderr)
return 1
def read(filename):
with open(args.output) as f:
return f.read()
if not os.path.exists(args.output) or read(args.output) != output:
with open(args.output, 'w') as f:
f.write(output)
os.chmod(args.output, os.stat(args.input).st_mode & 0o777)
if __name__ == '__main__':
sys.exit(main())