File indexing completed on 2024-03-24 03:55:10
0001 #!/usr/bin/env python 0002 # 0003 # SPDX-FileCopyrightText: 2016 Shaheed Haque <srhaque@theiet.org> 0004 # SPDX-FileCopyrightText: 2016 Stephen Kelly <steveire@gmail.com> 0005 # 0006 # SPDX-License-Identifier: BSD-3-Clause 0007 0008 """SIP file generator for PyQt.""" 0009 0010 from __future__ import print_function 0011 import argparse 0012 import gettext 0013 import inspect 0014 import logging 0015 import sys 0016 import traceback 0017 from clang import cindex 0018 from clang.cindex import AccessSpecifier, AvailabilityKind, CursorKind, SourceRange, TokenKind, TypeKind, TranslationUnit 0019 0020 import rules_engine 0021 0022 0023 class HelpFormatter(argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter): 0024 pass 0025 0026 0027 logger = logging.getLogger(__name__) 0028 gettext.install(__name__) 0029 0030 EXPR_KINDS = [ 0031 CursorKind.UNEXPOSED_EXPR, 0032 CursorKind.CONDITIONAL_OPERATOR, CursorKind.UNARY_OPERATOR, CursorKind.BINARY_OPERATOR, 0033 CursorKind.INTEGER_LITERAL, CursorKind.FLOATING_LITERAL, CursorKind.STRING_LITERAL, 0034 CursorKind.CXX_BOOL_LITERAL_EXPR, CursorKind.CXX_STATIC_CAST_EXPR, CursorKind.DECL_REF_EXPR 0035 ] 0036 TEMPLATE_KINDS = [ 0037 CursorKind.TYPE_REF, CursorKind.TEMPLATE_REF, CursorKind.NAMESPACE_REF 0038 ] + EXPR_KINDS 0039 0040 def clang_diagnostic_to_logging_diagnostic(lvl): 0041 """ 0042 0043 The diagnostic levels in cindex.py are 0044 0045 Ignored = 0 0046 Note = 1 0047 Warning = 2 0048 Error = 3 0049 Fatal = 4 0050 0051 and the leves in the python logging module are 0052 0053 NOTSET 0 0054 DEBUG 10 0055 INFO 20 0056 WARNING 30 0057 ERROR 40 0058 CRITICAL 50 0059 0060 """ 0061 return (logging.NOTSET, 0062 logging.INFO, 0063 logging.WARNING, 0064 logging.ERROR, 0065 logging.CRITICAL)[lvl] 0066 0067 def diagnostic_word(lvl): 0068 return ("", "info", "warning", "error", "fatality")[lvl] 0069 0070 class SipGenerator(object): 0071 def __init__(self, project_rules, compile_flags, verbose=False, dump_includes=False, dump_privates=False): 0072 """ 0073 Constructor. 0074 0075 :param project_rules: The rules for the file. 0076 :param compile_flags: The compile flags for the file. 0077 :param dump_includes: Turn on diagnostics for include files. 0078 :param dump_privates: Turn on diagnostics for omitted private items. 0079 """ 0080 self.rules = project_rules 0081 self.compile_flags = compile_flags 0082 self.verbose = verbose 0083 self.dump_includes = dump_includes 0084 self.dump_privates = dump_privates 0085 self.diagnostics = set() 0086 self.tu = None 0087 self.unpreprocessed_source = None 0088 0089 @staticmethod 0090 def describe(cursor, text=None): 0091 if not text: 0092 text = cursor.spelling 0093 return "{} on line {} '{}'".format(cursor.kind.name, cursor.extent.start.line, text) 0094 0095 def create_sip(self, h_file, include_filename): 0096 """ 0097 Actually convert the given source header file into its SIP equivalent. 0098 0099 :param h_file: The source (header) file of interest. 0100 :param include_filename: The (header) to generate in the sip file. 0101 """ 0102 0103 # 0104 # Read in the original file. 0105 # 0106 source = h_file 0107 self.unpreprocessed_source = [] 0108 with open(source, "rb") as f: 0109 for line in f: 0110 self.unpreprocessed_source.append(line) 0111 0112 index = cindex.Index.create() 0113 self.tu = index.parse(source, ["-x", "c++"] + self.compile_flags) 0114 for diag in self.tu.diagnostics: 0115 # 0116 # We expect to be run over hundreds of files. Any parsing issues are likely to be very repetitive. 0117 # So, to avoid bothering the user, we suppress duplicates. 0118 # 0119 loc = diag.location 0120 msg = "{}:{}[{}] {}".format(loc.file, loc.line, loc.column, diag.spelling) 0121 if (diag.spelling == "#pragma once in main file"): 0122 continue 0123 if msg in self.diagnostics: 0124 continue 0125 self.diagnostics.add(msg) 0126 logger.log(clang_diagnostic_to_logging_diagnostic(diag.severity), 0127 "Parse {}: {}".format(diagnostic_word(diag.severity), msg)) 0128 if self.dump_includes: 0129 for include in sorted(set(self.tu.get_includes())): 0130 logger.debug(_("Used includes {}").format(include.include.name)) 0131 # 0132 # Run through the top level children in the translation unit. 0133 # 0134 body = self._container_get(self.tu.cursor, -1, h_file, include_filename) 0135 return body, self.tu.get_includes 0136 0137 def skippable_attribute(self, parent, member, text, sip): 0138 """ 0139 We don't seem to have access to the __attribute__(())s, but at least we can look for stuff we care about. 0140 0141 :param member: The attribute. 0142 :param text: The raw source corresponding to the region of member. 0143 """ 0144 if member.kind != CursorKind.VISIBILITY_ATTR: 0145 return False 0146 if member.spelling == "hidden": 0147 if self.dump_privates: 0148 logger.debug("Ignoring private {}".format(SipGenerator.describe(parent))) 0149 sip["name"] = "" 0150 return True 0151 return False 0152 0153 def _container_get(self, container, level, h_file, include_filename): 0154 """ 0155 Generate the (recursive) translation for a class or namespace. 0156 0157 :param container: A class or namespace. 0158 :param h_file: Name of header file being processed. 0159 :param level: Recursion level controls indentation. 0160 :return: A string. 0161 """ 0162 0163 if container.kind.is_translation_unit(): 0164 # 0165 # Any module-related manual code (%ExportedHeaderCode, %ModuleCode, %ModuleHeaderCode or other 0166 # module-level directives? 0167 # 0168 sip = { 0169 "name": include_filename, 0170 "decl": "" 0171 } 0172 self.rules.modulecode(include_filename, sip) 0173 body = sip["code"] 0174 else: 0175 body = "" 0176 0177 sip = { 0178 "name": container.displayname, 0179 "annotations": set() 0180 } 0181 name = container.displayname 0182 if container.access_specifier == AccessSpecifier.PRIVATE: 0183 if self.dump_privates: 0184 logger.debug("Ignoring private {}".format(SipGenerator.describe(container))) 0185 return "" 0186 base_specifiers = [] 0187 template_type_parameters = [] 0188 had_copy_constructor = False 0189 had_deleted_copy_constructor = False; 0190 had_const_member = False 0191 for member in container.get_children(): 0192 # 0193 # Only emit items in the translation unit. 0194 # 0195 if member.location.file.name != self.tu.spelling: 0196 continue 0197 decl = "" 0198 if member.kind in [CursorKind.CXX_METHOD, CursorKind.FUNCTION_DECL, CursorKind.FUNCTION_TEMPLATE, 0199 CursorKind.CONSTRUCTOR, CursorKind.DESTRUCTOR, CursorKind.CONVERSION_FUNCTION]: 0200 decl = self._fn_get(container, member, level + 1) 0201 elif member.kind == CursorKind.ENUM_DECL: 0202 decl = self._enum_get(container, member, level + 1) + ";\n" 0203 elif member.kind == CursorKind.CXX_ACCESS_SPEC_DECL: 0204 decl = self._get_access_specifier(member, level + 1) 0205 elif member.kind == CursorKind.TYPEDEF_DECL: 0206 decl = self._typedef_get(container, member, level + 1) 0207 elif member.kind == CursorKind.CXX_BASE_SPECIFIER: 0208 # 0209 # Strip off the leading "class". Except for TypeKind.UNEXPOSED... 0210 # 0211 base_specifiers.append(member.displayname.split(None, 2)[-1]) 0212 elif member.kind == CursorKind.TEMPLATE_TYPE_PARAMETER: 0213 template_type_parameters.append(member.displayname) 0214 elif member.kind == CursorKind.TEMPLATE_NON_TYPE_PARAMETER: 0215 template_type_parameters.append(member.type.spelling + " " + member.displayname) 0216 elif member.kind in [CursorKind.VAR_DECL, CursorKind.FIELD_DECL]: 0217 had_const_member = had_const_member or member.type.is_const_qualified() 0218 decl = self._var_get(container, member, level + 1) 0219 elif member.kind in [CursorKind.NAMESPACE, CursorKind.CLASS_DECL, 0220 CursorKind.CLASS_TEMPLATE, CursorKind.CLASS_TEMPLATE_PARTIAL_SPECIALIZATION, 0221 CursorKind.STRUCT_DECL, CursorKind.UNION_DECL]: 0222 decl = self._container_get(member, level + 1, h_file, include_filename) 0223 elif member.kind in TEMPLATE_KINDS + [CursorKind.USING_DECLARATION, CursorKind.USING_DIRECTIVE, 0224 CursorKind.CXX_FINAL_ATTR]: 0225 # 0226 # Ignore: 0227 # 0228 # TEMPLATE_KINDS: Template type parameter. 0229 # CursorKind.USING_DECLARATION, CursorKind.USING_DIRECTIVE: Using? Pah! 0230 # CursorKind.CXX_FINAL_ATTR: Again, not much to be done with this. 0231 # 0232 pass 0233 else: 0234 text = self._read_source(member.extent) 0235 if self.skippable_attribute(container, member, text, sip): 0236 if not sip["name"]: 0237 return "" 0238 else: 0239 SipGenerator._report_ignoring(container, member) 0240 0241 def is_copy_constructor(member): 0242 if member.kind != CursorKind.CONSTRUCTOR: 0243 return False 0244 numParams = 0 0245 hasSelfType = False 0246 for child in member.get_children(): 0247 numParams += 1 0248 if child.kind == CursorKind.PARM_DECL: 0249 paramType = child.type.spelling 0250 paramType = paramType.split("::")[-1] 0251 paramType = paramType.replace("const", "").replace("&", "").strip() 0252 hasSelfType = paramType == container.displayname 0253 return numParams == 1 and hasSelfType 0254 0255 def has_parameter_default(parameter): 0256 for member in parameter.get_children(): 0257 if member.kind.is_expression(): 0258 return True 0259 return False 0260 0261 def is_default_constructor(member): 0262 if member.kind != CursorKind.CONSTRUCTOR: 0263 return False 0264 numParams = 0 0265 for parameter in member.get_children(): 0266 if (has_parameter_default(parameter)): 0267 break 0268 numParams += 1 0269 return numParams == 0 0270 0271 if is_copy_constructor(member): 0272 had_copy_constructor = True 0273 # We need to generate a fake private copy constructor for deleted constructors 0274 if member.availability == AvailabilityKind.NOT_AVAILABLE and member.access_specifier != AccessSpecifier.PRIVATE: 0275 had_deleted_copy_constructor = True 0276 continue 0277 0278 # 0279 # Discard almost anything which is private. 0280 # 0281 if member.access_specifier == AccessSpecifier.PRIVATE: 0282 if member.kind == CursorKind.CXX_ACCESS_SPEC_DECL: 0283 # 0284 # We need these because... 0285 # 0286 pass 0287 elif is_copy_constructor(member) or is_default_constructor(member): 0288 # 0289 # ...we need to pass private copy constructors to the SIP compiler. 0290 # 0291 pass 0292 else: 0293 if self.dump_privates: 0294 logger.debug("Ignoring private {}".format(SipGenerator.describe(member))) 0295 continue 0296 0297 if decl: 0298 if self.verbose: 0299 pad = " " * ((level + 1) * 4) 0300 body += pad + "// {}\n".format(SipGenerator.describe(member)) 0301 body += decl 0302 0303 0304 if container.kind == CursorKind.TRANSLATION_UNIT: 0305 return body 0306 0307 if container.kind == CursorKind.NAMESPACE: 0308 container_type = "namespace " + name 0309 elif container.kind in [CursorKind.CLASS_DECL, CursorKind.CLASS_TEMPLATE, 0310 CursorKind.CLASS_TEMPLATE_PARTIAL_SPECIALIZATION]: 0311 container_type = "class " + name 0312 elif container.kind == CursorKind.STRUCT_DECL: 0313 container_type = "struct " + name 0314 elif container.kind == CursorKind.UNION_DECL: 0315 container_type = "union " + name 0316 else: 0317 raise AssertionError( 0318 _("Unexpected container {}: {}[{}]").format(container.kind, name, container.extent.start.line)) 0319 0320 sip["decl"] = container_type 0321 sip["template_parameters"] = template_type_parameters 0322 0323 pad = " " * (level * 4) 0324 0325 # 0326 # Empty containers are still useful if they provide namespaces or forward declarations. 0327 # 0328 if not body: 0329 text = self._read_source(container.extent) 0330 if not text.endswith("}"): 0331 # 0332 # Forward declaration. 0333 # 0334 modifying_rule = self.rules.forward_declaration_rules().apply(container, sip) 0335 if sip["name"]: 0336 if modifying_rule: 0337 body += "// Modified {} (by {}):\n".format(SipGenerator.describe(container), modifying_rule) 0338 if "External" in sip["annotations"]: 0339 body += pad + sip["decl"] 0340 body += " /External/;\n" 0341 else: 0342 body = pad + "// Discarded {} (by {})\n".format(SipGenerator.describe(container), "default forward declaration handling") 0343 0344 else: 0345 body = pad + "// Discarded {} (by {})\n".format(SipGenerator.describe(container), modifying_rule) 0346 else: 0347 # 0348 # Generate private copy constructor for non-copyable types. 0349 # 0350 if (had_deleted_copy_constructor) or (had_const_member and not had_copy_constructor): 0351 body += " private:\n {}(const {} &); // Generated\n".format(name, container.type.get_canonical().spelling) 0352 # 0353 # Flesh out the SIP context for the rules engine. 0354 # 0355 sip["base_specifiers"] = base_specifiers 0356 sip["body"] = body 0357 modifying_rule = self.rules.container_rules().apply(container, sip) 0358 if sip["name"]: 0359 decl = "" 0360 if modifying_rule: 0361 decl += "// Modified {} (by {}):\n".format(SipGenerator.describe(container), modifying_rule) 0362 decl += pad + sip["decl"] 0363 0364 if sip["base_specifiers"]: 0365 decl += ": " + ", ".join(sip["base_specifiers"]) 0366 if sip["annotations"]: 0367 decl += " /" + ",".join(sip["annotations"]) + "/" 0368 if sip["template_parameters"]: 0369 decl = pad + "template <" + ", ".join(sip["template_parameters"]) + ">\n" + decl 0370 decl += "\n" + pad + "{\n" 0371 decl += "%TypeHeaderCode\n#include <{}>\n%End\n".format(include_filename) 0372 body = decl + sip["body"] + pad + "};\n" 0373 else: 0374 body = pad + "// Discarded {} (by {})\n".format(SipGenerator.describe(container), modifying_rule) 0375 return body 0376 0377 def _get_access_specifier(self, member, level): 0378 """ 0379 Skip access specifiers embedded in the Q_OBJECT macro. 0380 """ 0381 access_specifier_text = self._read_source(member.extent) 0382 if access_specifier_text == "Q_OBJECT": 0383 return "" 0384 pad = " " * ((level - 1) * 4) 0385 access_specifier = "" 0386 if (access_specifier_text in ("Q_SIGNALS:", "signals:", 0387 "public Q_SLOTS:", "public slots:", 0388 "protected Q_SLOTS:", "protected slots:")): 0389 access_specifier = access_specifier_text 0390 elif member.access_specifier == AccessSpecifier.PRIVATE: 0391 access_specifier = "private:" 0392 elif member.access_specifier == AccessSpecifier.PROTECTED: 0393 access_specifier = "protected:" 0394 elif member.access_specifier == AccessSpecifier.PUBLIC: 0395 access_specifier = "public:" 0396 0397 decl = pad + access_specifier + "\n" 0398 return decl 0399 0400 def _enum_get(self, container, enum, level): 0401 pad = " " * (level * 4) 0402 scoped = "" 0403 if enum.is_scoped_enum(): 0404 scoped = "class " 0405 decl = pad + "enum {}{} {{\n".format(scoped, enum.displayname) 0406 enumerations = [] 0407 for enum in enum.get_children(): 0408 # 0409 # Skip visibility attributes and the like. 0410 # 0411 if enum.kind == CursorKind.ENUM_CONSTANT_DECL: 0412 enumerations.append(pad + " {}".format(enum.displayname)) 0413 decl += ",\n".join(enumerations) + "\n" 0414 decl += pad + "}" 0415 return decl 0416 0417 def _fn_get(self, container, function, level): 0418 """ 0419 Generate the translation for a function. 0420 0421 :param container: A class or namespace. 0422 :param function: The function object. 0423 :param level: Recursion level controls indentation. 0424 :return: A string. 0425 """ 0426 if container.kind == CursorKind.TRANSLATION_UNIT and \ 0427 (function.semantic_parent.kind == CursorKind.CLASS_DECL or 0428 function.semantic_parent.kind == CursorKind.STRUCT_DECL) and \ 0429 function.is_definition(): 0430 # Skip inline methods 0431 return 0432 0433 sip = { 0434 "name": function.spelling, 0435 } 0436 parameters = [] 0437 parameter_modifying_rules = [] 0438 template_parameters = [] 0439 for child in function.get_children(): 0440 if child.kind == CursorKind.PARM_DECL: 0441 parameter = child.displayname or "__{}".format(len(parameters)) 0442 theType = child.type.get_canonical() 0443 typeSpelling = theType.spelling 0444 if theType.kind == TypeKind.POINTER: 0445 typeSpelling = theType.get_pointee().spelling + "* " 0446 0447 decl = "{} {}".format(typeSpelling, parameter) 0448 child_sip = { 0449 "name": parameter, 0450 "decl": decl, 0451 "init": self._fn_get_parameter_default(function, child), 0452 "annotations": set() 0453 } 0454 modifying_rule = self.rules.parameter_rules().apply(container, function, child, child_sip) 0455 if modifying_rule: 0456 parameter_modifying_rules.append("// Modified {} (by {}):\n".format(SipGenerator.describe(child), modifying_rule)) 0457 decl = child_sip["decl"] 0458 if child_sip["annotations"]: 0459 decl += " /" + ",".join(child_sip["annotations"]) + "/" 0460 if child_sip["init"]: 0461 decl += " = " + child_sip["init"] 0462 parameters.append(decl) 0463 elif child.kind in [CursorKind.COMPOUND_STMT, CursorKind.CXX_OVERRIDE_ATTR, 0464 CursorKind.MEMBER_REF, CursorKind.DECL_REF_EXPR, CursorKind.CALL_EXPR] + TEMPLATE_KINDS: 0465 # 0466 # Ignore: 0467 # 0468 # CursorKind.COMPOUND_STMT: Function body. 0469 # CursorKind.CXX_OVERRIDE_ATTR: The "override" keyword. 0470 # CursorKind.MEMBER_REF, CursorKind.DECL_REF_EXPR, CursorKind.CALL_EXPR: Constructor initialisers. 0471 # TEMPLATE_KINDS: The result type. 0472 # 0473 pass 0474 elif child.kind == CursorKind.TEMPLATE_TYPE_PARAMETER: 0475 template_parameters.append(child.displayname) 0476 elif child.kind == CursorKind.TEMPLATE_NON_TYPE_PARAMETER: 0477 template_parameters.append(child.type.spelling + " " + child.displayname) 0478 else: 0479 text = self._read_source(child.extent) 0480 if self.skippable_attribute(function, child, text, sip): 0481 if not sip["name"]: 0482 return "" 0483 else: 0484 SipGenerator._report_ignoring(function, child) 0485 # 0486 # Flesh out the SIP context for the rules engine. 0487 # 0488 sip["template_parameters"] = template_parameters 0489 if function.kind in [CursorKind.CONSTRUCTOR, CursorKind.DESTRUCTOR]: 0490 sip["fn_result"] = "" 0491 else: 0492 sip["fn_result"] = function.result_type.spelling 0493 sip["parameters"] = parameters 0494 sip["prefix"], sip["suffix"] = self._fn_get_decorators(function) 0495 modifying_rule = self.rules.function_rules().apply(container, function, sip) 0496 pad = " " * (level * 4) 0497 if sip["name"]: 0498 # 0499 # Any method-related code (%MethodCode, %VirtualCatcherCode, VirtualCallCode 0500 # or other method-related directives)? 0501 # 0502 self.rules.methodcode(function, sip) 0503 decl = "" 0504 if modifying_rule: 0505 decl += "// Modified {} (by {}):\n".format(SipGenerator.describe(function), modifying_rule) + pad 0506 decl += pad.join(parameter_modifying_rules) 0507 if parameter_modifying_rules: 0508 decl += pad 0509 0510 decl += sip["name"] + "(" + ", ".join(sip["parameters"]) + ")" 0511 if sip["fn_result"]: 0512 decl = sip["fn_result"] + " " + decl 0513 decl = pad + sip["prefix"] + decl + sip["suffix"] 0514 if sip["template_parameters"]: 0515 decl = pad + "template <" + ", ".join(sip["template_parameters"]) + ">\n" + decl 0516 decl += ";\n" 0517 decl += sip["code"] 0518 else: 0519 decl = pad + "// Discarded {} (by {})\n".format(SipGenerator.describe(function), modifying_rule) 0520 return decl 0521 0522 def _fn_get_decorators(self, function): 0523 """ 0524 The parser does not provide direct access to the complete keywords (explicit, const, static, etc) of a function 0525 in the displayname. It would be nice to get these from the AST, but I cannot find where they are hiding. 0526 0527 Now, we could resort to using the original source. That does not bode well if you have macros (QOBJECT, 0528 xxxDEPRECATED?), inlined bodies and the like, using the rule engine could be used to patch corner cases... 0529 0530 ...or we can try to guess what SIP cares about, i.e static and maybe const. Luckily (?), we have those to hand! 0531 0532 :param function: The function object. 0533 :return: prefix, suffix String containing any prefix or suffix keywords. 0534 """ 0535 suffix = "" 0536 if function.is_const_method(): 0537 suffix += " const" 0538 prefix = "" 0539 if function.is_static_method(): 0540 prefix += "static " 0541 if function.is_virtual_method(): 0542 prefix += "virtual " 0543 if function.is_pure_virtual_method(): 0544 suffix += " = 0" 0545 return prefix, suffix 0546 0547 def _fn_get_parameter_default(self, function, parameter): 0548 """ 0549 The parser does not seem to provide access to the complete text of a parameter. 0550 This makes it hard to find any default values, so we: 0551 0552 1. Run the lexer from "here" to the end of the file, bailing out when we see the "," 0553 or a ")" marking the end. 0554 2. Watch for the assignment. 0555 """ 0556 def _get_param_type(parameter): 0557 result = parameter.type.get_declaration().type 0558 0559 if result.kind != TypeKind.ENUM and result.kind != TypeKind.TYPEDEF and parameter.type.kind == TypeKind.LVALUEREFERENCE: 0560 if parameter.type.get_pointee().get_declaration().type.kind != TypeKind.INVALID: 0561 return parameter.type.get_pointee().get_declaration().type 0562 return parameter.type.get_pointee() 0563 0564 if parameter.type.get_declaration().type.kind == TypeKind.INVALID: 0565 return parameter.type 0566 0567 if (parameter.type.get_declaration().type.kind == TypeKind.TYPEDEF): 0568 isQFlags = False 0569 for member in parameter.type.get_declaration().get_children(): 0570 if member.kind == CursorKind.TEMPLATE_REF and member.spelling == "QFlags": 0571 isQFlags = True 0572 if isQFlags and member.kind == CursorKind.TYPE_REF: 0573 result = member.type 0574 break 0575 0576 return result 0577 0578 def _get_param_value(text, parameterType): 0579 if text == "0" or text == "nullptr": 0580 return text 0581 if text == "{}": 0582 if parameterType.kind == TypeKind.ENUM: 0583 return "0" 0584 if parameterType.kind == TypeKind.POINTER: 0585 return "nullptr" 0586 if parameterType.spelling.startswith("const "): 0587 return parameterType.spelling[6:] + "()" 0588 return parameterType.spelling + "()" 0589 if not "::" in parameterType.spelling: 0590 return text 0591 try: 0592 typeText, typeInit = text.split("(") 0593 typeInit = "(" + typeInit 0594 except: 0595 typeText = text 0596 typeInit = "" 0597 0598 if parameterType.kind == TypeKind.ENUM and parameterType.get_declaration().is_scoped_enum(): 0599 prefix = parameterType.spelling 0600 else: 0601 prefix = parameterType.spelling.rsplit("::", 1)[0] 0602 if "::" in typeText: 0603 typeText = typeText.rsplit("::", 1)[1] 0604 return prefix + "::" + typeText + typeInit 0605 0606 0607 for member in parameter.get_children(): 0608 if member.kind.is_expression(): 0609 0610 possible_extent = SourceRange.from_locations(parameter.extent.start, function.extent.end) 0611 text = "" 0612 bracket_level = 0 0613 found_start = False 0614 found_end = False 0615 for token in self.tu.get_tokens(extent=possible_extent): 0616 if (token.spelling == "="): 0617 found_start = True 0618 continue 0619 if token.spelling == "," and bracket_level == 0: 0620 found_end = True 0621 break 0622 elif token.spelling == "(": 0623 bracket_level += 1 0624 text += token.spelling 0625 elif token.spelling == ")": 0626 if bracket_level == 0: 0627 found_end = True 0628 break 0629 bracket_level -= 1 0630 text += token.spelling 0631 if bracket_level == 0: 0632 found_end = True 0633 break 0634 elif found_start: 0635 text += token.spelling 0636 if not found_end and text: 0637 RuntimeError(_("No end found for {}::{}, '{}'").format(function.spelling, parameter.spelling, text)) 0638 0639 parameterType = _get_param_type(parameter) 0640 0641 return _get_param_value(text, parameterType) 0642 return "" 0643 0644 def _typedef_get(self, container, typedef, level): 0645 """ 0646 Generate the translation for a typedef. 0647 0648 :param container: A class or namespace. 0649 :param typedef: The typedef object. 0650 :param level: Recursion level controls indentation. 0651 :return: A string. 0652 """ 0653 0654 sip = { 0655 "name": typedef.displayname, 0656 "decl": typedef.underlying_typedef_type.spelling, 0657 "annotations": set(), 0658 } 0659 0660 self.rules.typedef_rules().apply(container, typedef, sip) 0661 0662 pad = " " * (level * 4) 0663 if sip["name"]: 0664 decl = pad + "typedef {} {}".format(sip["decl"], sip["name"]) 0665 decl += ";\n" 0666 else: 0667 decl = pad + "// Discarded {}\n".format(SipGenerator.describe(typedef)) 0668 return decl 0669 0670 def _var_get(self, container, variable, level): 0671 """ 0672 Generate the translation for a variable. 0673 0674 :param container: A class or namespace. 0675 :param variable: The variable object. 0676 :param level: Recursion level controls indentation. 0677 :return: A string. 0678 """ 0679 0680 sip = { 0681 "name": variable.spelling 0682 } 0683 for child in variable.get_children(): 0684 if child.kind in TEMPLATE_KINDS + [CursorKind.STRUCT_DECL, CursorKind.UNION_DECL]: 0685 # 0686 # Ignore: 0687 # 0688 # TEMPLATE_KINDS, CursorKind.STRUCT_DECL, CursorKind.UNION_DECL: : The variable type. 0689 # 0690 pass 0691 else: 0692 text = self._read_source(child.extent) 0693 if self.skippable_attribute(variable, child, text, sip): 0694 if not sip["name"]: 0695 return "" 0696 else: 0697 SipGenerator._report_ignoring(variable, child) 0698 # 0699 # Flesh out the SIP context for the rules engine. 0700 # 0701 decl = "{} {}".format(variable.type.spelling, variable.spelling) 0702 sip["decl"] = decl 0703 modifying_rule = self.rules.variable_rules().apply(container, variable, sip) 0704 0705 pad = " " * (level * 4) 0706 if sip["name"]: 0707 decl = sip["decl"] 0708 # 0709 # SIP does not support protected variables, so we ignore them. 0710 # 0711 if variable.access_specifier == AccessSpecifier.PROTECTED: 0712 decl = pad + "// Discarded {}\n".format(SipGenerator.describe(variable)) 0713 else: 0714 decl = pad + decl + ";\n" 0715 else: 0716 decl = pad + "// Discarded {} (by {})\n".format(SipGenerator.describe(variable), modifying_rule) 0717 return decl 0718 0719 def _read_source(self, extent): 0720 """ 0721 Read the given range from the unpre-processed source. 0722 0723 :param extent: The range of text required. 0724 """ 0725 # Extent columns are specified in bytes 0726 extract = self.unpreprocessed_source[extent.start.line - 1:extent.end.line] 0727 if extent.start.line == extent.end.line: 0728 extract[0] = extract[0][extent.start.column - 1:extent.end.column - 1] 0729 else: 0730 extract[0] = extract[0][extent.start.column - 1:] 0731 extract[-1] = extract[-1][:extent.end.column - 1] 0732 # 0733 # Return a single line of text. 0734 # Replace all kinds of newline variants (DOS, UNIX, MAC style) by single spaces 0735 # 0736 return b''.join(extract).decode('utf-8').replace("\r\n", " ").replace("\n", " ").replace("\r", " ") 0737 0738 @staticmethod 0739 def _report_ignoring(parent, child, text=None): 0740 if not text: 0741 text = child.displayname or child.spelling 0742 logger.debug(_("Ignoring {} {} child {}").format(parent.kind.name, parent.spelling, SipGenerator.describe(child, text))) 0743 0744 0745 def main(argv=None): 0746 """ 0747 Take a single C++ header file and generate the corresponding SIP file. 0748 Beyond simple generation of the SIP file from the corresponding C++ 0749 header file, a set of rules can be used to customize the generated 0750 SIP file. 0751 0752 Examples: 0753 0754 sip_generator.py /usr/include/KF5/KItemModels/kselectionproxymodel.h 0755 """ 0756 if argv is None: 0757 argv = sys.argv 0758 parser = argparse.ArgumentParser(epilog=inspect.getdoc(main), 0759 formatter_class=HelpFormatter) 0760 parser.add_argument("-v", "--verbose", action="store_true", default=False, help=_("Enable verbose output")) 0761 parser.add_argument("--flags", 0762 help=_("Semicolon-separated C++ compile flags to use")) 0763 parser.add_argument("--include_filename", help=_("C++ header include to compile")) 0764 parser.add_argument("libclang", help=_("libclang library to use for parsing")) 0765 parser.add_argument("project_rules", help=_("Project rules")) 0766 parser.add_argument("source", help=_("C++ header to process")) 0767 parser.add_argument("output", help=_("output filename to write")) 0768 try: 0769 args = parser.parse_args(argv[1:]) 0770 if args.verbose: 0771 logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(name)s %(levelname)s: %(message)s') 0772 else: 0773 logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s') 0774 # 0775 # Generate! 0776 # 0777 0778 cindex.Config.set_library_file(args.libclang) 0779 0780 rules = rules_engine.rules(args.project_rules) 0781 g = SipGenerator(rules, args.flags.lstrip().split(";"), args.verbose) 0782 body, includes = g.create_sip(args.source, args.include_filename) 0783 with open(args.output, "w") as outputFile: 0784 outputFile.write(body) 0785 except Exception: 0786 tbk = traceback.format_exc() 0787 print(tbk) 0788 return -1 0789 0790 0791 if __name__ == "__main__": 0792 if sys.argv[1] != "--self-check": 0793 sys.exit(main()) 0794 else: 0795 from PyQt5.Qt import PYQT_CONFIGURATION 0796 cindex.Config.set_library_file(sys.argv[2])