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