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