blob: 6c1eab73deac87cf3384104b53b8ef64b61cfcf2 [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 Bishope2e402f2016-11-30 18:00:17 -0500138 self.cast = kw.pop('cast', None)
Brad Bishop75800cf2017-01-21 15:24:18 -0500139 super(TrivialArgument, self).__init__(**kw)
140
141 def argument(self, loader, indent):
142 a = str(self.value)
143 for d in self.decorators:
144 a = d(a)
145
146 return a
Brad Bishop14a9fe52016-11-12 12:51:26 -0500147
Brad Bishop22cfbe62016-11-30 13:25:10 -0500148 def cppArg(self):
149 '''Transform string types to c++ string constants.'''
Brad Bishop22cfbe62016-11-30 13:25:10 -0500150
Brad Bishope2e402f2016-11-30 18:00:17 -0500151 a = self.value
Brad Bishop75800cf2017-01-21 15:24:18 -0500152 if self.type == 'string':
Brad Bishope2e402f2016-11-30 18:00:17 -0500153 a = '"%s"' % a
154
155 if self.cast:
156 a = 'static_cast<%s>(%s)' % (self.cast, a)
157
158 return a
Brad Bishop14a9fe52016-11-12 12:51:26 -0500159
160
Brad Bishop9b5a12f2017-01-21 14:42:11 -0500161class InitializerList(Argument):
162 '''Initializer list arguments.'''
163
164 def __init__(self, **kw):
165 self.values = kw.pop('values')
166 super(InitializerList, self).__init__(**kw)
167
168 def argument(self, loader, indent):
169 return self.render(
170 loader,
171 'argument.mako.cpp',
172 arg=self,
173 indent=indent)
174
175
Brad Bishop75800cf2017-01-21 15:24:18 -0500176class DbusSignature(Argument):
177 '''DBus signature arguments.'''
178
179 def __init__(self, **kw):
180 self.sig = {x: y for x, y in kw.iteritems()}
181 kw.clear()
182 super(DbusSignature, self).__init__(**kw)
183
184 def argument(self, loader, indent):
185 return self.render(
186 loader,
187 'signature.mako.cpp',
188 signature=self,
189 indent=indent)
190
191
Brad Bishop22cfbe62016-11-30 13:25:10 -0500192class MethodCall(NamedElement, Renderer):
193 '''Render syntatically correct c++ method calls.'''
194
195 def __init__(self, **kw):
196 self.namespace = kw.pop('namespace', [])
Brad Bishope2e402f2016-11-30 18:00:17 -0500197 self.template = kw.pop('template', '')
Brad Bishop22cfbe62016-11-30 13:25:10 -0500198 self.pointer = kw.pop('pointer', False)
199 self.args = \
Brad Bishop75800cf2017-01-21 15:24:18 -0500200 [TrivialArgument(**x) for x in kw.pop('args', [])]
Brad Bishop22cfbe62016-11-30 13:25:10 -0500201 super(MethodCall, self).__init__(**kw)
202
203 def bare_method(self):
204 '''Provide the method name and encompassing
205 namespace without any arguments.'''
Brad Bishope2e402f2016-11-30 18:00:17 -0500206
207 m = '::'.join(self.namespace + [self.name])
208 if self.template:
209 m += '<%s>' % self.template
210
211 return m
Brad Bishop14a9fe52016-11-12 12:51:26 -0500212
Brad Bishopcab2bdd2017-01-21 15:00:54 -0500213 def call(self, loader, indent):
214 return self.render(
215 loader,
216 'method.mako.cpp',
217 method=self,
218 indent=indent)
219
220 def argument(self, loader, indent):
221 return self.call(loader, indent)
222
Brad Bishop14a9fe52016-11-12 12:51:26 -0500223
Brad Bishop9b5a12f2017-01-21 14:42:11 -0500224class Vector(MethodCall):
225 '''Convenience type for vectors.'''
226
227 def __init__(self, **kw):
228 kw['name'] = 'vector'
229 kw['namespace'] = ['std']
230 kw['args'] = [InitializerList(values=kw.pop('args'))]
231 super(Vector, self).__init__(**kw)
232
233
234class Wrapper(MethodCall):
235 '''Convenience type for functions that wrap other functions.'''
236
237 def __init__(self, **kw):
238 m = MethodCall(
239 name=kw.pop('name'),
240 namespace=kw.pop('namespace', []),
241 templates=kw.pop('templates', []),
242 args=kw.pop('args', []))
243
244 kw['name'] = kw.pop('wrapper_name')
245 kw['namespace'] = kw.pop('wrapper_namespace', [])
246 kw['args'] = [m]
247 super(Wrapper, self).__init__(**kw)
248
249
Brad Bishop22cfbe62016-11-30 13:25:10 -0500250class Filter(MethodCall):
251 '''Provide common attributes for any filter.'''
Brad Bishopbf066a62016-10-19 08:09:44 -0400252
Brad Bishop22cfbe62016-11-30 13:25:10 -0500253 def __init__(self, **kw):
254 kw['namespace'] = ['filters']
255 super(Filter, self).__init__(**kw)
Brad Bishopbf066a62016-10-19 08:09:44 -0400256
Brad Bishop0a6a4792016-11-12 12:10:07 -0500257
Brad Bishop22cfbe62016-11-30 13:25:10 -0500258class Action(MethodCall):
259 '''Provide common attributes for any action.'''
Brad Bishop561a5652016-10-26 21:13:32 -0500260
Brad Bishop22cfbe62016-11-30 13:25:10 -0500261 def __init__(self, **kw):
262 kw['namespace'] = ['actions']
263 super(Action, self).__init__(**kw)
Brad Bishop92665b22016-10-26 20:51:16 -0500264
Brad Bishopcfb3c892016-11-12 11:43:37 -0500265
Brad Bishop22cfbe62016-11-30 13:25:10 -0500266class DestroyObject(Action):
267 '''Render a destroyObject action.'''
268
269 def __init__(self, **kw):
270 mapped = kw.pop('args')
271 kw['args'] = [
272 {'value': mapped['path'], 'type':'string'},
273 ]
274 super(DestroyObject, self).__init__(**kw)
275
276
Brad Bishope2e402f2016-11-30 18:00:17 -0500277class SetProperty(Action):
278 '''Render a setProperty action.'''
279
280 def __init__(self, **kw):
281 mapped = kw.pop('args')
282 member = Interface(mapped['interface']).namespace()
283 member = '&%s' % '::'.join(
284 member.split('::') + [NamedElement(
285 name=mapped['property']).camelCase])
286
Brad Bishop75800cf2017-01-21 15:24:18 -0500287 memberType = cppTypeName(mapped['value'].get('type'))
Brad Bishope2e402f2016-11-30 18:00:17 -0500288
289 kw['template'] = Interface(mapped['interface']).namespace()
290 kw['args'] = [
291 {'value': mapped['path'], 'type':'string'},
292 {'value': mapped['interface'], 'type':'string'},
293 {'value': member, 'cast': '{0} ({1}::*)({0})'.format(
294 memberType,
295 Interface(mapped['interface']).namespace())},
296 mapped['value'],
297 ]
298 super(SetProperty, self).__init__(**kw)
299
300
Brad Bishop22cfbe62016-11-30 13:25:10 -0500301class NoopAction(Action):
302 '''Render a noop action.'''
303
304 def __init__(self, **kw):
305 kw['pointer'] = True
306 super(NoopAction, self).__init__(**kw)
307
308
309class NoopFilter(Filter):
310 '''Render a noop filter.'''
311
312 def __init__(self, **kw):
313 kw['pointer'] = True
314 super(NoopFilter, self).__init__(**kw)
315
316
317class PropertyChanged(Filter):
318 '''Render a propertyChanged filter.'''
319
320 def __init__(self, **kw):
321 mapped = kw.pop('args')
322 kw['args'] = [
323 {'value': mapped['interface'], 'type':'string'},
324 {'value': mapped['property'], 'type':'string'},
325 mapped['value']
326 ]
327 super(PropertyChanged, self).__init__(**kw)
328
329
330class Event(NamedElement, Renderer):
331 '''Render an inventory manager event.'''
332
333 action_map = {
334 'noop': NoopAction,
335 'destroyObject': DestroyObject,
Brad Bishope2e402f2016-11-30 18:00:17 -0500336 'setProperty': SetProperty,
Brad Bishopcfb3c892016-11-12 11:43:37 -0500337 }
338
Brad Bishop22cfbe62016-11-30 13:25:10 -0500339 def __init__(self, **kw):
340 self.cls = kw.pop('type')
341 self.actions = \
342 [self.action_map[x['name']](**x)
343 for x in kw.pop('actions', [{'name': 'noop'}])]
344 super(Event, self).__init__(**kw)
Brad Bishopcfb3c892016-11-12 11:43:37 -0500345
Brad Bishopcfb3c892016-11-12 11:43:37 -0500346
Brad Bishop22cfbe62016-11-30 13:25:10 -0500347class MatchEvent(Event):
348 '''Associate one or more dbus signal match signatures with
349 a filter.'''
350
351 filter_map = {
352 'none': NoopFilter,
353 'propertyChangedTo': PropertyChanged,
354 }
355
356 def __init__(self, **kw):
357 self.signatures = \
358 [DbusSignature(**x) for x in kw.pop('signatures', [])]
359 self.filters = \
360 [self.filter_map[x['name']](**x)
361 for x in kw.pop('filters', [{'name': 'none'}])]
362 super(MatchEvent, self).__init__(**kw)
363
364
365class Everything(Renderer):
366 '''Parse/render entry point.'''
367
368 class_map = {
369 'match': MatchEvent,
370 }
371
372 @staticmethod
373 def load(args):
374 # Invoke sdbus++ to generate any extra interface bindings for
375 # extra interfaces that aren't defined externally.
376 yaml_files = []
377 extra_ifaces_dir = os.path.join(args.inputdir, 'extra_interfaces.d')
378 if os.path.exists(extra_ifaces_dir):
379 for directory, _, files in os.walk(extra_ifaces_dir):
380 if not files:
381 continue
382
383 yaml_files += map(
384 lambda f: os.path.relpath(
385 os.path.join(directory, f),
386 extra_ifaces_dir),
387 filter(lambda f: f.endswith('.interface.yaml'), files))
388
389 genfiles = {
390 'server-cpp': lambda x: '%s.cpp' % (
391 x.replace(os.sep, '.')),
392 'server-header': lambda x: os.path.join(
393 os.path.join(
394 *x.split('.')), 'server.hpp')
395 }
396
397 for i in yaml_files:
398 iface = i.replace('.interface.yaml', '').replace(os.sep, '.')
399 for process, f in genfiles.iteritems():
400
401 dest = os.path.join(args.outputdir, f(iface))
402 parent = os.path.dirname(dest)
403 if parent and not os.path.exists(parent):
404 os.makedirs(parent)
405
406 with open(dest, 'w') as fd:
407 subprocess.call([
408 'sdbus++',
409 '-r',
410 extra_ifaces_dir,
411 'interface',
412 process,
413 iface],
414 stdout=fd)
415
416 # Aggregate all the event YAML in the events.d directory
417 # into a single list of events.
418
419 events_dir = os.path.join(args.inputdir, 'events.d')
420 yaml_files = filter(
421 lambda x: x.endswith('.yaml'),
422 os.listdir(events_dir))
423
424 events = []
425 for x in yaml_files:
426 with open(os.path.join(events_dir, x), 'r') as fd:
Brad Bishopeffbd932017-01-22 11:07:54 -0500427 for e in yaml.safe_load(fd.read()).get('events', {}):
Brad Bishop22cfbe62016-11-30 13:25:10 -0500428 events.append(e)
429
430 return Everything(
431 *events,
432 interfaces=Everything.get_interfaces(args))
433
434 @staticmethod
435 def get_interfaces(args):
436 '''Aggregate all the interface YAML in the interfaces.d
437 directory into a single list of interfaces.'''
438
439 interfaces_dir = os.path.join(args.inputdir, 'interfaces.d')
440 yaml_files = filter(
441 lambda x: x.endswith('.yaml'),
442 os.listdir(interfaces_dir))
443
444 interfaces = []
445 for x in yaml_files:
446 with open(os.path.join(interfaces_dir, x), 'r') as fd:
Brad Bishopeffbd932017-01-22 11:07:54 -0500447 for i in yaml.safe_load(fd.read()):
Brad Bishop22cfbe62016-11-30 13:25:10 -0500448 interfaces.append(i)
449
450 return interfaces
451
452 def __init__(self, *a, **kw):
453 self.interfaces = \
454 [Interface(x) for x in kw.pop('interfaces', [])]
455 self.events = [
456 self.class_map[x['type']](**x) for x in a]
457 super(Everything, self).__init__(**kw)
458
459 def list_interfaces(self, *a):
460 print ' '.join([str(i) for i in self.interfaces])
461
462 def generate_cpp(self, loader):
463 '''Render the template with the provided events and interfaces.'''
464 with open(os.path.join(
465 args.outputdir,
466 'generated.cpp'), 'w') as fd:
467 fd.write(
468 self.render(
469 loader,
470 'generated.mako.cpp',
471 events=self.events,
Brad Bishop9b5a12f2017-01-21 14:42:11 -0500472 interfaces=self.interfaces,
473 indent=Indent()))
Brad Bishopbf066a62016-10-19 08:09:44 -0400474
Brad Bishop95dd98f2016-11-12 12:39:15 -0500475
476if __name__ == '__main__':
477 script_dir = os.path.dirname(os.path.realpath(__file__))
Brad Bishop14a9fe52016-11-12 12:51:26 -0500478 valid_commands = {
479 'generate-cpp': 'generate_cpp',
Brad Bishop22cfbe62016-11-30 13:25:10 -0500480 'list-interfaces': 'list_interfaces'
481 }
Brad Bishop95dd98f2016-11-12 12:39:15 -0500482
483 parser = argparse.ArgumentParser(
484 description='Phosphor Inventory Manager (PIM) YAML '
485 'scanner and code generator.')
486 parser.add_argument(
487 '-o', '--output-dir', dest='outputdir',
488 default='.', help='Output directory.')
489 parser.add_argument(
490 '-d', '--dir', dest='inputdir',
491 default=os.path.join(script_dir, 'example'),
492 help='Location of files to process.')
Brad Bishopf4666f52016-11-12 12:44:42 -0500493 parser.add_argument(
494 'command', metavar='COMMAND', type=str,
495 choices=valid_commands.keys(),
Brad Bishopc029f6a2017-01-18 19:43:26 -0500496 help='%s.' % " | ".join(valid_commands.keys()))
Brad Bishop95dd98f2016-11-12 12:39:15 -0500497
498 args = parser.parse_args()
Brad Bishop22cfbe62016-11-30 13:25:10 -0500499
500 if sys.version_info < (3, 0):
501 lookup = mako.lookup.TemplateLookup(
502 directories=[script_dir],
503 disable_unicode=True)
504 else:
505 lookup = mako.lookup.TemplateLookup(
506 directories=[script_dir])
507
508 function = getattr(
509 Everything.load(args),
510 valid_commands[args.command])
511 function(lookup)
Brad Bishop95dd98f2016-11-12 12:39:15 -0500512
513
Brad Bishopbf066a62016-10-19 08:09:44 -0400514# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4