123 lines · python
1#!/usr/bin/env python32r"""Emulates the bits of CMake's configure_file() function needed in LLVM.3 4The CMake build uses configure_file() for several things. This emulates that5function for the GN build. In the GN build, this runs at build time instead6of 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 the15empty string, which is falsy, but FOO=0 sets it to '0' which is truthy):16 171.) #cmakedefine01 FOO18 Checks if key FOO is set to a truthy value, and depending on that prints19 one of the following two lines:20 21 #define FOO 122 #define FOO 023 242.) #cmakedefine FOO [...]25 Checks if key FOO is set to a truthy value, and depending on that prints26 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 the32input file, or if the input file references keys that weren't passed in.33"""34 35import argparse36import os37import re38import sys39 40 41def main():42 parser = argparse.ArgumentParser(43 epilog=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter44 )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 156 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 180 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" % var90 if not values[var]:91 in_line = "/* #undef %s */\n" % var92 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 199 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 1110 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