blob: 140ca549840d740a7c61e1de338324d4869c5331 [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 Bishop22cfbe62016-11-30 13:25:10 -0500118class Argument(sdbusplus.property.Property):
119 '''Bridge sdbusplus property typenames to syntatically correct c++.'''
Brad Bishop14a9fe52016-11-12 12:51:26 -0500120
Brad Bishop22cfbe62016-11-30 13:25:10 -0500121 def __init__(self, **kw):
122 self.value = kw.pop('value')
Brad Bishope2e402f2016-11-30 18:00:17 -0500123 self.cast = kw.pop('cast', None)
Brad Bishop22cfbe62016-11-30 13:25:10 -0500124 super(Argument, self).__init__(**kw)
Brad Bishop14a9fe52016-11-12 12:51:26 -0500125
Brad Bishop22cfbe62016-11-30 13:25:10 -0500126 def cppArg(self):
127 '''Transform string types to c++ string constants.'''
Brad Bishop22cfbe62016-11-30 13:25:10 -0500128
Brad Bishope2e402f2016-11-30 18:00:17 -0500129 a = self.value
130 if self.typeName == 'string':
131 a = '"%s"' % a
132
133 if self.cast:
134 a = 'static_cast<%s>(%s)' % (self.cast, a)
135
136 return a
Brad Bishop14a9fe52016-11-12 12:51:26 -0500137
138
Brad Bishop9b5a12f2017-01-21 14:42:11 -0500139class InitializerList(Argument):
140 '''Initializer list arguments.'''
141
142 def __init__(self, **kw):
143 self.values = kw.pop('values')
144 super(InitializerList, self).__init__(**kw)
145
146 def argument(self, loader, indent):
147 return self.render(
148 loader,
149 'argument.mako.cpp',
150 arg=self,
151 indent=indent)
152
153
Brad Bishop22cfbe62016-11-30 13:25:10 -0500154class MethodCall(NamedElement, Renderer):
155 '''Render syntatically correct c++ method calls.'''
156
157 def __init__(self, **kw):
158 self.namespace = kw.pop('namespace', [])
Brad Bishope2e402f2016-11-30 18:00:17 -0500159 self.template = kw.pop('template', '')
Brad Bishop22cfbe62016-11-30 13:25:10 -0500160 self.pointer = kw.pop('pointer', False)
161 self.args = \
162 [Argument(**x) for x in kw.pop('args', [])]
163 super(MethodCall, self).__init__(**kw)
164
165 def bare_method(self):
166 '''Provide the method name and encompassing
167 namespace without any arguments.'''
Brad Bishope2e402f2016-11-30 18:00:17 -0500168
169 m = '::'.join(self.namespace + [self.name])
170 if self.template:
171 m += '<%s>' % self.template
172
173 return m
Brad Bishop14a9fe52016-11-12 12:51:26 -0500174
Brad Bishopcab2bdd2017-01-21 15:00:54 -0500175 def call(self, loader, indent):
176 return self.render(
177 loader,
178 'method.mako.cpp',
179 method=self,
180 indent=indent)
181
182 def argument(self, loader, indent):
183 return self.call(loader, indent)
184
Brad Bishop14a9fe52016-11-12 12:51:26 -0500185
Brad Bishop9b5a12f2017-01-21 14:42:11 -0500186class Vector(MethodCall):
187 '''Convenience type for vectors.'''
188
189 def __init__(self, **kw):
190 kw['name'] = 'vector'
191 kw['namespace'] = ['std']
192 kw['args'] = [InitializerList(values=kw.pop('args'))]
193 super(Vector, self).__init__(**kw)
194
195
196class Wrapper(MethodCall):
197 '''Convenience type for functions that wrap other functions.'''
198
199 def __init__(self, **kw):
200 m = MethodCall(
201 name=kw.pop('name'),
202 namespace=kw.pop('namespace', []),
203 templates=kw.pop('templates', []),
204 args=kw.pop('args', []))
205
206 kw['name'] = kw.pop('wrapper_name')
207 kw['namespace'] = kw.pop('wrapper_namespace', [])
208 kw['args'] = [m]
209 super(Wrapper, self).__init__(**kw)
210
211
Brad Bishop22cfbe62016-11-30 13:25:10 -0500212class Filter(MethodCall):
213 '''Provide common attributes for any filter.'''
Brad Bishopbf066a62016-10-19 08:09:44 -0400214
Brad Bishop22cfbe62016-11-30 13:25:10 -0500215 def __init__(self, **kw):
216 kw['namespace'] = ['filters']
217 super(Filter, self).__init__(**kw)
Brad Bishopbf066a62016-10-19 08:09:44 -0400218
Brad Bishop0a6a4792016-11-12 12:10:07 -0500219
Brad Bishop22cfbe62016-11-30 13:25:10 -0500220class Action(MethodCall):
221 '''Provide common attributes for any action.'''
Brad Bishop561a5652016-10-26 21:13:32 -0500222
Brad Bishop22cfbe62016-11-30 13:25:10 -0500223 def __init__(self, **kw):
224 kw['namespace'] = ['actions']
225 super(Action, self).__init__(**kw)
Brad Bishop92665b22016-10-26 20:51:16 -0500226
Brad Bishopcfb3c892016-11-12 11:43:37 -0500227
Brad Bishop22cfbe62016-11-30 13:25:10 -0500228class DbusSignature(NamedElement, Renderer):
229 '''Represent a dbus signal match signature.'''
Brad Bishopcfb3c892016-11-12 11:43:37 -0500230
Brad Bishop22cfbe62016-11-30 13:25:10 -0500231 def __init__(self, **kw):
232 self.sig = {x: y for x, y in kw.iteritems()}
233 kw.clear()
234 super(DbusSignature, self).__init__(**kw)
235
Brad Bishopcab2bdd2017-01-21 15:00:54 -0500236 def argument(self, loader, indent):
237 return self.render(
238 loader,
239 'signature.mako.cpp',
240 signature=self,
241 indent=indent)
242
Brad Bishop22cfbe62016-11-30 13:25:10 -0500243
244class DestroyObject(Action):
245 '''Render a destroyObject action.'''
246
247 def __init__(self, **kw):
248 mapped = kw.pop('args')
249 kw['args'] = [
250 {'value': mapped['path'], 'type':'string'},
251 ]
252 super(DestroyObject, self).__init__(**kw)
253
254
Brad Bishope2e402f2016-11-30 18:00:17 -0500255class SetProperty(Action):
256 '''Render a setProperty action.'''
257
258 def __init__(self, **kw):
259 mapped = kw.pop('args')
260 member = Interface(mapped['interface']).namespace()
261 member = '&%s' % '::'.join(
262 member.split('::') + [NamedElement(
263 name=mapped['property']).camelCase])
264
265 memberType = Argument(**mapped['value']).cppTypeName
266
267 kw['template'] = Interface(mapped['interface']).namespace()
268 kw['args'] = [
269 {'value': mapped['path'], 'type':'string'},
270 {'value': mapped['interface'], 'type':'string'},
271 {'value': member, 'cast': '{0} ({1}::*)({0})'.format(
272 memberType,
273 Interface(mapped['interface']).namespace())},
274 mapped['value'],
275 ]
276 super(SetProperty, self).__init__(**kw)
277
278
Brad Bishop22cfbe62016-11-30 13:25:10 -0500279class NoopAction(Action):
280 '''Render a noop action.'''
281
282 def __init__(self, **kw):
283 kw['pointer'] = True
284 super(NoopAction, self).__init__(**kw)
285
286
287class NoopFilter(Filter):
288 '''Render a noop filter.'''
289
290 def __init__(self, **kw):
291 kw['pointer'] = True
292 super(NoopFilter, self).__init__(**kw)
293
294
295class PropertyChanged(Filter):
296 '''Render a propertyChanged filter.'''
297
298 def __init__(self, **kw):
299 mapped = kw.pop('args')
300 kw['args'] = [
301 {'value': mapped['interface'], 'type':'string'},
302 {'value': mapped['property'], 'type':'string'},
303 mapped['value']
304 ]
305 super(PropertyChanged, self).__init__(**kw)
306
307
308class Event(NamedElement, Renderer):
309 '''Render an inventory manager event.'''
310
311 action_map = {
312 'noop': NoopAction,
313 'destroyObject': DestroyObject,
Brad Bishope2e402f2016-11-30 18:00:17 -0500314 'setProperty': SetProperty,
Brad Bishopcfb3c892016-11-12 11:43:37 -0500315 }
316
Brad Bishop22cfbe62016-11-30 13:25:10 -0500317 def __init__(self, **kw):
318 self.cls = kw.pop('type')
319 self.actions = \
320 [self.action_map[x['name']](**x)
321 for x in kw.pop('actions', [{'name': 'noop'}])]
322 super(Event, self).__init__(**kw)
Brad Bishopcfb3c892016-11-12 11:43:37 -0500323
Brad Bishopcfb3c892016-11-12 11:43:37 -0500324
Brad Bishop22cfbe62016-11-30 13:25:10 -0500325class MatchEvent(Event):
326 '''Associate one or more dbus signal match signatures with
327 a filter.'''
328
329 filter_map = {
330 'none': NoopFilter,
331 'propertyChangedTo': PropertyChanged,
332 }
333
334 def __init__(self, **kw):
335 self.signatures = \
336 [DbusSignature(**x) for x in kw.pop('signatures', [])]
337 self.filters = \
338 [self.filter_map[x['name']](**x)
339 for x in kw.pop('filters', [{'name': 'none'}])]
340 super(MatchEvent, self).__init__(**kw)
341
342
343class Everything(Renderer):
344 '''Parse/render entry point.'''
345
346 class_map = {
347 'match': MatchEvent,
348 }
349
350 @staticmethod
351 def load(args):
352 # Invoke sdbus++ to generate any extra interface bindings for
353 # extra interfaces that aren't defined externally.
354 yaml_files = []
355 extra_ifaces_dir = os.path.join(args.inputdir, 'extra_interfaces.d')
356 if os.path.exists(extra_ifaces_dir):
357 for directory, _, files in os.walk(extra_ifaces_dir):
358 if not files:
359 continue
360
361 yaml_files += map(
362 lambda f: os.path.relpath(
363 os.path.join(directory, f),
364 extra_ifaces_dir),
365 filter(lambda f: f.endswith('.interface.yaml'), files))
366
367 genfiles = {
368 'server-cpp': lambda x: '%s.cpp' % (
369 x.replace(os.sep, '.')),
370 'server-header': lambda x: os.path.join(
371 os.path.join(
372 *x.split('.')), 'server.hpp')
373 }
374
375 for i in yaml_files:
376 iface = i.replace('.interface.yaml', '').replace(os.sep, '.')
377 for process, f in genfiles.iteritems():
378
379 dest = os.path.join(args.outputdir, f(iface))
380 parent = os.path.dirname(dest)
381 if parent and not os.path.exists(parent):
382 os.makedirs(parent)
383
384 with open(dest, 'w') as fd:
385 subprocess.call([
386 'sdbus++',
387 '-r',
388 extra_ifaces_dir,
389 'interface',
390 process,
391 iface],
392 stdout=fd)
393
394 # Aggregate all the event YAML in the events.d directory
395 # into a single list of events.
396
397 events_dir = os.path.join(args.inputdir, 'events.d')
398 yaml_files = filter(
399 lambda x: x.endswith('.yaml'),
400 os.listdir(events_dir))
401
402 events = []
403 for x in yaml_files:
404 with open(os.path.join(events_dir, x), 'r') as fd:
Brad Bishopeffbd932017-01-22 11:07:54 -0500405 for e in yaml.safe_load(fd.read()).get('events', {}):
Brad Bishop22cfbe62016-11-30 13:25:10 -0500406 events.append(e)
407
408 return Everything(
409 *events,
410 interfaces=Everything.get_interfaces(args))
411
412 @staticmethod
413 def get_interfaces(args):
414 '''Aggregate all the interface YAML in the interfaces.d
415 directory into a single list of interfaces.'''
416
417 interfaces_dir = os.path.join(args.inputdir, 'interfaces.d')
418 yaml_files = filter(
419 lambda x: x.endswith('.yaml'),
420 os.listdir(interfaces_dir))
421
422 interfaces = []
423 for x in yaml_files:
424 with open(os.path.join(interfaces_dir, x), 'r') as fd:
Brad Bishopeffbd932017-01-22 11:07:54 -0500425 for i in yaml.safe_load(fd.read()):
Brad Bishop22cfbe62016-11-30 13:25:10 -0500426 interfaces.append(i)
427
428 return interfaces
429
430 def __init__(self, *a, **kw):
431 self.interfaces = \
432 [Interface(x) for x in kw.pop('interfaces', [])]
433 self.events = [
434 self.class_map[x['type']](**x) for x in a]
435 super(Everything, self).__init__(**kw)
436
437 def list_interfaces(self, *a):
438 print ' '.join([str(i) for i in self.interfaces])
439
440 def generate_cpp(self, loader):
441 '''Render the template with the provided events and interfaces.'''
442 with open(os.path.join(
443 args.outputdir,
444 'generated.cpp'), 'w') as fd:
445 fd.write(
446 self.render(
447 loader,
448 'generated.mako.cpp',
449 events=self.events,
Brad Bishop9b5a12f2017-01-21 14:42:11 -0500450 interfaces=self.interfaces,
451 indent=Indent()))
Brad Bishopbf066a62016-10-19 08:09:44 -0400452
Brad Bishop95dd98f2016-11-12 12:39:15 -0500453
454if __name__ == '__main__':
455 script_dir = os.path.dirname(os.path.realpath(__file__))
Brad Bishop14a9fe52016-11-12 12:51:26 -0500456 valid_commands = {
457 'generate-cpp': 'generate_cpp',
Brad Bishop22cfbe62016-11-30 13:25:10 -0500458 'list-interfaces': 'list_interfaces'
459 }
Brad Bishop95dd98f2016-11-12 12:39:15 -0500460
461 parser = argparse.ArgumentParser(
462 description='Phosphor Inventory Manager (PIM) YAML '
463 'scanner and code generator.')
464 parser.add_argument(
465 '-o', '--output-dir', dest='outputdir',
466 default='.', help='Output directory.')
467 parser.add_argument(
468 '-d', '--dir', dest='inputdir',
469 default=os.path.join(script_dir, 'example'),
470 help='Location of files to process.')
Brad Bishopf4666f52016-11-12 12:44:42 -0500471 parser.add_argument(
472 'command', metavar='COMMAND', type=str,
473 choices=valid_commands.keys(),
Brad Bishopc029f6a2017-01-18 19:43:26 -0500474 help='%s.' % " | ".join(valid_commands.keys()))
Brad Bishop95dd98f2016-11-12 12:39:15 -0500475
476 args = parser.parse_args()
Brad Bishop22cfbe62016-11-30 13:25:10 -0500477
478 if sys.version_info < (3, 0):
479 lookup = mako.lookup.TemplateLookup(
480 directories=[script_dir],
481 disable_unicode=True)
482 else:
483 lookup = mako.lookup.TemplateLookup(
484 directories=[script_dir])
485
486 function = getattr(
487 Everything.load(args),
488 valid_commands[args.command])
489 function(lookup)
Brad Bishop95dd98f2016-11-12 12:39:15 -0500490
491
Brad Bishopbf066a62016-10-19 08:09:44 -0400492# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4