1# Copyright 2015 gRPC authors. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15import argparse 16import glob 17import multiprocessing 18import os 19import pickle 20import shutil 21import sys 22import tempfile 23from typing import Dict, List, Union 24 25import _utils 26import yaml 27 28PROJECT_ROOT = os.path.join( 29 os.path.dirname(os.path.abspath(__file__)), "..", ".." 30) 31os.chdir(PROJECT_ROOT) 32# TODO(lidiz) find a better way for plugins to reference each other 33sys.path.append(os.path.join(PROJECT_ROOT, "tools", "buildgen", "plugins")) 34 35# from tools.run_tests.python_utils import jobset 36jobset = _utils.import_python_module( 37 os.path.join( 38 PROJECT_ROOT, "tools", "run_tests", "python_utils", "jobset.py" 39 ) 40) 41 42PREPROCESSED_BUILD = ".preprocessed_build" 43test = {} if os.environ.get("TEST", "false") == "true" else None 44 45assert sys.argv[1:], "run generate_projects.sh instead of this directly" 46parser = argparse.ArgumentParser() 47parser.add_argument( 48 "build_files", 49 nargs="+", 50 default=[], 51 help="build files describing build specs", 52) 53parser.add_argument( 54 "--templates", nargs="+", default=[], help="mako template files to render" 55) 56parser.add_argument( 57 "--output_merged", 58 "-m", 59 default="", 60 type=str, 61 help="merge intermediate results to a file", 62) 63parser.add_argument( 64 "--jobs", 65 "-j", 66 default=multiprocessing.cpu_count(), 67 type=int, 68 help="maximum parallel jobs", 69) 70parser.add_argument( 71 "--base", default=".", type=str, help="base path for generated files" 72) 73args = parser.parse_args() 74 75 76def preprocess_build_files() -> _utils.Bunch: 77 """Merges build yaml into a one dictionary then pass it to plugins.""" 78 build_spec = dict() 79 for build_file in args.build_files: 80 with open(build_file, "r") as f: 81 _utils.merge_json(build_spec, yaml.safe_load(f.read())) 82 # Executes plugins. Plugins update the build spec in-place. 83 for py_file in sorted(glob.glob("tools/buildgen/plugins/*.py")): 84 plugin = _utils.import_python_module(py_file) 85 plugin.mako_plugin(build_spec) 86 if args.output_merged: 87 with open(args.output_merged, "w") as f: 88 f.write(yaml.dump(build_spec)) 89 # Makes build_spec sort of immutable and dot-accessible 90 return _utils.to_bunch(build_spec) 91 92 93def generate_template_render_jobs(templates: List[str]) -> List[jobset.JobSpec]: 94 """Generate JobSpecs for each one of the template rendering work.""" 95 jobs = [] 96 base_cmd = [sys.executable, "tools/buildgen/_mako_renderer.py"] 97 for template in sorted(templates, reverse=True): 98 root, f = os.path.split(template) 99 if os.path.splitext(f)[1] == ".template": 100 out_dir = args.base + root[len("templates") :] 101 out = os.path.join(out_dir, os.path.splitext(f)[0]) 102 if not os.path.exists(out_dir): 103 os.makedirs(out_dir) 104 cmd = base_cmd[:] 105 cmd.append("-P") 106 cmd.append(PREPROCESSED_BUILD) 107 cmd.append("-o") 108 if test is None: 109 cmd.append(out) 110 else: 111 tf = tempfile.mkstemp() 112 test[out] = tf[1] 113 os.close(tf[0]) 114 cmd.append(test[out]) 115 cmd.append(args.base + "/" + root + "/" + f) 116 jobs.append( 117 jobset.JobSpec(cmd, shortname=out, timeout_seconds=None) 118 ) 119 return jobs 120 121 122def main() -> None: 123 templates = args.templates 124 if not templates: 125 for root, _, files in os.walk("templates"): 126 for f in files: 127 templates.append(os.path.join(root, f)) 128 129 build_spec = preprocess_build_files() 130 with open(PREPROCESSED_BUILD, "wb") as f: 131 pickle.dump(build_spec, f) 132 133 err_cnt, _ = jobset.run( 134 generate_template_render_jobs(templates), maxjobs=args.jobs 135 ) 136 if err_cnt != 0: 137 print( 138 "ERROR: %s error(s) found while generating projects." % err_cnt, 139 file=sys.stderr, 140 ) 141 sys.exit(1) 142 143 if test is not None: 144 for s, g in test.items(): 145 if os.path.isfile(g): 146 assert 0 == os.system("diff %s %s" % (s, g)), s 147 os.unlink(g) 148 else: 149 assert 0 == os.system("diff -r %s %s" % (s, g)), s 150 shutil.rmtree(g, ignore_errors=True) 151 152 153if __name__ == "__main__": 154 main() 155