blob: 0e5ee25c153bb633ce4aff062c559ec8723a6a96 [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 PropertyChanged(Filter):
302 '''Render a propertyChanged filter.'''
303
304 def __init__(self, **kw):
305 mapped = kw.pop('args')
306 kw['args'] = [
307 {'value': mapped['interface'], 'type':'string'},
308 {'value': mapped['property'], 'type':'string'},
309 mapped['value']
310 ]
311 super(PropertyChanged, self).__init__(**kw)
312
313
314class Event(NamedElement, Renderer):
315 '''Render an inventory manager event.'''
316
317 action_map = {
Brad Bishop22cfbe62016-11-30 13:25:10 -0500318 'destroyObject': DestroyObject,
Brad Bishope2e402f2016-11-30 18:00:17 -0500319 'setProperty': SetProperty,
Brad Bishopcfb3c892016-11-12 11:43:37 -0500320 }
321
Brad Bishop22cfbe62016-11-30 13:25:10 -0500322 def __init__(self, **kw):
323 self.cls = kw.pop('type')
324 self.actions = \
325 [self.action_map[x['name']](**x)
Brad Bishop5d9cb052017-01-21 15:42:18 -0500326 for x in kw.pop('actions', [])]
Brad Bishop22cfbe62016-11-30 13:25:10 -0500327 super(Event, self).__init__(**kw)
Brad Bishopcfb3c892016-11-12 11:43:37 -0500328
Brad Bishopcfb3c892016-11-12 11:43:37 -0500329
Brad Bishop22cfbe62016-11-30 13:25:10 -0500330class MatchEvent(Event):
331 '''Associate one or more dbus signal match signatures with
332 a filter.'''
333
334 filter_map = {
Brad Bishop22cfbe62016-11-30 13:25:10 -0500335 'propertyChangedTo': PropertyChanged,
336 }
337
338 def __init__(self, **kw):
339 self.signatures = \
340 [DbusSignature(**x) for x in kw.pop('signatures', [])]
341 self.filters = \
342 [self.filter_map[x['name']](**x)
Brad Bishop5d9cb052017-01-21 15:42:18 -0500343 for x in kw.pop('filters')]
Brad Bishop22cfbe62016-11-30 13:25:10 -0500344 super(MatchEvent, self).__init__(**kw)
345
346
347class Everything(Renderer):
348 '''Parse/render entry point.'''
349
350 class_map = {
351 'match': MatchEvent,
352 }
353
354 @staticmethod
355 def load(args):
356 # Invoke sdbus++ to generate any extra interface bindings for
357 # extra interfaces that aren't defined externally.
358 yaml_files = []
359 extra_ifaces_dir = os.path.join(args.inputdir, 'extra_interfaces.d')
360 if os.path.exists(extra_ifaces_dir):
361 for directory, _, files in os.walk(extra_ifaces_dir):
362 if not files:
363 continue
364
365 yaml_files += map(
366 lambda f: os.path.relpath(
367 os.path.join(directory, f),
368 extra_ifaces_dir),
369 filter(lambda f: f.endswith('.interface.yaml'), files))
370
371 genfiles = {
372 'server-cpp': lambda x: '%s.cpp' % (
373 x.replace(os.sep, '.')),
374 'server-header': lambda x: os.path.join(
375 os.path.join(
376 *x.split('.')), 'server.hpp')
377 }
378
379 for i in yaml_files:
380 iface = i.replace('.interface.yaml', '').replace(os.sep, '.')
381 for process, f in genfiles.iteritems():
382
383 dest = os.path.join(args.outputdir, f(iface))
384 parent = os.path.dirname(dest)
385 if parent and not os.path.exists(parent):
386 os.makedirs(parent)
387
388 with open(dest, 'w') as fd:
389 subprocess.call([
390 'sdbus++',
391 '-r',
392 extra_ifaces_dir,
393 'interface',
394 process,
395 iface],
396 stdout=fd)
397
398 # Aggregate all the event YAML in the events.d directory
399 # into a single list of events.
400
401 events_dir = os.path.join(args.inputdir, 'events.d')
402 yaml_files = filter(
403 lambda x: x.endswith('.yaml'),
404 os.listdir(events_dir))
405
406 events = []
407 for x in yaml_files:
408 with open(os.path.join(events_dir, x), 'r') as fd:
Brad Bishopeffbd932017-01-22 11:07:54 -0500409 for e in yaml.safe_load(fd.read()).get('events', {}):
Brad Bishop22cfbe62016-11-30 13:25:10 -0500410 events.append(e)
411
412 return Everything(
413 *events,
414 interfaces=Everything.get_interfaces(args))
415
416 @staticmethod
417 def get_interfaces(args):
418 '''Aggregate all the interface YAML in the interfaces.d
419 directory into a single list of interfaces.'''
420
421 interfaces_dir = os.path.join(args.inputdir, 'interfaces.d')
422 yaml_files = filter(
423 lambda x: x.endswith('.yaml'),
424 os.listdir(interfaces_dir))
425
426 interfaces = []
427 for x in yaml_files:
428 with open(os.path.join(interfaces_dir, x), 'r') as fd:
Brad Bishopeffbd932017-01-22 11:07:54 -0500429 for i in yaml.safe_load(fd.read()):
Brad Bishop22cfbe62016-11-30 13:25:10 -0500430 interfaces.append(i)
431
432 return interfaces
433
434 def __init__(self, *a, **kw):
435 self.interfaces = \
436 [Interface(x) for x in kw.pop('interfaces', [])]
437 self.events = [
438 self.class_map[x['type']](**x) for x in a]
439 super(Everything, self).__init__(**kw)
440
441 def list_interfaces(self, *a):
442 print ' '.join([str(i) for i in self.interfaces])
443
444 def generate_cpp(self, loader):
445 '''Render the template with the provided events and interfaces.'''
446 with open(os.path.join(
447 args.outputdir,
448 'generated.cpp'), 'w') as fd:
449 fd.write(
450 self.render(
451 loader,
452 'generated.mako.cpp',
453 events=self.events,
Brad Bishop9b5a12f2017-01-21 14:42:11 -0500454 interfaces=self.interfaces,
455 indent=Indent()))
Brad Bishopbf066a62016-10-19 08:09:44 -0400456
Brad Bishop95dd98f2016-11-12 12:39:15 -0500457
458if __name__ == '__main__':
459 script_dir = os.path.dirname(os.path.realpath(__file__))
Brad Bishop14a9fe52016-11-12 12:51:26 -0500460 valid_commands = {
461 'generate-cpp': 'generate_cpp',
Brad Bishop22cfbe62016-11-30 13:25:10 -0500462 'list-interfaces': 'list_interfaces'
463 }
Brad Bishop95dd98f2016-11-12 12:39:15 -0500464
465 parser = argparse.ArgumentParser(
466 description='Phosphor Inventory Manager (PIM) YAML '
467 'scanner and code generator.')
468 parser.add_argument(
469 '-o', '--output-dir', dest='outputdir',
470 default='.', help='Output directory.')
471 parser.add_argument(
472 '-d', '--dir', dest='inputdir',
473 default=os.path.join(script_dir, 'example'),
474 help='Location of files to process.')
Brad Bishopf4666f52016-11-12 12:44:42 -0500475 parser.add_argument(
476 'command', metavar='COMMAND', type=str,
477 choices=valid_commands.keys(),
Brad Bishopc029f6a2017-01-18 19:43:26 -0500478 help='%s.' % " | ".join(valid_commands.keys()))
Brad Bishop95dd98f2016-11-12 12:39:15 -0500479
480 args = parser.parse_args()
Brad Bishop22cfbe62016-11-30 13:25:10 -0500481
482 if sys.version_info < (3, 0):
483 lookup = mako.lookup.TemplateLookup(
484 directories=[script_dir],
485 disable_unicode=True)
486 else:
487 lookup = mako.lookup.TemplateLookup(
488 directories=[script_dir])
489
490 function = getattr(
491 Everything.load(args),
492 valid_commands[args.command])
493 function(lookup)
Brad Bishop95dd98f2016-11-12 12:39:15 -0500494
495
Brad Bishopbf066a62016-10-19 08:09:44 -0400496# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4