|  | #!/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:]) |