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