blob: 86643aafc4464d0e8ef1ce3c9f1247460390e74e [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 Bishop22cfbe62016-11-30 13:25:10 -0500245class DestroyObject(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 Bishopc93bcc92017-01-21 16:23:39 -0500249 args = [TrivialArgument(value=kw.pop('path'), type='string')]
250 kw['args'] = args
Brad Bishop22cfbe62016-11-30 13:25:10 -0500251 super(DestroyObject, self).__init__(**kw)
252
253
Brad Bishope2e402f2016-11-30 18:00:17 -0500254class SetProperty(Action):
Brad Bishopc93bcc92017-01-21 16:23:39 -0500255 '''Assemble a setProperty action.'''
Brad Bishope2e402f2016-11-30 18:00:17 -0500256
257 def __init__(self, **kw):
Brad Bishopc93bcc92017-01-21 16:23:39 -0500258 args = []
259
260 value = kw.pop('value')
261 prop = kw.pop('property')
262 iface = kw.pop('interface')
263 iface = Interface(iface)
264 namespace = iface.namespace().split('::')[:-1]
265 name = iface[-1]
266 t = Template(namespace=namespace, name=iface[-1])
267
Brad Bishope2e402f2016-11-30 18:00:17 -0500268 member = '&%s' % '::'.join(
Brad Bishopc93bcc92017-01-21 16:23:39 -0500269 namespace + [name, NamedElement(name=prop).camelCase])
270 member_type = cppTypeName(value['type'])
271 member_cast = '{0} ({1}::*)({0})'.format(member_type, t.qualified())
Brad Bishope2e402f2016-11-30 18:00:17 -0500272
Brad Bishopc93bcc92017-01-21 16:23:39 -0500273 args.append(TrivialArgument(value=kw.pop('path'), type='string'))
274 args.append(TrivialArgument(value=str(iface), type='string'))
275 args.append(TrivialArgument(
276 value=member, decorators=[Cast('static', member_cast)]))
277 args.append(TrivialArgument(**value))
Brad Bishope2e402f2016-11-30 18:00:17 -0500278
Brad Bishopc93bcc92017-01-21 16:23:39 -0500279 kw['templates'] = [Template(name=name, namespace=namespace)]
280 kw['args'] = args
Brad Bishope2e402f2016-11-30 18:00:17 -0500281 super(SetProperty, self).__init__(**kw)
282
283
Brad Bishop22cfbe62016-11-30 13:25:10 -0500284class PropertyChanged(Filter):
Brad Bishopc93bcc92017-01-21 16:23:39 -0500285 '''Assemble a propertyChanged filter.'''
Brad Bishop22cfbe62016-11-30 13:25:10 -0500286
287 def __init__(self, **kw):
Brad Bishopc93bcc92017-01-21 16:23:39 -0500288 args = []
289 args.append(TrivialArgument(value=kw.pop('interface'), type='string'))
290 args.append(TrivialArgument(value=kw.pop('property'), type='string'))
291 args.append(TrivialArgument(
292 decorators=[
293 Literal(kw['value'].get('type', None))], **kw.pop('value')))
294 kw['args'] = args
Brad Bishop22cfbe62016-11-30 13:25:10 -0500295 super(PropertyChanged, self).__init__(**kw)
296
297
Brad Bishopc93bcc92017-01-21 16:23:39 -0500298class Event(MethodCall):
299 '''Assemble an inventory manager event.'''
Brad Bishop22cfbe62016-11-30 13:25:10 -0500300
301 action_map = {
Brad Bishop22cfbe62016-11-30 13:25:10 -0500302 'destroyObject': DestroyObject,
Brad Bishope2e402f2016-11-30 18:00:17 -0500303 'setProperty': SetProperty,
Brad Bishopcfb3c892016-11-12 11:43:37 -0500304 }
305
Brad Bishopc93bcc92017-01-21 16:23:39 -0500306 filter_map = {
307 'propertyChangedTo': PropertyChanged,
308 }
309
Brad Bishop22cfbe62016-11-30 13:25:10 -0500310 def __init__(self, **kw):
Brad Bishopc93bcc92017-01-21 16:23:39 -0500311 self.summary = kw.pop('name')
312
313 filters = [
314 self.filter_map[x['name']](**x) for x in kw.pop('filters', [])]
315
316 event = MethodCall(
317 name='make_shared',
318 namespace=['std'],
319 templates=[Template(
320 name=kw.pop('event'),
321 namespace=kw.pop('event_namespace', []))],
322 args=kw.pop('event_args', []) + [filters[0]])
323
324 events = Vector(
325 templates=[Template(name='EventBasePtr', namespace=['details'])],
326 args=[event])
327
328 action_type = Template(name='ActionBasePtr', namespace=['details'])
329 action_args = [
330 self.action_map[x['name']](**x) for x in kw.pop('actions', [])]
331 actions = Vector(
332 templates=[action_type],
333 args=action_args)
334
335 kw['name'] = 'make_tuple'
336 kw['namespace'] = ['std']
337 kw['args'] = [events, actions]
Brad Bishop22cfbe62016-11-30 13:25:10 -0500338 super(Event, self).__init__(**kw)
Brad Bishopcfb3c892016-11-12 11:43:37 -0500339
Brad Bishopcfb3c892016-11-12 11:43:37 -0500340
Brad Bishop22cfbe62016-11-30 13:25:10 -0500341class MatchEvent(Event):
342 '''Associate one or more dbus signal match signatures with
343 a filter.'''
344
Brad Bishop22cfbe62016-11-30 13:25:10 -0500345 def __init__(self, **kw):
Brad Bishopc93bcc92017-01-21 16:23:39 -0500346 kw['event'] = 'DbusSignal'
347 kw['event_namespace'] = ['details']
348 kw['event_args'] = [
349 DbusSignature(**x) for x in kw.pop('signatures', [])]
350
Brad Bishop22cfbe62016-11-30 13:25:10 -0500351 super(MatchEvent, self).__init__(**kw)
352
353
354class Everything(Renderer):
355 '''Parse/render entry point.'''
356
357 class_map = {
358 'match': MatchEvent,
359 }
360
361 @staticmethod
362 def load(args):
363 # Invoke sdbus++ to generate any extra interface bindings for
364 # extra interfaces that aren't defined externally.
365 yaml_files = []
366 extra_ifaces_dir = os.path.join(args.inputdir, 'extra_interfaces.d')
367 if os.path.exists(extra_ifaces_dir):
368 for directory, _, files in os.walk(extra_ifaces_dir):
369 if not files:
370 continue
371
372 yaml_files += map(
373 lambda f: os.path.relpath(
374 os.path.join(directory, f),
375 extra_ifaces_dir),
376 filter(lambda f: f.endswith('.interface.yaml'), files))
377
378 genfiles = {
379 'server-cpp': lambda x: '%s.cpp' % (
380 x.replace(os.sep, '.')),
381 'server-header': lambda x: os.path.join(
382 os.path.join(
383 *x.split('.')), 'server.hpp')
384 }
385
386 for i in yaml_files:
387 iface = i.replace('.interface.yaml', '').replace(os.sep, '.')
388 for process, f in genfiles.iteritems():
389
390 dest = os.path.join(args.outputdir, f(iface))
391 parent = os.path.dirname(dest)
392 if parent and not os.path.exists(parent):
393 os.makedirs(parent)
394
395 with open(dest, 'w') as fd:
396 subprocess.call([
397 'sdbus++',
398 '-r',
399 extra_ifaces_dir,
400 'interface',
401 process,
402 iface],
403 stdout=fd)
404
405 # Aggregate all the event YAML in the events.d directory
406 # into a single list of events.
407
408 events_dir = os.path.join(args.inputdir, 'events.d')
409 yaml_files = filter(
410 lambda x: x.endswith('.yaml'),
411 os.listdir(events_dir))
412
413 events = []
414 for x in yaml_files:
415 with open(os.path.join(events_dir, x), 'r') as fd:
Brad Bishopeffbd932017-01-22 11:07:54 -0500416 for e in yaml.safe_load(fd.read()).get('events', {}):
Brad Bishop22cfbe62016-11-30 13:25:10 -0500417 events.append(e)
418
419 return Everything(
420 *events,
421 interfaces=Everything.get_interfaces(args))
422
423 @staticmethod
424 def get_interfaces(args):
425 '''Aggregate all the interface YAML in the interfaces.d
426 directory into a single list of interfaces.'''
427
428 interfaces_dir = os.path.join(args.inputdir, 'interfaces.d')
429 yaml_files = filter(
430 lambda x: x.endswith('.yaml'),
431 os.listdir(interfaces_dir))
432
433 interfaces = []
434 for x in yaml_files:
435 with open(os.path.join(interfaces_dir, x), 'r') as fd:
Brad Bishopeffbd932017-01-22 11:07:54 -0500436 for i in yaml.safe_load(fd.read()):
Brad Bishop22cfbe62016-11-30 13:25:10 -0500437 interfaces.append(i)
438
439 return interfaces
440
441 def __init__(self, *a, **kw):
442 self.interfaces = \
443 [Interface(x) for x in kw.pop('interfaces', [])]
444 self.events = [
445 self.class_map[x['type']](**x) for x in a]
446 super(Everything, self).__init__(**kw)
447
448 def list_interfaces(self, *a):
449 print ' '.join([str(i) for i in self.interfaces])
450
451 def generate_cpp(self, loader):
452 '''Render the template with the provided events and interfaces.'''
453 with open(os.path.join(
454 args.outputdir,
455 'generated.cpp'), 'w') as fd:
456 fd.write(
457 self.render(
458 loader,
459 'generated.mako.cpp',
460 events=self.events,
Brad Bishop9b5a12f2017-01-21 14:42:11 -0500461 interfaces=self.interfaces,
462 indent=Indent()))
Brad Bishopbf066a62016-10-19 08:09:44 -0400463
Brad Bishop95dd98f2016-11-12 12:39:15 -0500464
465if __name__ == '__main__':
466 script_dir = os.path.dirname(os.path.realpath(__file__))
Brad Bishop14a9fe52016-11-12 12:51:26 -0500467 valid_commands = {
468 'generate-cpp': 'generate_cpp',
Brad Bishop22cfbe62016-11-30 13:25:10 -0500469 'list-interfaces': 'list_interfaces'
470 }
Brad Bishop95dd98f2016-11-12 12:39:15 -0500471
472 parser = argparse.ArgumentParser(
473 description='Phosphor Inventory Manager (PIM) YAML '
474 'scanner and code generator.')
475 parser.add_argument(
476 '-o', '--output-dir', dest='outputdir',
477 default='.', help='Output directory.')
478 parser.add_argument(
479 '-d', '--dir', dest='inputdir',
480 default=os.path.join(script_dir, 'example'),
481 help='Location of files to process.')
Brad Bishopf4666f52016-11-12 12:44:42 -0500482 parser.add_argument(
483 'command', metavar='COMMAND', type=str,
484 choices=valid_commands.keys(),
Brad Bishopc029f6a2017-01-18 19:43:26 -0500485 help='%s.' % " | ".join(valid_commands.keys()))
Brad Bishop95dd98f2016-11-12 12:39:15 -0500486
487 args = parser.parse_args()
Brad Bishop22cfbe62016-11-30 13:25:10 -0500488
489 if sys.version_info < (3, 0):
490 lookup = mako.lookup.TemplateLookup(
491 directories=[script_dir],
492 disable_unicode=True)
493 else:
494 lookup = mako.lookup.TemplateLookup(
495 directories=[script_dir])
496
497 function = getattr(
498 Everything.load(args),
499 valid_commands[args.command])
500 function(lookup)
Brad Bishop95dd98f2016-11-12 12:39:15 -0500501
502
Brad Bishopbf066a62016-10-19 08:09:44 -0400503# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4