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 return flag_parser.parse_args(args) 91 92 93args = ParseCommandLineArguments(sys.argv[1:]) 94 95 96def _InjectGithubPath(path): 97 base, ext = os.path.splitext(path) 98 return base + ".github" + ext 99 100 101def _GenerateExperimentFiles(args, mode): 102 if mode == "test": 103 _EXPERIMENTS_DEFS = ( 104 "test/core/experiments/fixtures/test_experiments.yaml" 105 ) 106 _EXPERIMENTS_ROLLOUTS = ( 107 "test/core/experiments/fixtures/test_experiments_rollout.yaml" 108 ) 109 _EXPERIMENTS_HDR_FILE = "test/core/experiments/fixtures/experiments.h" 110 _EXPERIMENTS_SRC_FILE = "test/core/experiments/fixtures/experiments.cc" 111 _EXPERIMENTS_BZL_FILE = "bazel/test_experiments.bzl" 112 else: 113 _EXPERIMENTS_DEFS = "src/core/lib/experiments/experiments.yaml" 114 _EXPERIMENTS_ROLLOUTS = "src/core/lib/experiments/rollouts.yaml" 115 _EXPERIMENTS_HDR_FILE = "src/core/lib/experiments/experiments.h" 116 _EXPERIMENTS_SRC_FILE = "src/core/lib/experiments/experiments.cc" 117 _EXPERIMENTS_BZL_FILE = "bazel/experiments.bzl" 118 if "/google3/" in REPO_ROOT: 119 _EXPERIMENTS_ROLLOUTS = _InjectGithubPath(_EXPERIMENTS_ROLLOUTS) 120 _EXPERIMENTS_HDR_FILE = _InjectGithubPath(_EXPERIMENTS_HDR_FILE) 121 _EXPERIMENTS_SRC_FILE = _InjectGithubPath(_EXPERIMENTS_SRC_FILE) 122 _EXPERIMENTS_BZL_FILE = _InjectGithubPath(_EXPERIMENTS_BZL_FILE) 123 124 with open(_EXPERIMENTS_DEFS) as f: 125 attrs = yaml.safe_load(f.read()) 126 127 if not exp.AreExperimentsOrdered(attrs): 128 print("Experiments are not ordered") 129 sys.exit(1) 130 131 with open(_EXPERIMENTS_ROLLOUTS) as f: 132 rollouts = yaml.safe_load(f.read()) 133 134 if not exp.AreExperimentsOrdered(rollouts): 135 print("Rollouts are not ordered") 136 sys.exit(1) 137 138 compiler = exp.ExperimentsCompiler( 139 DEFAULTS, 140 FINAL_RETURN, 141 FINAL_DEFINE, 142 PLATFORMS_DEFINE, 143 BZL_LIST_FOR_DEFAULTS, 144 ) 145 146 experiment_annotation = "gRPC Experiments: " 147 for attr in attrs: 148 exp_definition = exp.ExperimentDefinition(attr) 149 if not exp_definition.IsValid(args.check): 150 sys.exit(1) 151 experiment_annotation += exp_definition.name + ":0," 152 if not compiler.AddExperimentDefinition(exp_definition): 153 print("Experiment = %s ERROR adding" % exp_definition.name) 154 sys.exit(1) 155 156 if len(experiment_annotation) > 2000: 157 print("comma-delimited string of experiments is too long") 158 sys.exit(1) 159 160 for rollout_attr in rollouts: 161 if not compiler.AddRolloutSpecification(rollout_attr): 162 print("ERROR adding rollout spec") 163 sys.exit(1) 164 165 print(f"Mode = {mode} Generating experiments headers") 166 compiler.GenerateExperimentsHdr(_EXPERIMENTS_HDR_FILE, mode) 167 168 print(f"Mode = {mode} Generating experiments srcs") 169 compiler.GenerateExperimentsSrc( 170 _EXPERIMENTS_SRC_FILE, _EXPERIMENTS_HDR_FILE, mode 171 ) 172 173 print("Generating experiments.bzl") 174 compiler.GenExperimentsBzl(mode, _EXPERIMENTS_BZL_FILE) 175 if mode == "test": 176 print("Generating experiments tests") 177 compiler.GenTest( 178 os.path.join(REPO_ROOT, "test/core/experiments/experiments_test.cc") 179 ) 180 181 182_GenerateExperimentFiles(args, "production") 183_GenerateExperimentFiles(args, "test") 184