blob: 4fd6fcc3043f18696fafddf9e1c8ba59baeffe42 [file] [log] [blame]
Brad Bishopbf066a62016-10-19 08:09:44 -04001#!/usr/bin/env python
2
Brad Bishop22cfbe62016-11-30 13:25:10 -05003'''Phosphor Inventory Manager YAML parser and code generator.
4
5The parser workflow is broken down as follows:
6 1 - Import YAML files as native python type(s) instance(s).
7 2 - Create an instance of the Everything class from the
8 native python type instance(s) with the Everything.load
9 method.
10 3 - The Everything class constructor orchestrates conversion of the
11 native python type(s) instances(s) to render helper types.
12 Each render helper type constructor imports its attributes
13 from the native python type(s) instances(s).
14 4 - Present the converted YAML to the command processing method
15 requested by the script user.
16'''
17
Brad Bishopbf066a62016-10-19 08:09:44 -040018import sys
19import os
Brad Bishopbf066a62016-10-19 08:09:44 -040020import argparse
Brad Bishopcfb3c892016-11-12 11:43:37 -050021import subprocess
Brad Bishop22cfbe62016-11-30 13:25:10 -050022import yaml
23import mako.lookup
24import sdbusplus.property
25from sdbusplus.namedelement import NamedElement
26from sdbusplus.renderer import Renderer
Brad Bishopbf066a62016-10-19 08:09:44 -040027
28
Brad Bishop9b5a12f2017-01-21 14:42:11 -050029def cppTypeName(yaml_type):
30 ''' Convert yaml types to cpp types.'''
31 return sdbusplus.property.Property(type=yaml_type).cppTypeName
32
33
Brad Bishop22cfbe62016-11-30 13:25:10 -050034class Interface(list):
35 '''Provide various interface transformations.'''
36
37 def __init__(self, iface):
38 super(Interface, self).__init__(iface.split('.'))
39
40 def namespace(self):
41 '''Represent as an sdbusplus namespace.'''
42 return '::'.join(['sdbusplus'] + self[:-1] + ['server', self[-1]])
43
44 def header(self):
45 '''Represent as an sdbusplus server binding header.'''
46 return os.sep.join(self + ['server.hpp'])
47
48 def __str__(self):
49 return '.'.join(self)
Brad Bishopbf066a62016-10-19 08:09:44 -040050
Brad Bishopcfb3c892016-11-12 11:43:37 -050051
Brad Bishop9b5a12f2017-01-21 14:42:11 -050052class Indent(object):
53 '''Help templates be depth agnostic.'''
54
55 def __init__(self, depth=0):
56 self.depth = depth
57
58 def __add__(self, depth):
59 return Indent(self.depth + depth)
60
61 def __call__(self, depth):
62 '''Render an indent at the current depth plus depth.'''
63 return 4*' '*(depth + self.depth)
64
65
66class Template(NamedElement):
67 '''Associate a template name with its namespace.'''
68
69 def __init__(self, **kw):
70 self.namespace = kw.pop('namespace')
71 super(Template, self).__init__(**kw)
72
73 def qualified(self):
74 return '::'.join(self.namespace + [self.name])
75
76
77class Quote(object):
78 '''Decorate an argument by quoting it.'''
79
80 def __call__(self, arg):
81 return '"{0}"'.format(arg)
82
83
84class Cast(object):
85 '''Decorate an argument by casting it.'''
86
87 def __init__(self, cast, target):
88 '''cast is the cast type (static, const, etc...).
89 target is the cast target type.'''
90 self.cast = cast
91 self.target = target
92
93 def __call__(self, arg):
94 return '{0}_cast<{1}>({2})'.format(self.cast, self.target, arg)
95
96
97class Literal(object):
98 '''Decorate an argument with a literal operator.'''
99
100 literals = {
101 'string': 's',
102 'int64': 'll',
103 'uint64': 'ull'
104 }
105
106 def __init__(self, type):
107 self.type = type
108
109 def __call__(self, arg):
110 literal = self.literals.get(self.type)
111
112 if literal:
113 return '{0}{1}'.format(arg, literal)
114
115 return arg
116
117
Brad Bishop75800cf2017-01-21 15:24:18 -0500118class Argument(NamedElement, Renderer):
119 '''Define argument type inteface.'''
120
121 def __init__(self, **kw):
122 self.type = kw.pop('type', None)
123 super(Argument, self).__init__(**kw)
124
125 def argument(self, loader, indent):
126 raise NotImplementedError
127
128
129class TrivialArgument(Argument):
130 '''Non-array type arguments.'''
Brad Bishop14a9fe52016-11-12 12:51:26 -0500131
Brad Bishop22cfbe62016-11-30 13:25:10 -0500132 def __init__(self, **kw):
133 self.value = kw.pop('value')
Brad Bishop75800cf2017-01-21 15:24:18 -0500134 self.decorators = kw.pop('decorators', [])
135 if kw.get('type', None) == 'string':
136 self.decorators.insert(0, Quote())
137
Brad Bishop75800cf2017-01-21 15:24:18 -0500138 super(TrivialArgument, self).__init__(**kw)
139
140 def argument(self, loader, indent):
141 a = str(self.value)
142 for d in self.decorators:
143 a = d(a)
144
145 return a
Brad Bishop14a9fe52016-11-12 12:51:26 -0500146
Brad Bishop14a9fe52016-11-12 12:51:26 -0500147
Brad Bishop9b5a12f2017-01-21 14:42:11 -0500148class InitializerList(Argument):
149 '''Initializer list arguments.'''
150
151 def __init__(self, **kw):
152 self.values = kw.pop('values')
153 super(InitializerList, self).__init__(**kw)
154
155 def argument(self, loader, indent):
156 return self.render(
157 loader,
158 'argument.mako.cpp',
159 arg=self,
160 indent=indent)
161
162
Brad Bishop75800cf2017-01-21 15:24:18 -0500163class DbusSignature(Argument):
164 '''DBus signature arguments.'''
165
166 def __init__(self, **kw):
167 self.sig = {x: y for x, y in kw.iteritems()}
168 kw.clear()
169 super(DbusSignature, self).__init__(**kw)
170
171 def argument(self, loader, indent):
172 return self.render(
173 loader,
174 'signature.mako.cpp',
175 signature=self,
176 indent=indent)
177
178
Brad Bishopc93bcc92017-01-21 16:23:39 -0500179class MethodCall(Argument):
Brad Bishop22cfbe62016-11-30 13:25:10 -0500180 '''Render syntatically correct c++ method calls.'''
181
182 def __init__(self, **kw):
183 self.namespace = kw.pop('namespace', [])
Brad Bishopc93bcc92017-01-21 16:23:39 -0500184 self.templates = kw.pop('templates', [])
185 self.args = kw.pop('args', [])
Brad Bishop22cfbe62016-11-30 13:25:10 -0500186 super(MethodCall, self).__init__(**kw)
187
Brad Bishopcab2bdd2017-01-21 15:00:54 -0500188 def call(self, loader, indent):
189 return self.render(
190 loader,
191 'method.mako.cpp',
192 method=self,
193 indent=indent)
194
195 def argument(self, loader, indent):
196 return self.call(loader, indent)
197
Brad Bishop14a9fe52016-11-12 12:51:26 -0500198
Brad Bishop9b5a12f2017-01-21 14:42:11 -0500199class Vector(MethodCall):
200 '''Convenience type for vectors.'''
201
202 def __init__(self, **kw):
203 kw['name'] = 'vector'
204 kw['namespace'] = ['std']
205 kw['args'] = [InitializerList(values=kw.pop('args'))]
206 super(Vector, self).__init__(**kw)
207
208
209class Wrapper(MethodCall):
210 '''Convenience type for functions that wrap other functions.'''
211
212 def __init__(self, **kw):
213 m = MethodCall(
214 name=kw.pop('name'),
215 namespace=kw.pop('namespace', []),
216 templates=kw.pop('templates', []),
217 args=kw.pop('args', []))
218
219 kw['name'] = kw.pop('wrapper_name')
220 kw['namespace'] = kw.pop('wrapper_namespace', [])
221 kw['args'] = [m]
222 super(Wrapper, self).__init__(**kw)
223
224
Brad Bishopc93bcc92017-01-21 16:23:39 -0500225class Filter(Wrapper):
226 '''Convenience type for filters'''
Brad Bishopbf066a62016-10-19 08:09:44 -0400227
Brad Bishop22cfbe62016-11-30 13:25:10 -0500228 def __init__(self, **kw):
Brad Bishopc93bcc92017-01-21 16:23:39 -0500229 kw['wrapper_name'] = 'make_filter'
230 kw['wrapper_namespace'] = ['details']
Brad Bishop22cfbe62016-11-30 13:25:10 -0500231 kw['namespace'] = ['filters']
232 super(Filter, self).__init__(**kw)
Brad Bishopbf066a62016-10-19 08:09:44 -0400233
Brad Bishop0a6a4792016-11-12 12:10:07 -0500234
Brad Bishopc93bcc92017-01-21 16:23:39 -0500235class Action(Wrapper):
236 '''Convenience type for actions'''
Brad Bishop561a5652016-10-26 21:13:32 -0500237
Brad Bishop22cfbe62016-11-30 13:25:10 -0500238 def __init__(self, **kw):
Brad Bishopc93bcc92017-01-21 16:23:39 -0500239 kw['wrapper_name'] = 'make_action'
240 kw['wrapper_namespace'] = ['details']
Brad Bishop22cfbe62016-11-30 13:25:10 -0500241 kw['namespace'] = ['actions']
242 super(Action, self).__init__(**kw)
Brad Bishop92665b22016-10-26 20:51:16 -0500243
Brad Bishopcfb3c892016-11-12 11:43:37 -0500244
Brad Bishopdb92c282017-01-21 23:44:28 -0500245class CreateObjects(Action):
246 '''Assemble a createObjects action.'''
247
248 def __init__(self, **kw):
249 objs = []
250
251 for path, interfaces in kw.pop('objs').iteritems():
252 key_o = TrivialArgument(
253 value=path,
254 type='string',
255 decorators=[Literal('string')])
256 value_i = []
257
258 for interface, properties, in interfaces.iteritems():
259 key_i = TrivialArgument(value=interface, type='string')
260 value_p = []
261
262 for prop, value in properties.iteritems():
263 key_p = TrivialArgument(value=prop, type='string')
264 value_v = TrivialArgument(
265 decorators=[Literal(value.get('type', None))],
266 **value)
267 value_p.append(InitializerList(values=[key_p, value_v]))
268
269 value_p = InitializerList(values=value_p)
270 value_i.append(InitializerList(values=[key_i, value_p]))
271
272 value_i = InitializerList(values=value_i)
273 objs.append(InitializerList(values=[key_o, value_i]))
274
275 kw['args'] = [InitializerList(values=objs)]
276 super(CreateObjects, self).__init__(**kw)
277
278
Brad Bishop7b7e7122017-01-21 21:21:46 -0500279class DestroyObjects(Action):
Brad Bishopc93bcc92017-01-21 16:23:39 -0500280 '''Assemble a destroyObject action.'''
Brad Bishop22cfbe62016-11-30 13:25:10 -0500281
282 def __init__(self, **kw):
Brad Bishop7b7e7122017-01-21 21:21:46 -0500283 values = [{'value': x, 'type': 'string'} for x in kw.pop('paths')]
284 args = [InitializerList(
285 values=[TrivialArgument(**x) for x in values])]
Brad Bishopc93bcc92017-01-21 16:23:39 -0500286 kw['args'] = args
Brad Bishop7b7e7122017-01-21 21:21:46 -0500287 super(DestroyObjects, self).__init__(**kw)
Brad Bishop22cfbe62016-11-30 13:25:10 -0500288
289
Brad Bishope2e402f2016-11-30 18:00:17 -0500290class SetProperty(Action):
Brad Bishopc93bcc92017-01-21 16:23:39 -0500291 '''Assemble a setProperty action.'''
Brad Bishope2e402f2016-11-30 18:00:17 -0500292
293 def __init__(self, **kw):
Brad Bishopc93bcc92017-01-21 16:23:39 -0500294 args = []
295
296 value = kw.pop('value')
297 prop = kw.pop('property')
298 iface = kw.pop('interface')
299 iface = Interface(iface)
300 namespace = iface.namespace().split('::')[:-1]
301 name = iface[-1]
302 t = Template(namespace=namespace, name=iface[-1])
303
Brad Bishope2e402f2016-11-30 18:00:17 -0500304 member = '&%s' % '::'.join(
Brad Bishopc93bcc92017-01-21 16:23:39 -0500305 namespace + [name, NamedElement(name=prop).camelCase])
306 member_type = cppTypeName(value['type'])
307 member_cast = '{0} ({1}::*)({0})'.format(member_type, t.qualified())
Brad Bishope2e402f2016-11-30 18:00:17 -0500308
Brad Bishopc93bcc92017-01-21 16:23:39 -0500309 args.append(TrivialArgument(value=kw.pop('path'), type='string'))
310 args.append(TrivialArgument(value=str(iface), type='string'))
311 args.append(TrivialArgument(
312 value=member, decorators=[Cast('static', member_cast)]))
313 args.append(TrivialArgument(**value))
Brad Bishope2e402f2016-11-30 18:00:17 -0500314
Brad Bishopc93bcc92017-01-21 16:23:39 -0500315 kw['templates'] = [Template(name=name, namespace=namespace)]
316 kw['args'] = args
Brad Bishope2e402f2016-11-30 18:00:17 -0500317 super(SetProperty, self).__init__(**kw)
318
319
Brad Bishop22cfbe62016-11-30 13:25:10 -0500320class PropertyChanged(Filter):
Brad Bishopc93bcc92017-01-21 16:23:39 -0500321 '''Assemble a propertyChanged filter.'''
Brad Bishop22cfbe62016-11-30 13:25:10 -0500322
323 def __init__(self, **kw):
Brad Bishopc93bcc92017-01-21 16:23:39 -0500324 args = []
325 args.append(TrivialArgument(value=kw.pop('interface'), type='string'))
326 args.append(TrivialArgument(value=kw.pop('property'), type='string'))
327 args.append(TrivialArgument(
328 decorators=[
329 Literal(kw['value'].get('type', None))], **kw.pop('value')))
330 kw['args'] = args
Brad Bishop22cfbe62016-11-30 13:25:10 -0500331 super(PropertyChanged, self).__init__(**kw)
332
333
Brad Bishop040e18b2017-01-21 22:04:00 -0500334class PropertyIs(Filter):
335 '''Assemble a propertyIs filter.'''
336
337 def __init__(self, **kw):
338 args = []
339 args.append(TrivialArgument(value=kw.pop('path'), type='string'))
340 args.append(TrivialArgument(value=kw.pop('interface'), type='string'))
341 args.append(TrivialArgument(value=kw.pop('property'), type='string'))
342 args.append(TrivialArgument(
343 decorators=[
344 Literal(kw['value'].get('type', None))], **kw.pop('value')))
345
346 service = kw.pop('service', None)
347 if service:
348 args.append(TrivialArgument(value=service, type='string'))
349
350 kw['args'] = args
351 super(PropertyIs, self).__init__(**kw)
352
353
Brad Bishopc93bcc92017-01-21 16:23:39 -0500354class Event(MethodCall):
355 '''Assemble an inventory manager event.'''
Brad Bishop22cfbe62016-11-30 13:25:10 -0500356
357 action_map = {
Brad Bishop7b7e7122017-01-21 21:21:46 -0500358 'destroyObjects': DestroyObjects,
Brad Bishopdb92c282017-01-21 23:44:28 -0500359 'createObjects': CreateObjects,
Brad Bishope2e402f2016-11-30 18:00:17 -0500360 'setProperty': SetProperty,
Brad Bishopcfb3c892016-11-12 11:43:37 -0500361 }
362
Brad Bishopc93bcc92017-01-21 16:23:39 -0500363 filter_map = {
364 'propertyChangedTo': PropertyChanged,
Brad Bishop040e18b2017-01-21 22:04:00 -0500365 'propertyIs': PropertyIs,
Brad Bishopc93bcc92017-01-21 16:23:39 -0500366 }
367
Brad Bishop22cfbe62016-11-30 13:25:10 -0500368 def __init__(self, **kw):
Brad Bishopc93bcc92017-01-21 16:23:39 -0500369 self.summary = kw.pop('name')
370
371 filters = [
372 self.filter_map[x['name']](**x) for x in kw.pop('filters', [])]
Brad Bishop064c94a2017-01-21 21:33:30 -0500373 filters = Vector(
374 templates=[Template(name='FilterBasePtr', namespace=['details'])],
375 args=filters)
Brad Bishopc93bcc92017-01-21 16:23:39 -0500376
377 event = MethodCall(
378 name='make_shared',
379 namespace=['std'],
380 templates=[Template(
381 name=kw.pop('event'),
382 namespace=kw.pop('event_namespace', []))],
Brad Bishop064c94a2017-01-21 21:33:30 -0500383 args=kw.pop('event_args', []) + [filters])
Brad Bishopc93bcc92017-01-21 16:23:39 -0500384
385 events = Vector(
386 templates=[Template(name='EventBasePtr', namespace=['details'])],
387 args=[event])
388
389 action_type = Template(name='ActionBasePtr', namespace=['details'])
390 action_args = [
391 self.action_map[x['name']](**x) for x in kw.pop('actions', [])]
392 actions = Vector(
393 templates=[action_type],
394 args=action_args)
395
396 kw['name'] = 'make_tuple'
397 kw['namespace'] = ['std']
398 kw['args'] = [events, actions]
Brad Bishop22cfbe62016-11-30 13:25:10 -0500399 super(Event, self).__init__(**kw)
Brad Bishopcfb3c892016-11-12 11:43:37 -0500400
Brad Bishopcfb3c892016-11-12 11:43:37 -0500401
Brad Bishop22cfbe62016-11-30 13:25:10 -0500402class MatchEvent(Event):
403 '''Associate one or more dbus signal match signatures with
404 a filter.'''
405
Brad Bishop22cfbe62016-11-30 13:25:10 -0500406 def __init__(self, **kw):
Brad Bishopc93bcc92017-01-21 16:23:39 -0500407 kw['event'] = 'DbusSignal'
408 kw['event_namespace'] = ['details']
409 kw['event_args'] = [
410 DbusSignature(**x) for x in kw.pop('signatures', [])]
411
Brad Bishop22cfbe62016-11-30 13:25:10 -0500412 super(MatchEvent, self).__init__(**kw)
413
414
Brad Bishop828df832017-01-21 22:20:43 -0500415class StartupEvent(Event):
416 '''Assemble a startup event.'''
417
418 def __init__(self, **kw):
419 kw['event'] = 'StartupEvent'
420 kw['event_namespace'] = ['details']
421 super(StartupEvent, self).__init__(**kw)
422
423
Brad Bishop22cfbe62016-11-30 13:25:10 -0500424class Everything(Renderer):
425 '''Parse/render entry point.'''
426
427 class_map = {
428 'match': MatchEvent,
Brad Bishop828df832017-01-21 22:20:43 -0500429 'startup': StartupEvent,
Brad Bishop22cfbe62016-11-30 13:25:10 -0500430 }
431
432 @staticmethod
433 def load(args):
Brad Bishop22cfbe62016-11-30 13:25:10 -0500434 # Aggregate all the event YAML in the events.d directory
435 # into a single list of events.
436
Brad Bishop22cfbe62016-11-30 13:25:10 -0500437 events = []
Brad Bishopa6fcd562017-02-03 11:00:27 -0500438 events_dir = os.path.join(args.inputdir, 'events.d')
439
440 if os.path.exists(events_dir):
441 yaml_files = filter(
442 lambda x: x.endswith('.yaml'),
443 os.listdir(events_dir))
444
445 for x in yaml_files:
446 with open(os.path.join(events_dir, x), 'r') as fd:
447 for e in yaml.safe_load(fd.read()).get('events', {}):
448 events.append(e)
Brad Bishop22cfbe62016-11-30 13:25:10 -0500449
450 return Everything(
451 *events,
452 interfaces=Everything.get_interfaces(args))
453
454 @staticmethod
455 def get_interfaces(args):
456 '''Aggregate all the interface YAML in the interfaces.d
457 directory into a single list of interfaces.'''
458
Brad Bishop22cfbe62016-11-30 13:25:10 -0500459 interfaces = []
Brad Bishopa6fcd562017-02-03 11:00:27 -0500460 interfaces_dir = os.path.join(args.inputdir, 'interfaces.d')
461 if os.path.exists(interfaces_dir):
462 yaml_files = filter(
463 lambda x: x.endswith('.yaml'),
464 os.listdir(interfaces_dir))
465
466 for x in yaml_files:
467 with open(os.path.join(interfaces_dir, x), 'r') as fd:
468 for i in yaml.safe_load(fd.read()):
469 interfaces.append(i)
Brad Bishop22cfbe62016-11-30 13:25:10 -0500470
471 return interfaces
472
473 def __init__(self, *a, **kw):
474 self.interfaces = \
475 [Interface(x) for x in kw.pop('interfaces', [])]
476 self.events = [
477 self.class_map[x['type']](**x) for x in a]
478 super(Everything, self).__init__(**kw)
479
Brad Bishop22cfbe62016-11-30 13:25:10 -0500480 def generate_cpp(self, loader):
481 '''Render the template with the provided events and interfaces.'''
482 with open(os.path.join(
483 args.outputdir,
484 'generated.cpp'), 'w') as fd:
485 fd.write(
486 self.render(
487 loader,
488 'generated.mako.cpp',
489 events=self.events,
Brad Bishop9b5a12f2017-01-21 14:42:11 -0500490 interfaces=self.interfaces,
491 indent=Indent()))
Brad Bishopbf066a62016-10-19 08:09:44 -0400492
Brad Bishop95dd98f2016-11-12 12:39:15 -0500493
494if __name__ == '__main__':
495 script_dir = os.path.dirname(os.path.realpath(__file__))
Brad Bishop14a9fe52016-11-12 12:51:26 -0500496 valid_commands = {
497 'generate-cpp': 'generate_cpp',
Brad Bishop22cfbe62016-11-30 13:25:10 -0500498 }
Brad Bishop95dd98f2016-11-12 12:39:15 -0500499
500 parser = argparse.ArgumentParser(
501 description='Phosphor Inventory Manager (PIM) YAML '
502 'scanner and code generator.')
503 parser.add_argument(
504 '-o', '--output-dir', dest='outputdir',
505 default='.', help='Output directory.')
506 parser.add_argument(
507 '-d', '--dir', dest='inputdir',
508 default=os.path.join(script_dir, 'example'),
509 help='Location of files to process.')
Brad Bishopf4666f52016-11-12 12:44:42 -0500510 parser.add_argument(
511 'command', metavar='COMMAND', type=str,
512 choices=valid_commands.keys(),
Brad Bishopc029f6a2017-01-18 19:43:26 -0500513 help='%s.' % " | ".join(valid_commands.keys()))
Brad Bishop95dd98f2016-11-12 12:39:15 -0500514
515 args = parser.parse_args()
Brad Bishop22cfbe62016-11-30 13:25:10 -0500516
517 if sys.version_info < (3, 0):
518 lookup = mako.lookup.TemplateLookup(
519 directories=[script_dir],
520 disable_unicode=True)
521 else:
522 lookup = mako.lookup.TemplateLookup(
523 directories=[script_dir])
524
525 function = getattr(
526 Everything.load(args),
527 valid_commands[args.command])
528 function(lookup)
Brad Bishop95dd98f2016-11-12 12:39:15 -0500529
530
Brad Bishopbf066a62016-10-19 08:09:44 -0400531# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4