blob: e5771d6057a7b32051a6f7ddeb77f598a1e204b6 [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')
52 super(Argument, self).__init__(**kw)
Brad Bishop14a9fe52016-11-12 12:51:26 -050053
Brad Bishop22cfbe62016-11-30 13:25:10 -050054 def cppArg(self):
55 '''Transform string types to c++ string constants.'''
56 if self.typeName == 'string':
57 return '"%s"' % self.value
58
59 return self.value
Brad Bishop14a9fe52016-11-12 12:51:26 -050060
61
Brad Bishop22cfbe62016-11-30 13:25:10 -050062class MethodCall(NamedElement, Renderer):
63 '''Render syntatically correct c++ method calls.'''
64
65 def __init__(self, **kw):
66 self.namespace = kw.pop('namespace', [])
67 self.pointer = kw.pop('pointer', False)
68 self.args = \
69 [Argument(**x) for x in kw.pop('args', [])]
70 super(MethodCall, self).__init__(**kw)
71
72 def bare_method(self):
73 '''Provide the method name and encompassing
74 namespace without any arguments.'''
75 return '::'.join(self.namespace + [self.name])
Brad Bishop14a9fe52016-11-12 12:51:26 -050076
77
Brad Bishop22cfbe62016-11-30 13:25:10 -050078class Filter(MethodCall):
79 '''Provide common attributes for any filter.'''
Brad Bishopbf066a62016-10-19 08:09:44 -040080
Brad Bishop22cfbe62016-11-30 13:25:10 -050081 def __init__(self, **kw):
82 kw['namespace'] = ['filters']
83 super(Filter, self).__init__(**kw)
Brad Bishopbf066a62016-10-19 08:09:44 -040084
Brad Bishop0a6a4792016-11-12 12:10:07 -050085
Brad Bishop22cfbe62016-11-30 13:25:10 -050086class Action(MethodCall):
87 '''Provide common attributes for any action.'''
Brad Bishop561a5652016-10-26 21:13:32 -050088
Brad Bishop22cfbe62016-11-30 13:25:10 -050089 def __init__(self, **kw):
90 kw['namespace'] = ['actions']
91 super(Action, self).__init__(**kw)
Brad Bishop92665b22016-10-26 20:51:16 -050092
Brad Bishopcfb3c892016-11-12 11:43:37 -050093
Brad Bishop22cfbe62016-11-30 13:25:10 -050094class DbusSignature(NamedElement, Renderer):
95 '''Represent a dbus signal match signature.'''
Brad Bishopcfb3c892016-11-12 11:43:37 -050096
Brad Bishop22cfbe62016-11-30 13:25:10 -050097 def __init__(self, **kw):
98 self.sig = {x: y for x, y in kw.iteritems()}
99 kw.clear()
100 super(DbusSignature, self).__init__(**kw)
101
102
103class DestroyObject(Action):
104 '''Render a destroyObject action.'''
105
106 def __init__(self, **kw):
107 mapped = kw.pop('args')
108 kw['args'] = [
109 {'value': mapped['path'], 'type':'string'},
110 ]
111 super(DestroyObject, self).__init__(**kw)
112
113
114class NoopAction(Action):
115 '''Render a noop action.'''
116
117 def __init__(self, **kw):
118 kw['pointer'] = True
119 super(NoopAction, self).__init__(**kw)
120
121
122class NoopFilter(Filter):
123 '''Render a noop filter.'''
124
125 def __init__(self, **kw):
126 kw['pointer'] = True
127 super(NoopFilter, self).__init__(**kw)
128
129
130class PropertyChanged(Filter):
131 '''Render a propertyChanged filter.'''
132
133 def __init__(self, **kw):
134 mapped = kw.pop('args')
135 kw['args'] = [
136 {'value': mapped['interface'], 'type':'string'},
137 {'value': mapped['property'], 'type':'string'},
138 mapped['value']
139 ]
140 super(PropertyChanged, self).__init__(**kw)
141
142
143class Event(NamedElement, Renderer):
144 '''Render an inventory manager event.'''
145
146 action_map = {
147 'noop': NoopAction,
148 'destroyObject': DestroyObject,
Brad Bishopcfb3c892016-11-12 11:43:37 -0500149 }
150
Brad Bishop22cfbe62016-11-30 13:25:10 -0500151 def __init__(self, **kw):
152 self.cls = kw.pop('type')
153 self.actions = \
154 [self.action_map[x['name']](**x)
155 for x in kw.pop('actions', [{'name': 'noop'}])]
156 super(Event, self).__init__(**kw)
Brad Bishopcfb3c892016-11-12 11:43:37 -0500157
Brad Bishopcfb3c892016-11-12 11:43:37 -0500158
Brad Bishop22cfbe62016-11-30 13:25:10 -0500159class MatchEvent(Event):
160 '''Associate one or more dbus signal match signatures with
161 a filter.'''
162
163 filter_map = {
164 'none': NoopFilter,
165 'propertyChangedTo': PropertyChanged,
166 }
167
168 def __init__(self, **kw):
169 self.signatures = \
170 [DbusSignature(**x) for x in kw.pop('signatures', [])]
171 self.filters = \
172 [self.filter_map[x['name']](**x)
173 for x in kw.pop('filters', [{'name': 'none'}])]
174 super(MatchEvent, self).__init__(**kw)
175
176
177class Everything(Renderer):
178 '''Parse/render entry point.'''
179
180 class_map = {
181 'match': MatchEvent,
182 }
183
184 @staticmethod
185 def load(args):
186 # Invoke sdbus++ to generate any extra interface bindings for
187 # extra interfaces that aren't defined externally.
188 yaml_files = []
189 extra_ifaces_dir = os.path.join(args.inputdir, 'extra_interfaces.d')
190 if os.path.exists(extra_ifaces_dir):
191 for directory, _, files in os.walk(extra_ifaces_dir):
192 if not files:
193 continue
194
195 yaml_files += map(
196 lambda f: os.path.relpath(
197 os.path.join(directory, f),
198 extra_ifaces_dir),
199 filter(lambda f: f.endswith('.interface.yaml'), files))
200
201 genfiles = {
202 'server-cpp': lambda x: '%s.cpp' % (
203 x.replace(os.sep, '.')),
204 'server-header': lambda x: os.path.join(
205 os.path.join(
206 *x.split('.')), 'server.hpp')
207 }
208
209 for i in yaml_files:
210 iface = i.replace('.interface.yaml', '').replace(os.sep, '.')
211 for process, f in genfiles.iteritems():
212
213 dest = os.path.join(args.outputdir, f(iface))
214 parent = os.path.dirname(dest)
215 if parent and not os.path.exists(parent):
216 os.makedirs(parent)
217
218 with open(dest, 'w') as fd:
219 subprocess.call([
220 'sdbus++',
221 '-r',
222 extra_ifaces_dir,
223 'interface',
224 process,
225 iface],
226 stdout=fd)
227
228 # Aggregate all the event YAML in the events.d directory
229 # into a single list of events.
230
231 events_dir = os.path.join(args.inputdir, 'events.d')
232 yaml_files = filter(
233 lambda x: x.endswith('.yaml'),
234 os.listdir(events_dir))
235
236 events = []
237 for x in yaml_files:
238 with open(os.path.join(events_dir, x), 'r') as fd:
239 for e in yaml.load(fd.read()).get('events', {}):
240 events.append(e)
241
242 return Everything(
243 *events,
244 interfaces=Everything.get_interfaces(args))
245
246 @staticmethod
247 def get_interfaces(args):
248 '''Aggregate all the interface YAML in the interfaces.d
249 directory into a single list of interfaces.'''
250
251 interfaces_dir = os.path.join(args.inputdir, 'interfaces.d')
252 yaml_files = filter(
253 lambda x: x.endswith('.yaml'),
254 os.listdir(interfaces_dir))
255
256 interfaces = []
257 for x in yaml_files:
258 with open(os.path.join(interfaces_dir, x), 'r') as fd:
259 for i in yaml.load(fd.read()):
260 interfaces.append(i)
261
262 return interfaces
263
264 def __init__(self, *a, **kw):
265 self.interfaces = \
266 [Interface(x) for x in kw.pop('interfaces', [])]
267 self.events = [
268 self.class_map[x['type']](**x) for x in a]
269 super(Everything, self).__init__(**kw)
270
271 def list_interfaces(self, *a):
272 print ' '.join([str(i) for i in self.interfaces])
273
274 def generate_cpp(self, loader):
275 '''Render the template with the provided events and interfaces.'''
276 with open(os.path.join(
277 args.outputdir,
278 'generated.cpp'), 'w') as fd:
279 fd.write(
280 self.render(
281 loader,
282 'generated.mako.cpp',
283 events=self.events,
284 interfaces=self.interfaces))
Brad Bishopbf066a62016-10-19 08:09:44 -0400285
Brad Bishop95dd98f2016-11-12 12:39:15 -0500286
287if __name__ == '__main__':
288 script_dir = os.path.dirname(os.path.realpath(__file__))
Brad Bishop14a9fe52016-11-12 12:51:26 -0500289 valid_commands = {
290 'generate-cpp': 'generate_cpp',
Brad Bishop22cfbe62016-11-30 13:25:10 -0500291 'list-interfaces': 'list_interfaces'
292 }
Brad Bishop95dd98f2016-11-12 12:39:15 -0500293
294 parser = argparse.ArgumentParser(
295 description='Phosphor Inventory Manager (PIM) YAML '
296 'scanner and code generator.')
297 parser.add_argument(
298 '-o', '--output-dir', dest='outputdir',
299 default='.', help='Output directory.')
300 parser.add_argument(
301 '-d', '--dir', dest='inputdir',
302 default=os.path.join(script_dir, 'example'),
303 help='Location of files to process.')
Brad Bishopf4666f52016-11-12 12:44:42 -0500304 parser.add_argument(
305 'command', metavar='COMMAND', type=str,
306 choices=valid_commands.keys(),
307 help='Command to run.')
Brad Bishop95dd98f2016-11-12 12:39:15 -0500308
309 args = parser.parse_args()
Brad Bishop22cfbe62016-11-30 13:25:10 -0500310
311 if sys.version_info < (3, 0):
312 lookup = mako.lookup.TemplateLookup(
313 directories=[script_dir],
314 disable_unicode=True)
315 else:
316 lookup = mako.lookup.TemplateLookup(
317 directories=[script_dir])
318
319 function = getattr(
320 Everything.load(args),
321 valid_commands[args.command])
322 function(lookup)
Brad Bishop95dd98f2016-11-12 12:39:15 -0500323
324
Brad Bishopbf066a62016-10-19 08:09:44 -0400325# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4