TINF-96: add gobject-introspection; dep for connman-test
[profile/ivi/gobject-introspection.git] / giscanner / gdumpparser.py
1 # -*- Mode: Python -*-
2 # GObject-Introspection - a framework for introspecting GObject libraries
3 # Copyright (C) 2008  Johan Dahlin
4 #
5 # This library is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU Lesser General Public
7 # License as published by the Free Software Foundation; either
8 # version 2 of the License, or (at your option) any later version.
9 #
10 # This library is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 # Lesser General Public License for more details.
14 #
15 # You should have received a copy of the GNU Lesser General Public
16 # License along with this library; if not, write to the
17 # Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 # Boston, MA 02111-1307, USA.
19 #
20
21 import os
22 import sys
23 import tempfile
24 import shutil
25 import subprocess
26 from lxml import etree
27
28 from . import ast
29 from . import message
30 from . import utils
31 from .transformer import TransformerException
32 from .utils import to_underscores
33
34 # GParamFlags
35 G_PARAM_READABLE = 1 << 0
36 G_PARAM_WRITABLE = 1 << 1
37 G_PARAM_CONSTRUCT = 1 << 2
38 G_PARAM_CONSTRUCT_ONLY = 1 << 3
39 G_PARAM_LAX_VALIDATION = 1 << 4
40 G_PARAM_STATIC_NAME = 1 << 5
41 G_PARAM_STATIC_NICK = 1 << 6
42 G_PARAM_STATIC_BLURB = 1 << 7
43
44
45 class IntrospectionBinary(object):
46
47     def __init__(self, args, tmpdir=None):
48         self.args = args
49         if tmpdir is None:
50             self.tmpdir = tempfile.mkdtemp('', 'tmp-introspect')
51         else:
52             self.tmpdir = tmpdir
53
54
55 class Unresolved(object):
56
57     def __init__(self, target):
58         self.target = target
59
60
61 class UnknownTypeError(Exception):
62     pass
63
64
65 class GDumpParser(object):
66
67     def __init__(self, transformer):
68         self._transformer = transformer
69         self._namespace = transformer.namespace
70         self._binary = None
71         self._get_type_functions = []
72         self._error_quark_functions = []
73         self._error_domains = {}
74         self._boxed_types = {}
75         self._private_internal_types = {}
76
77     # Public API
78
79     def init_parse(self):
80         """Do parsing steps that don't involve the introspection binary
81
82         This does enough work that get_type_functions() can be called.
83
84         """
85
86         # First pass: parsing
87         for node in self._namespace.itervalues():
88             if isinstance(node, ast.Function):
89                 self._initparse_function(node)
90
91         if self._namespace.name == 'GObject' or self._namespace.name == 'GLib':
92             for node in self._namespace.itervalues():
93                 if isinstance(node, ast.Record):
94                     self._initparse_gobject_record(node)
95
96     def get_get_type_functions(self):
97         return self._get_type_functions
98
99     def get_error_quark_functions(self):
100         return self._error_quark_functions
101
102     def set_introspection_binary(self, binary):
103         self._binary = binary
104
105     def parse(self):
106         """Do remaining parsing steps requiring introspection binary"""
107
108         # Get all the GObject data by passing our list of get_type
109         # functions to the compiled binary, returning an XML blob.
110         tree = self._execute_binary_get_tree()
111         root = tree.getroot()
112         for child in root:
113             if child.tag == 'error-quark':
114                 self._introspect_error_quark(child)
115             else:
116                 self._introspect_type(child)
117
118         # Pair up boxed types and class records
119         for name, boxed in self._boxed_types.iteritems():
120             self._pair_boxed_type(boxed)
121         for node in self._namespace.itervalues():
122             if isinstance(node, (ast.Class, ast.Interface)):
123                 self._find_class_record(node)
124
125         # Clear the _get_type functions out of the namespace;
126         # Anyone who wants them can get them from the ast.Class/Interface/Boxed
127         to_remove = []
128         for name, node in self._namespace.iteritems():
129             if isinstance(node, ast.Registered) and node.get_type is not None:
130                 get_type_name = node.get_type
131                 if get_type_name == 'intern':
132                     continue
133                 assert get_type_name, node
134                 (ns, name) = self._transformer.split_csymbol(get_type_name)
135                 assert ns is self._namespace
136                 get_type_func = self._namespace.get(name)
137                 assert get_type_func, name
138                 to_remove.append(get_type_func)
139         for node in to_remove:
140             self._namespace.remove(node)
141
142     # Helper functions
143
144     def _execute_binary_get_tree(self):
145         """Load the library (or executable), returning an XML
146 blob containing data gleaned from GObject's primitive introspection."""
147         in_path = os.path.join(self._binary.tmpdir, 'functions.txt')
148         f = open(in_path, 'w')
149         for func in self._get_type_functions:
150             f.write('get-type:')
151             f.write(func)
152             f.write('\n')
153         for func in self._error_quark_functions:
154             f.write('error-quark:')
155             f.write(func)
156             f.write('\n')
157         f.close()
158         out_path = os.path.join(self._binary.tmpdir, 'dump.xml')
159
160         args = []
161         args.extend(self._binary.args)
162         args.append('--introspect-dump=%s,%s' % (in_path, out_path))
163
164         # Invoke the binary, having written our get_type functions to types.txt
165         try:
166             try:
167                 subprocess.check_call(args, stdout=sys.stdout, stderr=sys.stderr)
168             except subprocess.CalledProcessError, e:
169                 # Clean up temporaries
170                 raise SystemExit(e)
171             return etree.parse(out_path)
172         finally:
173             if not utils.have_debug_flag('save-temps'):
174                 shutil.rmtree(self._binary.tmpdir)
175
176     # Parser
177
178     def _initparse_function(self, func):
179         symbol = func.symbol
180         if symbol.startswith('_'):
181             return
182         elif (symbol.endswith('_get_type') or symbol.endswith('_get_gtype')):
183             self._initparse_get_type_function(func)
184         elif symbol.endswith('_error_quark'):
185             self._initparse_error_quark_function(func)
186
187     def _initparse_get_type_function(self, func):
188         if func.symbol == 'g_variant_get_gtype':
189             # We handle variants internally, see _initparse_gobject_record
190             return True
191
192         if func.is_type_meta_function():
193             self._get_type_functions.append(func.symbol)
194             return True
195
196         return False
197
198     def _initparse_error_quark_function(self, func):
199         if (func.retval.type.ctype != 'GQuark'):
200             return False
201         self._error_quark_functions.append(func.symbol)
202         return True
203
204     def _initparse_gobject_record(self, record):
205         if (record.name.startswith('ParamSpec')
206               and not record.name in ('ParamSpecPool', 'ParamSpecClass',
207                                       'ParamSpecTypeInfo')):
208             parent = None
209             if record.name != 'ParamSpec':
210                 parent = ast.Type(target_giname='GObject.ParamSpec')
211             prefix = to_underscores(record.name).lower()
212             node = ast.Class(record.name, parent,
213                              ctype=record.ctype,
214                              # GParamSpecXxx has g_type_name 'GParamXxx'
215                              gtype_name=record.ctype.replace('Spec', ''),
216                              get_type='intern',
217                              c_symbol_prefix=prefix)
218             node.fundamental = True
219             if record.name == 'ParamSpec':
220                 node.is_abstract = True
221             self._add_record_fields(node)
222             self._namespace.append(node, replace=True)
223         elif record.name == 'Variant':
224             self._boxed_types['GVariant'] = ast.Boxed('Variant',
225                                                       gtype_name='GVariant',
226                                                       get_type='intern',
227                                                       c_symbol_prefix='variant')
228         elif record.name == 'InitiallyUnownedClass':
229             record.fields = self._namespace.get('ObjectClass').fields
230             record.disguised = False
231
232     # Introspection over the data we get from the dynamic
233     # GObject/GType system out of the binary
234
235     def _introspect_type(self, xmlnode):
236         if xmlnode.tag in ('enum', 'flags'):
237             self._introspect_enum(xmlnode)
238         elif xmlnode.tag == 'class':
239             self._introspect_object(xmlnode)
240         elif xmlnode.tag == 'interface':
241             self._introspect_interface(xmlnode)
242         elif xmlnode.tag == 'boxed':
243             self._introspect_boxed(xmlnode)
244         elif xmlnode.tag == 'fundamental':
245             self._introspect_fundamental(xmlnode)
246         else:
247             raise ValueError("Unhandled introspection XML tag %s", xmlnode.tag)
248
249     def _introspect_enum(self, xmlnode):
250         type_name = xmlnode.attrib['name']
251         (get_type, c_symbol_prefix) = self._split_type_and_symbol_prefix(xmlnode)
252         try:
253             enum_name = self._transformer.strip_identifier(type_name)
254         except TransformerException, e:
255             message.fatal(e)
256
257         # The scanned member values are more accurate than the values that the
258         # we dumped from GEnumValue.value because GEnumValue.value has the
259         # values as a 32-bit signed integer, even if they were unsigned
260         # in the source code.
261         previous_values = {}
262         previous = self._namespace.get(enum_name)
263         if isinstance(previous, (ast.Enum, ast.Bitfield)):
264             for member in previous.members:
265                 previous_values[member.name] = member.value
266
267         members = []
268         for member in xmlnode.findall('member'):
269             # Keep the name closer to what we'd take from C by default;
270             # see http://bugzilla.gnome.org/show_bug.cgi?id=575613
271             name = member.attrib['nick'].replace('-', '_')
272
273             if name in previous_values:
274                 value = previous_values[name]
275             else:
276                 value = member.attrib['value']
277
278             members.append(ast.Member(name,
279                                       value,
280                                       member.attrib['name'],
281                                       member.attrib['nick']))
282
283
284         if xmlnode.tag == 'flags':
285             klass = ast.Bitfield
286         else:
287             klass = ast.Enum
288
289         node = klass(enum_name, type_name,
290                      gtype_name=type_name,
291                      c_symbol_prefix=c_symbol_prefix,
292                      members=members,
293                      get_type=xmlnode.attrib['get-type'])
294         self._namespace.append(node, replace=True)
295
296     def _split_type_and_symbol_prefix(self, xmlnode):
297         """Infer the C symbol prefix from the _get_type function."""
298         get_type = xmlnode.attrib['get-type']
299         (ns, name) = self._transformer.split_csymbol(get_type)
300         assert ns is self._namespace
301         if name in ('get_type', '_get_gtype'):
302             message.fatal("""The GObject name %r isn't compatibile
303 with the configured identifier prefixes:
304   %r
305 The class would have no name.  Most likely you want to specify a
306 different --identifier-prefix.""" % (xmlnode.attrib['name'], self._namespace.identifier_prefixes))
307         if name.endswith('_get_type'):
308             type_suffix = '_get_type'
309         else:
310             type_suffix = '_get_gtype'
311         return (get_type, name[:-len(type_suffix)])
312
313     def _introspect_object(self, xmlnode):
314         type_name = xmlnode.attrib['name']
315         is_abstract = bool(xmlnode.attrib.get('abstract', False))
316         (get_type, c_symbol_prefix) = self._split_type_and_symbol_prefix(xmlnode)
317         try:
318             object_name = self._transformer.strip_identifier(type_name)
319         except TransformerException, e:
320             message.fatal(e)
321         node = ast.Class(object_name, None,
322                          gtype_name=type_name,
323                          get_type=get_type,
324                          c_symbol_prefix=c_symbol_prefix,
325                          is_abstract=is_abstract)
326         self._parse_parents(xmlnode, node)
327         self._introspect_properties(node, xmlnode)
328         self._introspect_signals(node, xmlnode)
329         self._introspect_implemented_interfaces(node, xmlnode)
330         self._add_record_fields(node)
331
332         if node.name == 'InitiallyUnowned':
333             # http://bugzilla.gnome.org/show_bug.cgi?id=569408
334             # GInitiallyUnowned is actually a typedef for GObject, but
335             # that's not reflected in the GIR, where it appears as a
336             # subclass (as it appears in the GType hierarchy).  So
337             # what we do here is copy all of the GObject fields into
338             # GInitiallyUnowned so that struct offset computation
339             # works correctly.
340             node.fields = self._namespace.get('Object').fields
341
342         self._namespace.append(node, replace=True)
343
344     def _introspect_interface(self, xmlnode):
345         type_name = xmlnode.attrib['name']
346         (get_type, c_symbol_prefix) = self._split_type_and_symbol_prefix(xmlnode)
347         try:
348             interface_name = self._transformer.strip_identifier(type_name)
349         except TransformerException, e:
350             message.fatal(e)
351         node = ast.Interface(interface_name, None,
352                              gtype_name=type_name,
353                              get_type=get_type,
354                              c_symbol_prefix=c_symbol_prefix)
355         self._introspect_properties(node, xmlnode)
356         self._introspect_signals(node, xmlnode)
357         for child in xmlnode.findall('prerequisite'):
358             name = child.attrib['name']
359             prereq = ast.Type.create_from_gtype_name(name)
360             node.prerequisites.append(prereq)
361
362         record = self._namespace.get(node.name)
363         if isinstance(record, ast.Record):
364             node.ctype = record.ctype
365         else:
366             message.warn_node(node, "Couldn't find associated structure for '%r'" % (node.name, ))
367
368         # GtkFileChooserEmbed is an example of a private interface, we
369         # just filter them out
370         if xmlnode.attrib['get-type'].startswith('_'):
371             self._private_internal_types[type_name] = node
372         else:
373             self._namespace.append(node, replace=True)
374
375     ## WORKAROUND ##
376     # https://bugzilla.gnome.org/show_bug.cgi?id=550616
377     def _introspect_boxed_gstreamer_workaround(self, xmlnode):
378         node = ast.Boxed('ParamSpecMiniObject', gtype_name='GParamSpecMiniObject',
379                          get_type='gst_param_spec_mini_object_get_type',
380                          c_symbol_prefix='param_spec_mini_object')
381         self._boxed_types[node.gtype_name] = node
382
383     def _introspect_boxed(self, xmlnode):
384         type_name = xmlnode.attrib['name']
385
386         # Work around GStreamer legacy naming issue
387         # https://bugzilla.gnome.org/show_bug.cgi?id=550616
388         if type_name == 'GParamSpecMiniObject':
389             self._introspect_boxed_gstreamer_workaround(xmlnode)
390             return
391
392         try:
393             name = self._transformer.strip_identifier(type_name)
394         except TransformerException, e:
395             message.fatal(e)
396         # This one doesn't go in the main namespace; we associate it with
397         # the struct or union
398         (get_type, c_symbol_prefix) = self._split_type_and_symbol_prefix(xmlnode)
399         node = ast.Boxed(name, gtype_name=type_name,
400                          get_type=get_type,
401                          c_symbol_prefix=c_symbol_prefix)
402         self._boxed_types[node.gtype_name] = node
403
404     def _introspect_implemented_interfaces(self, node, xmlnode):
405         gt_interfaces = []
406         for interface in xmlnode.findall('implements'):
407             gitype = ast.Type.create_from_gtype_name(interface.attrib['name'])
408             gt_interfaces.append(gitype)
409         node.interfaces = gt_interfaces
410
411     def _introspect_properties(self, node, xmlnode):
412         for pspec in xmlnode.findall('property'):
413             ctype = pspec.attrib['type']
414             flags = int(pspec.attrib['flags'])
415             readable = (flags & G_PARAM_READABLE) != 0
416             writable = (flags & G_PARAM_WRITABLE) != 0
417             construct = (flags & G_PARAM_CONSTRUCT) != 0
418             construct_only = (flags & G_PARAM_CONSTRUCT_ONLY) != 0
419             node.properties.append(ast.Property(
420                 pspec.attrib['name'],
421                 ast.Type.create_from_gtype_name(ctype),
422                 readable, writable, construct, construct_only))
423         node.properties = node.properties
424
425     def _introspect_signals(self, node, xmlnode):
426         for signal_info in xmlnode.findall('signal'):
427             rctype = signal_info.attrib['return']
428             rtype = ast.Type.create_from_gtype_name(rctype)
429             return_ = ast.Return(rtype)
430             parameters = []
431             when = signal_info.attrib.get('when')
432             no_recurse = signal_info.attrib.get('no-recurse', '0') == '1'
433             detailed = signal_info.attrib.get('detailed', '0') == '1'
434             action = signal_info.attrib.get('action', '0') == '1'
435             no_hooks = signal_info.attrib.get('no-hooks', '0') == '1'
436             for i, parameter in enumerate(signal_info.findall('param')):
437                 if i == 0:
438                     argname = 'object'
439                 else:
440                     argname = 'p%s' % (i-1, )
441                 pctype = parameter.attrib['type']
442                 ptype = ast.Type.create_from_gtype_name(pctype)
443                 param = ast.Parameter(argname, ptype)
444                 param.transfer = ast.PARAM_TRANSFER_NONE
445                 parameters.append(param)
446             signal = ast.Signal(signal_info.attrib['name'], return_, parameters,
447                                 when=when, no_recurse=no_recurse, detailed=detailed,
448                                 action=action, no_hooks=no_hooks)
449             node.signals.append(signal)
450         node.signals = node.signals
451
452     def _parse_parents(self, xmlnode, node):
453         parents_str = xmlnode.attrib.get('parents', '')
454         if parents_str != '':
455             parent_types = map(lambda s: ast.Type.create_from_gtype_name(s),
456                                parents_str.split(','))
457         else:
458             parent_types = []
459         node.parent_chain = parent_types
460
461     def _introspect_fundamental(self, xmlnode):
462         type_name = xmlnode.attrib['name']
463
464         is_abstract = bool(xmlnode.attrib.get('abstract', False))
465         (get_type, c_symbol_prefix) = self._split_type_and_symbol_prefix(xmlnode)
466         try:
467             fundamental_name = self._transformer.strip_identifier(type_name)
468         except TransformerException, e:
469             message.warn(e)
470             return
471
472         node = ast.Class(fundamental_name, None,
473                          gtype_name=type_name,
474                          get_type=get_type,
475                          c_symbol_prefix=c_symbol_prefix,
476                          is_abstract=is_abstract)
477         self._parse_parents(xmlnode, node)
478         node.fundamental = True
479         self._introspect_implemented_interfaces(node, xmlnode)
480
481         self._add_record_fields(node)
482         self._namespace.append(node, replace=True)
483
484     def _add_record_fields(self, node):
485         # add record fields
486         record = self._namespace.get(node.name)
487         if not isinstance(record, ast.Record):
488             return
489         node.ctype = record.ctype
490         node.fields = record.fields
491         for field in node.fields:
492             if isinstance(field, ast.Field):
493                 # Object instance fields are assumed to be read-only
494                 # (see also _find_class_record and transformer.py)
495                 field.writable = False
496
497     def _introspect_error_quark(self, xmlnode):
498         symbol = xmlnode.attrib['function']
499         error_domain = xmlnode.attrib['domain']
500         function = self._namespace.get_by_symbol(symbol)
501         if function is None:
502             return
503
504         node = ast.ErrorQuarkFunction(function.name, function.retval,
505                                       function.parameters, function.throws,
506                                       function.symbol, error_domain)
507         self._namespace.append(node, replace=True)
508
509     def _pair_boxed_type(self, boxed):
510         try:
511             name = self._transformer.strip_identifier(boxed.gtype_name)
512         except TransformerException, e:
513             message.fatal(e)
514         pair_node = self._namespace.get(name)
515         if not pair_node:
516             # Keep the "bare" boxed instance
517             self._namespace.append(boxed)
518         elif isinstance(pair_node, (ast.Record, ast.Union)):
519             pair_node.add_gtype(boxed.gtype_name, boxed.get_type)
520             assert boxed.c_symbol_prefix is not None
521             pair_node.c_symbol_prefix = boxed.c_symbol_prefix
522             # Quick hack - reset the disguised flag; we're setting it
523             # incorrectly in the scanner
524             pair_node.disguised = False
525         else:
526             return False
527
528     def _strip_class_suffix(self, name):
529         if (name.endswith('Class') or
530             name.endswith('Iface')):
531             return name[:-5]
532         elif name.endswith('Interface'):
533             return name[:-9]
534         else:
535             return None
536
537     def _find_class_record(self, cls):
538         pair_record = None
539         if isinstance(cls, ast.Class):
540             pair_record = self._namespace.get(cls.name + 'Class')
541         else:
542             for suffix in ('Iface', 'Interface'):
543                 pair_record = self._namespace.get(cls.name + suffix)
544                 if pair_record:
545                     break
546         if not (pair_record and isinstance(pair_record, ast.Record)):
547             return
548
549         cls.glib_type_struct = pair_record.create_type()
550         cls.inherit_file_positions(pair_record)
551         pair_record.is_gtype_struct_for = cls.create_type()