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