1#!/usr/bin/env python3 2 3# Copyright 2022 gRPC authors. 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16""" 17Generate experiment related code artifacts. 18 19Invoke as: tools/codegen/core/gen_experiments.py 20Experiment definitions are in src/core/lib/experiments/experiments.yaml 21""" 22 23from __future__ import print_function 24 25import argparse 26import os 27import sys 28 29import experiments_compiler as exp 30import yaml 31 32REPO_ROOT = os.path.normpath( 33 os.path.join(os.path.dirname(__file__), "../../..") 34) 35print(REPO_ROOT) 36os.chdir(REPO_ROOT) 37 38DEFAULTS = { 39 "broken": "false", 40 False: "false", 41 True: "true", 42 "debug": "kDefaultForDebugOnly", 43} 44 45PLATFORMS_DEFINE = { 46 "windows": "GPR_WINDOWS", 47 "ios": "GRPC_CFSTREAM", 48 "posix": "", 49} 50 51FINAL_RETURN = { 52 "broken": "return false;", 53 False: "return false;", 54 True: "return true;", 55 "debug": "\n#ifdef NDEBUG\nreturn false;\n#else\nreturn true;\n#endif\n", 56} 57 58FINAL_DEFINE = { 59 "broken": None, 60 False: None, 61 True: "#define %s", 62 "debug": "#ifndef NDEBUG\n#define %s\n#endif", 63} 64 65BZL_LIST_FOR_DEFAULTS = { 66 "broken": None, 67 False: "off", 68 True: "on", 69 "debug": "dbg", 70} 71 72 73def ParseCommandLineArguments(args): 74 """Wrapper for argparse command line arguments handling. 75 76 Args: 77 args: List of command line arguments. 78 79 Returns: 80 Command line arguments namespace built by argparse.ArgumentParser(). 81 """ 82 # formatter_class=argparse.ArgumentDefaultsHelpFormatter is not used here 83 # intentionally, We want more formatting than this class can provide. 84 flag_parser = argparse.ArgumentParser() 85 flag_parser.add_argument( 86 "--check", 87 action="store_false", 88 help="If specified, disables checking experiment expiry dates", 89 ) 90 flag_parser.add_argument( 91 "--no_dbg_experiments", 92 action="store_true", 93 help="Prohibit 'debug' configurations", 94 default=False, 95 ) 96 return flag_parser.parse_args(args) 97 98 99args = ParseCommandLineArguments(sys.argv[1:]) 100 101 102def _InjectGithubPath(path): 103 base, ext = os.path.splitext(path) 104 return base + ".github" + ext 105 106 107def _GenerateExperimentFiles(args, mode): 108 if mode == "test": 109 _EXPERIMENTS_DEFS = ( 110 "test/core/experiments/fixtures/test_experiments.yaml" 111 ) 112 _EXPERIMENTS_ROLLOUTS = ( 113 "test/core/experiments/fixtures/test_experiments_rollout.yaml" 114 ) 115 _EXPERIMENTS_HDR_FILE = "test/core/experiments/fixtures/experiments.h" 116 _EXPERIMENTS_SRC_FILE = "test/core/experiments/fixtures/experiments.cc" 117 _EXPERIMENTS_BZL_FILE = "bazel/test_experiments.bzl" 118 else: 119 _EXPERIMENTS_DEFS = "src/core/lib/experiments/experiments.yaml" 120 _EXPERIMENTS_ROLLOUTS = "src/core/lib/experiments/rollouts.yaml" 121 _EXPERIMENTS_HDR_FILE = "src/core/lib/experiments/experiments.h" 122 _EXPERIMENTS_SRC_FILE = "src/core/lib/experiments/experiments.cc" 123 _EXPERIMENTS_BZL_FILE = "bazel/experiments.bzl" 124 if "/google3/" in REPO_ROOT: 125 _EXPERIMENTS_ROLLOUTS = _InjectGithubPath(_EXPERIMENTS_ROLLOUTS) 126 _EXPERIMENTS_HDR_FILE = _InjectGithubPath(_EXPERIMENTS_HDR_FILE) 127 _EXPERIMENTS_SRC_FILE = _InjectGithubPath(_EXPERIMENTS_SRC_FILE) 128 _EXPERIMENTS_BZL_FILE = _InjectGithubPath(_EXPERIMENTS_BZL_FILE) 129 130 with open(_EXPERIMENTS_DEFS) as f: 131 attrs = yaml.safe_load(f.read()) 132 133 if not exp.AreExperimentsOrdered(attrs): 134 print("Experiments are not ordered") 135 sys.exit(1) 136 137 with open(_EXPERIMENTS_ROLLOUTS) as f: 138 rollouts = yaml.safe_load(f.read()) 139 140 if not exp.AreExperimentsOrdered(rollouts): 141 print("Rollouts are not ordered") 142 sys.exit(1) 143 144 compiler = exp.ExperimentsCompiler( 145 DEFAULTS, 146 FINAL_RETURN, 147 FINAL_DEFINE, 148 PLATFORMS_DEFINE, 149 BZL_LIST_FOR_DEFAULTS, 150 ) 151 152 experiment_annotation = "gRPC Experiments: " 153 for attr in attrs: 154 exp_definition = exp.ExperimentDefinition(attr) 155 if not exp_definition.IsValid(args.check): 156 sys.exit(1) 157 experiment_annotation += exp_definition.name + ":0," 158 if not compiler.AddExperimentDefinition(exp_definition): 159 print("Experiment = %s ERROR adding" % exp_definition.name) 160 sys.exit(1) 161 162 if len(experiment_annotation) > 2000: 163 print("comma-delimited string of experiments is too long") 164 sys.exit(1) 165 166 for rollout_attr in rollouts: 167 if not compiler.AddRolloutSpecification(rollout_attr): 168 print("ERROR adding rollout spec") 169 sys.exit(1) 170 171 if mode != "test" and args.no_dbg_experiments: 172 print("Ensuring no debug experiments are configured") 173 compiler.EnsureNoDebugExperiments() 174 175 print(f"Mode = {mode} Generating experiments headers") 176 compiler.GenerateExperimentsHdr(_EXPERIMENTS_HDR_FILE, mode) 177 178 print(f"Mode = {mode} Generating experiments srcs") 179 compiler.GenerateExperimentsSrc( 180 _EXPERIMENTS_SRC_FILE, _EXPERIMENTS_HDR_FILE, mode 181 ) 182 183 print("Generating experiments.bzl") 184 compiler.GenExperimentsBzl(mode, _EXPERIMENTS_BZL_FILE) 185 if mode == "test": 186 print("Generating experiments tests") 187 compiler.GenTest( 188 os.path.join(REPO_ROOT, "test/core/experiments/experiments_test.cc") 189 ) 190 191 192_GenerateExperimentFiles(args, "production") 193_GenerateExperimentFiles(args, "test") 194