aboutsummaryrefslogtreecommitdiff
path: root/find-modules/sip_generator.py
diff options
context:
space:
mode:
Diffstat (limited to 'find-modules/sip_generator.py')
-rw-r--r--find-modules/sip_generator.py700
1 files changed, 700 insertions, 0 deletions
diff --git a/find-modules/sip_generator.py b/find-modules/sip_generator.py
new file mode 100644
index 00000000..10be1477
--- /dev/null
+++ b/find-modules/sip_generator.py
@@ -0,0 +1,700 @@
+#!/usr/bin/env python
+#=============================================================================
+# Copyright 2016 by Shaheed Haque (srhaque@theiet.org)
+# Copyright 2016 by Stephen Kelly (steveire@gmail.com)
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# 1. Redistributions of source code must retain the copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# 3. The name of the author may not be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#=============================================================================
+
+"""SIP file generator for PyQt."""
+
+from __future__ import print_function
+import argparse
+import gettext
+import inspect
+import logging
+import os
+import re
+import subprocess
+import sys
+import traceback
+from clang import cindex
+from clang.cindex import AccessSpecifier, CursorKind, SourceRange, StorageClass, TokenKind, TypeKind, TranslationUnit
+
+import rules_engine
+
+
+class HelpFormatter(argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter):
+ pass
+
+
+logger = logging.getLogger(__name__)
+gettext.install(__name__)
+
+EXPR_KINDS = [
+ CursorKind.UNEXPOSED_EXPR,
+ CursorKind.CONDITIONAL_OPERATOR, CursorKind.UNARY_OPERATOR, CursorKind.BINARY_OPERATOR,
+ CursorKind.INTEGER_LITERAL, CursorKind.FLOATING_LITERAL, CursorKind.STRING_LITERAL,
+ CursorKind.CXX_BOOL_LITERAL_EXPR, CursorKind.CXX_STATIC_CAST_EXPR, CursorKind.DECL_REF_EXPR
+]
+TEMPLATE_KINDS = [
+ CursorKind.TYPE_REF, CursorKind.TEMPLATE_REF, CursorKind.NAMESPACE_REF
+ ] + EXPR_KINDS
+
+
+class SipGenerator(object):
+ def __init__(self, project_rules, compile_flags, verbose=False, dump_includes=False, dump_privates=False):
+ """
+ Constructor.
+
+ :param project_rules: The rules for the file.
+ :param compile_flags: The compile flags for the file.
+ :param dump_includes: Turn on diagnostics for include files.
+ :param dump_privates: Turn on diagnostics for omitted private items.
+ """
+ self.rules = project_rules
+ self.compile_flags = compile_flags
+ self.verbose = verbose
+ self.dump_includes = dump_includes
+ self.dump_privates = dump_privates
+ self.diagnostics = set()
+ self.tu = None
+ self.unpreprocessed_source = None
+
+ @staticmethod
+ def describe(cursor, text=None):
+ if not text:
+ text = cursor.spelling
+ return "{} on line {} '{}'".format(cursor.kind.name, cursor.extent.start.line, text)
+
+ def create_sip(self, h_file, include_filename):
+ """
+ Actually convert the given source header file into its SIP equivalent.
+
+ :param h_file: The source (header) file of interest.
+ :param include_filename: The (header) to generate in the sip file.
+ """
+
+ #
+ # Read in the original file.
+ #
+ source = h_file
+ self.unpreprocessed_source = []
+ with open(source, "rU") as f:
+ for line in f:
+ self.unpreprocessed_source.append(line)
+
+ index = cindex.Index.create()
+ self.tu = index.parse(source, ["-x", "c++"] + self.compile_flags)
+ for diag in self.tu.diagnostics:
+ #
+ # We expect to be run over hundreds of files. Any parsing issues are likely to be very repetitive.
+ # So, to avoid bothering the user, we suppress duplicates.
+ #
+ loc = diag.location
+ msg = "{}:{}[{}] {}".format(loc.file, loc.line, loc.column, diag.spelling)
+ if msg in self.diagnostics:
+ continue
+ self.diagnostics.add(msg)
+ logger.log(diag.severity, "Parse error {}".format(msg))
+ if self.dump_includes:
+ for include in sorted(set(self.tu.get_includes())):
+ logger.debug(_("Used includes {}").format(include.include.name))
+ #
+ # Run through the top level children in the translation unit.
+ #
+ body = self._container_get(self.tu.cursor, -1, h_file, include_filename)
+ return body, self.tu.get_includes
+
+ CONTAINER_SKIPPABLE_UNEXPOSED_DECL = re.compile("_DECLARE_PRIVATE|friend|;")
+ FN_SKIPPABLE_ATTR = re.compile("_EXPORT|Q_REQUIRED_RESULT|format\(printf")
+ VAR_SKIPPABLE_ATTR = re.compile("_EXPORT")
+ TYPEDEF_SKIPPABLE_ATTR = re.compile("_EXPORT")
+
+ def _container_get(self, container, level, h_file, include_filename):
+ """
+ Generate the (recursive) translation for a class or namespace.
+
+ :param container: A class or namespace.
+ :param h_file: Name of header file being processed.
+ :param level: Recursion level controls indentation.
+ :return: A string.
+ """
+
+ def skippable_attribute(member, text):
+ """
+ We don't seem to have access to the __attribute__(())s, but at least we can look for stuff we care about.
+
+ :param member: The attribute.
+ :param text: The raw source corresponding to the region of member.
+ """
+ if text.find("_DEPRECATED") != -1:
+ sip["annotations"].add("Deprecated")
+ return True
+ SipGenerator._report_ignoring(container, member, text)
+
+ sip = {
+ "name": container.displayname,
+ "annotations": set()
+ }
+ name = container.displayname
+ if container.access_specifier == AccessSpecifier.PRIVATE:
+ if self.dump_privates:
+ logger.debug("Ignoring private {}".format(SipGenerator.describe(container)))
+ return ""
+ body = ""
+ base_specifiers = []
+ template_type_parameters = []
+ had_copy_constructor = False
+ had_const_member = False
+ for member in container.get_children():
+ #
+ # Only emit items in the translation unit.
+ #
+ if member.location.file.name != self.tu.spelling:
+ continue
+ decl = ""
+ if member.kind in [CursorKind.CXX_METHOD, CursorKind.FUNCTION_DECL, CursorKind.FUNCTION_TEMPLATE,
+ CursorKind.CONSTRUCTOR, CursorKind.DESTRUCTOR, CursorKind.CONVERSION_FUNCTION]:
+ decl = self._fn_get(container, member, level + 1)
+ elif member.kind == CursorKind.ENUM_DECL:
+ decl = self._enum_get(container, member, level + 1) + ";\n"
+ elif member.kind == CursorKind.CXX_ACCESS_SPEC_DECL:
+ decl = self._get_access_specifier(member, level + 1)
+ elif member.kind == CursorKind.TYPEDEF_DECL:
+ decl = self._typedef_get(member, level + 1)
+ elif member.kind == CursorKind.CXX_BASE_SPECIFIER:
+ #
+ # Strip off the leading "class". Except for TypeKind.UNEXPOSED...
+ #
+ base_specifiers.append(member.displayname.split(None, 2)[-1])
+ elif member.kind == CursorKind.TEMPLATE_TYPE_PARAMETER:
+ template_type_parameters.append(member.displayname)
+ elif member.kind == CursorKind.TEMPLATE_NON_TYPE_PARAMETER:
+ template_type_parameters.append(member.type.spelling + " " + member.displayname)
+ elif member.kind in [CursorKind.VAR_DECL, CursorKind.FIELD_DECL]:
+ had_const_member = had_const_member or member.type.is_const_qualified()
+ decl = self._var_get(container, member, level + 1)
+ elif member.kind in [CursorKind.NAMESPACE, CursorKind.CLASS_DECL,
+ CursorKind.CLASS_TEMPLATE, CursorKind.CLASS_TEMPLATE_PARTIAL_SPECIALIZATION,
+ CursorKind.STRUCT_DECL, CursorKind.UNION_DECL]:
+ decl = self._container_get(member, level + 1, h_file, include_filename)
+ elif member.kind in TEMPLATE_KINDS + [CursorKind.USING_DECLARATION, CursorKind.USING_DIRECTIVE,
+ CursorKind.CXX_FINAL_ATTR]:
+ #
+ # Ignore:
+ #
+ # TEMPLATE_KINDS: Template type parameter.
+ # CursorKind.USING_DECLARATION, CursorKind.USING_DIRECTIVE: Using? Pah!
+ # CursorKind.CXX_FINAL_ATTR: Again, not much to be done with this.
+ #
+ pass
+ else:
+ SipGenerator._report_ignoring(container, member)
+
+ def is_copy_constructor(member):
+ if member.kind != CursorKind.CONSTRUCTOR:
+ return False
+ numParams = 0
+ hasSelfType = False
+ for child in member.get_children():
+ numParams += 1
+ if child.kind == CursorKind.PARM_DECL:
+ paramType = child.type.spelling
+ paramType = paramType.split("::")[-1]
+ paramType = paramType.replace("const", "").replace("&", "").strip()
+ hasSelfType = paramType == container.displayname
+ return numParams == 1 and hasSelfType
+
+ def has_parameter_default(parameter):
+ for member in parameter.get_children():
+ if member.kind.is_expression():
+ return True
+ return False
+
+ def is_default_constructor(member):
+ if member.kind != CursorKind.CONSTRUCTOR:
+ return False
+ numParams = 0
+ for parameter in member.get_children():
+ if (has_parameter_default(parameter)):
+ break
+ numParams += 1
+ return numParams == 0
+
+ had_copy_constructor = had_copy_constructor or is_copy_constructor(member)
+ #
+ # Discard almost anything which is private.
+ #
+ if member.access_specifier == AccessSpecifier.PRIVATE:
+ if member.kind == CursorKind.CXX_ACCESS_SPEC_DECL:
+ #
+ # We need these because...
+ #
+ pass
+ elif is_copy_constructor(member) or is_default_constructor(member):
+ #
+ # ...we need to pass private copy contructors to the SIP compiler.
+ #
+ pass
+ else:
+ if self.dump_privates:
+ logger.debug("Ignoring private {}".format(SipGenerator.describe(member)))
+ continue
+
+ if decl:
+ if self.verbose:
+ pad = " " * ((level + 1) * 4)
+ body += pad + "// {}\n".format(SipGenerator.describe(member))
+ body += decl
+ #
+ # Empty containers are still useful if they provide namespaces or forward declarations.
+ #
+ if not body and level >= 0:
+ body = "\n"
+ text = self._read_source(container.extent)
+ if not text.endswith("}"):
+ #
+ # Forward declaration.
+ #
+ sip["annotations"].add("External")
+ if body and level >= 0:
+ if container.kind == CursorKind.NAMESPACE:
+ container_type = "namespace " + name
+ elif container.kind in [CursorKind.CLASS_DECL, CursorKind.CLASS_TEMPLATE,
+ CursorKind.CLASS_TEMPLATE_PARTIAL_SPECIALIZATION]:
+ container_type = "class " + name
+ elif container.kind == CursorKind.STRUCT_DECL:
+ container_type = "struct " + name
+ elif container.kind == CursorKind.UNION_DECL:
+ container_type = "union " + name
+ else:
+ raise AssertionError(
+ _("Unexpected container {}: {}[{}]").format(container.kind, name, container.extent.start.line))
+ #
+ # Generate private copy constructor for non-copyable types.
+ #
+ if had_const_member and not had_copy_constructor:
+ body += " private:\n {}(const {} &); // Generated\n".format(name, container.type.get_canonical().spelling)
+ #
+ # Flesh out the SIP context for the rules engine.
+ #
+ sip["template_parameters"] = ", ".join(template_type_parameters)
+ sip["decl"] = container_type
+ sip["base_specifiers"] = ", ".join(base_specifiers)
+ sip["body"] = body
+ self.rules.container_rules().apply(container, sip)
+ pad = " " * (level * 4)
+ if sip["name"]:
+ decl = pad + sip["decl"]
+ if "External" in sip["annotations"]:
+ #
+ # SIP /External/ does not seem to work as one might wish. Suppress.
+ #
+ body = decl + " /External/;\n"
+ body = pad + "// Discarded {}\n".format(SipGenerator.describe(container))
+ else:
+ if sip["base_specifiers"]:
+ decl += ": " + sip["base_specifiers"]
+ if sip["annotations"]:
+ decl += " /" + ",".join(sip["annotations"]) + "/"
+ if sip["template_parameters"]:
+ decl = pad + "template <" + sip["template_parameters"] + ">\n" + decl
+ decl += "\n" + pad + "{\n"
+ decl += "%TypeHeaderCode\n#include <{}>\n%End\n".format(include_filename)
+ body = decl + sip["body"] + pad + "};\n"
+ else:
+ body = pad + "// Discarded {}\n".format(SipGenerator.describe(container))
+ return body
+
+ def _get_access_specifier(self, member, level):
+ """
+ Skip access specifiers embedded in the Q_OBJECT macro.
+ """
+ access_specifier = self._read_source(member.extent)
+ if access_specifier == "Q_OBJECT":
+ return ""
+ pad = " " * ((level - 1) * 4)
+ decl = pad + access_specifier + "\n"
+ return decl
+
+ def _enum_get(self, container, enum, level):
+ pad = " " * (level * 4)
+ decl = pad + "enum {} {{\n".format(enum.displayname)
+ enumerations = []
+ for enum in enum.get_children():
+ enumerations.append(pad + " {}".format(enum.displayname))
+ assert enum.kind == CursorKind.ENUM_CONSTANT_DECL
+ decl += ",\n".join(enumerations) + "\n"
+ decl += pad + "}"
+ return decl
+
+ def _fn_get(self, container, function, level):
+ """
+ Generate the translation for a function.
+
+ :param container: A class or namespace.
+ :param function: The function object.
+ :param level: Recursion level controls indentation.
+ :return: A string.
+ """
+
+ def skippable_attribute(member, text):
+ """
+ We don't seem to have access to the __attribute__(())s, but at least we can look for stuff we care about.
+
+ :param member: The attribute.
+ :param text: The raw source corresponding to the region of member.
+ """
+ if SipGenerator.FN_SKIPPABLE_ATTR.search(text):
+ return True
+ SipGenerator._report_ignoring(function, member, text)
+
+ sip = {
+ "name": function.spelling,
+ }
+ parameters = []
+ template_parameters = []
+ for child in function.get_children():
+ if child.kind == CursorKind.PARM_DECL:
+ parameter = child.displayname or "__{}".format(len(parameters))
+ theType = child.type.get_canonical()
+ typeSpelling = theType.spelling
+ if theType.kind == TypeKind.POINTER:
+ typeSpelling = theType.get_pointee().spelling + "* "
+
+ decl = "{} {}".format(typeSpelling, parameter)
+ child_sip = {
+ "name": parameter,
+ "decl": decl,
+ "init": self._fn_get_parameter_default(function, child),
+ "annotations": set()
+ }
+ self.rules.parameter_rules().apply(container, function, child, child_sip)
+ decl = child_sip["decl"]
+ if child_sip["annotations"]:
+ decl += " /" + ",".join(child_sip["annotations"]) + "/"
+ if child_sip["init"]:
+ decl += " = " + child_sip["init"]
+ parameters.append(decl)
+ elif child.kind in [CursorKind.COMPOUND_STMT, CursorKind.CXX_OVERRIDE_ATTR,
+ CursorKind.MEMBER_REF, CursorKind.DECL_REF_EXPR, CursorKind.CALL_EXPR] + TEMPLATE_KINDS:
+ #
+ # Ignore:
+ #
+ # CursorKind.COMPOUND_STMT: Function body.
+ # CursorKind.CXX_OVERRIDE_ATTR: The "override" keyword.
+ # CursorKind.MEMBER_REF, CursorKind.DECL_REF_EXPR, CursorKind.CALL_EXPR: Constructor initialisers.
+ # TEMPLATE_KINDS: The result type.
+ #
+ pass
+ elif child.kind == CursorKind.TEMPLATE_TYPE_PARAMETER:
+ template_parameters.append(child.displayname)
+ elif child.kind == CursorKind.TEMPLATE_NON_TYPE_PARAMETER:
+ template_parameters.append(child.type.spelling + " " + child.displayname)
+ else:
+ text = self._read_source(child.extent)
+ if child.kind in [CursorKind.UNEXPOSED_ATTR, CursorKind.VISIBILITY_ATTR] and skippable_attribute(child,
+ text):
+ pass
+ else:
+ SipGenerator._report_ignoring(function, child)
+ #
+ # Flesh out the SIP context for the rules engine.
+ #
+ sip["template_parameters"] = template_parameters
+ if function.kind in [CursorKind.CONSTRUCTOR, CursorKind.DESTRUCTOR]:
+ sip["fn_result"] = ""
+ else:
+ sip["fn_result"] = function.result_type.spelling
+ sip["parameters"] = parameters
+ sip["prefix"], sip["suffix"] = self._fn_get_decorators(function)
+ self.rules.function_rules().apply(container, function, sip)
+ pad = " " * (level * 4)
+ if sip["name"]:
+ sip["template_parameters"] = ", ".join(sip["template_parameters"])
+ decl = sip["name"] + "(" + ", ".join(sip["parameters"]) + ")"
+ if sip["fn_result"]:
+ decl = sip["fn_result"] + " " + decl
+ decl = pad + sip["prefix"] + decl + sip["suffix"]
+ if sip["template_parameters"]:
+ decl = pad + "template <" + sip["template_parameters"] + ">\n" + decl
+ decl += ";\n"
+ else:
+ decl = pad + "// Discarded {}\n".format(SipGenerator.describe(function))
+ return decl
+
+ def _fn_get_decorators(self, function):
+ """
+ The parser does not provide direct access to the complete keywords (explicit, const, static, etc) of a function
+ in the displayname. It would be nice to get these from the AST, but I cannot find where they are hiding.
+
+ Now, we could resort to using the original source. That does not bode well if you have macros (QOBJECT,
+ xxxDEPRECATED?), inlined bodies and the like, using the rule engine could be used to patch corner cases...
+
+ ...or we can try to guess what SIP cares about, i.e static and maybe const. Luckily (?), we have those to hand!
+
+ :param function: The function object.
+ :return: prefix, suffix String containing any prefix or suffix keywords.
+ """
+ suffix = ""
+ if function.is_const_method():
+ suffix += " const"
+ prefix = ""
+ if function.is_static_method():
+ prefix += "static "
+ if function.is_virtual_method():
+ prefix += "virtual "
+ if function.is_pure_virtual_method():
+ suffix += " = 0"
+ return prefix, suffix
+
+ def _fn_get_parameter_default(self, function, parameter):
+ """
+ The parser does not seem to provide access to the complete text of a parameter.
+ This makes it hard to find any default values, so we:
+
+ 1. Run the lexer from "here" to the end of the file, bailing out when we see the ","
+ or a ")" marking the end.
+ 2. Watch for the assignment.
+ """
+ def _get_param_type(parameter):
+ result = parameter.type.get_declaration().type
+
+ if (parameter.type.get_declaration().type.kind == TypeKind.TYPEDEF):
+ isQFlags = False
+ for member in parameter.type.get_declaration().get_children():
+ if member.kind == CursorKind.TEMPLATE_REF and member.spelling == "QFlags":
+ isQFlags = True
+ if isQFlags and member.kind == CursorKind.TYPE_REF:
+ result = member.type
+ break
+
+ return result
+
+ def _get_param_value(text, parameterType):
+ if text == "0":
+ return text
+ if not "::" in parameterType.spelling:
+ return text
+ try:
+ typeText, typeInit = text.split("(")
+ typeInit = "(" + typeInit
+ except:
+ typeText = text
+ typeInit = ""
+
+ prefix = parameterType.spelling.rsplit("::", 1)[0]
+ if "::" in typeText:
+ typeText = typeText.rsplit("::", 1)[1]
+ return prefix + "::" + typeText + typeInit
+
+
+ for member in parameter.get_children():
+ if member.kind.is_expression():
+
+ possible_extent = SourceRange.from_locations(parameter.extent.start, function.extent.end)
+ text = ""
+ bracket_level = 0
+ found_start = False
+ found_end = False
+ for token in self.tu.get_tokens(extent=possible_extent):
+ if (token.spelling == "="):
+ found_start = True
+ continue
+ if token.spelling == "," and bracket_level == 0:
+ found_end = True
+ break
+ elif token.spelling == "(":
+ bracket_level += 1
+ text += token.spelling
+ elif token.spelling == ")":
+ if bracket_level == 0:
+ found_end = True
+ break
+ bracket_level -= 1
+ text += token.spelling
+ if bracket_level == 0:
+ found_end = True
+ break
+ elif found_start:
+ text += token.spelling
+ if not found_end and text:
+ RuntimeError(_("No end found for {}::{}, '{}'").format(function.spelling, parameter.spelling, text))
+
+ parameterType = _get_param_type(parameter)
+
+ return _get_param_value(text, parameterType)
+ return ""
+
+ def _typedef_get(self, typedef, level):
+ """
+ Generate the translation for a typedef.
+
+ :param container: A class or namespace.
+ :param typedef: The typedef object.
+ :param level: Recursion level controls indentation.
+ :return: A string.
+ """
+
+ pad = " " * (level * 4)
+
+ decl = pad + "typedef {} {}".format(typedef.underlying_typedef_type.spelling, typedef.displayname)
+ decl += ";\n"
+ return decl
+
+ def _var_get(self, container, variable, level):
+ """
+ Generate the translation for a variable.
+
+ :param container: A class or namespace.
+ :param variable: The variable object.
+ :param level: Recursion level controls indentation.
+ :return: A string.
+ """
+
+ def skippable_attribute(member, text):
+ """
+ We don't seem to have access to the __attribute__(())s, but at least we can look for stuff we care about.
+
+ :param member: The attribute.
+ :param text: The raw source corresponding to the region of member.
+ """
+ if SipGenerator.VAR_SKIPPABLE_ATTR.search(text):
+ return True
+ SipGenerator._report_ignoring(container, member, text)
+
+ sip = {
+ "name": variable.spelling
+ }
+ for child in variable.get_children():
+ if child.kind in TEMPLATE_KINDS + [CursorKind.STRUCT_DECL, CursorKind.UNION_DECL]:
+ #
+ # Ignore:
+ #
+ # TEMPLATE_KINDS, CursorKind.STRUCT_DECL, CursorKind.UNION_DECL: : The variable type.
+ #
+ pass
+ else:
+ text = self._read_source(child.extent)
+ if child.kind == CursorKind.VISIBILITY_ATTR and skippable_attribute(child, text):
+ pass
+ else:
+ SipGenerator._report_ignoring(variable, child)
+ #
+ # Flesh out the SIP context for the rules engine.
+ #
+ decl = "{} {}".format(variable.type.spelling, variable.spelling)
+ sip["decl"] = decl
+ self.rules.variable_rules().apply(container, variable, sip)
+
+ pad = " " * (level * 4)
+ if sip["name"]:
+ decl = sip["decl"]
+ #
+ # SIP does not support protected variables, so we ignore them.
+ #
+ if variable.access_specifier == AccessSpecifier.PROTECTED:
+ decl = pad + "// Discarded {}\n".format(SipGenerator.describe(variable))
+ else:
+ decl = pad + decl + ";\n"
+ else:
+ decl = pad + "// Discarded {}\n".format(SipGenerator.describe(variable))
+ return decl
+
+ def _read_source(self, extent):
+ """
+ Read the given range from the unpre-processed source.
+
+ :param extent: The range of text required.
+ """
+ extract = self.unpreprocessed_source[extent.start.line - 1:extent.end.line]
+ if extent.start.line == extent.end.line:
+ extract[0] = extract[0][extent.start.column - 1:extent.end.column - 1]
+ else:
+ extract[0] = extract[0][extent.start.column - 1:]
+ extract[-1] = extract[-1][:extent.end.column - 1]
+ #
+ # Return a single line of text.
+ #
+ return "".join(extract).replace("\n", " ")
+
+ @staticmethod
+ def _report_ignoring(parent, child, text=None):
+ if not text:
+ text = child.displayname or child.spelling
+ logger.debug(_("Ignoring {} {} child {}").format(parent.kind.name, parent.spelling, SipGenerator.describe(child, text)))
+
+
+def main(argv=None):
+ """
+ Take a single C++ header file and generate the corresponding SIP file.
+ Beyond simple generation of the SIP file from the corresponding C++
+ header file, a set of rules can be used to customise the generated
+ SIP file.
+
+ Examples:
+
+ sip_generator.py /usr/include/KF5/KItemModels/kselectionproxymodel.h
+ """
+ if argv is None:
+ argv = sys.argv
+ parser = argparse.ArgumentParser(epilog=inspect.getdoc(main),
+ formatter_class=HelpFormatter)
+ parser.add_argument("-v", "--verbose", action="store_true", default=False, help=_("Enable verbose output"))
+ parser.add_argument("--flags",
+ help=_("Semicolon-separated C++ compile flags to use"))
+ parser.add_argument("--include_filename", help=_("C++ header include to compile"))
+ parser.add_argument("libclang", help=_("libclang library to use for parsing"))
+ parser.add_argument("project_rules", help=_("Project rules"))
+ parser.add_argument("source", help=_("C++ header to process"))
+ parser.add_argument("output", help=_("output filename to write"))
+ try:
+ args = parser.parse_args(argv[1:])
+ if args.verbose:
+ logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(name)s %(levelname)s: %(message)s')
+ else:
+ logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s')
+ #
+ # Generate!
+ #
+
+ cindex.Config.set_library_file(args.libclang)
+
+ rules = rules_engine.rules(args.project_rules)
+ g = SipGenerator(rules, args.flags.lstrip().split(";"), args.verbose)
+ body, includes = g.create_sip(args.source, args.include_filename)
+ with open(args.output, "w") as outputFile:
+ outputFile.write(body)
+ except Exception as e:
+ tbk = traceback.format_exc()
+ print(tbk)
+ return -1
+
+
+if __name__ == "__main__":
+ if sys.argv[1] != "--self-check":
+ sys.exit(main())
+ else:
+ cindex.Config.set_library_file(sys.argv[2])