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__, formatter_class=argparse.RawDescriptionHelpFormatter 44 ) 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, help="output file") 48 args = parser.parse_args() 49 50 values = {} 51 for value in args.values: 52 key, val = value.split("=", 1) 53 if key in values: 54 print('duplicate key "%s" in args' % key, file=sys.stderr) 55 return 1 56 values[key] = val.replace("\\n", "\n") 57 unused_values = set(values.keys()) 58 59 # Matches e.g. '${FOO}' or '@FOO@' and captures FOO in group 1 or 2. 60 var_re = re.compile(r"\$\{([^}]*)\}|@([^@]*)@") 61 62 with open(args.input) as f: 63 in_lines = f.readlines() 64 out_lines = [] 65 for in_line in in_lines: 66 67 def repl(m): 68 key = m.group(1) or m.group(2) 69 unused_values.discard(key) 70 return values[key] 71 72 in_line = var_re.sub(repl, in_line) 73 if in_line.startswith("#cmakedefine01 "): 74 _, var = in_line.split() 75 if values[var] == "0": 76 print('error: "%s=0" used with #cmakedefine01 %s' % (var, var)) 77 print(" '0' evaluates as truthy with #cmakedefine01") 78 print(' use "%s=" instead' % var) 79 return 1 80 in_line = "#define %s %d\n" % (var, 1 if values[var] else 0) 81 unused_values.discard(var) 82 elif in_line.startswith("#cmakedefine "): 83 _, var = in_line.split(None, 1) 84 try: 85 var, val = var.split(None, 1) 86 in_line = "#define %s %s" % (var, val) # val ends in \n. 87 except: 88 var = var.rstrip() 89 in_line = "#define %s\n" % var 90 if not values[var]: 91 in_line = "/* #undef %s */\n" % var 92 unused_values.discard(var) 93 out_lines.append(in_line) 94 95 if unused_values: 96 print("unused values args:", file=sys.stderr) 97 print(" " + "\n ".join(unused_values), file=sys.stderr) 98 return 1 99 100 output = "".join(out_lines) 101 102 leftovers = var_re.findall(output) 103 if leftovers: 104 print( 105 "unprocessed values:\n", 106 "\n".join([x[0] or x[1] for x in leftovers]), 107 file=sys.stderr, 108 ) 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