|  | #!/usr/bin/env python3 | 
|  |  | 
|  | r""" | 
|  | This script will parse error log yaml file(s) and generate | 
|  | a header file which will then be used by the error logging client and | 
|  | server to collect and validate the error information generated by the | 
|  | openbmc software components. | 
|  |  | 
|  | This code uses a mako template to provide the basic template of the header | 
|  | file we're going to generate.  We then call it with information from the | 
|  | yaml to generate the header file. | 
|  | """ | 
|  |  | 
|  | from mako.template import Template | 
|  | from optparse import OptionParser | 
|  | import yaml | 
|  | import sys | 
|  | import os | 
|  |  | 
|  |  | 
|  | def order_inherited_errors(i_errors, i_parents): | 
|  | # the ordered list of errors | 
|  | errors = list() | 
|  | has_inheritance = False | 
|  | for error in i_errors: | 
|  | if(i_parents[error] is not None): | 
|  | has_inheritance = True | 
|  | break | 
|  |  | 
|  | if(has_inheritance): | 
|  | # Order the error codes list such that an error is never placed | 
|  | # before it's parent. This way generated code can ensure parent | 
|  | # definitions precede child error definitions. | 
|  | while(len(errors) < len(i_errors)): | 
|  | for error in i_errors: | 
|  | if(error in errors): | 
|  | # already ordererd | 
|  | continue | 
|  | if((not i_parents[error]) or (i_parents[error] in errors)): | 
|  | # parent present, or has no parent, either way this error | 
|  | # can be added | 
|  | errors.append(error) | 
|  | else: | 
|  | # no inherited errors | 
|  | errors = i_errors | 
|  |  | 
|  | return errors | 
|  |  | 
|  |  | 
|  | def check_error_inheritance(i_errors, i_parents): | 
|  | for error in i_errors: | 
|  | if(i_parents[error] and (i_parents[error] not in i_errors)): | 
|  | print(error + " inherits " + i_parents[error] + | 
|  | " but the latter is not defined") | 
|  | return False | 
|  | return True | 
|  |  | 
|  |  | 
|  | # Return the yaml files with their directory structure plus the file name | 
|  | # without the yaml extension, which will be used to set the namespaces. | 
|  | # Ex: file xyz/openbmc_project/Error/Callout/Device.errors.yaml | 
|  | # will have namespce xyz/openbmc_project/Error/Callout/Device | 
|  | def get_error_yaml_files(i_yaml_dir, i_test_dir): | 
|  | yaml_files = dict() | 
|  | if i_yaml_dir != "None": | 
|  | for root, dirs, files in os.walk(i_yaml_dir): | 
|  | for files in \ | 
|  | [file for file in files if file.endswith('.errors.yaml')]: | 
|  | splitdir = root.split(i_yaml_dir)[1] + "/" + files[:-12] | 
|  | if splitdir.startswith("/"): | 
|  | splitdir = splitdir[1:] | 
|  | yaml_files[(os.path.join(root, files))] = splitdir | 
|  | for root, dirs, files in os.walk(i_test_dir): | 
|  | for files in [file for file in files if file.endswith('.errors.yaml')]: | 
|  | splitdir = root.split(i_test_dir)[1] + "/" + files[:-12] | 
|  | yaml_files[(os.path.join(root, files))] = splitdir | 
|  | return yaml_files | 
|  |  | 
|  |  | 
|  | def get_meta_yaml_file(i_error_yaml_file): | 
|  | # the meta data will be defined in file name where we replace | 
|  | # <Interface>.errors.yaml with <Interface>.metadata.yaml | 
|  | meta_yaml = i_error_yaml_file.replace("errors", "metadata") | 
|  | return meta_yaml | 
|  |  | 
|  |  | 
|  | def get_cpp_type(i_type): | 
|  | typeMap = { | 
|  | 'boolean': 'bool', | 
|  | 'int8': 'int8_t', | 
|  | 'int16': 'int16_t', | 
|  | 'int32': 'int32_t', | 
|  | 'int64': 'int64_t', | 
|  | 'uint8': 'uint8_t', | 
|  | 'uint16': 'uint16_t', | 
|  | 'uint32': 'uint32_t', | 
|  | 'uint64': 'uint64_t', | 
|  | 'double': 'double', | 
|  | # const char* aids usage of constexpr | 
|  | 'string': 'const char*'} | 
|  |  | 
|  | return typeMap[i_type] | 
|  |  | 
|  |  | 
|  | def gen_elog_hpp(i_yaml_dir, i_test_dir, i_output_hpp, | 
|  | i_template_dir, i_elog_mako): | 
|  | r""" | 
|  | Read  yaml file(s) under input yaml dir, grab the relevant data and call | 
|  | the mako template to generate the output header file. | 
|  |  | 
|  | Description of arguments: | 
|  | i_yaml_dir                  directory containing base error yaml files | 
|  | i_test_dir                  directory containing test error yaml files | 
|  | i_output_hpp                name of the to be generated output hpp | 
|  | i_template_dir              directory containing error mako templates | 
|  | i_elog_mako                 error mako template to render | 
|  | """ | 
|  |  | 
|  | # Input parameters to mako template | 
|  | errors = list()  # Main error codes | 
|  | error_msg = dict()  # Error msg that corresponds to error code | 
|  | error_lvl = dict()  # Error code log level (debug, info, error, ...) | 
|  | meta = dict()  # The meta data names associated (ERRNO, FILE_NAME, ...) | 
|  | meta_data = dict()  # The meta data info (type, format) | 
|  | parents = dict() | 
|  | metadata_process = dict()  # metadata that have the 'process' keyword set | 
|  |  | 
|  | # Verify the input mako file | 
|  | template_path = os.path.join(i_template_dir, i_elog_mako) | 
|  | if (not (os.path.isfile(template_path))): | 
|  | print("Cannot find input template file " + template_path) | 
|  | exit(1) | 
|  | template_path = os.path.abspath(template_path) | 
|  |  | 
|  | error_yamls = get_error_yaml_files(i_yaml_dir, i_test_dir) | 
|  |  | 
|  | for error_yaml in error_yamls: | 
|  | # Verify the error yaml file | 
|  | if (not (os.path.isfile(error_yaml))): | 
|  | print("Cannot find input yaml file " + error_yaml) | 
|  | exit(1) | 
|  |  | 
|  | # Verify the metadata yaml file | 
|  | meta_yaml = get_meta_yaml_file(error_yaml) | 
|  |  | 
|  | get_elog_data(error_yaml, | 
|  | meta_yaml, | 
|  | error_yamls[error_yaml], | 
|  | # Last arg is a tuple | 
|  | (errors, | 
|  | error_msg, | 
|  | error_lvl, | 
|  | meta, | 
|  | meta_data, | 
|  | parents, | 
|  | metadata_process)) | 
|  |  | 
|  | if(not check_error_inheritance(errors, parents)): | 
|  | print("Error - failed to validate error inheritance") | 
|  | exit(1) | 
|  |  | 
|  | errors = order_inherited_errors(errors, parents) | 
|  |  | 
|  | # Load the mako template and call it with the required data | 
|  | yaml_dir = i_yaml_dir.strip("./") | 
|  | yaml_dir = yaml_dir.strip("../") | 
|  | template = Template(filename=template_path) | 
|  | f = open(i_output_hpp, 'w') | 
|  | f.write(template.render( | 
|  | errors=errors, | 
|  | error_msg=error_msg, | 
|  | error_lvl=error_lvl, | 
|  | meta=meta, | 
|  | meta_data=meta_data, | 
|  | parents=parents, | 
|  | metadata_process=metadata_process)) | 
|  | f.close() | 
|  |  | 
|  |  | 
|  | def get_elog_data(i_elog_yaml, | 
|  | i_elog_meta_yaml, | 
|  | i_namespace, | 
|  | o_elog_data): | 
|  | r""" | 
|  | Parse the error and metadata yaml files in order to pull out | 
|  | error metadata. | 
|  |  | 
|  | Use default values if metadata yaml file is not found. | 
|  |  | 
|  | Description of arguments: | 
|  | i_elog_yaml                 error yaml file | 
|  | i_elog_meta_yaml            metadata yaml file | 
|  | i_namespace                 namespace data | 
|  | o_elog_data                 error metadata | 
|  | """ | 
|  | (errors, error_msg, error_lvl, meta, | 
|  | meta_data, parents, metadata_process) = o_elog_data | 
|  | ifile = yaml.safe_load(open(i_elog_yaml)) | 
|  |  | 
|  | # for all the errors in error yaml file | 
|  | for error in ifile: | 
|  | if 'name' not in error: | 
|  | print("Error - Did not find name in entry %s in file %s " % ( | 
|  | str(error), i_elog_yaml)) | 
|  | exit(1) | 
|  | fullname = i_namespace.replace('/', '.') + ('.') + error['name'] | 
|  | errors.append(fullname) | 
|  |  | 
|  | if 'description' in error: | 
|  | error_msg[fullname] = error['description'].strip() | 
|  |  | 
|  | # set default values | 
|  | error_lvl[fullname] = "ERR" | 
|  | parents[fullname] = None | 
|  |  | 
|  | # check if meta data yaml file is found | 
|  | if not os.path.isfile(i_elog_meta_yaml): | 
|  | continue | 
|  | mfile = yaml.safe_load(open(i_elog_meta_yaml)) | 
|  |  | 
|  | # Find the meta data entry | 
|  | match = None | 
|  | for meta_entry in mfile: | 
|  | if meta_entry['name'] == error['name']: | 
|  | match = meta_entry | 
|  | break | 
|  |  | 
|  | if match is None: | 
|  | continue | 
|  |  | 
|  | error_lvl[fullname] = match.get('level', 'ERR') | 
|  |  | 
|  | # Get 0th inherited error (current support - single inheritance) | 
|  | if 'inherits' in match: | 
|  | parents[fullname] = match['inherits'][0] | 
|  |  | 
|  | # Put all errors in meta[] even the meta is empty | 
|  | # so that child errors could inherits such error without meta | 
|  | tmp_meta = [] | 
|  | if 'meta' in match: | 
|  | # grab all the meta data fields and info | 
|  | for i in match['meta']: | 
|  | str_short = i['str'].split('=')[0] | 
|  | tmp_meta.append(str_short) | 
|  | meta_data[str_short] = {} | 
|  | meta_data[str_short]['str'] = i['str'] | 
|  | meta_data[str_short]['str_short'] = str_short | 
|  | meta_data[str_short]['type'] = get_cpp_type(i['type']) | 
|  | if ('process' in i) and (i['process'] is True): | 
|  | metadata_process[str_short] = fullname + "." + str_short | 
|  | meta[fullname] = tmp_meta | 
|  |  | 
|  | # Debug | 
|  | # for i in errors: | 
|  | #   print "ERROR: " + errors[i] | 
|  | #   print " MSG:  " + error_msg[errors[i]] | 
|  | #   print " LVL:  " + error_lvl[errors[i]] | 
|  | #   print " META: " | 
|  | #   print meta[i] | 
|  |  | 
|  |  | 
|  | def main(i_args): | 
|  | parser = OptionParser() | 
|  |  | 
|  | parser.add_option("-m", "--mako", dest="elog_mako", | 
|  | default="elog-gen-template.mako.hpp", | 
|  | help="input mako template file to use") | 
|  |  | 
|  | parser.add_option("-o", "--output", dest="output_hpp", | 
|  | default="elog-errors.hpp", | 
|  | help="output hpp to generate, elog-errors.hpp default") | 
|  |  | 
|  | parser.add_option("-y", "--yamldir", dest="yamldir", | 
|  | default="None", | 
|  | help="Base directory of yaml files to process") | 
|  |  | 
|  | parser.add_option("-u", "--testdir", dest="testdir", | 
|  | default="./tools/example/", | 
|  | help="Unit test directory of yaml files to process") | 
|  |  | 
|  | parser.add_option("-t", "--templatedir", dest="templatedir", | 
|  | default="phosphor-logging/templates/", | 
|  | help="Base directory of files to process") | 
|  |  | 
|  | (options, args) = parser.parse_args(i_args) | 
|  |  | 
|  | gen_elog_hpp(options.yamldir, | 
|  | options.testdir, | 
|  | options.output_hpp, | 
|  | options.templatedir, | 
|  | options.elog_mako) | 
|  |  | 
|  |  | 
|  | # Only run if it's a script | 
|  | if __name__ == '__main__': | 
|  | main(sys.argv[1:]) |