| #!/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()) |