2 # GObject-Introspection - a framework for introspecting GObject libraries
3 # Copyright (C) 2008 Johan Dahlin
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.
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.
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.
26 from xml.etree.cElementTree import parse
31 from .transformer import TransformerException
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
44 class IntrospectionBinary(object):
46 def __init__(self, args, tmpdir=None):
49 self.tmpdir = tempfile.mkdtemp('', 'tmp-introspect')
54 class Unresolved(object):
56 def __init__(self, target):
60 class UnknownTypeError(Exception):
64 class GDumpParser(object):
66 def __init__(self, transformer):
67 self._transformer = transformer
68 self._namespace = transformer.namespace
70 self._get_type_functions = []
72 self._boxed_types = {}
73 self._private_internal_types = {}
78 """Do parsing steps that don't involve the introspection binary
80 This does enough work that get_type_functions() can be called.
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)
93 def get_get_type_functions(self):
94 return self._get_type_functions
96 def set_introspection_binary(self, binary):
100 """Do remaining parsing steps requiring introspection binary"""
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()
107 self._gtype_data[child.attrib['name']] = child
109 self._introspect_type(child)
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)
118 # Clear the _get_type functions out of the namespace;
119 # Anyone who wants them can get them from the ast.Class/Interface/Boxed
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':
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)
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:
148 out_path = os.path.join(self._binary.tmpdir, 'dump.xml')
151 args.extend(self._binary.args)
152 args.append('--introspect-dump=%s,%s' % (in_path, out_path))
154 # Invoke the binary, having written our get_type functions to types.txt
157 subprocess.check_call(args, stdout=sys.stdout, stderr=sys.stderr)
158 except subprocess.CalledProcessError, e:
159 # Clean up temporaries
161 return parse(out_path)
163 shutil.rmtree(self._binary.tmpdir)
165 def _create_gobject(self, node):
166 type_name = 'G' + node.name
167 if type_name == 'GObject':
170 elif type_name == 'GInitiallyUnowned':
171 parent_gitype = ast.Type(target_giname='GObject.Object')
172 symbol = 'g_initially_unowned_get_type'
175 gnode = glibast.GLibObject(node.name, parent_gitype, type_name, symbol, 'object', True)
176 if type_name == 'GObject':
177 gnode.fields.extend(node.fields)
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
186 gnode.fields = self._namespace.get('Object').fields
187 self._namespace.append(gnode, replace=True)
191 def _initparse_function(self, func):
193 if symbol.startswith('_'):
195 elif symbol.endswith('_get_type'):
196 self._initparse_get_type_function(func)
198 def _initparse_get_type_function(self, func):
199 if self._namespace.name == 'GLib':
200 # No GObjects in GLib
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
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 self._transformer.log_warning("function returns '%r', not a GType"
213 % (func.retval.type, ))
216 self._get_type_functions.append(func.symbol)
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)
225 if record.name == 'InitiallyUnownedClass':
226 record.fields = self._namespace.get('ObjectClass').fields
227 self._namespace.append(record, replace=True)
229 # Introspection over the data we get from the dynamic
230 # GObject/GType system out of the binary
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)
244 raise ValueError("Unhandled introspection XML tag %s", xmlnode.tag)
246 def _introspect_enum(self, node):
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']))
257 klass = (glibast.GLibFlags if node.tag == 'flags' else glibast.GLibEnum)
258 type_name = node.attrib['name']
260 enum_name = self._transformer.strip_identifier(type_name)
261 except TransformerException, e:
263 node = klass(enum_name, type_name, members, node.attrib['get-type'])
264 self._namespace.append(node, replace=True)
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')])
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
279 if type_name == 'GObject':
281 is_abstract = bool(xmlnode.attrib.get('abstract', False))
282 (get_type, c_symbol_prefix) = self._split_type_and_symbol_prefix(xmlnode)
284 object_name = self._transformer.strip_identifier(type_name)
285 except TransformerException, 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)
294 self._add_record_fields(node)
295 self._namespace.append(node, replace=True)
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)
301 interface_name = self._transformer.strip_identifier(type_name)
302 except TransformerException, 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
317 self._namespace.append(node, replace=True)
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
327 def _introspect_implemented_interfaces(self, node, xmlnode):
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
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,
348 node.properties = node.properties
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)
356 for i, parameter in enumerate(signal_info.findall('param')):
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
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(','))
377 node.parent_chain = parent_types
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
384 if not xmlnode.attrib.get('instantiatable', False):
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)
391 fundamental_name = self._transformer.strip_identifier(type_name)
392 except TransformerException, e:
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)
401 self._add_record_fields(node)
402 self._namespace.append(node, replace=True)
404 def _add_record_fields(self, node):
406 record = self._namespace.get(node.name)
407 if not isinstance(record, ast.Record):
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
416 def _pair_boxed_type(self, boxed):
418 name = self._transformer.strip_identifier(boxed.type_name)
419 except TransformerException, e:
421 pair_node = self._namespace.get(name)
423 boxed_item = glibast.GLibBoxedOther(name, boxed.type_name,
425 boxed.c_symbol_prefix)
426 elif isinstance(pair_node, ast.Record):
427 boxed_item = glibast.GLibBoxedStruct(pair_node.name, boxed.type_name,
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,
435 boxed.c_symbol_prefix)
436 boxed_item.inherit_file_positions(pair_node)
437 boxed_item.fields = pair_node.fields
440 self._namespace.append(boxed_item, replace=True)
442 def _strip_class_suffix(self, name):
443 if (name.endswith('Class') or
444 name.endswith('Iface')):
446 elif name.endswith('Interface'):
451 def _find_class_record(self, cls):
453 if isinstance(cls, ast.Class):
454 pair_record = self._namespace.get(cls.name + 'Class')
456 for suffix in ('Iface', 'Interface'):
457 pair_record = self._namespace.get(cls.name + suffix)
460 if not (pair_record and isinstance(pair_record, ast.Record)):
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()