1#!/usr/bin/env python 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 35from __future__ import print_function 36 37import argparse 38import os 39import re 40import sys 41 42 43def main(): 44 parser = argparse.ArgumentParser( 45 epilog=__doc__, 46 formatter_class=argparse.RawDescriptionHelpFormatter) 47 parser.add_argument('input', help='input file') 48 parser.add_argument('values', nargs='*', help='several KEY=VALUE pairs') 49 parser.add_argument('-o', '--output', required=True, 50 help='output file') 51 args = parser.parse_args() 52 53 values = {} 54 for value in args.values: 55 key, val = value.split('=', 1) 56 if key in values: 57 print('duplicate key "%s" in args' % key, file=sys.stderr) 58 return 1 59 values[key] = val.replace('\\n', '\n') 60 unused_values = set(values.keys()) 61 62 # Matches e.g. '${FOO}' or '@FOO@' and captures FOO in group 1 or 2. 63 var_re = re.compile(r'\$\{([^}]*)\}|@([^@]*)@') 64 65 with open(args.input) as f: 66 in_lines = f.readlines() 67 out_lines = [] 68 for in_line in in_lines: 69 def repl(m): 70 key = m.group(1) or m.group(2) 71 unused_values.discard(key) 72 return values[key] 73 in_line = var_re.sub(repl, in_line) 74 if in_line.startswith('#cmakedefine01 '): 75 _, var = in_line.split() 76 if values[var] == '0': 77 print('error: "%s=0" used with #cmakedefine01 %s' % (var, var)) 78 print(" '0' evaluates as truthy with #cmakedefine01") 79 print(' use "%s=" instead' % var) 80 return 1 81 in_line = '#define %s %d\n' % (var, 1 if values[var] else 0) 82 unused_values.discard(var) 83 elif in_line.startswith('#cmakedefine '): 84 _, var = in_line.split(None, 1) 85 try: 86 var, val = var.split(None, 1) 87 in_line = '#define %s %s' % (var, val) # val ends in \n. 88 except: 89 var = var.rstrip() 90 in_line = '#define %s\n' % var 91 if not values[var]: 92 in_line = '/* #undef %s */\n' % var 93 unused_values.discard(var) 94 out_lines.append(in_line) 95 96 if unused_values: 97 print('unused values args:', file=sys.stderr) 98 print(' ' + '\n '.join(unused_values), file=sys.stderr) 99 return 1 100 101 output = ''.join(out_lines) 102 103 leftovers = var_re.findall(output) 104 if leftovers: 105 print( 106 'unprocessed values:\n', 107 '\n'.join([x[0] or x[1] for x in leftovers]), 108 file=sys.stderr) 109 return 1 110 111 def read(filename): 112 with open(args.output) as f: 113 return f.read() 114 115 if not os.path.exists(args.output) or read(args.output) != output: 116 with open(args.output, 'w') as f: 117 f.write(output) 118 os.chmod(args.output, os.stat(args.input).st_mode & 0o777) 119 120 121if __name__ == '__main__': 122 sys.exit(main()) 123