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 # Verify the input mako file 129 template_path = os.path.join(i_template_dir, i_elog_mako) 130 if (not (os.path.isfile(template_path))): 131 print("Cannot find input template file " + template_path) 132 exit(1) 133 template_path = os.path.abspath(template_path) 134 135 error_yamls = get_error_yaml_files(i_yaml_dir, i_test_dir) 136 137 for error_yaml in error_yamls: 138 # Verify the error yaml file 139 if (not (os.path.isfile(error_yaml))): 140 print("Cannot find input yaml file " + error_yaml) 141 exit(1) 142 143 # Verify the metadata yaml file 144 meta_yaml = get_meta_yaml_file(error_yaml) 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 179 180def get_elog_data(i_elog_yaml, 181 i_elog_meta_yaml, 182 i_namespace, 183 o_elog_data): 184 r""" 185 Parse the error and metadata yaml files in order to pull out 186 error metadata. 187 188 Use default values if metadata yaml file is not found. 189 190 Description of arguments: 191 i_elog_yaml error yaml file 192 i_elog_meta_yaml metadata yaml file 193 i_namespace namespace data 194 o_elog_data error metadata 195 """ 196 (errors, error_msg, error_lvl, meta, 197 meta_data, parents, metadata_process) = o_elog_data 198 ifile = yaml.safe_load(open(i_elog_yaml)) 199 200 # for all the errors in error yaml file 201 for error in ifile: 202 if 'name' not in error: 203 print("Error - Did not find name in entry %s in file %s " % ( 204 str(error), i_elog_yaml)) 205 exit(1) 206 fullname = i_namespace.replace('/', '.') + ('.') + error['name'] 207 errors.append(fullname) 208 209 if 'description' in error: 210 error_msg[fullname] = error['description'].strip() 211 212 # set default values 213 error_lvl[fullname] = "ERR" 214 parents[fullname] = None 215 216 # check if meta data yaml file is found 217 if not os.path.isfile(i_elog_meta_yaml): 218 continue 219 mfile = yaml.safe_load(open(i_elog_meta_yaml)) 220 221 # Find the meta data entry 222 match = None 223 for meta_entry in mfile: 224 if meta_entry['name'] == error['name']: 225 match = meta_entry 226 break 227 228 if match is None: 229 print("Error - Did not find error named %s in %s" % ( 230 error['name'], i_elog_meta_yaml)) 231 continue 232 233 error_lvl[fullname] = match.get('level', 'ERR') 234 235 # Get 0th inherited error (current support - single inheritance) 236 if 'inherits' in match: 237 parents[fullname] = match['inherits'][0] 238 239 # Put all errors in meta[] even the meta is empty 240 # so that child errors could inherits such error without meta 241 tmp_meta = [] 242 if 'meta' in match: 243 # grab all the meta data fields and info 244 for i in match['meta']: 245 str_short = i['str'].split('=')[0] 246 tmp_meta.append(str_short) 247 meta_data[str_short] = {} 248 meta_data[str_short]['str'] = i['str'] 249 meta_data[str_short]['str_short'] = str_short 250 meta_data[str_short]['type'] = get_cpp_type(i['type']) 251 if ('process' in i) and (i['process'] is True): 252 metadata_process[str_short] = fullname + "." + str_short 253 meta[fullname] = tmp_meta 254 255 # Debug 256 # for i in errors: 257 # print "ERROR: " + errors[i] 258 # print " MSG: " + error_msg[errors[i]] 259 # print " LVL: " + error_lvl[errors[i]] 260 # print " META: " 261 # print meta[i] 262 263 264def main(i_args): 265 parser = OptionParser() 266 267 parser.add_option("-m", "--mako", dest="elog_mako", 268 default="elog-gen-template.mako.hpp", 269 help="input mako template file to use") 270 271 parser.add_option("-o", "--output", dest="output_hpp", 272 default="elog-errors.hpp", 273 help="output hpp to generate, elog-errors.hpp default") 274 275 parser.add_option("-y", "--yamldir", dest="yamldir", 276 default="None", 277 help="Base directory of yaml files to process") 278 279 parser.add_option("-u", "--testdir", dest="testdir", 280 default="./tools/example/", 281 help="Unit test directory of yaml files to process") 282 283 parser.add_option("-t", "--templatedir", dest="templatedir", 284 default="phosphor-logging/templates/", 285 help="Base directory of files to process") 286 287 (options, args) = parser.parse_args(i_args) 288 289 gen_elog_hpp(options.yamldir, 290 options.testdir, 291 options.output_hpp, 292 options.templatedir, 293 options.elog_mako) 294 295 296# Only run if it's a script 297if __name__ == '__main__': 298 main(sys.argv[1:]) 299