blob: 5e2cf007876d2ab51bac215d981a0502c5652369 [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 Bishopc93bcc92017-01-21 16:23:39 -0500300class Event(MethodCall):
301 '''Assemble an inventory manager event.'''
Brad Bishop22cfbe62016-11-30 13:25:10 -0500302
303 action_map = {
Brad Bishop7b7e7122017-01-21 21:21:46 -0500304 'destroyObjects': DestroyObjects,
Brad Bishope2e402f2016-11-30 18:00:17 -0500305 'setProperty': SetProperty,
Brad Bishopcfb3c892016-11-12 11:43:37 -0500306 }
307
Brad Bishopc93bcc92017-01-21 16:23:39 -0500308 filter_map = {
309 'propertyChangedTo': PropertyChanged,
310 }
311
Brad Bishop22cfbe62016-11-30 13:25:10 -0500312 def __init__(self, **kw):
Brad Bishopc93bcc92017-01-21 16:23:39 -0500313 self.summary = kw.pop('name')
314
315 filters = [
316 self.filter_map[x['name']](**x) for x in kw.pop('filters', [])]
317
318 event = MethodCall(
319 name='make_shared',
320 namespace=['std'],
321 templates=[Template(
322 name=kw.pop('event'),
323 namespace=kw.pop('event_namespace', []))],
324 args=kw.pop('event_args', []) + [filters[0]])
325
326 events = Vector(
327 templates=[Template(name='EventBasePtr', namespace=['details'])],
328 args=[event])
329
330 action_type = Template(name='ActionBasePtr', namespace=['details'])
331 action_args = [
332 self.action_map[x['name']](**x) for x in kw.pop('actions', [])]
333 actions = Vector(
334 templates=[action_type],
335 args=action_args)
336
337 kw['name'] = 'make_tuple'
338 kw['namespace'] = ['std']
339 kw['args'] = [events, actions]
Brad Bishop22cfbe62016-11-30 13:25:10 -0500340 super(Event, self).__init__(**kw)
Brad Bishopcfb3c892016-11-12 11:43:37 -0500341
Brad Bishopcfb3c892016-11-12 11:43:37 -0500342
Brad Bishop22cfbe62016-11-30 13:25:10 -0500343class MatchEvent(Event):
344 '''Associate one or more dbus signal match signatures with
345 a filter.'''
346
Brad Bishop22cfbe62016-11-30 13:25:10 -0500347 def __init__(self, **kw):
Brad Bishopc93bcc92017-01-21 16:23:39 -0500348 kw['event'] = 'DbusSignal'
349 kw['event_namespace'] = ['details']
350 kw['event_args'] = [
351 DbusSignature(**x) for x in kw.pop('signatures', [])]
352
Brad Bishop22cfbe62016-11-30 13:25:10 -0500353 super(MatchEvent, self).__init__(**kw)
354
355
356class Everything(Renderer):
357 '''Parse/render entry point.'''
358
359 class_map = {
360 'match': MatchEvent,
361 }
362
363 @staticmethod
364 def load(args):
365 # Invoke sdbus++ to generate any extra interface bindings for
366 # extra interfaces that aren't defined externally.
367 yaml_files = []
368 extra_ifaces_dir = os.path.join(args.inputdir, 'extra_interfaces.d')
369 if os.path.exists(extra_ifaces_dir):
370 for directory, _, files in os.walk(extra_ifaces_dir):
371 if not files:
372 continue
373
374 yaml_files += map(
375 lambda f: os.path.relpath(
376 os.path.join(directory, f),
377 extra_ifaces_dir),
378 filter(lambda f: f.endswith('.interface.yaml'), files))
379
380 genfiles = {
381 'server-cpp': lambda x: '%s.cpp' % (
382 x.replace(os.sep, '.')),
383 'server-header': lambda x: os.path.join(
384 os.path.join(
385 *x.split('.')), 'server.hpp')
386 }
387
388 for i in yaml_files:
389 iface = i.replace('.interface.yaml', '').replace(os.sep, '.')
390 for process, f in genfiles.iteritems():
391
392 dest = os.path.join(args.outputdir, f(iface))
393 parent = os.path.dirname(dest)
394 if parent and not os.path.exists(parent):
395 os.makedirs(parent)
396
397 with open(dest, 'w') as fd:
398 subprocess.call([
399 'sdbus++',
400 '-r',
401 extra_ifaces_dir,
402 'interface',
403 process,
404 iface],
405 stdout=fd)
406
407 # Aggregate all the event YAML in the events.d directory
408 # into a single list of events.
409
410 events_dir = os.path.join(args.inputdir, 'events.d')
411 yaml_files = filter(
412 lambda x: x.endswith('.yaml'),
413 os.listdir(events_dir))
414
415 events = []
416 for x in yaml_files:
417 with open(os.path.join(events_dir, x), 'r') as fd:
Brad Bishopeffbd932017-01-22 11:07:54 -0500418 for e in yaml.safe_load(fd.read()).get('events', {}):
Brad Bishop22cfbe62016-11-30 13:25:10 -0500419 events.append(e)
420
421 return Everything(
422 *events,
423 interfaces=Everything.get_interfaces(args))
424
425 @staticmethod
426 def get_interfaces(args):
427 '''Aggregate all the interface YAML in the interfaces.d
428 directory into a single list of interfaces.'''
429
430 interfaces_dir = os.path.join(args.inputdir, 'interfaces.d')
431 yaml_files = filter(
432 lambda x: x.endswith('.yaml'),
433 os.listdir(interfaces_dir))
434
435 interfaces = []
436 for x in yaml_files:
437 with open(os.path.join(interfaces_dir, x), 'r') as fd:
Brad Bishopeffbd932017-01-22 11:07:54 -0500438 for i in yaml.safe_load(fd.read()):
Brad Bishop22cfbe62016-11-30 13:25:10 -0500439 interfaces.append(i)
440
441 return interfaces
442
443 def __init__(self, *a, **kw):
444 self.interfaces = \
445 [Interface(x) for x in kw.pop('interfaces', [])]
446 self.events = [
447 self.class_map[x['type']](**x) for x in a]
448 super(Everything, self).__init__(**kw)
449
450 def list_interfaces(self, *a):
451 print ' '.join([str(i) for i in self.interfaces])
452
453 def generate_cpp(self, loader):
454 '''Render the template with the provided events and interfaces.'''
455 with open(os.path.join(
456 args.outputdir,
457 'generated.cpp'), 'w') as fd:
458 fd.write(
459 self.render(
460 loader,
461 'generated.mako.cpp',
462 events=self.events,
Brad Bishop9b5a12f2017-01-21 14:42:11 -0500463 interfaces=self.interfaces,
464 indent=Indent()))
Brad Bishopbf066a62016-10-19 08:09:44 -0400465
Brad Bishop95dd98f2016-11-12 12:39:15 -0500466
467if __name__ == '__main__':
468 script_dir = os.path.dirname(os.path.realpath(__file__))
Brad Bishop14a9fe52016-11-12 12:51:26 -0500469 valid_commands = {
470 'generate-cpp': 'generate_cpp',
Brad Bishop22cfbe62016-11-30 13:25:10 -0500471 'list-interfaces': 'list_interfaces'
472 }
Brad Bishop95dd98f2016-11-12 12:39:15 -0500473
474 parser = argparse.ArgumentParser(
475 description='Phosphor Inventory Manager (PIM) YAML '
476 'scanner and code generator.')
477 parser.add_argument(
478 '-o', '--output-dir', dest='outputdir',
479 default='.', help='Output directory.')
480 parser.add_argument(
481 '-d', '--dir', dest='inputdir',
482 default=os.path.join(script_dir, 'example'),
483 help='Location of files to process.')
Brad Bishopf4666f52016-11-12 12:44:42 -0500484 parser.add_argument(
485 'command', metavar='COMMAND', type=str,
486 choices=valid_commands.keys(),
Brad Bishopc029f6a2017-01-18 19:43:26 -0500487 help='%s.' % " | ".join(valid_commands.keys()))
Brad Bishop95dd98f2016-11-12 12:39:15 -0500488
489 args = parser.parse_args()
Brad Bishop22cfbe62016-11-30 13:25:10 -0500490
491 if sys.version_info < (3, 0):
492 lookup = mako.lookup.TemplateLookup(
493 directories=[script_dir],
494 disable_unicode=True)
495 else:
496 lookup = mako.lookup.TemplateLookup(
497 directories=[script_dir])
498
499 function = getattr(
500 Everything.load(args),
501 valid_commands[args.command])
502 function(lookup)
Brad Bishop95dd98f2016-11-12 12:39:15 -0500503
504
Brad Bishopbf066a62016-10-19 08:09:44 -0400505# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4