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