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