blob: 9c177c1e7450767aa97b634aa73938eb48381baa [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 Bishop22cfbe62016-11-30 13:25:10 -050029class Interface(list):
30 '''Provide various interface transformations.'''
31
32 def __init__(self, iface):
33 super(Interface, self).__init__(iface.split('.'))
34
35 def namespace(self):
36 '''Represent as an sdbusplus namespace.'''
37 return '::'.join(['sdbusplus'] + self[:-1] + ['server', self[-1]])
38
39 def header(self):
40 '''Represent as an sdbusplus server binding header.'''
41 return os.sep.join(self + ['server.hpp'])
42
43 def __str__(self):
44 return '.'.join(self)
Brad Bishopbf066a62016-10-19 08:09:44 -040045
Brad Bishopcfb3c892016-11-12 11:43:37 -050046
Brad Bishop22cfbe62016-11-30 13:25:10 -050047class Argument(sdbusplus.property.Property):
48 '''Bridge sdbusplus property typenames to syntatically correct c++.'''
Brad Bishop14a9fe52016-11-12 12:51:26 -050049
Brad Bishop22cfbe62016-11-30 13:25:10 -050050 def __init__(self, **kw):
51 self.value = kw.pop('value')
Brad Bishope2e402f2016-11-30 18:00:17 -050052 self.cast = kw.pop('cast', None)
Brad Bishop22cfbe62016-11-30 13:25:10 -050053 super(Argument, self).__init__(**kw)
Brad Bishop14a9fe52016-11-12 12:51:26 -050054
Brad Bishop22cfbe62016-11-30 13:25:10 -050055 def cppArg(self):
56 '''Transform string types to c++ string constants.'''
Brad Bishop22cfbe62016-11-30 13:25:10 -050057
Brad Bishope2e402f2016-11-30 18:00:17 -050058 a = self.value
59 if self.typeName == 'string':
60 a = '"%s"' % a
61
62 if self.cast:
63 a = 'static_cast<%s>(%s)' % (self.cast, a)
64
65 return a
Brad Bishop14a9fe52016-11-12 12:51:26 -050066
67
Brad Bishop22cfbe62016-11-30 13:25:10 -050068class MethodCall(NamedElement, Renderer):
69 '''Render syntatically correct c++ method calls.'''
70
71 def __init__(self, **kw):
72 self.namespace = kw.pop('namespace', [])
Brad Bishope2e402f2016-11-30 18:00:17 -050073 self.template = kw.pop('template', '')
Brad Bishop22cfbe62016-11-30 13:25:10 -050074 self.pointer = kw.pop('pointer', False)
75 self.args = \
76 [Argument(**x) for x in kw.pop('args', [])]
77 super(MethodCall, self).__init__(**kw)
78
79 def bare_method(self):
80 '''Provide the method name and encompassing
81 namespace without any arguments.'''
Brad Bishope2e402f2016-11-30 18:00:17 -050082
83 m = '::'.join(self.namespace + [self.name])
84 if self.template:
85 m += '<%s>' % self.template
86
87 return m
Brad Bishop14a9fe52016-11-12 12:51:26 -050088
Brad Bishopcab2bdd2017-01-21 15:00:54 -050089 def call(self, loader, indent):
90 return self.render(
91 loader,
92 'method.mako.cpp',
93 method=self,
94 indent=indent)
95
96 def argument(self, loader, indent):
97 return self.call(loader, indent)
98
Brad Bishop14a9fe52016-11-12 12:51:26 -050099
Brad Bishop22cfbe62016-11-30 13:25:10 -0500100class Filter(MethodCall):
101 '''Provide common attributes for any filter.'''
Brad Bishopbf066a62016-10-19 08:09:44 -0400102
Brad Bishop22cfbe62016-11-30 13:25:10 -0500103 def __init__(self, **kw):
104 kw['namespace'] = ['filters']
105 super(Filter, self).__init__(**kw)
Brad Bishopbf066a62016-10-19 08:09:44 -0400106
Brad Bishop0a6a4792016-11-12 12:10:07 -0500107
Brad Bishop22cfbe62016-11-30 13:25:10 -0500108class Action(MethodCall):
109 '''Provide common attributes for any action.'''
Brad Bishop561a5652016-10-26 21:13:32 -0500110
Brad Bishop22cfbe62016-11-30 13:25:10 -0500111 def __init__(self, **kw):
112 kw['namespace'] = ['actions']
113 super(Action, self).__init__(**kw)
Brad Bishop92665b22016-10-26 20:51:16 -0500114
Brad Bishopcfb3c892016-11-12 11:43:37 -0500115
Brad Bishop22cfbe62016-11-30 13:25:10 -0500116class DbusSignature(NamedElement, Renderer):
117 '''Represent a dbus signal match signature.'''
Brad Bishopcfb3c892016-11-12 11:43:37 -0500118
Brad Bishop22cfbe62016-11-30 13:25:10 -0500119 def __init__(self, **kw):
120 self.sig = {x: y for x, y in kw.iteritems()}
121 kw.clear()
122 super(DbusSignature, self).__init__(**kw)
123
Brad Bishopcab2bdd2017-01-21 15:00:54 -0500124 def argument(self, loader, indent):
125 return self.render(
126 loader,
127 'signature.mako.cpp',
128 signature=self,
129 indent=indent)
130
Brad Bishop22cfbe62016-11-30 13:25:10 -0500131
132class DestroyObject(Action):
133 '''Render a destroyObject action.'''
134
135 def __init__(self, **kw):
136 mapped = kw.pop('args')
137 kw['args'] = [
138 {'value': mapped['path'], 'type':'string'},
139 ]
140 super(DestroyObject, self).__init__(**kw)
141
142
Brad Bishope2e402f2016-11-30 18:00:17 -0500143class SetProperty(Action):
144 '''Render a setProperty action.'''
145
146 def __init__(self, **kw):
147 mapped = kw.pop('args')
148 member = Interface(mapped['interface']).namespace()
149 member = '&%s' % '::'.join(
150 member.split('::') + [NamedElement(
151 name=mapped['property']).camelCase])
152
153 memberType = Argument(**mapped['value']).cppTypeName
154
155 kw['template'] = Interface(mapped['interface']).namespace()
156 kw['args'] = [
157 {'value': mapped['path'], 'type':'string'},
158 {'value': mapped['interface'], 'type':'string'},
159 {'value': member, 'cast': '{0} ({1}::*)({0})'.format(
160 memberType,
161 Interface(mapped['interface']).namespace())},
162 mapped['value'],
163 ]
164 super(SetProperty, self).__init__(**kw)
165
166
Brad Bishop22cfbe62016-11-30 13:25:10 -0500167class NoopAction(Action):
168 '''Render a noop action.'''
169
170 def __init__(self, **kw):
171 kw['pointer'] = True
172 super(NoopAction, self).__init__(**kw)
173
174
175class NoopFilter(Filter):
176 '''Render a noop filter.'''
177
178 def __init__(self, **kw):
179 kw['pointer'] = True
180 super(NoopFilter, self).__init__(**kw)
181
182
183class PropertyChanged(Filter):
184 '''Render a propertyChanged filter.'''
185
186 def __init__(self, **kw):
187 mapped = kw.pop('args')
188 kw['args'] = [
189 {'value': mapped['interface'], 'type':'string'},
190 {'value': mapped['property'], 'type':'string'},
191 mapped['value']
192 ]
193 super(PropertyChanged, self).__init__(**kw)
194
195
196class Event(NamedElement, Renderer):
197 '''Render an inventory manager event.'''
198
199 action_map = {
200 'noop': NoopAction,
201 'destroyObject': DestroyObject,
Brad Bishope2e402f2016-11-30 18:00:17 -0500202 'setProperty': SetProperty,
Brad Bishopcfb3c892016-11-12 11:43:37 -0500203 }
204
Brad Bishop22cfbe62016-11-30 13:25:10 -0500205 def __init__(self, **kw):
206 self.cls = kw.pop('type')
207 self.actions = \
208 [self.action_map[x['name']](**x)
209 for x in kw.pop('actions', [{'name': 'noop'}])]
210 super(Event, self).__init__(**kw)
Brad Bishopcfb3c892016-11-12 11:43:37 -0500211
Brad Bishopcfb3c892016-11-12 11:43:37 -0500212
Brad Bishop22cfbe62016-11-30 13:25:10 -0500213class MatchEvent(Event):
214 '''Associate one or more dbus signal match signatures with
215 a filter.'''
216
217 filter_map = {
218 'none': NoopFilter,
219 'propertyChangedTo': PropertyChanged,
220 }
221
222 def __init__(self, **kw):
223 self.signatures = \
224 [DbusSignature(**x) for x in kw.pop('signatures', [])]
225 self.filters = \
226 [self.filter_map[x['name']](**x)
227 for x in kw.pop('filters', [{'name': 'none'}])]
228 super(MatchEvent, self).__init__(**kw)
229
230
231class Everything(Renderer):
232 '''Parse/render entry point.'''
233
234 class_map = {
235 'match': MatchEvent,
236 }
237
238 @staticmethod
239 def load(args):
240 # Invoke sdbus++ to generate any extra interface bindings for
241 # extra interfaces that aren't defined externally.
242 yaml_files = []
243 extra_ifaces_dir = os.path.join(args.inputdir, 'extra_interfaces.d')
244 if os.path.exists(extra_ifaces_dir):
245 for directory, _, files in os.walk(extra_ifaces_dir):
246 if not files:
247 continue
248
249 yaml_files += map(
250 lambda f: os.path.relpath(
251 os.path.join(directory, f),
252 extra_ifaces_dir),
253 filter(lambda f: f.endswith('.interface.yaml'), files))
254
255 genfiles = {
256 'server-cpp': lambda x: '%s.cpp' % (
257 x.replace(os.sep, '.')),
258 'server-header': lambda x: os.path.join(
259 os.path.join(
260 *x.split('.')), 'server.hpp')
261 }
262
263 for i in yaml_files:
264 iface = i.replace('.interface.yaml', '').replace(os.sep, '.')
265 for process, f in genfiles.iteritems():
266
267 dest = os.path.join(args.outputdir, f(iface))
268 parent = os.path.dirname(dest)
269 if parent and not os.path.exists(parent):
270 os.makedirs(parent)
271
272 with open(dest, 'w') as fd:
273 subprocess.call([
274 'sdbus++',
275 '-r',
276 extra_ifaces_dir,
277 'interface',
278 process,
279 iface],
280 stdout=fd)
281
282 # Aggregate all the event YAML in the events.d directory
283 # into a single list of events.
284
285 events_dir = os.path.join(args.inputdir, 'events.d')
286 yaml_files = filter(
287 lambda x: x.endswith('.yaml'),
288 os.listdir(events_dir))
289
290 events = []
291 for x in yaml_files:
292 with open(os.path.join(events_dir, x), 'r') as fd:
Brad Bishopeffbd932017-01-22 11:07:54 -0500293 for e in yaml.safe_load(fd.read()).get('events', {}):
Brad Bishop22cfbe62016-11-30 13:25:10 -0500294 events.append(e)
295
296 return Everything(
297 *events,
298 interfaces=Everything.get_interfaces(args))
299
300 @staticmethod
301 def get_interfaces(args):
302 '''Aggregate all the interface YAML in the interfaces.d
303 directory into a single list of interfaces.'''
304
305 interfaces_dir = os.path.join(args.inputdir, 'interfaces.d')
306 yaml_files = filter(
307 lambda x: x.endswith('.yaml'),
308 os.listdir(interfaces_dir))
309
310 interfaces = []
311 for x in yaml_files:
312 with open(os.path.join(interfaces_dir, x), 'r') as fd:
Brad Bishopeffbd932017-01-22 11:07:54 -0500313 for i in yaml.safe_load(fd.read()):
Brad Bishop22cfbe62016-11-30 13:25:10 -0500314 interfaces.append(i)
315
316 return interfaces
317
318 def __init__(self, *a, **kw):
319 self.interfaces = \
320 [Interface(x) for x in kw.pop('interfaces', [])]
321 self.events = [
322 self.class_map[x['type']](**x) for x in a]
323 super(Everything, self).__init__(**kw)
324
325 def list_interfaces(self, *a):
326 print ' '.join([str(i) for i in self.interfaces])
327
328 def generate_cpp(self, loader):
329 '''Render the template with the provided events and interfaces.'''
330 with open(os.path.join(
331 args.outputdir,
332 'generated.cpp'), 'w') as fd:
333 fd.write(
334 self.render(
335 loader,
336 'generated.mako.cpp',
337 events=self.events,
338 interfaces=self.interfaces))
Brad Bishopbf066a62016-10-19 08:09:44 -0400339
Brad Bishop95dd98f2016-11-12 12:39:15 -0500340
341if __name__ == '__main__':
342 script_dir = os.path.dirname(os.path.realpath(__file__))
Brad Bishop14a9fe52016-11-12 12:51:26 -0500343 valid_commands = {
344 'generate-cpp': 'generate_cpp',
Brad Bishop22cfbe62016-11-30 13:25:10 -0500345 'list-interfaces': 'list_interfaces'
346 }
Brad Bishop95dd98f2016-11-12 12:39:15 -0500347
348 parser = argparse.ArgumentParser(
349 description='Phosphor Inventory Manager (PIM) YAML '
350 'scanner and code generator.')
351 parser.add_argument(
352 '-o', '--output-dir', dest='outputdir',
353 default='.', help='Output directory.')
354 parser.add_argument(
355 '-d', '--dir', dest='inputdir',
356 default=os.path.join(script_dir, 'example'),
357 help='Location of files to process.')
Brad Bishopf4666f52016-11-12 12:44:42 -0500358 parser.add_argument(
359 'command', metavar='COMMAND', type=str,
360 choices=valid_commands.keys(),
Brad Bishopc029f6a2017-01-18 19:43:26 -0500361 help='%s.' % " | ".join(valid_commands.keys()))
Brad Bishop95dd98f2016-11-12 12:39:15 -0500362
363 args = parser.parse_args()
Brad Bishop22cfbe62016-11-30 13:25:10 -0500364
365 if sys.version_info < (3, 0):
366 lookup = mako.lookup.TemplateLookup(
367 directories=[script_dir],
368 disable_unicode=True)
369 else:
370 lookup = mako.lookup.TemplateLookup(
371 directories=[script_dir])
372
373 function = getattr(
374 Everything.load(args),
375 valid_commands[args.command])
376 function(lookup)
Brad Bishop95dd98f2016-11-12 12:39:15 -0500377
378
Brad Bishopbf066a62016-10-19 08:09:44 -0400379# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4