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 = "/".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 145 get_elog_data(error_yaml, 146 meta_yaml, 147 error_yamls[error_yaml], 148 # Last arg is a tuple 149 (errors, 150 error_msg, 151 error_lvl, 152 meta, 153 meta_data, 154 parents, 155 metadata_process)) 156 157 if(not check_error_inheritance(errors, parents)): 158 print("Error - failed to validate error inheritance") 159 exit(1) 160 161 errors = order_inherited_errors(errors, parents) 162 163 # Load the mako template and call it with the required data 164 yaml_dir = i_yaml_dir.strip("./") 165 yaml_dir = yaml_dir.strip("../") 166 template = Template(filename=template_path) 167 f = open(i_output_hpp, 'w') 168 f.write(template.render( 169 errors=errors, 170 error_msg=error_msg, 171 error_lvl=error_lvl, 172 meta=meta, 173 meta_data=meta_data, 174 parents=parents, 175 metadata_process=metadata_process)) 176 f.close() 177 178def get_elog_data(i_elog_yaml, 179 i_elog_meta_yaml, 180 i_namespace, 181 o_elog_data): 182 r""" 183 Parse the error and metadata yaml files in order to pull out 184 error metadata. 185 186 Use default values if metadata yaml file is not found. 187 188 Description of arguments: 189 i_elog_yaml error yaml file 190 i_elog_meta_yaml metadata yaml file 191 i_namespace namespace data 192 o_elog_data error metadata 193 """ 194 (errors, error_msg, error_lvl, meta, 195 meta_data, parents, metadata_process) = o_elog_data 196 ifile = yaml.safe_load(open(i_elog_yaml)) 197 198 #for all the errors in error yaml file 199 for error in ifile: 200 if 'name' not in error: 201 print("Error - Did not find name in entry %s in file %s " % ( 202 str(error), i_elog_yaml)) 203 exit(1) 204 fullname = i_namespace.replace('/', '.') + ('.') + error['name'] 205 errors.append(fullname) 206 207 if 'description' in error: 208 error_msg[fullname] = error['description'].strip() 209 210 #set default values 211 error_lvl[fullname] = "ERR" 212 parents[fullname] = None 213 214 #check if meta data yaml file is found 215 if not os.path.isfile(i_elog_meta_yaml): 216 continue 217 mfile = yaml.safe_load(open(i_elog_meta_yaml)) 218 219 # Find the meta data entry 220 match = None 221 for meta_entry in mfile: 222 if meta_entry['name'] == error['name']: 223 match = meta_entry 224 break 225 226 if match is None: 227 print("Error - Did not find error named %s in %s" % ( 228 error['name'], i_elog_meta_yaml)) 229 continue 230 231 error_lvl[fullname] = match.get('level', 'ERR') 232 233 # Get 0th inherited error (current support - single inheritance) 234 if 'inherits' in match: 235 parents[fullname] = match['inherits'][0] 236 237 # Put all errors in meta[] even the meta is empty 238 # so that child errors could inherits such error without meta 239 tmp_meta = [] 240 if 'meta' in match: 241 # grab all the meta data fields and info 242 for i in match['meta']: 243 str_short = i['str'].split('=')[0] 244 tmp_meta.append(str_short) 245 meta_data[str_short] = {} 246 meta_data[str_short]['str'] = i['str'] 247 meta_data[str_short]['str_short'] = str_short 248 meta_data[str_short]['type'] = get_cpp_type(i['type']) 249 if ('process' in i) and (True == i['process']): 250 metadata_process[str_short] = fullname + "." + str_short 251 meta[fullname] = tmp_meta 252 253 # Debug 254 # for i in errors: 255 # print "ERROR: " + errors[i] 256 # print " MSG: " + error_msg[errors[i]] 257 # print " LVL: " + error_lvl[errors[i]] 258 # print " META: " 259 # print meta[i] 260 261 262def main(i_args): 263 parser = OptionParser() 264 265 parser.add_option("-m", "--mako", dest="elog_mako", 266 default="elog-gen-template.mako.hpp", 267 help="input mako template file to use") 268 269 parser.add_option("-o", "--output", dest="output_hpp", 270 default="elog-errors.hpp", 271 help="output hpp to generate, elog-errors.hpp default") 272 273 parser.add_option("-y", "--yamldir", dest="yamldir", 274 default="None", 275 help="Base directory of yaml files to process") 276 277 parser.add_option("-u", "--testdir", dest="testdir", 278 default="./tools/example/", 279 help="Unit test directory of yaml files to process") 280 281 parser.add_option("-t", "--templatedir", dest="templatedir", 282 default="phosphor-logging/templates/", 283 help="Base directory of files to process") 284 285 (options, args) = parser.parse_args(i_args) 286 287 gen_elog_hpp(options.yamldir, 288 options.testdir, 289 options.output_hpp, 290 options.templatedir, 291 options.elog_mako) 292 293# Only run if it's a script 294if __name__ == '__main__': 295 main(sys.argv[1:]) 296