| #!/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. | 
 | """ | 
 |  | 
 | import os | 
 | import sys | 
 | from optparse import OptionParser | 
 |  | 
 | from mako.template import Template | 
 |  | 
 | import yaml | 
 |  | 
 |  | 
 | 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:]) |