xref: /llvm-project/llvm/utils/gn/build/write_cmake_config.py (revision b71edfaa4ec3c998aadb35255ce2f60bba2940b0)
117296607SReid Kleckner#!/usr/bin/env python3
217296607SReid Klecknerr"""Emulates the bits of CMake's configure_file() function needed in LLVM.
317296607SReid Kleckner
417296607SReid KlecknerThe CMake build uses configure_file() for several things.  This emulates that
517296607SReid Klecknerfunction for the GN build.  In the GN build, this runs at build time instead
617296607SReid Klecknerof at generator time.
717296607SReid Kleckner
817296607SReid KlecknerTakes a list of KEY=VALUE pairs (where VALUE can be empty).
917296607SReid Kleckner
1017296607SReid KlecknerThe sequence `\` `n` in each VALUE is replaced by a newline character.
1117296607SReid Kleckner
1217296607SReid KlecknerOn each line, replaces '${KEY}' or '@KEY@' with VALUE.
1317296607SReid Kleckner
1417296607SReid KlecknerThen, handles these special cases (note that FOO= sets the value of FOO to the
1517296607SReid Klecknerempty string, which is falsy, but FOO=0 sets it to '0' which is truthy):
1617296607SReid Kleckner
1717296607SReid Kleckner1.) #cmakedefine01 FOO
1817296607SReid Kleckner    Checks if key FOO is set to a truthy value, and depending on that prints
1917296607SReid Kleckner    one of the following two lines:
2017296607SReid Kleckner
2117296607SReid Kleckner        #define FOO 1
2217296607SReid Kleckner        #define FOO 0
2317296607SReid Kleckner
2417296607SReid Kleckner2.) #cmakedefine FOO [...]
2517296607SReid Kleckner    Checks if key FOO is set to a truthy value, and depending on that prints
2617296607SReid Kleckner    one of the following two lines:
2717296607SReid Kleckner
2817296607SReid Kleckner        #define FOO [...]
2917296607SReid Kleckner        /* #undef FOO */
3017296607SReid Kleckner
3117296607SReid KlecknerFails if any of the KEY=VALUE arguments aren't needed for processing the
3217296607SReid Klecknerinput file, or if the input file references keys that weren't passed in.
3317296607SReid Kleckner"""
3417296607SReid Kleckner
3517296607SReid Klecknerimport argparse
3617296607SReid Klecknerimport os
3717296607SReid Klecknerimport re
3817296607SReid Klecknerimport sys
3917296607SReid Kleckner
4017296607SReid Kleckner
4117296607SReid Klecknerdef main():
4217296607SReid Kleckner    parser = argparse.ArgumentParser(
43*b71edfaaSTobias Hieta        epilog=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter
44*b71edfaaSTobias Hieta    )
45*b71edfaaSTobias Hieta    parser.add_argument("input", help="input file")
46*b71edfaaSTobias Hieta    parser.add_argument("values", nargs="*", help="several KEY=VALUE pairs")
47*b71edfaaSTobias Hieta    parser.add_argument("-o", "--output", required=True, help="output file")
4817296607SReid Kleckner    args = parser.parse_args()
4917296607SReid Kleckner
5017296607SReid Kleckner    values = {}
5117296607SReid Kleckner    for value in args.values:
52*b71edfaaSTobias Hieta        key, val = value.split("=", 1)
5317296607SReid Kleckner        if key in values:
5417296607SReid Kleckner            print('duplicate key "%s" in args' % key, file=sys.stderr)
5517296607SReid Kleckner            return 1
56*b71edfaaSTobias Hieta        values[key] = val.replace("\\n", "\n")
5717296607SReid Kleckner    unused_values = set(values.keys())
5817296607SReid Kleckner
5917296607SReid Kleckner    # Matches e.g. '${FOO}' or '@FOO@' and captures FOO in group 1 or 2.
60*b71edfaaSTobias Hieta    var_re = re.compile(r"\$\{([^}]*)\}|@([^@]*)@")
6117296607SReid Kleckner
6217296607SReid Kleckner    with open(args.input) as f:
6317296607SReid Kleckner        in_lines = f.readlines()
6417296607SReid Kleckner    out_lines = []
6517296607SReid Kleckner    for in_line in in_lines:
66*b71edfaaSTobias Hieta
6717296607SReid Kleckner        def repl(m):
6817296607SReid Kleckner            key = m.group(1) or m.group(2)
6917296607SReid Kleckner            unused_values.discard(key)
7017296607SReid Kleckner            return values[key]
71*b71edfaaSTobias Hieta
7217296607SReid Kleckner        in_line = var_re.sub(repl, in_line)
73*b71edfaaSTobias Hieta        if in_line.startswith("#cmakedefine01 "):
7417296607SReid Kleckner            _, var = in_line.split()
75*b71edfaaSTobias Hieta            if values[var] == "0":
7617296607SReid Kleckner                print('error: "%s=0" used with #cmakedefine01 %s' % (var, var))
7717296607SReid Kleckner                print("       '0' evaluates as truthy with #cmakedefine01")
7817296607SReid Kleckner                print('       use "%s=" instead' % var)
7917296607SReid Kleckner                return 1
80*b71edfaaSTobias Hieta            in_line = "#define %s %d\n" % (var, 1 if values[var] else 0)
8117296607SReid Kleckner            unused_values.discard(var)
82*b71edfaaSTobias Hieta        elif in_line.startswith("#cmakedefine "):
8317296607SReid Kleckner            _, var = in_line.split(None, 1)
8417296607SReid Kleckner            try:
8517296607SReid Kleckner                var, val = var.split(None, 1)
86*b71edfaaSTobias Hieta                in_line = "#define %s %s" % (var, val)  # val ends in \n.
8717296607SReid Kleckner            except:
8817296607SReid Kleckner                var = var.rstrip()
89*b71edfaaSTobias Hieta                in_line = "#define %s\n" % var
9017296607SReid Kleckner            if not values[var]:
91*b71edfaaSTobias Hieta                in_line = "/* #undef %s */\n" % var
9217296607SReid Kleckner            unused_values.discard(var)
9317296607SReid Kleckner        out_lines.append(in_line)
9417296607SReid Kleckner
9517296607SReid Kleckner    if unused_values:
96*b71edfaaSTobias Hieta        print("unused values args:", file=sys.stderr)
97*b71edfaaSTobias Hieta        print("    " + "\n    ".join(unused_values), file=sys.stderr)
9817296607SReid Kleckner        return 1
9917296607SReid Kleckner
100*b71edfaaSTobias Hieta    output = "".join(out_lines)
10117296607SReid Kleckner
10217296607SReid Kleckner    leftovers = var_re.findall(output)
10317296607SReid Kleckner    if leftovers:
10417296607SReid Kleckner        print(
105*b71edfaaSTobias Hieta            "unprocessed values:\n",
106*b71edfaaSTobias Hieta            "\n".join([x[0] or x[1] for x in leftovers]),
107*b71edfaaSTobias Hieta            file=sys.stderr,
108*b71edfaaSTobias Hieta        )
10917296607SReid Kleckner        return 1
11017296607SReid Kleckner
11117296607SReid Kleckner    def read(filename):
11217296607SReid Kleckner        with open(args.output) as f:
11317296607SReid Kleckner            return f.read()
11417296607SReid Kleckner
11517296607SReid Kleckner    if not os.path.exists(args.output) or read(args.output) != output:
116*b71edfaaSTobias Hieta        with open(args.output, "w") as f:
11717296607SReid Kleckner            f.write(output)
11817296607SReid Kleckner        os.chmod(args.output, os.stat(args.input).st_mode & 0o777)
11917296607SReid Kleckner
12017296607SReid Kleckner
121*b71edfaaSTobias Hietaif __name__ == "__main__":
12217296607SReid Kleckner    sys.exit(main())
123