mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-12-27 07:56:14 +01:00
1e10d6b6dd
When building a valist marshaller, we can avoid a string copy if the argument is known to always be static. The marshaller we ship in `gmarshal.c` got this right, but marshallers generated by glib-genmarshal were missing the optimisation. Fix that, and add a unit test. Signed-off-by: Philip Withnall <withnall@endlessm.com> Fixes: #1792
1069 lines
40 KiB
Plaintext
Executable File
1069 lines
40 KiB
Plaintext
Executable File
#!/usr/bin/env @PYTHON@
|
|
|
|
# pylint: disable=too-many-lines, missing-docstring, invalid-name
|
|
|
|
# This file is part of GLib
|
|
#
|
|
# This library is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU Lesser General Public
|
|
# License as published by the Free Software Foundation; either
|
|
# version 2.1 of the License, or (at your option) any later version.
|
|
#
|
|
# This library is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
# Lesser General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Lesser General Public
|
|
# License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import argparse
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
VERSION_STR = '''glib-genmarshal version @VERSION@
|
|
glib-genmarshal comes with ABSOLUTELY NO WARRANTY.
|
|
You may redistribute copies of glib-genmarshal under the terms of
|
|
the GNU General Public License which can be found in the
|
|
GLib source package. Sources, examples and contact
|
|
information are available at http://www.gtk.org'''
|
|
|
|
GETTERS_STR = '''#ifdef G_ENABLE_DEBUG
|
|
#define g_marshal_value_peek_boolean(v) g_value_get_boolean (v)
|
|
#define g_marshal_value_peek_char(v) g_value_get_schar (v)
|
|
#define g_marshal_value_peek_uchar(v) g_value_get_uchar (v)
|
|
#define g_marshal_value_peek_int(v) g_value_get_int (v)
|
|
#define g_marshal_value_peek_uint(v) g_value_get_uint (v)
|
|
#define g_marshal_value_peek_long(v) g_value_get_long (v)
|
|
#define g_marshal_value_peek_ulong(v) g_value_get_ulong (v)
|
|
#define g_marshal_value_peek_int64(v) g_value_get_int64 (v)
|
|
#define g_marshal_value_peek_uint64(v) g_value_get_uint64 (v)
|
|
#define g_marshal_value_peek_enum(v) g_value_get_enum (v)
|
|
#define g_marshal_value_peek_flags(v) g_value_get_flags (v)
|
|
#define g_marshal_value_peek_float(v) g_value_get_float (v)
|
|
#define g_marshal_value_peek_double(v) g_value_get_double (v)
|
|
#define g_marshal_value_peek_string(v) (char*) g_value_get_string (v)
|
|
#define g_marshal_value_peek_param(v) g_value_get_param (v)
|
|
#define g_marshal_value_peek_boxed(v) g_value_get_boxed (v)
|
|
#define g_marshal_value_peek_pointer(v) g_value_get_pointer (v)
|
|
#define g_marshal_value_peek_object(v) g_value_get_object (v)
|
|
#define g_marshal_value_peek_variant(v) g_value_get_variant (v)
|
|
#else /* !G_ENABLE_DEBUG */
|
|
/* WARNING: This code accesses GValues directly, which is UNSUPPORTED API.
|
|
* Do not access GValues directly in your code. Instead, use the
|
|
* g_value_get_*() functions
|
|
*/
|
|
#define g_marshal_value_peek_boolean(v) (v)->data[0].v_int
|
|
#define g_marshal_value_peek_char(v) (v)->data[0].v_int
|
|
#define g_marshal_value_peek_uchar(v) (v)->data[0].v_uint
|
|
#define g_marshal_value_peek_int(v) (v)->data[0].v_int
|
|
#define g_marshal_value_peek_uint(v) (v)->data[0].v_uint
|
|
#define g_marshal_value_peek_long(v) (v)->data[0].v_long
|
|
#define g_marshal_value_peek_ulong(v) (v)->data[0].v_ulong
|
|
#define g_marshal_value_peek_int64(v) (v)->data[0].v_int64
|
|
#define g_marshal_value_peek_uint64(v) (v)->data[0].v_uint64
|
|
#define g_marshal_value_peek_enum(v) (v)->data[0].v_long
|
|
#define g_marshal_value_peek_flags(v) (v)->data[0].v_ulong
|
|
#define g_marshal_value_peek_float(v) (v)->data[0].v_float
|
|
#define g_marshal_value_peek_double(v) (v)->data[0].v_double
|
|
#define g_marshal_value_peek_string(v) (v)->data[0].v_pointer
|
|
#define g_marshal_value_peek_param(v) (v)->data[0].v_pointer
|
|
#define g_marshal_value_peek_boxed(v) (v)->data[0].v_pointer
|
|
#define g_marshal_value_peek_pointer(v) (v)->data[0].v_pointer
|
|
#define g_marshal_value_peek_object(v) (v)->data[0].v_pointer
|
|
#define g_marshal_value_peek_variant(v) (v)->data[0].v_pointer
|
|
#endif /* !G_ENABLE_DEBUG */'''
|
|
|
|
DEPRECATED_MSG_STR = 'The token "{}" is deprecated; use "{}" instead'
|
|
|
|
VA_ARG_STR = \
|
|
' arg{:d} = ({:s}) va_arg (args_copy, {:s});'
|
|
STATIC_CHECK_STR = \
|
|
'(param_types[{:d}] & G_SIGNAL_TYPE_STATIC_SCOPE) == 0 && '
|
|
BOX_TYPED_STR = \
|
|
' arg{idx:d} = {box_func} (param_types[{idx:d}] & ~G_SIGNAL_TYPE_STATIC_SCOPE, arg{idx:d});'
|
|
BOX_UNTYPED_STR = \
|
|
' arg{idx:d} = {box_func} (arg{idx:d});'
|
|
UNBOX_TYPED_STR = \
|
|
' {unbox_func} (param_types[{idx:d}] & ~G_SIGNAL_TYPE_STATIC_SCOPE, arg{idx:d});'
|
|
UNBOX_UNTYPED_STR = \
|
|
' {unbox_func} (arg{idx:d});'
|
|
|
|
STD_PREFIX = 'g_cclosure_marshal'
|
|
|
|
# These are part of our ABI; keep this in sync with gmarshal.h
|
|
GOBJECT_MARSHALLERS = {
|
|
'g_cclosure_marshal_VOID__VOID',
|
|
'g_cclosure_marshal_VOID__BOOLEAN',
|
|
'g_cclosure_marshal_VOID__CHAR',
|
|
'g_cclosure_marshal_VOID__UCHAR',
|
|
'g_cclosure_marshal_VOID__INT',
|
|
'g_cclosure_marshal_VOID__UINT',
|
|
'g_cclosure_marshal_VOID__LONG',
|
|
'g_cclosure_marshal_VOID__ULONG',
|
|
'g_cclosure_marshal_VOID__ENUM',
|
|
'g_cclosure_marshal_VOID__FLAGS',
|
|
'g_cclosure_marshal_VOID__FLOAT',
|
|
'g_cclosure_marshal_VOID__DOUBLE',
|
|
'g_cclosure_marshal_VOID__STRING',
|
|
'g_cclosure_marshal_VOID__PARAM',
|
|
'g_cclosure_marshal_VOID__BOXED',
|
|
'g_cclosure_marshal_VOID__POINTER',
|
|
'g_cclosure_marshal_VOID__OBJECT',
|
|
'g_cclosure_marshal_VOID__VARIANT',
|
|
'g_cclosure_marshal_VOID__UINT_POINTER',
|
|
'g_cclosure_marshal_BOOLEAN__FLAGS',
|
|
'g_cclosure_marshal_STRING__OBJECT_POINTER',
|
|
'g_cclosure_marshal_BOOLEAN__BOXED_BOXED',
|
|
}
|
|
|
|
|
|
# pylint: disable=too-few-public-methods
|
|
class Color:
|
|
'''ANSI Terminal colors'''
|
|
GREEN = '\033[1;32m'
|
|
BLUE = '\033[1;34m'
|
|
YELLOW = '\033[1;33m'
|
|
RED = '\033[1;31m'
|
|
END = '\033[0m'
|
|
|
|
|
|
def print_color(msg, color=Color.END, prefix='MESSAGE'):
|
|
'''Print a string with a color prefix'''
|
|
if os.isatty(sys.stderr.fileno()):
|
|
real_prefix = '{start}{prefix}{end}'.format(start=color, prefix=prefix, end=Color.END)
|
|
else:
|
|
real_prefix = prefix
|
|
sys.stderr.write('{prefix}: {msg}\n'.format(prefix=real_prefix, msg=msg))
|
|
|
|
|
|
def print_error(msg):
|
|
'''Print an error, and terminate'''
|
|
print_color(msg, color=Color.RED, prefix='ERROR')
|
|
sys.exit(1)
|
|
|
|
|
|
def print_warning(msg, fatal=False):
|
|
'''Print a warning, and optionally terminate'''
|
|
if fatal:
|
|
color = Color.RED
|
|
prefix = 'ERROR'
|
|
else:
|
|
color = Color.YELLOW
|
|
prefix = 'WARNING'
|
|
print_color(msg, color, prefix)
|
|
if fatal:
|
|
sys.exit(1)
|
|
|
|
|
|
def print_info(msg):
|
|
'''Print a message'''
|
|
print_color(msg, color=Color.GREEN, prefix='INFO')
|
|
|
|
|
|
def generate_licensing_comment(outfile):
|
|
outfile.write('/* This file is generated by glib-genmarshal, do not '
|
|
'modify it. This code is licensed under the same license as '
|
|
'the containing project. Note that it links to GLib, so '
|
|
'must comply with the LGPL linking clauses. */\n')
|
|
|
|
|
|
def generate_header_preamble(outfile, prefix='', std_includes=True, use_pragma=False):
|
|
'''Generate the preamble for the marshallers header file'''
|
|
generate_licensing_comment(outfile)
|
|
|
|
if use_pragma:
|
|
outfile.write('#pragma once\n')
|
|
outfile.write('\n')
|
|
else:
|
|
outfile.write('#ifndef __{}_MARSHAL_H__\n'.format(prefix.upper()))
|
|
outfile.write('#define __{}_MARSHAL_H__\n'.format(prefix.upper()))
|
|
outfile.write('\n')
|
|
# Maintain compatibility with the old C-based tool
|
|
if std_includes:
|
|
outfile.write('#include <glib-object.h>\n')
|
|
outfile.write('\n')
|
|
|
|
outfile.write('G_BEGIN_DECLS\n')
|
|
outfile.write('\n')
|
|
|
|
|
|
def generate_header_postamble(outfile, prefix='', use_pragma=False):
|
|
'''Generate the postamble for the marshallers header file'''
|
|
outfile.write('\n')
|
|
outfile.write('G_END_DECLS\n')
|
|
|
|
if not use_pragma:
|
|
outfile.write('\n')
|
|
outfile.write('#endif /* __{}_MARSHAL_H__ */\n'.format(prefix.upper()))
|
|
|
|
|
|
def generate_body_preamble(outfile, std_includes=True, include_headers=None, cpp_defines=None, cpp_undefines=None):
|
|
'''Generate the preamble for the marshallers source file'''
|
|
generate_licensing_comment(outfile)
|
|
|
|
for header in (include_headers or []):
|
|
outfile.write('#include "{}"\n'.format(header))
|
|
if include_headers:
|
|
outfile.write('\n')
|
|
|
|
for define in (cpp_defines or []):
|
|
s = define.split('=')
|
|
symbol = s[0]
|
|
value = s[1] if len(s) > 1 else '1'
|
|
outfile.write('#define {} {}\n'.format(symbol, value))
|
|
if cpp_defines:
|
|
outfile.write('\n')
|
|
|
|
for undefine in (cpp_undefines or []):
|
|
outfile.write('#undef {}\n'.format(undefine))
|
|
if cpp_undefines:
|
|
outfile.write('\n')
|
|
|
|
if std_includes:
|
|
outfile.write('#include <glib-object.h>\n')
|
|
outfile.write('\n')
|
|
|
|
outfile.write(GETTERS_STR)
|
|
outfile.write('\n\n')
|
|
|
|
|
|
# Marshaller arguments, as a dictionary where the key is the token used in
|
|
# the source file, and the value is another dictionary with the following
|
|
# keys:
|
|
#
|
|
# - signal: the token used in the marshaller prototype (mandatory)
|
|
# - ctype: the C type for the marshaller argument (mandatory)
|
|
# - getter: the function used to retrieve the argument from the GValue
|
|
# array when invoking the callback (optional)
|
|
# - promoted: the C type used by va_arg() to retrieve the argument from
|
|
# the va_list when invoking the callback (optional, only used when
|
|
# generating va_list marshallers)
|
|
# - box: an array of two elements, containing the boxing and unboxing
|
|
# functions for the given type (optional, only used when generating
|
|
# va_list marshallers)
|
|
# - static-check: a boolean value, if the given type should perform
|
|
# a static type check before boxing or unboxing the argument (optional,
|
|
# only used when generating va_list marshallers)
|
|
# - takes-type: a boolean value, if the boxing and unboxing functions
|
|
# for the given type require the type (optional, only used when
|
|
# generating va_list marshallers)
|
|
# - deprecated: whether the token has been deprecated (optional)
|
|
# - replaced-by: the token used to replace a deprecated token (optional,
|
|
# only used if deprecated is True)
|
|
IN_ARGS = {
|
|
'VOID': {
|
|
'signal': 'VOID',
|
|
'ctype': 'void',
|
|
},
|
|
'BOOLEAN': {
|
|
'signal': 'BOOLEAN',
|
|
'ctype': 'gboolean',
|
|
'getter': 'g_marshal_value_peek_boolean',
|
|
},
|
|
'CHAR': {
|
|
'signal': 'CHAR',
|
|
'ctype': 'gchar',
|
|
'promoted': 'gint',
|
|
'getter': 'g_marshal_value_peek_char',
|
|
},
|
|
'UCHAR': {
|
|
'signal': 'UCHAR',
|
|
'ctype': 'guchar',
|
|
'promoted': 'guint',
|
|
'getter': 'g_marshal_value_peek_uchar',
|
|
},
|
|
'INT': {
|
|
'signal': 'INT',
|
|
'ctype': 'gint',
|
|
'getter': 'g_marshal_value_peek_int',
|
|
},
|
|
'UINT': {
|
|
'signal': 'UINT',
|
|
'ctype': 'guint',
|
|
'getter': 'g_marshal_value_peek_uint',
|
|
},
|
|
'LONG': {
|
|
'signal': 'LONG',
|
|
'ctype': 'glong',
|
|
'getter': 'g_marshal_value_peek_long',
|
|
},
|
|
'ULONG': {
|
|
'signal': 'ULONG',
|
|
'ctype': 'gulong',
|
|
'getter': 'g_marshal_value_peek_ulong',
|
|
},
|
|
'INT64': {
|
|
'signal': 'INT64',
|
|
'ctype': 'gint64',
|
|
'getter': 'g_marshal_value_peek_int64',
|
|
},
|
|
'UINT64': {
|
|
'signal': 'UINT64',
|
|
'ctype': 'guint64',
|
|
'getter': 'g_marshal_value_peek_uint64',
|
|
},
|
|
'ENUM': {
|
|
'signal': 'ENUM',
|
|
'ctype': 'gint',
|
|
'getter': 'g_marshal_value_peek_enum',
|
|
},
|
|
'FLAGS': {
|
|
'signal': 'FLAGS',
|
|
'ctype': 'guint',
|
|
'getter': 'g_marshal_value_peek_flags',
|
|
},
|
|
'FLOAT': {
|
|
'signal': 'FLOAT',
|
|
'ctype': 'gfloat',
|
|
'promoted': 'gdouble',
|
|
'getter': 'g_marshal_value_peek_float',
|
|
},
|
|
'DOUBLE': {
|
|
'signal': 'DOUBLE',
|
|
'ctype': 'gdouble',
|
|
'getter': 'g_marshal_value_peek_double',
|
|
},
|
|
'STRING': {
|
|
'signal': 'STRING',
|
|
'ctype': 'gpointer',
|
|
'getter': 'g_marshal_value_peek_string',
|
|
'box': ['g_strdup', 'g_free'],
|
|
'static-check': True,
|
|
},
|
|
'PARAM': {
|
|
'signal': 'PARAM',
|
|
'ctype': 'gpointer',
|
|
'getter': 'g_marshal_value_peek_param',
|
|
'box': ['g_param_spec_ref', 'g_param_spec_unref'],
|
|
},
|
|
'BOXED': {
|
|
'signal': 'BOXED',
|
|
'ctype': 'gpointer',
|
|
'getter': 'g_marshal_value_peek_boxed',
|
|
'box': ['g_boxed_copy', 'g_boxed_free'],
|
|
'static-check': True,
|
|
'takes-type': True,
|
|
},
|
|
'POINTER': {
|
|
'signal': 'POINTER',
|
|
'ctype': 'gpointer',
|
|
'getter': 'g_marshal_value_peek_pointer',
|
|
},
|
|
'OBJECT': {
|
|
'signal': 'OBJECT',
|
|
'ctype': 'gpointer',
|
|
'getter': 'g_marshal_value_peek_object',
|
|
'box': ['g_object_ref', 'g_object_unref'],
|
|
},
|
|
'VARIANT': {
|
|
'signal': 'VARIANT',
|
|
'ctype': 'gpointer',
|
|
'getter': 'g_marshal_value_peek_variant',
|
|
'box': ['g_variant_ref_sink', 'g_variant_unref'],
|
|
'static-check': True,
|
|
'takes-type': False,
|
|
},
|
|
|
|
# Deprecated tokens
|
|
'NONE': {
|
|
'signal': 'VOID',
|
|
'ctype': 'void',
|
|
'deprecated': True,
|
|
'replaced_by': 'VOID'
|
|
},
|
|
'BOOL': {
|
|
'signal': 'BOOLEAN',
|
|
'ctype': 'gboolean',
|
|
'getter': 'g_marshal_value_peek_boolean',
|
|
'deprecated': True,
|
|
'replaced_by': 'BOOLEAN'
|
|
}
|
|
}
|
|
|
|
|
|
# Marshaller return values, as a dictionary where the key is the token used
|
|
# in the source file, and the value is another dictionary with the following
|
|
# keys:
|
|
#
|
|
# - signal: the token used in the marshaller prototype (mandatory)
|
|
# - ctype: the C type for the marshaller argument (mandatory)
|
|
# - setter: the function used to set the return value of the callback
|
|
# into a GValue (optional)
|
|
# - deprecated: whether the token has been deprecated (optional)
|
|
# - replaced-by: the token used to replace a deprecated token (optional,
|
|
# only used if deprecated is True)
|
|
OUT_ARGS = {
|
|
'VOID': {
|
|
'signal': 'VOID',
|
|
'ctype': 'void',
|
|
},
|
|
'BOOLEAN': {
|
|
'signal': 'BOOLEAN',
|
|
'ctype': 'gboolean',
|
|
'setter': 'g_value_set_boolean',
|
|
},
|
|
'CHAR': {
|
|
'signal': 'CHAR',
|
|
'ctype': 'gchar',
|
|
'setter': 'g_value_set_char',
|
|
},
|
|
'UCHAR': {
|
|
'signal': 'UCHAR',
|
|
'ctype': 'guchar',
|
|
'setter': 'g_value_set_uchar',
|
|
},
|
|
'INT': {
|
|
'signal': 'INT',
|
|
'ctype': 'gint',
|
|
'setter': 'g_value_set_int',
|
|
},
|
|
'UINT': {
|
|
'signal': 'UINT',
|
|
'ctype': 'guint',
|
|
'setter': 'g_value_set_uint',
|
|
},
|
|
'LONG': {
|
|
'signal': 'LONG',
|
|
'ctype': 'glong',
|
|
'setter': 'g_value_set_long',
|
|
},
|
|
'ULONG': {
|
|
'signal': 'ULONG',
|
|
'ctype': 'gulong',
|
|
'setter': 'g_value_set_ulong',
|
|
},
|
|
'INT64': {
|
|
'signal': 'INT64',
|
|
'ctype': 'gint64',
|
|
'setter': 'g_value_set_int64',
|
|
},
|
|
'UINT64': {
|
|
'signal': 'UINT64',
|
|
'ctype': 'guint64',
|
|
'setter': 'g_value_set_uint64',
|
|
},
|
|
'ENUM': {
|
|
'signal': 'ENUM',
|
|
'ctype': 'gint',
|
|
'setter': 'g_value_set_enum',
|
|
},
|
|
'FLAGS': {
|
|
'signal': 'FLAGS',
|
|
'ctype': 'guint',
|
|
'setter': 'g_value_set_flags',
|
|
},
|
|
'FLOAT': {
|
|
'signal': 'FLOAT',
|
|
'ctype': 'gfloat',
|
|
'setter': 'g_value_set_float',
|
|
},
|
|
'DOUBLE': {
|
|
'signal': 'DOUBLE',
|
|
'ctype': 'gdouble',
|
|
'setter': 'g_value_set_double',
|
|
},
|
|
'STRING': {
|
|
'signal': 'STRING',
|
|
'ctype': 'gchar*',
|
|
'setter': 'g_value_take_string',
|
|
},
|
|
'PARAM': {
|
|
'signal': 'PARAM',
|
|
'ctype': 'GParamSpec*',
|
|
'setter': 'g_value_take_param',
|
|
},
|
|
'BOXED': {
|
|
'signal': 'BOXED',
|
|
'ctype': 'gpointer',
|
|
'setter': 'g_value_take_boxed',
|
|
},
|
|
'POINTER': {
|
|
'signal': 'POINTER',
|
|
'ctype': 'gpointer',
|
|
'setter': 'g_value_set_pointer',
|
|
},
|
|
'OBJECT': {
|
|
'signal': 'OBJECT',
|
|
'ctype': 'GObject*',
|
|
'setter': 'g_value_take_object',
|
|
},
|
|
'VARIANT': {
|
|
'signal': 'VARIANT',
|
|
'ctype': 'GVariant*',
|
|
'setter': 'g_value_take_variant',
|
|
},
|
|
|
|
# Deprecated tokens
|
|
'NONE': {
|
|
'signal': 'VOID',
|
|
'ctype': 'void',
|
|
'setter': None,
|
|
'deprecated': True,
|
|
'replaced_by': 'VOID',
|
|
},
|
|
'BOOL': {
|
|
'signal': 'BOOLEAN',
|
|
'ctype': 'gboolean',
|
|
'setter': 'g_value_set_boolean',
|
|
'deprecated': True,
|
|
'replaced_by': 'BOOLEAN',
|
|
},
|
|
}
|
|
|
|
|
|
def check_args(retval, params, fatal_warnings=False):
|
|
'''Check the @retval and @params tokens for invalid and deprecated symbols.'''
|
|
if retval not in OUT_ARGS:
|
|
print_error('Unknown return value type "{}"'.format(retval))
|
|
|
|
if OUT_ARGS[retval].get('deprecated', False):
|
|
replaced_by = OUT_ARGS[retval]['replaced_by']
|
|
print_warning(DEPRECATED_MSG_STR.format(retval, replaced_by), fatal_warnings)
|
|
|
|
for param in params:
|
|
if param not in IN_ARGS:
|
|
print_error('Unknown parameter type "{}"'.format(param))
|
|
else:
|
|
if IN_ARGS[param].get('deprecated', False):
|
|
replaced_by = IN_ARGS[param]['replaced_by']
|
|
print_warning(DEPRECATED_MSG_STR.format(param, replaced_by), fatal_warnings)
|
|
|
|
|
|
def indent(text, level=0, fill=' '):
|
|
'''Indent @text by @level columns, using the @fill character'''
|
|
return ''.join([fill for x in range(level)]) + text
|
|
|
|
|
|
# pylint: disable=too-few-public-methods
|
|
class Visibility:
|
|
'''Symbol visibility options'''
|
|
NONE = 0
|
|
INTERNAL = 1
|
|
EXTERN = 2
|
|
|
|
|
|
def generate_marshaller_name(prefix, retval, params, replace_deprecated=True):
|
|
'''Generate a marshaller name for the given @prefix, @retval, and @params.
|
|
If @replace_deprecated is True, the generated name will replace deprecated
|
|
tokens.'''
|
|
if replace_deprecated:
|
|
real_retval = OUT_ARGS[retval]['signal']
|
|
real_params = []
|
|
for param in params:
|
|
real_params.append(IN_ARGS[param]['signal'])
|
|
else:
|
|
real_retval = retval
|
|
real_params = params
|
|
return '{prefix}_{retval}__{args}'.format(prefix=prefix,
|
|
retval=real_retval,
|
|
args='_'.join(real_params))
|
|
|
|
|
|
def generate_prototype(retval, params,
|
|
prefix='g_cclosure_user_marshal',
|
|
visibility=Visibility.NONE,
|
|
va_marshal=False):
|
|
'''Generate a marshaller declaration with the given @visibility. If @va_marshal
|
|
is True, the marshaller will use variadic arguments in place of a GValue array.'''
|
|
signature = []
|
|
|
|
if visibility == Visibility.INTERNAL:
|
|
signature += ['G_GNUC_INTERNAL']
|
|
elif visibility == Visibility.EXTERN:
|
|
signature += ['extern']
|
|
|
|
function_name = generate_marshaller_name(prefix, retval, params)
|
|
|
|
if not va_marshal:
|
|
signature += ['void ' + function_name + ' (GClosure *closure,']
|
|
width = len('void ') + len(function_name) + 2
|
|
|
|
signature += [indent('GValue *return_value,', level=width, fill=' ')]
|
|
signature += [indent('guint n_param_values,', level=width, fill=' ')]
|
|
signature += [indent('const GValue *param_values,', level=width, fill=' ')]
|
|
signature += [indent('gpointer invocation_hint,', level=width, fill=' ')]
|
|
signature += [indent('gpointer marshal_data);', level=width, fill=' ')]
|
|
else:
|
|
signature += ['void ' + function_name + 'v (GClosure *closure,']
|
|
width = len('void ') + len(function_name) + 3
|
|
|
|
signature += [indent('GValue *return_value,', level=width, fill=' ')]
|
|
signature += [indent('gpointer instance,', level=width, fill=' ')]
|
|
signature += [indent('va_list args,', level=width, fill=' ')]
|
|
signature += [indent('gpointer marshal_data,', level=width, fill=' ')]
|
|
signature += [indent('int n_params,', level=width, fill=' ')]
|
|
signature += [indent('GType *param_types);', level=width, fill=' ')]
|
|
|
|
return signature
|
|
|
|
|
|
# pylint: disable=too-many-statements, too-many-locals, too-many-branches
|
|
def generate_body(retval, params, prefix, va_marshal=False):
|
|
'''Generate a marshaller definition. If @va_marshal is True, the marshaller
|
|
will use va_list and variadic arguments in place of a GValue array.'''
|
|
retval_setter = OUT_ARGS[retval].get('setter', None)
|
|
# If there's no return value then we can mark the retval argument as unused
|
|
# and get a minor optimisation, as well as avoid a compiler warning
|
|
if not retval_setter:
|
|
unused = ' G_GNUC_UNUSED'
|
|
else:
|
|
unused = ''
|
|
|
|
body = ['void']
|
|
|
|
function_name = generate_marshaller_name(prefix, retval, params)
|
|
|
|
if not va_marshal:
|
|
body += [function_name + ' (GClosure *closure,']
|
|
width = len(function_name) + 2
|
|
|
|
body += [indent('GValue *return_value{},'.format(unused), level=width, fill=' ')]
|
|
body += [indent('guint n_param_values,', level=width, fill=' ')]
|
|
body += [indent('const GValue *param_values,', level=width, fill=' ')]
|
|
body += [indent('gpointer invocation_hint G_GNUC_UNUSED,', level=width, fill=' ')]
|
|
body += [indent('gpointer marshal_data)', level=width, fill=' ')]
|
|
else:
|
|
body += [function_name + 'v (GClosure *closure,']
|
|
width = len(function_name) + 3
|
|
|
|
body += [indent('GValue *return_value{},'.format(unused), level=width, fill=' ')]
|
|
body += [indent('gpointer instance,', level=width, fill=' ')]
|
|
body += [indent('va_list args,', level=width, fill=' ')]
|
|
body += [indent('gpointer marshal_data,', level=width, fill=' ')]
|
|
body += [indent('int n_params,', level=width, fill=' ')]
|
|
body += [indent('GType *param_types)', level=width, fill=' ')]
|
|
|
|
# Filter the arguments that have a getter
|
|
get_args = [x for x in params if IN_ARGS[x].get('getter', None) is not None]
|
|
|
|
body += ['{']
|
|
|
|
# Generate the type of the marshaller function
|
|
typedef_marshal = generate_marshaller_name('GMarshalFunc', retval, params)
|
|
|
|
typedef = ' typedef {ctype} (*{func_name}) ('.format(ctype=OUT_ARGS[retval]['ctype'],
|
|
func_name=typedef_marshal)
|
|
pad = len(typedef)
|
|
typedef += 'gpointer data1,'
|
|
body += [typedef]
|
|
|
|
for idx, in_arg in enumerate(get_args):
|
|
body += [indent('{} arg{:d},'.format(IN_ARGS[in_arg]['ctype'], idx + 1), level=pad)]
|
|
|
|
body += [indent('gpointer data2);', level=pad)]
|
|
|
|
# Variable declarations
|
|
body += [' GCClosure *cc = (GCClosure *) closure;']
|
|
body += [' gpointer data1, data2;']
|
|
body += [' {} callback;'.format(typedef_marshal)]
|
|
|
|
if retval_setter:
|
|
body += [' {} v_return;'.format(OUT_ARGS[retval]['ctype'])]
|
|
|
|
if va_marshal:
|
|
for idx, arg in enumerate(get_args):
|
|
body += [' {} arg{:d};'.format(IN_ARGS[arg]['ctype'], idx)]
|
|
|
|
if get_args:
|
|
body += [' va_list args_copy;']
|
|
body += ['']
|
|
|
|
body += [' G_VA_COPY (args_copy, args);']
|
|
|
|
for idx, arg in enumerate(get_args):
|
|
ctype = IN_ARGS[arg]['ctype']
|
|
promoted_ctype = IN_ARGS[arg].get('promoted', ctype)
|
|
body += [VA_ARG_STR.format(idx, ctype, promoted_ctype)]
|
|
if IN_ARGS[arg].get('box', None):
|
|
box_func = IN_ARGS[arg]['box'][0]
|
|
if IN_ARGS[arg].get('static-check', False):
|
|
static_check = STATIC_CHECK_STR.format(idx)
|
|
else:
|
|
static_check = ''
|
|
arg_check = 'arg{:d} != NULL'.format(idx)
|
|
body += [' if ({}{})'.format(static_check, arg_check)]
|
|
if IN_ARGS[arg].get('takes-type', False):
|
|
body += [BOX_TYPED_STR.format(idx=idx, box_func=box_func)]
|
|
else:
|
|
body += [BOX_UNTYPED_STR.format(idx=idx, box_func=box_func)]
|
|
|
|
body += [' va_end (args_copy);']
|
|
|
|
body += ['']
|
|
|
|
# Preconditions check
|
|
if retval_setter:
|
|
body += [' g_return_if_fail (return_value != NULL);']
|
|
|
|
if not va_marshal:
|
|
body += [' g_return_if_fail (n_param_values == {:d});'.format(len(get_args) + 1)]
|
|
|
|
body += ['']
|
|
|
|
# Marshal instance, data, and callback set up
|
|
body += [' if (G_CCLOSURE_SWAP_DATA (closure))']
|
|
body += [' {']
|
|
body += [' data1 = closure->data;']
|
|
if va_marshal:
|
|
body += [' data2 = instance;']
|
|
else:
|
|
body += [' data2 = g_value_peek_pointer (param_values + 0);']
|
|
body += [' }']
|
|
body += [' else']
|
|
body += [' {']
|
|
if va_marshal:
|
|
body += [' data1 = instance;']
|
|
else:
|
|
body += [' data1 = g_value_peek_pointer (param_values + 0);']
|
|
body += [' data2 = closure->data;']
|
|
body += [' }']
|
|
# pylint: disable=line-too-long
|
|
body += [' callback = ({}) (marshal_data ? marshal_data : cc->callback);'.format(typedef_marshal)]
|
|
body += ['']
|
|
|
|
# Marshal callback action
|
|
if retval_setter:
|
|
callback = ' {} callback ('.format(' v_return =')
|
|
else:
|
|
callback = ' callback ('
|
|
|
|
pad = len(callback)
|
|
body += [callback + 'data1,']
|
|
|
|
if va_marshal:
|
|
for idx, arg in enumerate(get_args):
|
|
body += [indent('arg{:d},'.format(idx), level=pad)]
|
|
else:
|
|
for idx, arg in enumerate(get_args):
|
|
arg_getter = IN_ARGS[arg]['getter']
|
|
body += [indent('{} (param_values + {:d}),'.format(arg_getter, idx + 1), level=pad)]
|
|
|
|
body += [indent('data2);', level=pad)]
|
|
|
|
if va_marshal:
|
|
boxed_args = [x for x in get_args if IN_ARGS[x].get('box', None) is not None]
|
|
if not boxed_args:
|
|
body += ['']
|
|
else:
|
|
for idx, arg in enumerate(get_args):
|
|
if not IN_ARGS[arg].get('box', None):
|
|
continue
|
|
unbox_func = IN_ARGS[arg]['box'][1]
|
|
if IN_ARGS[arg].get('static-check', False):
|
|
static_check = STATIC_CHECK_STR.format(idx)
|
|
else:
|
|
static_check = ''
|
|
arg_check = 'arg{:d} != NULL'.format(idx)
|
|
body += [' if ({}{})'.format(static_check, arg_check)]
|
|
if IN_ARGS[arg].get('takes-type', False):
|
|
body += [UNBOX_TYPED_STR.format(idx=idx, unbox_func=unbox_func)]
|
|
else:
|
|
body += [UNBOX_UNTYPED_STR.format(idx=idx, unbox_func=unbox_func)]
|
|
|
|
if retval_setter:
|
|
body += ['']
|
|
body += [' {} (return_value, v_return);'.format(retval_setter)]
|
|
|
|
body += ['}']
|
|
|
|
return body
|
|
|
|
|
|
def generate_marshaller_alias(outfile, marshaller, real_marshaller,
|
|
include_va=False,
|
|
source_location=None):
|
|
'''Generate an alias between @marshaller and @real_marshaller, including
|
|
an optional alias for va_list marshallers'''
|
|
if source_location:
|
|
outfile.write('/* {} */\n'.format(source_location))
|
|
|
|
outfile.write('#define {}\t{}\n'.format(marshaller, real_marshaller))
|
|
|
|
if include_va:
|
|
outfile.write('#define {}v\t{}v\n'.format(marshaller, real_marshaller))
|
|
|
|
outfile.write('\n')
|
|
|
|
|
|
def generate_marshallers_header(outfile, retval, params,
|
|
prefix='g_cclosure_user_marshal',
|
|
internal=False,
|
|
include_va=False, source_location=None):
|
|
'''Generate a declaration for a marshaller function, to be used in the header,
|
|
with the given @retval, @params, and @prefix. An optional va_list marshaller
|
|
for the same arguments is also generated. The generated buffer is written to
|
|
the @outfile stream object.'''
|
|
if source_location:
|
|
outfile.write('/* {} */\n'.format(source_location))
|
|
|
|
if internal:
|
|
visibility = Visibility.INTERNAL
|
|
else:
|
|
visibility = Visibility.EXTERN
|
|
|
|
signature = generate_prototype(retval, params, prefix, visibility, False)
|
|
if include_va:
|
|
signature += generate_prototype(retval, params, prefix, visibility, True)
|
|
signature += ['']
|
|
|
|
outfile.write('\n'.join(signature))
|
|
outfile.write('\n')
|
|
|
|
|
|
def generate_marshallers_body(outfile, retval, params,
|
|
prefix='g_cclosure_user_marshal',
|
|
include_prototype=True,
|
|
internal=False,
|
|
include_va=False, source_location=None):
|
|
'''Generate a definition for a marshaller function, to be used in the source,
|
|
with the given @retval, @params, and @prefix. An optional va_list marshaller
|
|
for the same arguments is also generated. The generated buffer is written to
|
|
the @outfile stream object.'''
|
|
if source_location:
|
|
outfile.write('/* {} */\n'.format(source_location))
|
|
|
|
if include_prototype:
|
|
# Declaration visibility
|
|
if internal:
|
|
decl_visibility = Visibility.INTERNAL
|
|
else:
|
|
decl_visibility = Visibility.EXTERN
|
|
proto = ['/* Prototype for -Wmissing-prototypes */']
|
|
# Add C++ guards in case somebody compiles the generated code
|
|
# with a C++ compiler
|
|
proto += ['G_BEGIN_DECLS']
|
|
proto += generate_prototype(retval, params, prefix, decl_visibility, False)
|
|
proto += ['G_END_DECLS']
|
|
outfile.write('\n'.join(proto))
|
|
outfile.write('\n')
|
|
|
|
body = generate_body(retval, params, prefix, False)
|
|
outfile.write('\n'.join(body))
|
|
outfile.write('\n\n')
|
|
|
|
if include_va:
|
|
if include_prototype:
|
|
# Declaration visibility
|
|
if internal:
|
|
decl_visibility = Visibility.INTERNAL
|
|
else:
|
|
decl_visibility = Visibility.EXTERN
|
|
proto = ['/* Prototype for -Wmissing-prototypes */']
|
|
# Add C++ guards here as well
|
|
proto += ['G_BEGIN_DECLS']
|
|
proto += generate_prototype(retval, params, prefix, decl_visibility, True)
|
|
proto += ['G_END_DECLS']
|
|
outfile.write('\n'.join(proto))
|
|
outfile.write('\n')
|
|
|
|
body = generate_body(retval, params, prefix, True)
|
|
outfile.write('\n'.join(body))
|
|
outfile.write('\n\n')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
arg_parser = argparse.ArgumentParser(description='Generate signal marshallers for GObject')
|
|
arg_parser.add_argument('--prefix', metavar='STRING',
|
|
default='g_cclosure_user_marshal',
|
|
help='Specify marshaller prefix')
|
|
arg_parser.add_argument('--output', metavar='FILE',
|
|
type=argparse.FileType('w'),
|
|
default=sys.stdout,
|
|
help='Write output into the specified file')
|
|
arg_parser.add_argument('--skip-source',
|
|
action='store_true',
|
|
help='Skip source location comments')
|
|
arg_parser.add_argument('--internal',
|
|
action='store_true',
|
|
help='Mark generated functions as internal')
|
|
arg_parser.add_argument('--valist-marshallers',
|
|
action='store_true',
|
|
help='Generate va_list marshallers')
|
|
arg_parser.add_argument('-v', '--version',
|
|
action='store_true',
|
|
dest='show_version',
|
|
help='Print version information, and exit')
|
|
arg_parser.add_argument('--g-fatal-warnings',
|
|
action='store_true',
|
|
dest='fatal_warnings',
|
|
help='Make warnings fatal')
|
|
arg_parser.add_argument('--include-header', metavar='HEADER', nargs='?',
|
|
action='append',
|
|
dest='include_headers',
|
|
help='Include the specified header in the body')
|
|
arg_parser.add_argument('--pragma-once',
|
|
action='store_true',
|
|
help='Use "pragma once" as the inclusion guard')
|
|
arg_parser.add_argument('-D',
|
|
action='append',
|
|
dest='cpp_defines',
|
|
default=[],
|
|
help='Pre-processor define')
|
|
arg_parser.add_argument('-U',
|
|
action='append',
|
|
dest='cpp_undefines',
|
|
default=[],
|
|
help='Pre-processor undefine')
|
|
arg_parser.add_argument('files', metavar='FILE', nargs='*',
|
|
type=argparse.FileType('r'),
|
|
help='Files with lists of marshallers to generate, ' +
|
|
'or "-" for standard input')
|
|
arg_parser.add_argument('--prototypes',
|
|
action='store_true',
|
|
help='Generate the marshallers prototype in the C code')
|
|
arg_parser.add_argument('--header',
|
|
action='store_true',
|
|
help='Generate C headers')
|
|
arg_parser.add_argument('--body',
|
|
action='store_true',
|
|
help='Generate C code')
|
|
|
|
group = arg_parser.add_mutually_exclusive_group()
|
|
group.add_argument('--stdinc',
|
|
action='store_true',
|
|
dest='stdinc', default=True,
|
|
help='Include standard marshallers')
|
|
group.add_argument('--nostdinc',
|
|
action='store_false',
|
|
dest='stdinc', default=True,
|
|
help='Use standard marshallers')
|
|
|
|
group = arg_parser.add_mutually_exclusive_group()
|
|
group.add_argument('--quiet',
|
|
action='store_true',
|
|
help='Only print warnings and errors')
|
|
group.add_argument('--verbose',
|
|
action='store_true',
|
|
help='Be verbose, and include debugging information')
|
|
|
|
args = arg_parser.parse_args()
|
|
|
|
if args.show_version:
|
|
print(VERSION_STR)
|
|
sys.exit(0)
|
|
|
|
# Backward compatibility hack; some projects use both arguments to
|
|
# generate the marshallers prototype in the C source, even though
|
|
# it's not really a supported use case. We keep this behaviour by
|
|
# forcing the --prototypes and --body arguments instead. We make this
|
|
# warning non-fatal even with --g-fatal-warnings, as it's a deprecation
|
|
compatibility_mode = False
|
|
if args.header and args.body:
|
|
print_warning('Using --header and --body at the same time is deprecated; ' +
|
|
'use --body --prototypes instead', False)
|
|
args.prototypes = True
|
|
args.header = False
|
|
compatibility_mode = True
|
|
|
|
if args.header:
|
|
generate_header_preamble(args.output,
|
|
prefix=args.prefix,
|
|
std_includes=args.stdinc,
|
|
use_pragma=args.pragma_once)
|
|
elif args.body:
|
|
generate_body_preamble(args.output,
|
|
std_includes=args.stdinc,
|
|
include_headers=args.include_headers,
|
|
cpp_defines=args.cpp_defines,
|
|
cpp_undefines=args.cpp_undefines)
|
|
|
|
seen_marshallers = set()
|
|
|
|
for infile in args.files:
|
|
if not args.quiet:
|
|
print_info('Reading {}...'.format(infile.name))
|
|
|
|
line_count = 0
|
|
for line in infile:
|
|
line_count += 1
|
|
|
|
if line == '\n' or line.startswith('#'):
|
|
continue
|
|
|
|
matches = re.match(r'^([A-Z0-9]+)\s?:\s?([A-Z0-9,\s]+)$', line.strip())
|
|
if not matches or len(matches.groups()) != 2:
|
|
print_warning('Invalid entry: "{}"'.format(line.strip()), args.fatal_warnings)
|
|
continue
|
|
|
|
if not args.skip_source:
|
|
location = '{} ({}:{:d})'.format(line.strip(), infile.name, line_count)
|
|
else:
|
|
location = None
|
|
|
|
retval = matches.group(1).strip()
|
|
params = [x.strip() for x in matches.group(2).split(',')]
|
|
check_args(retval, params, args.fatal_warnings)
|
|
|
|
raw_marshaller = generate_marshaller_name(args.prefix, retval, params, False)
|
|
if raw_marshaller in seen_marshallers:
|
|
if args.verbose:
|
|
print_info('Skipping repeated marshaller {}'.format(line.strip()))
|
|
continue
|
|
|
|
if args.header:
|
|
if args.verbose:
|
|
print_info('Generating declaration for {}'.format(line.strip()))
|
|
generate_std_alias = False
|
|
if args.stdinc:
|
|
std_marshaller = generate_marshaller_name(STD_PREFIX, retval, params)
|
|
if std_marshaller in GOBJECT_MARSHALLERS:
|
|
if args.verbose:
|
|
print_info('Skipping default marshaller {}'.format(line.strip()))
|
|
generate_std_alias = True
|
|
|
|
marshaller = generate_marshaller_name(args.prefix, retval, params)
|
|
if generate_std_alias:
|
|
generate_marshaller_alias(args.output, marshaller, std_marshaller,
|
|
source_location=location,
|
|
include_va=args.valist_marshallers)
|
|
else:
|
|
generate_marshallers_header(args.output, retval, params,
|
|
prefix=args.prefix,
|
|
internal=args.internal,
|
|
include_va=args.valist_marshallers,
|
|
source_location=location)
|
|
# If the marshaller is defined using a deprecated token, we want to maintain
|
|
# compatibility and generate an alias for the old name pointing to the new
|
|
# one
|
|
if marshaller != raw_marshaller:
|
|
if args.verbose:
|
|
print_info('Generating alias for deprecated tokens')
|
|
generate_marshaller_alias(args.output, raw_marshaller, marshaller,
|
|
include_va=args.valist_marshallers)
|
|
elif args.body:
|
|
if args.verbose:
|
|
print_info('Generating definition for {}'.format(line.strip()))
|
|
generate_std_alias = False
|
|
if args.stdinc:
|
|
std_marshaller = generate_marshaller_name(STD_PREFIX, retval, params)
|
|
if std_marshaller in GOBJECT_MARSHALLERS:
|
|
if args.verbose:
|
|
print_info('Skipping default marshaller {}'.format(line.strip()))
|
|
generate_std_alias = True
|
|
marshaller = generate_marshaller_name(args.prefix, retval, params)
|
|
if generate_std_alias:
|
|
# We need to generate the alias if we are in compatibility mode
|
|
if compatibility_mode:
|
|
generate_marshaller_alias(args.output, marshaller, std_marshaller,
|
|
source_location=location,
|
|
include_va=args.valist_marshallers)
|
|
else:
|
|
generate_marshallers_body(args.output, retval, params,
|
|
prefix=args.prefix,
|
|
internal=args.internal,
|
|
include_prototype=args.prototypes,
|
|
include_va=args.valist_marshallers,
|
|
source_location=location)
|
|
if compatibility_mode and marshaller != raw_marshaller:
|
|
if args.verbose:
|
|
print_info('Generating alias for deprecated tokens')
|
|
generate_marshaller_alias(args.output, raw_marshaller, marshaller,
|
|
include_va=args.valist_marshallers)
|
|
|
|
seen_marshallers.add(raw_marshaller)
|
|
|
|
if args.header:
|
|
generate_header_postamble(args.output, prefix=args.prefix, use_pragma=args.pragma_once)
|