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