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
32 from .utils import to_underscores
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
45 class IntrospectionBinary(object):
47 def __init__(self, args, tmpdir=None):
50 self.tmpdir = tempfile.mkdtemp('', 'tmp-introspect')
55 class Unresolved(object):
57 def __init__(self, target):
61 class UnknownTypeError(Exception):
65 class GDumpParser(object):
67 def __init__(self, transformer):
68 self._transformer = transformer
69 self._namespace = transformer.namespace
71 self._get_type_functions = []
72 self._error_quark_functions = []
73 self._error_domains = {}
74 self._boxed_types = {}
75 self._private_internal_types = {}
80 """Do parsing steps that don't involve the introspection binary
82 This does enough work that get_type_functions() can be called.
87 for node in self._namespace.itervalues():
88 if isinstance(node, ast.Function):
89 self._initparse_function(node)
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)
96 def get_get_type_functions(self):
97 return self._get_type_functions
99 def get_error_quark_functions(self):
100 return self._error_quark_functions
102 def set_introspection_binary(self, binary):
103 self._binary = binary
106 """Do remaining parsing steps requiring introspection binary"""
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()
113 if child.tag == 'error-quark':
114 self._introspect_error_quark(child)
116 self._introspect_type(child)
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)
125 # Clear the _get_type functions out of the namespace;
126 # Anyone who wants them can get them from the ast.Class/Interface/Boxed
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':
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)
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:
153 for func in self._error_quark_functions:
154 f.write('error-quark:')
158 out_path = os.path.join(self._binary.tmpdir, 'dump.xml')
161 args.extend(self._binary.args)
162 args.append('--introspect-dump=%s,%s' % (in_path, out_path))
164 # Invoke the binary, having written our get_type functions to types.txt
167 subprocess.check_call(args, stdout=sys.stdout, stderr=sys.stderr)
168 except subprocess.CalledProcessError, e:
169 # Clean up temporaries
171 return parse(out_path)
173 if not utils.have_debug_flag('save-temps'):
174 shutil.rmtree(self._binary.tmpdir)
178 def _initparse_function(self, func):
180 if symbol.startswith('_'):
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)
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
192 if func.is_type_meta_function():
193 self._get_type_functions.append(func.symbol)
198 def _initparse_error_quark_function(self, func):
199 if (func.retval.type.ctype != 'GQuark'):
201 self._error_quark_functions.append(func.symbol)
204 def _initparse_gobject_record(self, record):
205 if (record.name.startswith('ParamSpec')
206 and not record.name in ('ParamSpecPool', 'ParamSpecClass',
207 'ParamSpecTypeInfo')):
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,
214 # GParamSpecXxx has g_type_name 'GParamXxx'
215 gtype_name=record.ctype.replace('Spec', ''),
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',
227 c_symbol_prefix='variant')
228 elif record.name == 'InitiallyUnownedClass':
229 record.fields = self._namespace.get('ObjectClass').fields
230 record.disguised = False
232 # Introspection over the data we get from the dynamic
233 # GObject/GType system out of the binary
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)
247 raise ValueError("Unhandled introspection XML tag %s", xmlnode.tag)
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)
253 enum_name = self._transformer.strip_identifier(type_name)
254 except TransformerException, e:
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.
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
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('-', '_')
273 if name in previous_values:
274 value = previous_values[name]
276 value = member.attrib['value']
278 members.append(ast.Member(name,
280 member.attrib['name'],
281 member.attrib['nick']))
284 if xmlnode.tag == 'flags':
289 node = klass(enum_name, type_name,
290 gtype_name=type_name,
291 c_symbol_prefix=c_symbol_prefix,
293 get_type=xmlnode.attrib['get-type'])
294 self._namespace.append(node, replace=True)
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:
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'
310 type_suffix = '_get_gtype'
311 return (get_type, name[:-len(type_suffix)])
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)
318 object_name = self._transformer.strip_identifier(type_name)
319 except TransformerException, e:
321 node = ast.Class(object_name, None,
322 gtype_name=type_name,
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)
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
340 node.fields = self._namespace.get('Object').fields
342 self._namespace.append(node, replace=True)
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)
348 interface_name = self._transformer.strip_identifier(type_name)
349 except TransformerException, e:
351 node = ast.Interface(interface_name, None,
352 gtype_name=type_name,
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)
362 record = self._namespace.get(node.name)
363 if isinstance(record, ast.Record):
364 node.ctype = record.ctype
366 message.warn_node(node, "Couldn't find associated structure for '%r'" % (node.name, ))
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
373 self._namespace.append(node, replace=True)
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
383 def _introspect_boxed(self, xmlnode):
384 type_name = xmlnode.attrib['name']
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)
393 name = self._transformer.strip_identifier(type_name)
394 except TransformerException, 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,
401 c_symbol_prefix=c_symbol_prefix)
402 self._boxed_types[node.gtype_name] = node
404 def _introspect_implemented_interfaces(self, node, xmlnode):
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
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
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)
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')):
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
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(','))
459 node.parent_chain = parent_types
461 def _introspect_fundamental(self, xmlnode):
462 type_name = xmlnode.attrib['name']
464 is_abstract = bool(xmlnode.attrib.get('abstract', False))
465 (get_type, c_symbol_prefix) = self._split_type_and_symbol_prefix(xmlnode)
467 fundamental_name = self._transformer.strip_identifier(type_name)
468 except TransformerException, e:
472 node = ast.Class(fundamental_name, None,
473 gtype_name=type_name,
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)
481 self._add_record_fields(node)
482 self._namespace.append(node, replace=True)
484 def _add_record_fields(self, node):
486 record = self._namespace.get(node.name)
487 if not isinstance(record, ast.Record):
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
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)
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)
509 def _pair_boxed_type(self, boxed):
511 name = self._transformer.strip_identifier(boxed.gtype_name)
512 except TransformerException, e:
514 pair_node = self._namespace.get(name)
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
528 def _strip_class_suffix(self, name):
529 if (name.endswith('Class') or
530 name.endswith('Iface')):
532 elif name.endswith('Interface'):
537 def _find_class_record(self, cls):
539 if isinstance(cls, ast.Class):
540 pair_record = self._namespace.get(cls.name + 'Class')
542 for suffix in ('Iface', 'Interface'):
543 pair_record = self._namespace.get(cls.name + suffix)
546 if not (pair_record and isinstance(pair_record, ast.Record)):
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()