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 29import yaml 30from mako import exceptions 31from mako.lookup import TemplateLookup 32from mako.runtime import Context 33from mako.template import Template 34 35PROJECT_ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", 36 "..") 37# TODO(lidiz) find a better way for plugins to reference each other 38sys.path.append(os.path.join(PROJECT_ROOT, 'tools', 'buildgen', 'plugins')) 39 40 41def out(msg: str) -> None: 42 print(msg, file=sys.stderr) 43 44 45def showhelp() -> None: 46 out('mako-renderer.py [-o out] [-m cache] [-P preprocessed_input] [-d dict] [-d dict...]' 47 ' [-t template] [-w preprocessed_output]') 48 49 50def render_template(template: Template, context: Context) -> None: 51 """Render the mako template with given context. 52 53 Prints an error template to indicate where and what in the template caused 54 the render failure. 55 """ 56 try: 57 template.render_context(context) 58 except: 59 out(exceptions.text_error_template().render()) 60 raise 61 62 63def main(argv: List[str]) -> None: 64 got_input = False 65 module_directory = None 66 preprocessed_output = None 67 dictionary = {} 68 json_dict = {} 69 got_output = False 70 output_name = None 71 got_preprocessed_input = False 72 output_merged = None 73 74 try: 75 opts, args = getopt.getopt(argv, 'hM:m:o:t:P:') 76 except getopt.GetoptError: 77 out('Unknown option') 78 showhelp() 79 sys.exit(2) 80 81 for opt, arg in opts: 82 if opt == '-h': 83 out('Displaying showhelp') 84 showhelp() 85 sys.exit() 86 elif opt == '-o': 87 if got_output: 88 out('Got more than one output') 89 showhelp() 90 sys.exit(3) 91 got_output = True 92 output_name = arg 93 elif opt == '-m': 94 if module_directory is not None: 95 out('Got more than one cache directory') 96 showhelp() 97 sys.exit(4) 98 module_directory = arg 99 elif opt == '-M': 100 if output_merged is not None: 101 out('Got more than one output merged path') 102 showhelp() 103 sys.exit(5) 104 output_merged = arg 105 elif opt == '-P': 106 assert not got_preprocessed_input 107 assert json_dict == {} 108 with open(arg, 'rb') as dict_file: 109 dictionary = pickle.load(dict_file) 110 got_preprocessed_input = True 111 112 cleared_dir = False 113 for arg in args: 114 got_input = True 115 with open(arg) as f: 116 srcs = list(yaml.load_all(f.read(), Loader=yaml.FullLoader)) 117 for src in srcs: 118 if isinstance(src, str): 119 assert len(srcs) == 1 120 template = Template(src, 121 filename=arg, 122 module_directory=module_directory, 123 lookup=TemplateLookup(directories=['.'])) 124 with open(output_name, 'w') as output_file: 125 render_template(template, Context(output_file, 126 **dictionary)) 127 else: 128 # we have optional control data: this template represents 129 # a directory 130 if not cleared_dir: 131 if not os.path.exists(output_name): 132 pass 133 elif os.path.isfile(output_name): 134 os.unlink(output_name) 135 else: 136 shutil.rmtree(output_name, ignore_errors=True) 137 cleared_dir = True 138 items = [] 139 if 'foreach' in src: 140 for el in dictionary[src['foreach']]: 141 if 'cond' in src: 142 args = dict(dictionary) 143 args['selected'] = el 144 if not eval(src['cond'], {}, args): 145 continue 146 items.append(el) 147 assert items 148 else: 149 items = [None] 150 for item in items: 151 args = dict(dictionary) 152 args['selected'] = item 153 item_output_name = os.path.join( 154 output_name, 155 Template(src['output_name']).render(**args)) 156 if not os.path.exists(os.path.dirname(item_output_name)): 157 os.makedirs(os.path.dirname(item_output_name)) 158 template = Template( 159 src['template'], 160 filename=arg, 161 module_directory=module_directory, 162 lookup=TemplateLookup(directories=['.'])) 163 with open(item_output_name, 'w') as output_file: 164 render_template(template, Context(output_file, **args)) 165 166 if not got_input and not preprocessed_output: 167 out('Got nothing to do') 168 showhelp() 169 170 171if __name__ == '__main__': 172 main(sys.argv[1:]) 173