blob: a7d2a2e15aa39734f888c6953fcae580321b3442 [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', [])]
Brad Bishop064c94a2017-01-21 21:33:30 -0500317 filters = Vector(
318 templates=[Template(name='FilterBasePtr', namespace=['details'])],
319 args=filters)
Brad Bishopc93bcc92017-01-21 16:23:39 -0500320
321 event = MethodCall(
322 name='make_shared',
323 namespace=['std'],
324 templates=[Template(
325 name=kw.pop('event'),
326 namespace=kw.pop('event_namespace', []))],
Brad Bishop064c94a2017-01-21 21:33:30 -0500327 args=kw.pop('event_args', []) + [filters])
Brad Bishopc93bcc92017-01-21 16:23:39 -0500328
329 events = Vector(
330 templates=[Template(name='EventBasePtr', namespace=['details'])],
331 args=[event])
332
333 action_type = Template(name='ActionBasePtr', namespace=['details'])
334 action_args = [
335 self.action_map[x['name']](**x) for x in kw.pop('actions', [])]
336 actions = Vector(
337 templates=[action_type],
338 args=action_args)
339
340 kw['name'] = 'make_tuple'
341 kw['namespace'] = ['std']
342 kw['args'] = [events, actions]
Brad Bishop22cfbe62016-11-30 13:25:10 -0500343 super(Event, self).__init__(**kw)
Brad Bishopcfb3c892016-11-12 11:43:37 -0500344
Brad Bishopcfb3c892016-11-12 11:43:37 -0500345
Brad Bishop22cfbe62016-11-30 13:25:10 -0500346class MatchEvent(Event):
347 '''Associate one or more dbus signal match signatures with
348 a filter.'''
349
Brad Bishop22cfbe62016-11-30 13:25:10 -0500350 def __init__(self, **kw):
Brad Bishopc93bcc92017-01-21 16:23:39 -0500351 kw['event'] = 'DbusSignal'
352 kw['event_namespace'] = ['details']
353 kw['event_args'] = [
354 DbusSignature(**x) for x in kw.pop('signatures', [])]
355
Brad Bishop22cfbe62016-11-30 13:25:10 -0500356 super(MatchEvent, self).__init__(**kw)
357
358
359class Everything(Renderer):
360 '''Parse/render entry point.'''
361
362 class_map = {
363 'match': MatchEvent,
364 }
365
366 @staticmethod
367 def load(args):
368 # Invoke sdbus++ to generate any extra interface bindings for
369 # extra interfaces that aren't defined externally.
370 yaml_files = []
371 extra_ifaces_dir = os.path.join(args.inputdir, 'extra_interfaces.d')
372 if os.path.exists(extra_ifaces_dir):
373 for directory, _, files in os.walk(extra_ifaces_dir):
374 if not files:
375 continue
376
377 yaml_files += map(
378 lambda f: os.path.relpath(
379 os.path.join(directory, f),
380 extra_ifaces_dir),
381 filter(lambda f: f.endswith('.interface.yaml'), files))
382
383 genfiles = {
384 'server-cpp': lambda x: '%s.cpp' % (
385 x.replace(os.sep, '.')),
386 'server-header': lambda x: os.path.join(
387 os.path.join(
388 *x.split('.')), 'server.hpp')
389 }
390
391 for i in yaml_files:
392 iface = i.replace('.interface.yaml', '').replace(os.sep, '.')
393 for process, f in genfiles.iteritems():
394
395 dest = os.path.join(args.outputdir, f(iface))
396 parent = os.path.dirname(dest)
397 if parent and not os.path.exists(parent):
398 os.makedirs(parent)
399
400 with open(dest, 'w') as fd:
401 subprocess.call([
402 'sdbus++',
403 '-r',
404 extra_ifaces_dir,
405 'interface',
406 process,
407 iface],
408 stdout=fd)
409
410 # Aggregate all the event YAML in the events.d directory
411 # into a single list of events.
412
413 events_dir = os.path.join(args.inputdir, 'events.d')
414 yaml_files = filter(
415 lambda x: x.endswith('.yaml'),
416 os.listdir(events_dir))
417
418 events = []
419 for x in yaml_files:
420 with open(os.path.join(events_dir, x), 'r') as fd:
Brad Bishopeffbd932017-01-22 11:07:54 -0500421 for e in yaml.safe_load(fd.read()).get('events', {}):
Brad Bishop22cfbe62016-11-30 13:25:10 -0500422 events.append(e)
423
424 return Everything(
425 *events,
426 interfaces=Everything.get_interfaces(args))
427
428 @staticmethod
429 def get_interfaces(args):
430 '''Aggregate all the interface YAML in the interfaces.d
431 directory into a single list of interfaces.'''
432
433 interfaces_dir = os.path.join(args.inputdir, 'interfaces.d')
434 yaml_files = filter(
435 lambda x: x.endswith('.yaml'),
436 os.listdir(interfaces_dir))
437
438 interfaces = []
439 for x in yaml_files:
440 with open(os.path.join(interfaces_dir, x), 'r') as fd:
Brad Bishopeffbd932017-01-22 11:07:54 -0500441 for i in yaml.safe_load(fd.read()):
Brad Bishop22cfbe62016-11-30 13:25:10 -0500442 interfaces.append(i)
443
444 return interfaces
445
446 def __init__(self, *a, **kw):
447 self.interfaces = \
448 [Interface(x) for x in kw.pop('interfaces', [])]
449 self.events = [
450 self.class_map[x['type']](**x) for x in a]
451 super(Everything, self).__init__(**kw)
452
453 def list_interfaces(self, *a):
454 print ' '.join([str(i) for i in self.interfaces])
455
456 def generate_cpp(self, loader):
457 '''Render the template with the provided events and interfaces.'''
458 with open(os.path.join(
459 args.outputdir,
460 'generated.cpp'), 'w') as fd:
461 fd.write(
462 self.render(
463 loader,
464 'generated.mako.cpp',
465 events=self.events,
Brad Bishop9b5a12f2017-01-21 14:42:11 -0500466 interfaces=self.interfaces,
467 indent=Indent()))
Brad Bishopbf066a62016-10-19 08:09:44 -0400468
Brad Bishop95dd98f2016-11-12 12:39:15 -0500469
470if __name__ == '__main__':
471 script_dir = os.path.dirname(os.path.realpath(__file__))
Brad Bishop14a9fe52016-11-12 12:51:26 -0500472 valid_commands = {
473 'generate-cpp': 'generate_cpp',
Brad Bishop22cfbe62016-11-30 13:25:10 -0500474 'list-interfaces': 'list_interfaces'
475 }
Brad Bishop95dd98f2016-11-12 12:39:15 -0500476
477 parser = argparse.ArgumentParser(
478 description='Phosphor Inventory Manager (PIM) YAML '
479 'scanner and code generator.')
480 parser.add_argument(
481 '-o', '--output-dir', dest='outputdir',
482 default='.', help='Output directory.')
483 parser.add_argument(
484 '-d', '--dir', dest='inputdir',
485 default=os.path.join(script_dir, 'example'),
486 help='Location of files to process.')
Brad Bishopf4666f52016-11-12 12:44:42 -0500487 parser.add_argument(
488 'command', metavar='COMMAND', type=str,
489 choices=valid_commands.keys(),
Brad Bishopc029f6a2017-01-18 19:43:26 -0500490 help='%s.' % " | ".join(valid_commands.keys()))
Brad Bishop95dd98f2016-11-12 12:39:15 -0500491
492 args = parser.parse_args()
Brad Bishop22cfbe62016-11-30 13:25:10 -0500493
494 if sys.version_info < (3, 0):
495 lookup = mako.lookup.TemplateLookup(
496 directories=[script_dir],
497 disable_unicode=True)
498 else:
499 lookup = mako.lookup.TemplateLookup(
500 directories=[script_dir])
501
502 function = getattr(
503 Everything.load(args),
504 valid_commands[args.command])
505 function(lookup)
Brad Bishop95dd98f2016-11-12 12:39:15 -0500506
507
Brad Bishopbf066a62016-10-19 08:09:44 -0400508# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4