[scanner] Move over remaining callsites to message
[platform/upstream/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 xml.etree.cElementTree import parse
27
28 from . import ast
29 from . import glibast
30 from . import message
31 from .transformer import TransformerException
32
33 # GParamFlags
34 G_PARAM_READABLE = 1 << 0
35 G_PARAM_WRITABLE = 1 << 1
36 G_PARAM_CONSTRUCT = 1 << 2
37 G_PARAM_CONSTRUCT_ONLY = 1 << 3
38 G_PARAM_LAX_VALIDATION = 1 << 4
39 G_PARAM_STATIC_NAME = 1 << 5
40 G_PARAM_STATIC_NICK = 1 << 6
41 G_PARAM_STATIC_BLURB = 1 << 7
42
43
44 class IntrospectionBinary(object):
45
46     def __init__(self, args, tmpdir=None):
47         self.args = args
48         if tmpdir is None:
49             self.tmpdir = tempfile.mkdtemp('', 'tmp-introspect')
50         else:
51             self.tmpdir = tmpdir
52
53
54 class Unresolved(object):
55
56     def __init__(self, target):
57         self.target = target
58
59
60 class UnknownTypeError(Exception):
61     pass
62
63
64 class GDumpParser(object):
65
66     def __init__(self, transformer):
67         self._transformer = transformer
68         self._namespace = transformer.namespace
69         self._binary = None
70         self._get_type_functions = []
71         self._gtype_data = {}
72         self._boxed_types = {}
73         self._private_internal_types = {}
74
75     # Public API
76
77     def init_parse(self):
78         """Do parsing steps that don't involve the introspection binary
79
80         This does enough work that get_type_functions() can be called.
81
82         """
83
84         # First pass: parsing
85         for node in self._namespace.itervalues():
86             if isinstance(node, ast.Function):
87                 self._initparse_function(node)
88         if self._namespace.name == 'GObject':
89             for node in self._namespace.itervalues():
90                 if isinstance(node, ast.Record):
91                     self._initparse_gobject_record(node)
92
93     def get_get_type_functions(self):
94         return self._get_type_functions
95
96     def set_introspection_binary(self, binary):
97         self._binary = binary
98
99     def parse(self):
100         """Do remaining parsing steps requiring introspection binary"""
101
102         # Get all the GObject data by passing our list of get_type
103         # functions to the compiled binary, returning an XML blob.
104         tree = self._execute_binary_get_tree()
105         root = tree.getroot()
106         for child in root:
107             self._gtype_data[child.attrib['name']] = child
108         for child in root:
109             self._introspect_type(child)
110
111         # Pair up boxed types and class records
112         for name, boxed in self._boxed_types.iteritems():
113             self._pair_boxed_type(boxed)
114         for node in self._namespace.itervalues():
115             if isinstance(node, (ast.Class, ast.Interface)):
116                 self._find_class_record(node)
117
118         # Clear the _get_type functions out of the namespace;
119         # Anyone who wants them can get them from the ast.Class/Interface/Boxed
120         to_remove = []
121         for name, node in self._namespace.iteritems():
122             if isinstance(node, (ast.Class, ast.Interface, glibast.GLibBoxed,
123                                  glibast.GLibEnum, glibast.GLibFlags)):
124                 get_type_name = node.get_type
125                 if get_type_name == 'intern':
126                     continue
127                 assert get_type_name, node
128                 (ns, name) = self._transformer.split_csymbol(get_type_name)
129                 assert ns is self._namespace
130                 get_type_func = self._namespace.get(name)
131                 assert get_type_func, name
132                 to_remove.append(get_type_func)
133         for node in to_remove:
134             self._namespace.remove(node)
135
136     # Helper functions
137
138     def _execute_binary_get_tree(self):
139         """Load the library (or executable), returning an XML
140 blob containing data gleaned from GObject's primitive introspection."""
141         in_path = os.path.join(self._binary.tmpdir, 'types.txt')
142         f = open(in_path, 'w')
143         # TODO: Introspect GQuark functions
144         for func in self._get_type_functions:
145             f.write(func)
146             f.write('\n')
147         f.close()
148         out_path = os.path.join(self._binary.tmpdir, 'dump.xml')
149
150         args = []
151         args.extend(self._binary.args)
152         args.append('--introspect-dump=%s,%s' % (in_path, out_path))
153
154         # Invoke the binary, having written our get_type functions to types.txt
155         try:
156             try:
157                 subprocess.check_call(args, stdout=sys.stdout, stderr=sys.stderr)
158             except subprocess.CalledProcessError, e:
159                 # Clean up temporaries
160                 raise SystemExit(e)
161             return parse(out_path)
162         finally:
163             shutil.rmtree(self._binary.tmpdir)
164
165     def _create_gobject(self, node):
166         type_name = 'G' + node.name
167         if type_name == 'GObject':
168             parent_gitype = None
169             symbol = 'intern'
170         elif type_name == 'GInitiallyUnowned':
171             parent_gitype = ast.Type(target_giname='GObject.Object')
172             symbol = 'g_initially_unowned_get_type'
173         else:
174             assert False
175         gnode = glibast.GLibObject(node.name, parent_gitype, type_name, symbol, 'object', True)
176         if type_name == 'GObject':
177             gnode.fields.extend(node.fields)
178         else:
179             # http://bugzilla.gnome.org/show_bug.cgi?id=569408
180             # GInitiallyUnowned is actually a typedef for GObject, but
181             # that's not reflected in the GIR, where it appears as a
182             # subclass (as it appears in the GType hierarchy).  So
183             # what we do here is copy all of the GObject fields into
184             # GInitiallyUnowned so that struct offset computation
185             # works correctly.
186             gnode.fields = self._namespace.get('Object').fields
187         self._namespace.append(gnode, replace=True)
188
189     # Parser
190
191     def _initparse_function(self, func):
192         symbol = func.symbol
193         if symbol.startswith('_'):
194             return
195         elif symbol.endswith('_get_type'):
196             self._initparse_get_type_function(func)
197
198     def _initparse_get_type_function(self, func):
199         if self._namespace.name == 'GLib':
200             # No GObjects in GLib
201             return False
202         if (self._namespace.name == 'GObject' and
203             func.symbol in ('g_object_get_type', 'g_initially_unowned_get_type')):
204             # We handle these internally, see _create_gobject
205             return True
206         if func.parameters:
207             return False
208         # GType *_get_type(void)
209         rettype = func.retval.type
210         if not (rettype.is_equiv(ast.TYPE_GTYPE)
211                 or rettype.target_giname == 'Gtk.Type'):
212             message.warn("function returns '%r', not a GType" % (
213                 func.retval.type, ))
214             return False
215
216         self._get_type_functions.append(func.symbol)
217         return True
218
219     def _initparse_gobject_record(self, record):
220         # Special handling for when we're parsing GObject
221         internal_names = ("Object", 'InitiallyUnowned')
222         if record.name in internal_names:
223             self._create_gobject(record)
224             return
225         if record.name == 'InitiallyUnownedClass':
226             record.fields = self._namespace.get('ObjectClass').fields
227         self._namespace.append(record, replace=True)
228
229     # Introspection over the data we get from the dynamic
230     # GObject/GType system out of the binary
231
232     def _introspect_type(self, xmlnode):
233         if xmlnode.tag in ('enum', 'flags'):
234             self._introspect_enum(xmlnode)
235         elif xmlnode.tag == 'class':
236             self._introspect_object(xmlnode)
237         elif xmlnode.tag == 'interface':
238             self._introspect_interface(xmlnode)
239         elif xmlnode.tag == 'boxed':
240             self._introspect_boxed(xmlnode)
241         elif xmlnode.tag == 'fundamental':
242             self._introspect_fundamental(xmlnode)
243         else:
244             raise ValueError("Unhandled introspection XML tag %s", xmlnode.tag)
245
246     def _introspect_enum(self, node):
247         members = []
248         for member in node.findall('member'):
249             # Keep the name closer to what we'd take from C by default;
250             # see http://bugzilla.gnome.org/show_bug.cgi?id=575613
251             name = member.attrib['nick'].replace('-', '_')
252             members.append(glibast.GLibEnumMember(name,
253                                           member.attrib['value'],
254                                           member.attrib['name'],
255                                           member.attrib['nick']))
256
257         klass = (glibast.GLibFlags if node.tag == 'flags' else glibast.GLibEnum)
258         type_name = node.attrib['name']
259         try:
260             enum_name = self._transformer.strip_identifier(type_name)
261         except TransformerException, e:
262             message.fatal(e)
263         node = klass(enum_name, type_name, members, node.attrib['get-type'])
264         self._namespace.append(node, replace=True)
265
266     def _split_type_and_symbol_prefix(self, xmlnode):
267         """Infer the C symbol prefix from the _get_type function."""
268         get_type = xmlnode.attrib['get-type']
269         (ns, name) = self._transformer.split_csymbol(get_type)
270         assert ns is self._namespace
271         assert name.endswith('_get_type')
272         return (get_type, name[:-len('_get_type')])
273
274     def _introspect_object(self, xmlnode):
275         type_name = xmlnode.attrib['name']
276         # We handle this specially above; in 2.16 and below there
277         # was no g_object_get_type, for later versions we need
278         # to skip it
279         if type_name == 'GObject':
280             return
281         is_abstract = bool(xmlnode.attrib.get('abstract', False))
282         (get_type, c_symbol_prefix) = self._split_type_and_symbol_prefix(xmlnode)
283         try:
284             object_name = self._transformer.strip_identifier(type_name)
285         except TransformerException, e:
286             message.fatal(e)
287         node = glibast.GLibObject(object_name, None, type_name,
288                                   get_type, c_symbol_prefix, is_abstract)
289         self._parse_parents(xmlnode, node)
290         self._introspect_properties(node, xmlnode)
291         self._introspect_signals(node, xmlnode)
292         self._introspect_implemented_interfaces(node, xmlnode)
293
294         self._add_record_fields(node)
295         self._namespace.append(node, replace=True)
296
297     def _introspect_interface(self, xmlnode):
298         type_name = xmlnode.attrib['name']
299         (get_type, c_symbol_prefix) = self._split_type_and_symbol_prefix(xmlnode)
300         try:
301             interface_name = self._transformer.strip_identifier(type_name)
302         except TransformerException, e:
303             message.fatal(e)
304         node = glibast.GLibInterface(interface_name, None, type_name,
305                                      get_type, c_symbol_prefix)
306         self._introspect_properties(node, xmlnode)
307         self._introspect_signals(node, xmlnode)
308         for child in xmlnode.findall('prerequisite'):
309             name = child.attrib['name']
310             prereq = self._transformer.create_type_from_gtype_name(name)
311             node.prerequisites.append(prereq)
312         # GtkFileChooserEmbed is an example of a private interface, we
313         # just filter them out
314         if xmlnode.attrib['get-type'].startswith('_'):
315             self._private_internal_types[type_name] = node
316         else:
317             self._namespace.append(node, replace=True)
318
319     def _introspect_boxed(self, xmlnode):
320         type_name = xmlnode.attrib['name']
321         # This one doesn't go in the main namespace; we associate it with
322         # the struct or union
323         (get_type, c_symbol_prefix) = self._split_type_and_symbol_prefix(xmlnode)
324         node = glibast.GLibBoxed(type_name, get_type, c_symbol_prefix)
325         self._boxed_types[node.type_name] = node
326
327     def _introspect_implemented_interfaces(self, node, xmlnode):
328         gt_interfaces = []
329         for interface in xmlnode.findall('implements'):
330             gitype = self._transformer.create_type_from_gtype_name(interface.attrib['name'])
331             gt_interfaces.append(gitype)
332         node.interfaces = gt_interfaces
333
334     def _introspect_properties(self, node, xmlnode):
335         for pspec in xmlnode.findall('property'):
336             ctype = pspec.attrib['type']
337             flags = int(pspec.attrib['flags'])
338             readable = (flags & G_PARAM_READABLE) != 0
339             writable = (flags & G_PARAM_WRITABLE) != 0
340             construct = (flags & G_PARAM_CONSTRUCT) != 0
341             construct_only = (flags & G_PARAM_CONSTRUCT_ONLY) != 0
342             node.properties.append(ast.Property(
343                 pspec.attrib['name'],
344                 self._transformer.create_type_from_gtype_name(ctype),
345                 readable, writable, construct, construct_only,
346                 ctype,
347                 ))
348         node.properties = node.properties
349
350     def _introspect_signals(self, node, xmlnode):
351         for signal_info in xmlnode.findall('signal'):
352             rctype = signal_info.attrib['return']
353             rtype = self._transformer.create_type_from_gtype_name(rctype)
354             return_ = ast.Return(rtype)
355             parameters = []
356             for i, parameter in enumerate(signal_info.findall('param')):
357                 if i == 0:
358                     argname = 'object'
359                 else:
360                     argname = 'p%s' % (i-1, )
361                 pctype = parameter.attrib['type']
362                 ptype = self._transformer.create_type_from_gtype_name(pctype)
363                 param = ast.Parameter(argname, ptype)
364                 param.transfer = ast.PARAM_TRANSFER_NONE
365                 parameters.append(param)
366             signal = glibast.GLibSignal(signal_info.attrib['name'], return_, parameters)
367             node.signals.append(signal)
368         node.signals = node.signals
369
370     def _parse_parents(self, xmlnode, node):
371         parents_str = xmlnode.attrib.get('parents', '')
372         if parents_str != '':
373             parent_types = map(lambda s: self._transformer.create_type_from_gtype_name(s),
374                                parents_str.split(','))
375         else:
376             parent_types = []
377         node.parent_chain = parent_types
378
379     def _introspect_fundamental(self, xmlnode):
380         # We only care about types that can be instantiatable, other
381         # fundamental types such as the Clutter.Fixed/CoglFixed registers
382         # are not yet interesting from an introspection perspective and
383         # are ignored
384         if not xmlnode.attrib.get('instantiatable', False):
385             return
386
387         type_name = xmlnode.attrib['name']
388         is_abstract = bool(xmlnode.attrib.get('abstract', False))
389         (get_type, c_symbol_prefix) = self._split_type_and_symbol_prefix(xmlnode)
390         try:
391             fundamental_name = self._transformer.strip_identifier(type_name)
392         except TransformerException, e:
393             message.fatal(e)
394
395         node = glibast.GLibObject(fundamental_name, None, type_name,
396                                   get_type, c_symbol_prefix, is_abstract)
397         self._parse_parents(xmlnode, node)
398         node.fundamental = True
399         self._introspect_implemented_interfaces(node, xmlnode)
400
401         self._add_record_fields(node)
402         self._namespace.append(node, replace=True)
403
404     def _add_record_fields(self, node):
405         # add record fields
406         record = self._namespace.get(node.name)
407         if not isinstance(record, ast.Record):
408             return
409         node.fields = record.fields
410         for field in node.fields:
411             if isinstance(field, ast.Field):
412                 # Object instance fields are assumed to be read-only
413                 # (see also _find_class_record and transformer.py)
414                 field.writable = False
415
416     def _pair_boxed_type(self, boxed):
417         try:
418             name = self._transformer.strip_identifier(boxed.type_name)
419         except TransformerException, e:
420             message.fatal(e)
421         pair_node = self._namespace.get(name)
422         if not pair_node:
423             boxed_item = glibast.GLibBoxedOther(name, boxed.type_name,
424                                         boxed.get_type,
425                                         boxed.c_symbol_prefix)
426         elif isinstance(pair_node, ast.Record):
427             boxed_item = glibast.GLibBoxedStruct(pair_node.name, boxed.type_name,
428                                          boxed.get_type,
429                                          boxed.c_symbol_prefix)
430             boxed_item.inherit_file_positions(pair_node)
431             boxed_item.fields = pair_node.fields
432         elif isinstance(pair_node, ast.Union):
433             boxed_item = glibast.GLibBoxedUnion(pair_node.name, boxed.type_name,
434                                         boxed.get_type,
435                                         boxed.c_symbol_prefix)
436             boxed_item.inherit_file_positions(pair_node)
437             boxed_item.fields = pair_node.fields
438         else:
439             return False
440         self._namespace.append(boxed_item, replace=True)
441
442     def _strip_class_suffix(self, name):
443         if (name.endswith('Class') or
444             name.endswith('Iface')):
445             return name[:-5]
446         elif name.endswith('Interface'):
447             return name[:-9]
448         else:
449             return None
450
451     def _find_class_record(self, cls):
452         pair_record = None
453         if isinstance(cls, ast.Class):
454             pair_record = self._namespace.get(cls.name + 'Class')
455         else:
456             for suffix in ('Iface', 'Interface'):
457                 pair_record = self._namespace.get(cls.name + suffix)
458                 if pair_record:
459                     break
460         if not (pair_record and isinstance(pair_record, ast.Record)):
461             return
462
463         gclass_struct = glibast.GLibRecord.from_record(pair_record)
464         self._namespace.append(gclass_struct, replace=True)
465         cls.glib_type_struct = gclass_struct.create_type()
466         cls.inherit_file_positions(pair_record)
467         gclass_struct.is_gtype_struct_for = cls.create_type()