xref: /aosp_15_r20/external/grpc-grpc/tools/buildgen/_mako_renderer.py (revision cc02d7e222339f7a4f6ba5f422e6413f4bd931f2)
1#!/usr/bin/env python3
2# Copyright 2015 gRPC authors.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#     http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15"""Simple Mako renderer.
16
17Just a wrapper around the mako rendering library.
18"""
19
20import getopt
21import glob
22import importlib.util
23import os
24import pickle
25import shutil
26import sys
27from typing import List
28
29from mako import exceptions
30from mako.lookup import TemplateLookup
31from mako.runtime import Context
32from mako.template import Template
33import yaml
34
35PROJECT_ROOT = os.path.join(
36    os.path.dirname(os.path.abspath(__file__)), "..", ".."
37)
38# TODO(lidiz) find a better way for plugins to reference each other
39sys.path.append(os.path.join(PROJECT_ROOT, "tools", "buildgen", "plugins"))
40
41
42def out(msg: str) -> None:
43    print(msg, file=sys.stderr)
44
45
46def showhelp() -> None:
47    out(
48        "mako-renderer.py [-o out] [-m cache] [-P preprocessed_input] [-d dict]"
49        " [-d dict...] [-t template] [-w preprocessed_output]"
50    )
51
52
53def render_template(template: Template, context: Context) -> None:
54    """Render the mako template with given context.
55
56    Prints an error template to indicate where and what in the template caused
57    the render failure.
58    """
59    try:
60        template.render_context(context)
61    except:
62        out(exceptions.text_error_template().render())
63        raise
64
65
66def main(argv: List[str]) -> None:
67    got_input = False
68    module_directory = None
69    preprocessed_output = None
70    dictionary = {}
71    json_dict = {}
72    got_output = False
73    output_name = None
74    got_preprocessed_input = False
75    output_merged = None
76
77    try:
78        opts, args = getopt.getopt(argv, "hM:m:o:t:P:")
79    except getopt.GetoptError:
80        out("Unknown option")
81        showhelp()
82        sys.exit(2)
83
84    for opt, arg in opts:
85        if opt == "-h":
86            out("Displaying showhelp")
87            showhelp()
88            sys.exit()
89        elif opt == "-o":
90            if got_output:
91                out("Got more than one output")
92                showhelp()
93                sys.exit(3)
94            got_output = True
95            output_name = arg
96        elif opt == "-m":
97            if module_directory is not None:
98                out("Got more than one cache directory")
99                showhelp()
100                sys.exit(4)
101            module_directory = arg
102        elif opt == "-M":
103            if output_merged is not None:
104                out("Got more than one output merged path")
105                showhelp()
106                sys.exit(5)
107            output_merged = arg
108        elif opt == "-P":
109            assert not got_preprocessed_input
110            assert json_dict == {}
111            with open(arg, "rb") as dict_file:
112                dictionary = pickle.load(dict_file)
113            got_preprocessed_input = True
114
115    cleared_dir = False
116    for arg in args:
117        got_input = True
118        with open(arg) as f:
119            srcs = list(yaml.safe_load_all(f.read()))
120        for src in srcs:
121            if isinstance(src, str):
122                assert len(srcs) == 1
123                template = Template(
124                    src,
125                    filename=arg,
126                    module_directory=module_directory,
127                    lookup=TemplateLookup(directories=["."]),
128                )
129                with open(output_name, "w") as output_file:
130                    render_template(
131                        template, Context(output_file, **dictionary)
132                    )
133            else:
134                # we have optional control data: this template represents
135                # a directory
136                if not cleared_dir:
137                    if not os.path.exists(output_name):
138                        pass
139                    elif os.path.isfile(output_name):
140                        os.unlink(output_name)
141                    else:
142                        shutil.rmtree(output_name, ignore_errors=True)
143                    cleared_dir = True
144                items = []
145                if "foreach" in src:
146                    for el in dictionary[src["foreach"]]:
147                        if "cond" in src:
148                            args = dict(dictionary)
149                            args["selected"] = el
150                            if not eval(src["cond"], {}, args):
151                                continue
152                        items.append(el)
153                    assert items
154                else:
155                    items = [None]
156                for item in items:
157                    args = dict(dictionary)
158                    args["selected"] = item
159                    item_output_name = os.path.join(
160                        output_name, Template(src["output_name"]).render(**args)
161                    )
162                    if not os.path.exists(os.path.dirname(item_output_name)):
163                        os.makedirs(os.path.dirname(item_output_name))
164                    template = Template(
165                        src["template"],
166                        filename=arg,
167                        module_directory=module_directory,
168                        lookup=TemplateLookup(directories=["."]),
169                    )
170                    with open(item_output_name, "w") as output_file:
171                        render_template(template, Context(output_file, **args))
172
173    if not got_input and not preprocessed_output:
174        out("Got nothing to do")
175        showhelp()
176
177
178if __name__ == "__main__":
179    main(sys.argv[1:])
180