1#!/usr/bin/env python3 2 3r""" 4This script will parse error log yaml file(s) and generate 5a header file which will then be used by the error logging client and 6server to collect and validate the error information generated by the 7openbmc software components. 8 9This code uses a mako template to provide the basic template of the header 10file we're going to generate. We then call it with information from the 11yaml to generate the header file. 12""" 13 14import os 15import sys 16from optparse import OptionParser 17 18from mako.template import Template 19 20import yaml 21 22 23def order_inherited_errors(i_errors, i_parents): 24 # the ordered list of errors 25 errors = list() 26 has_inheritance = False 27 for error in i_errors: 28 if i_parents[error] is not None: 29 has_inheritance = True 30 break 31 32 if has_inheritance: 33 # Order the error codes list such that an error is never placed 34 # before it's parent. This way generated code can ensure parent 35 # definitions precede child error definitions. 36 while len(errors) < len(i_errors): 37 for error in i_errors: 38 if error in errors: 39 # already ordererd 40 continue 41 if (not i_parents[error]) or (i_parents[error] in errors): 42 # parent present, or has no parent, either way this error 43 # can be added 44 errors.append(error) 45 else: 46 # no inherited errors 47 errors = i_errors 48 49 return errors 50 51 52def check_error_inheritance(i_errors, i_parents): 53 for error in i_errors: 54 if i_parents[error] and (i_parents[error] not in i_errors): 55 print( 56 error 57 + " inherits " 58 + i_parents[error] 59 + " but the latter is not defined" 60 ) 61 return False 62 return True 63 64 65# Return the yaml files with their directory structure plus the file name 66# without the yaml extension, which will be used to set the namespaces. 67# Ex: file xyz/openbmc_project/Error/Callout/Device.errors.yaml 68# will have namespce xyz/openbmc_project/Error/Callout/Device 69def get_error_yaml_files(i_yaml_dir, i_test_dir): 70 yaml_files = dict() 71 if i_yaml_dir != "None": 72 for root, dirs, files in os.walk(i_yaml_dir): 73 for files in [ 74 file for file in files if file.endswith(".errors.yaml") 75 ]: 76 splitdir = root.split(i_yaml_dir)[1] + "/" + files[:-12] 77 if splitdir.startswith("/"): 78 splitdir = splitdir[1:] 79 yaml_files[(os.path.join(root, files))] = splitdir 80 for root, dirs, files in os.walk(i_test_dir): 81 for files in [file for file in files if file.endswith(".errors.yaml")]: 82 splitdir = root.split(i_test_dir)[1] + "/" + files[:-12] 83 yaml_files[(os.path.join(root, files))] = splitdir 84 return yaml_files 85 86 87def get_meta_yaml_file(i_error_yaml_file): 88 # the meta data will be defined in file name where we replace 89 # <Interface>.errors.yaml with <Interface>.metadata.yaml 90 meta_yaml = i_error_yaml_file.replace("errors", "metadata") 91 return meta_yaml 92 93 94def get_cpp_type(i_type): 95 typeMap = { 96 "boolean": "bool", 97 "int8": "int8_t", 98 "int16": "int16_t", 99 "int32": "int32_t", 100 "int64": "int64_t", 101 "uint8": "uint8_t", 102 "uint16": "uint16_t", 103 "uint32": "uint32_t", 104 "uint64": "uint64_t", 105 "double": "double", 106 # const char* aids usage of constexpr 107 "string": "const char*", 108 } 109 110 return typeMap[i_type] 111 112 113def gen_elog_hpp( 114 i_yaml_dir, i_test_dir, i_output_hpp, i_template_dir, i_elog_mako 115): 116 r""" 117 Read yaml file(s) under input yaml dir, grab the relevant data and call 118 the mako template to generate the output header file. 119 120 Description of arguments: 121 i_yaml_dir directory containing base error yaml files 122 i_test_dir directory containing test error yaml files 123 i_output_hpp name of the to be generated output hpp 124 i_template_dir directory containing error mako templates 125 i_elog_mako error mako template to render 126 """ 127 128 # Input parameters to mako template 129 errors = list() # Main error codes 130 error_msg = dict() # Error msg that corresponds to error code 131 error_lvl = dict() # Error code log level (debug, info, error, ...) 132 meta = dict() # The meta data names associated (ERRNO, FILE_NAME, ...) 133 meta_data = dict() # The meta data info (type, format) 134 parents = dict() 135 metadata_process = dict() # metadata that have the 'process' keyword set 136 137 # Verify the input mako file 138 template_path = os.path.join(i_template_dir, i_elog_mako) 139 if not (os.path.isfile(template_path)): 140 print("Cannot find input template file " + template_path) 141 exit(1) 142 template_path = os.path.abspath(template_path) 143 144 error_yamls = get_error_yaml_files(i_yaml_dir, i_test_dir) 145 146 for error_yaml in error_yamls: 147 # Verify the error yaml file 148 if not (os.path.isfile(error_yaml)): 149 print("Cannot find input yaml file " + error_yaml) 150 exit(1) 151 152 # Verify the metadata yaml file 153 meta_yaml = get_meta_yaml_file(error_yaml) 154 155 get_elog_data( 156 error_yaml, 157 meta_yaml, 158 error_yamls[error_yaml], 159 # Last arg is a tuple 160 ( 161 errors, 162 error_msg, 163 error_lvl, 164 meta, 165 meta_data, 166 parents, 167 metadata_process, 168 ), 169 ) 170 171 if not check_error_inheritance(errors, parents): 172 print("Error - failed to validate error inheritance") 173 exit(1) 174 175 errors = order_inherited_errors(errors, parents) 176 177 # Load the mako template and call it with the required data 178 yaml_dir = i_yaml_dir.strip("./") 179 yaml_dir = yaml_dir.strip("../") 180 template = Template(filename=template_path) 181 f = open(i_output_hpp, "w") 182 f.write( 183 template.render( 184 errors=errors, 185 error_msg=error_msg, 186 error_lvl=error_lvl, 187 meta=meta, 188 meta_data=meta_data, 189 parents=parents, 190 metadata_process=metadata_process, 191 ) 192 ) 193 f.close() 194 195 196def get_elog_data(i_elog_yaml, i_elog_meta_yaml, i_namespace, o_elog_data): 197 r""" 198 Parse the error and metadata yaml files in order to pull out 199 error metadata. 200 201 Use default values if metadata yaml file is not found. 202 203 Description of arguments: 204 i_elog_yaml error yaml file 205 i_elog_meta_yaml metadata yaml file 206 i_namespace namespace data 207 o_elog_data error metadata 208 """ 209 ( 210 errors, 211 error_msg, 212 error_lvl, 213 meta, 214 meta_data, 215 parents, 216 metadata_process, 217 ) = o_elog_data 218 ifile = yaml.safe_load(open(i_elog_yaml)) 219 220 # for all the errors in error yaml file 221 for error in ifile: 222 if "name" not in error: 223 print( 224 "Error - Did not find name in entry %s in file %s " 225 % (str(error), i_elog_yaml) 226 ) 227 exit(1) 228 fullname = i_namespace.replace("/", ".") + "." + error["name"] 229 errors.append(fullname) 230 231 if "description" in error: 232 error_msg[fullname] = error["description"].strip() 233 234 # set default values 235 error_lvl[fullname] = "ERR" 236 parents[fullname] = None 237 238 # check if meta data yaml file is found 239 if not os.path.isfile(i_elog_meta_yaml): 240 continue 241 mfile = yaml.safe_load(open(i_elog_meta_yaml)) 242 243 # Find the meta data entry 244 match = None 245 for meta_entry in mfile: 246 if meta_entry["name"] == error["name"]: 247 match = meta_entry 248 break 249 250 if match is None: 251 continue 252 253 error_lvl[fullname] = match.get("level", "ERR") 254 255 # Get 0th inherited error (current support - single inheritance) 256 if "inherits" in match: 257 parents[fullname] = match["inherits"][0] 258 259 # Put all errors in meta[] even the meta is empty 260 # so that child errors could inherits such error without meta 261 tmp_meta = [] 262 if "meta" in match: 263 # grab all the meta data fields and info 264 for i in match["meta"]: 265 str_short = i["str"].split("=")[0] 266 tmp_meta.append(str_short) 267 meta_data[str_short] = {} 268 meta_data[str_short]["str"] = i["str"] 269 meta_data[str_short]["str_short"] = str_short 270 meta_data[str_short]["type"] = get_cpp_type(i["type"]) 271 if ("process" in i) and (i["process"] is True): 272 metadata_process[str_short] = fullname + "." + str_short 273 meta[fullname] = tmp_meta 274 275 # Debug 276 # for i in errors: 277 # print "ERROR: " + errors[i] 278 # print " MSG: " + error_msg[errors[i]] 279 # print " LVL: " + error_lvl[errors[i]] 280 # print " META: " 281 # print meta[i] 282 283 284def main(i_args): 285 parser = OptionParser() 286 287 parser.add_option( 288 "-m", 289 "--mako", 290 dest="elog_mako", 291 default="elog-gen-template.mako.hpp", 292 help="input mako template file to use", 293 ) 294 295 parser.add_option( 296 "-o", 297 "--output", 298 dest="output_hpp", 299 default="elog-errors.hpp", 300 help="output hpp to generate, elog-errors.hpp default", 301 ) 302 303 parser.add_option( 304 "-y", 305 "--yamldir", 306 dest="yamldir", 307 default="None", 308 help="Base directory of yaml files to process", 309 ) 310 311 parser.add_option( 312 "-u", 313 "--testdir", 314 dest="testdir", 315 default="./tools/example/", 316 help="Unit test directory of yaml files to process", 317 ) 318 319 parser.add_option( 320 "-t", 321 "--templatedir", 322 dest="templatedir", 323 default="phosphor-logging/templates/", 324 help="Base directory of files to process", 325 ) 326 327 (options, args) = parser.parse_args(i_args) 328 329 gen_elog_hpp( 330 options.yamldir, 331 options.testdir, 332 options.output_hpp, 333 options.templatedir, 334 options.elog_mako, 335 ) 336 337 338# Only run if it's a script 339if __name__ == "__main__": 340 main(sys.argv[1:]) 341