blob: 6835258ddcf718a476c54dc8c19ff75954595364 [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 Bishop7b7e7122017-01-21 21:21:46 -0500245class DestroyObjects(Action):
Brad Bishopc93bcc92017-01-21 16:23:39 -0500246 '''Assemble a destroyObject action.'''
Brad Bishop22cfbe62016-11-30 13:25:10 -0500247
248 def __init__(self, **kw):
Brad Bishop7b7e7122017-01-21 21:21:46 -0500249 values = [{'value': x, 'type': 'string'} for x in kw.pop('paths')]
250 args = [InitializerList(
251 values=[TrivialArgument(**x) for x in values])]
Brad Bishopc93bcc92017-01-21 16:23:39 -0500252 kw['args'] = args
Brad Bishop7b7e7122017-01-21 21:21:46 -0500253 super(DestroyObjects, self).__init__(**kw)
Brad Bishop22cfbe62016-11-30 13:25:10 -0500254
255
Brad Bishope2e402f2016-11-30 18:00:17 -0500256class SetProperty(Action):
Brad Bishopc93bcc92017-01-21 16:23:39 -0500257 '''Assemble a setProperty action.'''
Brad Bishope2e402f2016-11-30 18:00:17 -0500258
259 def __init__(self, **kw):
Brad Bishopc93bcc92017-01-21 16:23:39 -0500260 args = []
261
262 value = kw.pop('value')
263 prop = kw.pop('property')
264 iface = kw.pop('interface')
265 iface = Interface(iface)
266 namespace = iface.namespace().split('::')[:-1]
267 name = iface[-1]
268 t = Template(namespace=namespace, name=iface[-1])
269
Brad Bishope2e402f2016-11-30 18:00:17 -0500270 member = '&%s' % '::'.join(
Brad Bishopc93bcc92017-01-21 16:23:39 -0500271 namespace + [name, NamedElement(name=prop).camelCase])
272 member_type = cppTypeName(value['type'])
273 member_cast = '{0} ({1}::*)({0})'.format(member_type, t.qualified())
Brad Bishope2e402f2016-11-30 18:00:17 -0500274
Brad Bishopc93bcc92017-01-21 16:23:39 -0500275 args.append(TrivialArgument(value=kw.pop('path'), type='string'))
276 args.append(TrivialArgument(value=str(iface), type='string'))
277 args.append(TrivialArgument(
278 value=member, decorators=[Cast('static', member_cast)]))
279 args.append(TrivialArgument(**value))
Brad Bishope2e402f2016-11-30 18:00:17 -0500280
Brad Bishopc93bcc92017-01-21 16:23:39 -0500281 kw['templates'] = [Template(name=name, namespace=namespace)]
282 kw['args'] = args
Brad Bishope2e402f2016-11-30 18:00:17 -0500283 super(SetProperty, self).__init__(**kw)
284
285
Brad Bishop22cfbe62016-11-30 13:25:10 -0500286class PropertyChanged(Filter):
Brad Bishopc93bcc92017-01-21 16:23:39 -0500287 '''Assemble a propertyChanged filter.'''
Brad Bishop22cfbe62016-11-30 13:25:10 -0500288
289 def __init__(self, **kw):
Brad Bishopc93bcc92017-01-21 16:23:39 -0500290 args = []
291 args.append(TrivialArgument(value=kw.pop('interface'), type='string'))
292 args.append(TrivialArgument(value=kw.pop('property'), type='string'))
293 args.append(TrivialArgument(
294 decorators=[
295 Literal(kw['value'].get('type', None))], **kw.pop('value')))
296 kw['args'] = args
Brad Bishop22cfbe62016-11-30 13:25:10 -0500297 super(PropertyChanged, self).__init__(**kw)
298
299
Brad Bishop040e18b2017-01-21 22:04:00 -0500300class PropertyIs(Filter):
301 '''Assemble a propertyIs filter.'''
302
303 def __init__(self, **kw):
304 args = []
305 args.append(TrivialArgument(value=kw.pop('path'), type='string'))
306 args.append(TrivialArgument(value=kw.pop('interface'), type='string'))
307 args.append(TrivialArgument(value=kw.pop('property'), type='string'))
308 args.append(TrivialArgument(
309 decorators=[
310 Literal(kw['value'].get('type', None))], **kw.pop('value')))
311
312 service = kw.pop('service', None)
313 if service:
314 args.append(TrivialArgument(value=service, type='string'))
315
316 kw['args'] = args
317 super(PropertyIs, self).__init__(**kw)
318
319
Brad Bishopc93bcc92017-01-21 16:23:39 -0500320class Event(MethodCall):
321 '''Assemble an inventory manager event.'''
Brad Bishop22cfbe62016-11-30 13:25:10 -0500322
323 action_map = {
Brad Bishop7b7e7122017-01-21 21:21:46 -0500324 'destroyObjects': DestroyObjects,
Brad Bishope2e402f2016-11-30 18:00:17 -0500325 'setProperty': SetProperty,
Brad Bishopcfb3c892016-11-12 11:43:37 -0500326 }
327
Brad Bishopc93bcc92017-01-21 16:23:39 -0500328 filter_map = {
329 'propertyChangedTo': PropertyChanged,
Brad Bishop040e18b2017-01-21 22:04:00 -0500330 'propertyIs': PropertyIs,
Brad Bishopc93bcc92017-01-21 16:23:39 -0500331 }
332
Brad Bishop22cfbe62016-11-30 13:25:10 -0500333 def __init__(self, **kw):
Brad Bishopc93bcc92017-01-21 16:23:39 -0500334 self.summary = kw.pop('name')
335
336 filters = [
337 self.filter_map[x['name']](**x) for x in kw.pop('filters', [])]
Brad Bishop064c94a2017-01-21 21:33:30 -0500338 filters = Vector(
339 templates=[Template(name='FilterBasePtr', namespace=['details'])],
340 args=filters)
Brad Bishopc93bcc92017-01-21 16:23:39 -0500341
342 event = MethodCall(
343 name='make_shared',
344 namespace=['std'],
345 templates=[Template(
346 name=kw.pop('event'),
347 namespace=kw.pop('event_namespace', []))],
Brad Bishop064c94a2017-01-21 21:33:30 -0500348 args=kw.pop('event_args', []) + [filters])
Brad Bishopc93bcc92017-01-21 16:23:39 -0500349
350 events = Vector(
351 templates=[Template(name='EventBasePtr', namespace=['details'])],
352 args=[event])
353
354 action_type = Template(name='ActionBasePtr', namespace=['details'])
355 action_args = [
356 self.action_map[x['name']](**x) for x in kw.pop('actions', [])]
357 actions = Vector(
358 templates=[action_type],
359 args=action_args)
360
361 kw['name'] = 'make_tuple'
362 kw['namespace'] = ['std']
363 kw['args'] = [events, actions]
Brad Bishop22cfbe62016-11-30 13:25:10 -0500364 super(Event, self).__init__(**kw)
Brad Bishopcfb3c892016-11-12 11:43:37 -0500365
Brad Bishopcfb3c892016-11-12 11:43:37 -0500366
Brad Bishop22cfbe62016-11-30 13:25:10 -0500367class MatchEvent(Event):
368 '''Associate one or more dbus signal match signatures with
369 a filter.'''
370
Brad Bishop22cfbe62016-11-30 13:25:10 -0500371 def __init__(self, **kw):
Brad Bishopc93bcc92017-01-21 16:23:39 -0500372 kw['event'] = 'DbusSignal'
373 kw['event_namespace'] = ['details']
374 kw['event_args'] = [
375 DbusSignature(**x) for x in kw.pop('signatures', [])]
376
Brad Bishop22cfbe62016-11-30 13:25:10 -0500377 super(MatchEvent, self).__init__(**kw)
378
379
380class Everything(Renderer):
381 '''Parse/render entry point.'''
382
383 class_map = {
384 'match': MatchEvent,
385 }
386
387 @staticmethod
388 def load(args):
389 # Invoke sdbus++ to generate any extra interface bindings for
390 # extra interfaces that aren't defined externally.
391 yaml_files = []
392 extra_ifaces_dir = os.path.join(args.inputdir, 'extra_interfaces.d')
393 if os.path.exists(extra_ifaces_dir):
394 for directory, _, files in os.walk(extra_ifaces_dir):
395 if not files:
396 continue
397
398 yaml_files += map(
399 lambda f: os.path.relpath(
400 os.path.join(directory, f),
401 extra_ifaces_dir),
402 filter(lambda f: f.endswith('.interface.yaml'), files))
403
404 genfiles = {
405 'server-cpp': lambda x: '%s.cpp' % (
406 x.replace(os.sep, '.')),
407 'server-header': lambda x: os.path.join(
408 os.path.join(
409 *x.split('.')), 'server.hpp')
410 }
411
412 for i in yaml_files:
413 iface = i.replace('.interface.yaml', '').replace(os.sep, '.')
414 for process, f in genfiles.iteritems():
415
416 dest = os.path.join(args.outputdir, f(iface))
417 parent = os.path.dirname(dest)
418 if parent and not os.path.exists(parent):
419 os.makedirs(parent)
420
421 with open(dest, 'w') as fd:
422 subprocess.call([
423 'sdbus++',
424 '-r',
425 extra_ifaces_dir,
426 'interface',
427 process,
428 iface],
429 stdout=fd)
430
431 # Aggregate all the event YAML in the events.d directory
432 # into a single list of events.
433
434 events_dir = os.path.join(args.inputdir, 'events.d')
435 yaml_files = filter(
436 lambda x: x.endswith('.yaml'),
437 os.listdir(events_dir))
438
439 events = []
440 for x in yaml_files:
441 with open(os.path.join(events_dir, x), 'r') as fd:
Brad Bishopeffbd932017-01-22 11:07:54 -0500442 for e in yaml.safe_load(fd.read()).get('events', {}):
Brad Bishop22cfbe62016-11-30 13:25:10 -0500443 events.append(e)
444
445 return Everything(
446 *events,
447 interfaces=Everything.get_interfaces(args))
448
449 @staticmethod
450 def get_interfaces(args):
451 '''Aggregate all the interface YAML in the interfaces.d
452 directory into a single list of interfaces.'''
453
454 interfaces_dir = os.path.join(args.inputdir, 'interfaces.d')
455 yaml_files = filter(
456 lambda x: x.endswith('.yaml'),
457 os.listdir(interfaces_dir))
458
459 interfaces = []
460 for x in yaml_files:
461 with open(os.path.join(interfaces_dir, x), 'r') as fd:
Brad Bishopeffbd932017-01-22 11:07:54 -0500462 for i in yaml.safe_load(fd.read()):
Brad Bishop22cfbe62016-11-30 13:25:10 -0500463 interfaces.append(i)
464
465 return interfaces
466
467 def __init__(self, *a, **kw):
468 self.interfaces = \
469 [Interface(x) for x in kw.pop('interfaces', [])]
470 self.events = [
471 self.class_map[x['type']](**x) for x in a]
472 super(Everything, self).__init__(**kw)
473
474 def list_interfaces(self, *a):
475 print ' '.join([str(i) for i in self.interfaces])
476
477 def generate_cpp(self, loader):
478 '''Render the template with the provided events and interfaces.'''
479 with open(os.path.join(
480 args.outputdir,
481 'generated.cpp'), 'w') as fd:
482 fd.write(
483 self.render(
484 loader,
485 'generated.mako.cpp',
486 events=self.events,
Brad Bishop9b5a12f2017-01-21 14:42:11 -0500487 interfaces=self.interfaces,
488 indent=Indent()))
Brad Bishopbf066a62016-10-19 08:09:44 -0400489
Brad Bishop95dd98f2016-11-12 12:39:15 -0500490
491if __name__ == '__main__':
492 script_dir = os.path.dirname(os.path.realpath(__file__))
Brad Bishop14a9fe52016-11-12 12:51:26 -0500493 valid_commands = {
494 'generate-cpp': 'generate_cpp',
Brad Bishop22cfbe62016-11-30 13:25:10 -0500495 'list-interfaces': 'list_interfaces'
496 }
Brad Bishop95dd98f2016-11-12 12:39:15 -0500497
498 parser = argparse.ArgumentParser(
499 description='Phosphor Inventory Manager (PIM) YAML '
500 'scanner and code generator.')
501 parser.add_argument(
502 '-o', '--output-dir', dest='outputdir',
503 default='.', help='Output directory.')
504 parser.add_argument(
505 '-d', '--dir', dest='inputdir',
506 default=os.path.join(script_dir, 'example'),
507 help='Location of files to process.')
Brad Bishopf4666f52016-11-12 12:44:42 -0500508 parser.add_argument(
509 'command', metavar='COMMAND', type=str,
510 choices=valid_commands.keys(),
Brad Bishopc029f6a2017-01-18 19:43:26 -0500511 help='%s.' % " | ".join(valid_commands.keys()))
Brad Bishop95dd98f2016-11-12 12:39:15 -0500512
513 args = parser.parse_args()
Brad Bishop22cfbe62016-11-30 13:25:10 -0500514
515 if sys.version_info < (3, 0):
516 lookup = mako.lookup.TemplateLookup(
517 directories=[script_dir],
518 disable_unicode=True)
519 else:
520 lookup = mako.lookup.TemplateLookup(
521 directories=[script_dir])
522
523 function = getattr(
524 Everything.load(args),
525 valid_commands[args.command])
526 function(lookup)
Brad Bishop95dd98f2016-11-12 12:39:15 -0500527
528
Brad Bishopbf066a62016-10-19 08:09:44 -0400529# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4