blob: ce43c46efc8abd74f3089467fd5fce5a67d229bf [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):
434 # Invoke sdbus++ to generate any extra interface bindings for
435 # extra interfaces that aren't defined externally.
436 yaml_files = []
437 extra_ifaces_dir = os.path.join(args.inputdir, 'extra_interfaces.d')
438 if os.path.exists(extra_ifaces_dir):
439 for directory, _, files in os.walk(extra_ifaces_dir):
440 if not files:
441 continue
442
443 yaml_files += map(
444 lambda f: os.path.relpath(
445 os.path.join(directory, f),
446 extra_ifaces_dir),
447 filter(lambda f: f.endswith('.interface.yaml'), files))
448
449 genfiles = {
450 'server-cpp': lambda x: '%s.cpp' % (
451 x.replace(os.sep, '.')),
452 'server-header': lambda x: os.path.join(
453 os.path.join(
454 *x.split('.')), 'server.hpp')
455 }
456
457 for i in yaml_files:
458 iface = i.replace('.interface.yaml', '').replace(os.sep, '.')
459 for process, f in genfiles.iteritems():
460
461 dest = os.path.join(args.outputdir, f(iface))
462 parent = os.path.dirname(dest)
463 if parent and not os.path.exists(parent):
464 os.makedirs(parent)
465
466 with open(dest, 'w') as fd:
467 subprocess.call([
468 'sdbus++',
469 '-r',
470 extra_ifaces_dir,
471 'interface',
472 process,
473 iface],
474 stdout=fd)
475
476 # Aggregate all the event YAML in the events.d directory
477 # into a single list of events.
478
479 events_dir = os.path.join(args.inputdir, 'events.d')
480 yaml_files = filter(
481 lambda x: x.endswith('.yaml'),
482 os.listdir(events_dir))
483
484 events = []
485 for x in yaml_files:
486 with open(os.path.join(events_dir, x), 'r') as fd:
Brad Bishopeffbd932017-01-22 11:07:54 -0500487 for e in yaml.safe_load(fd.read()).get('events', {}):
Brad Bishop22cfbe62016-11-30 13:25:10 -0500488 events.append(e)
489
490 return Everything(
491 *events,
492 interfaces=Everything.get_interfaces(args))
493
494 @staticmethod
495 def get_interfaces(args):
496 '''Aggregate all the interface YAML in the interfaces.d
497 directory into a single list of interfaces.'''
498
499 interfaces_dir = os.path.join(args.inputdir, 'interfaces.d')
500 yaml_files = filter(
501 lambda x: x.endswith('.yaml'),
502 os.listdir(interfaces_dir))
503
504 interfaces = []
505 for x in yaml_files:
506 with open(os.path.join(interfaces_dir, x), 'r') as fd:
Brad Bishopeffbd932017-01-22 11:07:54 -0500507 for i in yaml.safe_load(fd.read()):
Brad Bishop22cfbe62016-11-30 13:25:10 -0500508 interfaces.append(i)
509
510 return interfaces
511
512 def __init__(self, *a, **kw):
513 self.interfaces = \
514 [Interface(x) for x in kw.pop('interfaces', [])]
515 self.events = [
516 self.class_map[x['type']](**x) for x in a]
517 super(Everything, self).__init__(**kw)
518
519 def list_interfaces(self, *a):
520 print ' '.join([str(i) for i in self.interfaces])
521
522 def generate_cpp(self, loader):
523 '''Render the template with the provided events and interfaces.'''
524 with open(os.path.join(
525 args.outputdir,
526 'generated.cpp'), 'w') as fd:
527 fd.write(
528 self.render(
529 loader,
530 'generated.mako.cpp',
531 events=self.events,
Brad Bishop9b5a12f2017-01-21 14:42:11 -0500532 interfaces=self.interfaces,
533 indent=Indent()))
Brad Bishopbf066a62016-10-19 08:09:44 -0400534
Brad Bishop95dd98f2016-11-12 12:39:15 -0500535
536if __name__ == '__main__':
537 script_dir = os.path.dirname(os.path.realpath(__file__))
Brad Bishop14a9fe52016-11-12 12:51:26 -0500538 valid_commands = {
539 'generate-cpp': 'generate_cpp',
Brad Bishop22cfbe62016-11-30 13:25:10 -0500540 'list-interfaces': 'list_interfaces'
541 }
Brad Bishop95dd98f2016-11-12 12:39:15 -0500542
543 parser = argparse.ArgumentParser(
544 description='Phosphor Inventory Manager (PIM) YAML '
545 'scanner and code generator.')
546 parser.add_argument(
547 '-o', '--output-dir', dest='outputdir',
548 default='.', help='Output directory.')
549 parser.add_argument(
550 '-d', '--dir', dest='inputdir',
551 default=os.path.join(script_dir, 'example'),
552 help='Location of files to process.')
Brad Bishopf4666f52016-11-12 12:44:42 -0500553 parser.add_argument(
554 'command', metavar='COMMAND', type=str,
555 choices=valid_commands.keys(),
Brad Bishopc029f6a2017-01-18 19:43:26 -0500556 help='%s.' % " | ".join(valid_commands.keys()))
Brad Bishop95dd98f2016-11-12 12:39:15 -0500557
558 args = parser.parse_args()
Brad Bishop22cfbe62016-11-30 13:25:10 -0500559
560 if sys.version_info < (3, 0):
561 lookup = mako.lookup.TemplateLookup(
562 directories=[script_dir],
563 disable_unicode=True)
564 else:
565 lookup = mako.lookup.TemplateLookup(
566 directories=[script_dir])
567
568 function = getattr(
569 Everything.load(args),
570 valid_commands[args.command])
571 function(lookup)
Brad Bishop95dd98f2016-11-12 12:39:15 -0500572
573
Brad Bishopbf066a62016-10-19 08:09:44 -0400574# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4