blob: 242edda983cf285527ac6b9d550777ff455abf7a [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
415class Everything(Renderer):
416 '''Parse/render entry point.'''
417
418 class_map = {
419 'match': MatchEvent,
420 }
421
422 @staticmethod
423 def load(args):
424 # Invoke sdbus++ to generate any extra interface bindings for
425 # extra interfaces that aren't defined externally.
426 yaml_files = []
427 extra_ifaces_dir = os.path.join(args.inputdir, 'extra_interfaces.d')
428 if os.path.exists(extra_ifaces_dir):
429 for directory, _, files in os.walk(extra_ifaces_dir):
430 if not files:
431 continue
432
433 yaml_files += map(
434 lambda f: os.path.relpath(
435 os.path.join(directory, f),
436 extra_ifaces_dir),
437 filter(lambda f: f.endswith('.interface.yaml'), files))
438
439 genfiles = {
440 'server-cpp': lambda x: '%s.cpp' % (
441 x.replace(os.sep, '.')),
442 'server-header': lambda x: os.path.join(
443 os.path.join(
444 *x.split('.')), 'server.hpp')
445 }
446
447 for i in yaml_files:
448 iface = i.replace('.interface.yaml', '').replace(os.sep, '.')
449 for process, f in genfiles.iteritems():
450
451 dest = os.path.join(args.outputdir, f(iface))
452 parent = os.path.dirname(dest)
453 if parent and not os.path.exists(parent):
454 os.makedirs(parent)
455
456 with open(dest, 'w') as fd:
457 subprocess.call([
458 'sdbus++',
459 '-r',
460 extra_ifaces_dir,
461 'interface',
462 process,
463 iface],
464 stdout=fd)
465
466 # Aggregate all the event YAML in the events.d directory
467 # into a single list of events.
468
469 events_dir = os.path.join(args.inputdir, 'events.d')
470 yaml_files = filter(
471 lambda x: x.endswith('.yaml'),
472 os.listdir(events_dir))
473
474 events = []
475 for x in yaml_files:
476 with open(os.path.join(events_dir, x), 'r') as fd:
Brad Bishopeffbd932017-01-22 11:07:54 -0500477 for e in yaml.safe_load(fd.read()).get('events', {}):
Brad Bishop22cfbe62016-11-30 13:25:10 -0500478 events.append(e)
479
480 return Everything(
481 *events,
482 interfaces=Everything.get_interfaces(args))
483
484 @staticmethod
485 def get_interfaces(args):
486 '''Aggregate all the interface YAML in the interfaces.d
487 directory into a single list of interfaces.'''
488
489 interfaces_dir = os.path.join(args.inputdir, 'interfaces.d')
490 yaml_files = filter(
491 lambda x: x.endswith('.yaml'),
492 os.listdir(interfaces_dir))
493
494 interfaces = []
495 for x in yaml_files:
496 with open(os.path.join(interfaces_dir, x), 'r') as fd:
Brad Bishopeffbd932017-01-22 11:07:54 -0500497 for i in yaml.safe_load(fd.read()):
Brad Bishop22cfbe62016-11-30 13:25:10 -0500498 interfaces.append(i)
499
500 return interfaces
501
502 def __init__(self, *a, **kw):
503 self.interfaces = \
504 [Interface(x) for x in kw.pop('interfaces', [])]
505 self.events = [
506 self.class_map[x['type']](**x) for x in a]
507 super(Everything, self).__init__(**kw)
508
509 def list_interfaces(self, *a):
510 print ' '.join([str(i) for i in self.interfaces])
511
512 def generate_cpp(self, loader):
513 '''Render the template with the provided events and interfaces.'''
514 with open(os.path.join(
515 args.outputdir,
516 'generated.cpp'), 'w') as fd:
517 fd.write(
518 self.render(
519 loader,
520 'generated.mako.cpp',
521 events=self.events,
Brad Bishop9b5a12f2017-01-21 14:42:11 -0500522 interfaces=self.interfaces,
523 indent=Indent()))
Brad Bishopbf066a62016-10-19 08:09:44 -0400524
Brad Bishop95dd98f2016-11-12 12:39:15 -0500525
526if __name__ == '__main__':
527 script_dir = os.path.dirname(os.path.realpath(__file__))
Brad Bishop14a9fe52016-11-12 12:51:26 -0500528 valid_commands = {
529 'generate-cpp': 'generate_cpp',
Brad Bishop22cfbe62016-11-30 13:25:10 -0500530 'list-interfaces': 'list_interfaces'
531 }
Brad Bishop95dd98f2016-11-12 12:39:15 -0500532
533 parser = argparse.ArgumentParser(
534 description='Phosphor Inventory Manager (PIM) YAML '
535 'scanner and code generator.')
536 parser.add_argument(
537 '-o', '--output-dir', dest='outputdir',
538 default='.', help='Output directory.')
539 parser.add_argument(
540 '-d', '--dir', dest='inputdir',
541 default=os.path.join(script_dir, 'example'),
542 help='Location of files to process.')
Brad Bishopf4666f52016-11-12 12:44:42 -0500543 parser.add_argument(
544 'command', metavar='COMMAND', type=str,
545 choices=valid_commands.keys(),
Brad Bishopc029f6a2017-01-18 19:43:26 -0500546 help='%s.' % " | ".join(valid_commands.keys()))
Brad Bishop95dd98f2016-11-12 12:39:15 -0500547
548 args = parser.parse_args()
Brad Bishop22cfbe62016-11-30 13:25:10 -0500549
550 if sys.version_info < (3, 0):
551 lookup = mako.lookup.TemplateLookup(
552 directories=[script_dir],
553 disable_unicode=True)
554 else:
555 lookup = mako.lookup.TemplateLookup(
556 directories=[script_dir])
557
558 function = getattr(
559 Everything.load(args),
560 valid_commands[args.command])
561 function(lookup)
Brad Bishop95dd98f2016-11-12 12:39:15 -0500562
563
Brad Bishopbf066a62016-10-19 08:09:44 -0400564# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4