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