blob: 088590c6702b7f298b342521222bd80b4cd73d15 [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
89
Brad Bishop22cfbe62016-11-30 13:25:10 -050090class Filter(MethodCall):
91 '''Provide common attributes for any filter.'''
Brad Bishopbf066a62016-10-19 08:09:44 -040092
Brad Bishop22cfbe62016-11-30 13:25:10 -050093 def __init__(self, **kw):
94 kw['namespace'] = ['filters']
95 super(Filter, self).__init__(**kw)
Brad Bishopbf066a62016-10-19 08:09:44 -040096
Brad Bishop0a6a4792016-11-12 12:10:07 -050097
Brad Bishop22cfbe62016-11-30 13:25:10 -050098class Action(MethodCall):
99 '''Provide common attributes for any action.'''
Brad Bishop561a5652016-10-26 21:13:32 -0500100
Brad Bishop22cfbe62016-11-30 13:25:10 -0500101 def __init__(self, **kw):
102 kw['namespace'] = ['actions']
103 super(Action, self).__init__(**kw)
Brad Bishop92665b22016-10-26 20:51:16 -0500104
Brad Bishopcfb3c892016-11-12 11:43:37 -0500105
Brad Bishop22cfbe62016-11-30 13:25:10 -0500106class DbusSignature(NamedElement, Renderer):
107 '''Represent a dbus signal match signature.'''
Brad Bishopcfb3c892016-11-12 11:43:37 -0500108
Brad Bishop22cfbe62016-11-30 13:25:10 -0500109 def __init__(self, **kw):
110 self.sig = {x: y for x, y in kw.iteritems()}
111 kw.clear()
112 super(DbusSignature, self).__init__(**kw)
113
114
115class DestroyObject(Action):
116 '''Render a destroyObject action.'''
117
118 def __init__(self, **kw):
119 mapped = kw.pop('args')
120 kw['args'] = [
121 {'value': mapped['path'], 'type':'string'},
122 ]
123 super(DestroyObject, self).__init__(**kw)
124
125
Brad Bishope2e402f2016-11-30 18:00:17 -0500126class SetProperty(Action):
127 '''Render a setProperty action.'''
128
129 def __init__(self, **kw):
130 mapped = kw.pop('args')
131 member = Interface(mapped['interface']).namespace()
132 member = '&%s' % '::'.join(
133 member.split('::') + [NamedElement(
134 name=mapped['property']).camelCase])
135
136 memberType = Argument(**mapped['value']).cppTypeName
137
138 kw['template'] = Interface(mapped['interface']).namespace()
139 kw['args'] = [
140 {'value': mapped['path'], 'type':'string'},
141 {'value': mapped['interface'], 'type':'string'},
142 {'value': member, 'cast': '{0} ({1}::*)({0})'.format(
143 memberType,
144 Interface(mapped['interface']).namespace())},
145 mapped['value'],
146 ]
147 super(SetProperty, self).__init__(**kw)
148
149
Brad Bishop22cfbe62016-11-30 13:25:10 -0500150class NoopAction(Action):
151 '''Render a noop action.'''
152
153 def __init__(self, **kw):
154 kw['pointer'] = True
155 super(NoopAction, self).__init__(**kw)
156
157
158class NoopFilter(Filter):
159 '''Render a noop filter.'''
160
161 def __init__(self, **kw):
162 kw['pointer'] = True
163 super(NoopFilter, self).__init__(**kw)
164
165
166class PropertyChanged(Filter):
167 '''Render a propertyChanged filter.'''
168
169 def __init__(self, **kw):
170 mapped = kw.pop('args')
171 kw['args'] = [
172 {'value': mapped['interface'], 'type':'string'},
173 {'value': mapped['property'], 'type':'string'},
174 mapped['value']
175 ]
176 super(PropertyChanged, self).__init__(**kw)
177
178
179class Event(NamedElement, Renderer):
180 '''Render an inventory manager event.'''
181
182 action_map = {
183 'noop': NoopAction,
184 'destroyObject': DestroyObject,
Brad Bishope2e402f2016-11-30 18:00:17 -0500185 'setProperty': SetProperty,
Brad Bishopcfb3c892016-11-12 11:43:37 -0500186 }
187
Brad Bishop22cfbe62016-11-30 13:25:10 -0500188 def __init__(self, **kw):
189 self.cls = kw.pop('type')
190 self.actions = \
191 [self.action_map[x['name']](**x)
192 for x in kw.pop('actions', [{'name': 'noop'}])]
193 super(Event, self).__init__(**kw)
Brad Bishopcfb3c892016-11-12 11:43:37 -0500194
Brad Bishopcfb3c892016-11-12 11:43:37 -0500195
Brad Bishop22cfbe62016-11-30 13:25:10 -0500196class MatchEvent(Event):
197 '''Associate one or more dbus signal match signatures with
198 a filter.'''
199
200 filter_map = {
201 'none': NoopFilter,
202 'propertyChangedTo': PropertyChanged,
203 }
204
205 def __init__(self, **kw):
206 self.signatures = \
207 [DbusSignature(**x) for x in kw.pop('signatures', [])]
208 self.filters = \
209 [self.filter_map[x['name']](**x)
210 for x in kw.pop('filters', [{'name': 'none'}])]
211 super(MatchEvent, self).__init__(**kw)
212
213
214class Everything(Renderer):
215 '''Parse/render entry point.'''
216
217 class_map = {
218 'match': MatchEvent,
219 }
220
221 @staticmethod
222 def load(args):
223 # Invoke sdbus++ to generate any extra interface bindings for
224 # extra interfaces that aren't defined externally.
225 yaml_files = []
226 extra_ifaces_dir = os.path.join(args.inputdir, 'extra_interfaces.d')
227 if os.path.exists(extra_ifaces_dir):
228 for directory, _, files in os.walk(extra_ifaces_dir):
229 if not files:
230 continue
231
232 yaml_files += map(
233 lambda f: os.path.relpath(
234 os.path.join(directory, f),
235 extra_ifaces_dir),
236 filter(lambda f: f.endswith('.interface.yaml'), files))
237
238 genfiles = {
239 'server-cpp': lambda x: '%s.cpp' % (
240 x.replace(os.sep, '.')),
241 'server-header': lambda x: os.path.join(
242 os.path.join(
243 *x.split('.')), 'server.hpp')
244 }
245
246 for i in yaml_files:
247 iface = i.replace('.interface.yaml', '').replace(os.sep, '.')
248 for process, f in genfiles.iteritems():
249
250 dest = os.path.join(args.outputdir, f(iface))
251 parent = os.path.dirname(dest)
252 if parent and not os.path.exists(parent):
253 os.makedirs(parent)
254
255 with open(dest, 'w') as fd:
256 subprocess.call([
257 'sdbus++',
258 '-r',
259 extra_ifaces_dir,
260 'interface',
261 process,
262 iface],
263 stdout=fd)
264
265 # Aggregate all the event YAML in the events.d directory
266 # into a single list of events.
267
268 events_dir = os.path.join(args.inputdir, 'events.d')
269 yaml_files = filter(
270 lambda x: x.endswith('.yaml'),
271 os.listdir(events_dir))
272
273 events = []
274 for x in yaml_files:
275 with open(os.path.join(events_dir, x), 'r') as fd:
276 for e in yaml.load(fd.read()).get('events', {}):
277 events.append(e)
278
279 return Everything(
280 *events,
281 interfaces=Everything.get_interfaces(args))
282
283 @staticmethod
284 def get_interfaces(args):
285 '''Aggregate all the interface YAML in the interfaces.d
286 directory into a single list of interfaces.'''
287
288 interfaces_dir = os.path.join(args.inputdir, 'interfaces.d')
289 yaml_files = filter(
290 lambda x: x.endswith('.yaml'),
291 os.listdir(interfaces_dir))
292
293 interfaces = []
294 for x in yaml_files:
295 with open(os.path.join(interfaces_dir, x), 'r') as fd:
296 for i in yaml.load(fd.read()):
297 interfaces.append(i)
298
299 return interfaces
300
301 def __init__(self, *a, **kw):
302 self.interfaces = \
303 [Interface(x) for x in kw.pop('interfaces', [])]
304 self.events = [
305 self.class_map[x['type']](**x) for x in a]
306 super(Everything, self).__init__(**kw)
307
308 def list_interfaces(self, *a):
309 print ' '.join([str(i) for i in self.interfaces])
310
311 def generate_cpp(self, loader):
312 '''Render the template with the provided events and interfaces.'''
313 with open(os.path.join(
314 args.outputdir,
315 'generated.cpp'), 'w') as fd:
316 fd.write(
317 self.render(
318 loader,
319 'generated.mako.cpp',
320 events=self.events,
321 interfaces=self.interfaces))
Brad Bishopbf066a62016-10-19 08:09:44 -0400322
Brad Bishop95dd98f2016-11-12 12:39:15 -0500323
324if __name__ == '__main__':
325 script_dir = os.path.dirname(os.path.realpath(__file__))
Brad Bishop14a9fe52016-11-12 12:51:26 -0500326 valid_commands = {
327 'generate-cpp': 'generate_cpp',
Brad Bishop22cfbe62016-11-30 13:25:10 -0500328 'list-interfaces': 'list_interfaces'
329 }
Brad Bishop95dd98f2016-11-12 12:39:15 -0500330
331 parser = argparse.ArgumentParser(
332 description='Phosphor Inventory Manager (PIM) YAML '
333 'scanner and code generator.')
334 parser.add_argument(
335 '-o', '--output-dir', dest='outputdir',
336 default='.', help='Output directory.')
337 parser.add_argument(
338 '-d', '--dir', dest='inputdir',
339 default=os.path.join(script_dir, 'example'),
340 help='Location of files to process.')
Brad Bishopf4666f52016-11-12 12:44:42 -0500341 parser.add_argument(
342 'command', metavar='COMMAND', type=str,
343 choices=valid_commands.keys(),
344 help='Command to run.')
Brad Bishop95dd98f2016-11-12 12:39:15 -0500345
346 args = parser.parse_args()
Brad Bishop22cfbe62016-11-30 13:25:10 -0500347
348 if sys.version_info < (3, 0):
349 lookup = mako.lookup.TemplateLookup(
350 directories=[script_dir],
351 disable_unicode=True)
352 else:
353 lookup = mako.lookup.TemplateLookup(
354 directories=[script_dir])
355
356 function = getattr(
357 Everything.load(args),
358 valid_commands[args.command])
359 function(lookup)
Brad Bishop95dd98f2016-11-12 12:39:15 -0500360
361
Brad Bishopbf066a62016-10-19 08:09:44 -0400362# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4