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