diff options
author | Stephen Kelly <steveire@gmail.com> | 2017-01-13 18:06:28 +0000 |
---|---|---|
committer | Stephen Kelly <steveire@gmail.com> | 2017-01-13 18:06:28 +0000 |
commit | af9f502f9629766130e171632d2072d563661959 (patch) | |
tree | ab77d12271937a6e579538991e8aa5704673665c /find-modules | |
parent | 39454cd893309c1a00eb7a9254045d52a8ced169 (diff) | |
download | extra-cmake-modules-af9f502f9629766130e171632d2072d563661959.tar.gz extra-cmake-modules-af9f502f9629766130e171632d2072d563661959.tar.bz2 |
Bindings: Implement ModuleCode and MethodCode databases
Diffstat (limited to 'find-modules')
-rw-r--r-- | find-modules/Qt5Ruleset.py | 14 | ||||
-rwxr-xr-x | find-modules/rules_engine.py | 268 | ||||
-rw-r--r-- | find-modules/sip_generator.py | 21 |
3 files changed, 301 insertions, 2 deletions
diff --git a/find-modules/Qt5Ruleset.py b/find-modules/Qt5Ruleset.py index 6ace2962..8ce6f8da 100644 --- a/find-modules/Qt5Ruleset.py +++ b/find-modules/Qt5Ruleset.py @@ -114,6 +114,8 @@ class RuleSet(rules_engine.RuleSet): self._fn_db = rules_engine.FunctionRuleDb(function_rules) self._param_db = rules_engine.ParameterRuleDb(parameter_rules) self._var_db = rules_engine.VariableRuleDb(variable_rules) + self._methodcode = rules_engine.MethodCodeDb({}) + self._modulecode = rules_engine.ModuleCodeDb({}) def container_rules(self): return self._container_db @@ -126,3 +128,15 @@ class RuleSet(rules_engine.RuleSet): def variable_rules(self): return self._var_db + + def methodcode_rules(self): + return self._methodcode + + def modulecode_rules(self): + return self._modulecode + + def methodcode(self, function, sip): + return self._methodcode.apply(function, sip) + + def modulecode(self, filename, sip): + return self._modulecode.apply(filename, sip) diff --git a/find-modules/rules_engine.py b/find-modules/rules_engine.py index b5b783e0..3bd6ccfc 100755 --- a/find-modules/rules_engine.py +++ b/find-modules/rules_engine.py @@ -454,6 +454,240 @@ class VariableRuleDb(AbstractCompiledRuleDb): return None +class AbstractCompiledCodeDb(object): + __metaclass__ = ABCMeta + + def __init__(self, db): + caller = os.path.basename(inspect.stack()[2][1]) + self.name = "{}:{}".format(caller, type(self).__name__) + self.db = db + + @abstractmethod + def apply(self, function, sip): + raise NotImplemented(_("Missing subclass")) + + def trace_result(self, parents, item, original, modified): + """ + Record any modification both in the log and the returned result. If a rule fired, but + caused no modification, that is logged. + + :return: Modifying rule or None. + """ + fqn = parents + "::" + original["name"] + "[" + str(item.extent.start.line) + "]" + self._trace_result(fqn, original, modified) + + def _trace_result(self, fqn, original, modified): + """ + Record any modification both in the log and the returned result. If a rule fired, but + caused no modification, that is logged. + + :return: Modifying rule or None. + """ + if not modified["name"]: + logger.debug(_("Rule {} suppressed {}, {}").format(self, fqn, original)) + else: + delta = False + for k, v in original.iteritems(): + if v != modified[k]: + delta = True + break + if delta: + logger.debug(_("Rule {} modified {}, {}->{}").format(self, fqn, original, modified)) + else: + logger.warn(_("Rule {} did not modify {}, {}").format(self, fqn, original)) + return None + return self + + @abstractmethod + def dump_usage(self, fn): + raise NotImplemented(_("Missing subclass")) + + def __str__(self): + return self.name + +class MethodCodeDb(AbstractCompiledCodeDb): + """ + THE RULES FOR INJECTING METHOD-RELATED CODE (such as %MethodCode, + %VirtualCatcherCode, %VirtualCallCode and other method-level directives). + + These are used to customise the behaviour of the SIP generator by allowing + method-level code injection. + + The raw rule database must be an outer dictionary as follows: + + 0. Each key is the fully-qualified name of a "container" enclosing + methods. + + 1. Each value is an inner dictionary, each of whose keys is the name + of a method. + + Each inner dictionary has entries which update the declaration as follows: + + "parameters": Optional list. If present, update the argument list. + + "fn_result": Optional string. If present, update the return type. + + "code": Required. Either a string, with the %XXXCode content, + or a callable. + + In use, the database is directly indexed by "container" and then method + name. If "code" entry is a string, then the other optional keys are + interpreted as above. If "code" is a callable, it is called with the + following contract: + + def methodcode_xxx(function, sip, entry): + ''' + Return a modified declaration for the given function. + + :param function: The clang.cindex.Cursor for the function. + :param sip: A dict with keys as for function rules and (string) + "code" keys described above. + :param entry: The inner dictionary entry. + + :return: An updated set of sip.xxx values. + ''' + + :return: The compiled form of the rules. + """ + __metaclass__ = ABCMeta + + def __init__(self, db): + super(MethodCodeDb, self).__init__(db) + + for k, v in self.db.items(): + for l in v.keys(): + v[l]["usage"] = 0 + + def _get(self, item, name): + + parents = _parents(item) + entries = self.db.get(parents, None) + if not entries: + return None + + entry = entries.get(name, None) + if not entry: + return None + entry["usage"] += 1 + return entry + + def apply(self, function, sip): + """ + Walk over the code database for functions, applying the first matching transformation. + + :param function: The clang.cindex.Cursor for the function. + :param sip: The SIP dict (may be modified on return). + :return: Modifying rule or None (even if a rule matched, it may not modify things). + """ + entry = self._get(function, sip["name"]) + sip.setdefault("code", "") + if entry: + before = deepcopy(sip) + if callable(entry["code"]): + fn = entry["code"] + fn_file = os.path.basename(inspect.getfile(fn)) + trace = "// Generated (by {}:{}): {}\n".format(fn_file, fn.__name__, {k:v for (k,v) in entry.items() if k != "code"}) + fn(function, sip, entry) + else: + trace = "// Inserted (by {}:{}): {}\n".format(_parents(function), function.spelling, {k:v for (k,v) in entry.items() if k != "code"}) + sip["code"] = entry["code"] + sip["parameters"] = entry.get("parameters", sip["parameters"]) + sip["fn_result"] = entry.get("fn_result", sip["fn_result"]) + # + # Fetch/format the code. + # + sip["code"] = trace + textwrap.dedent(sip["code"]).strip() + "\n" + return self.trace_result(_parents(function), function, before, sip) + return None + + def dump_usage(self, fn): + """ Dump the usage counts.""" + for k in sorted(self.db.keys()): + vk = self.db[k] + for l in sorted(vk.keys()): + vl = vk[l] + fn(str(self) + " for " + k + "," + l, vl["usage"]) + +class ModuleCodeDb(AbstractCompiledCodeDb): + """ + THE RULES FOR INJECTING MODULE-RELATED CODE (such as %ExportedHeaderCode, + %ModuleCode, %ModuleHeaderCode or other module-level directives). + + These are used to customise the behaviour of the SIP generator by allowing + module-level code injection. + + The raw rule database must be a dictionary as follows: + + 0. Each key is the basenanme of a module file. + + 1. Each value has entries which update the declaration as follows: + + "code": Required. Either a string, with the %XXXCode content, + or a callable. + + If "code" is a callable, it is called with the following contract: + + def module_xxx(filename, sip, entry): + ''' + Return a string to insert for the file. + + :param filename: The filename. + :param sip: A dict with the key "name" for the module name + plus the "code" key described above. + :param entry: The dictionary entry. + + :return: A string. + ''' + + :return: The compiled form of the rules. + """ + __metaclass__ = ABCMeta + + def __init__(self, db): + super(ModuleCodeDb, self).__init__(db) + # + # Add a usage count for each item in the database. + # + for k, v in self.db.items(): + v["usage"] = 0 + + def _get(self, filename): + # + # Lookup for an actual hit. + # + entry = self.db.get(filename, None) + if not entry: + return None + entry["usage"] += 1 + return entry + + def apply(self, filename, sip): + entry = self._get(filename) + sip.setdefault("code", "") + if entry: + before = deepcopy(sip) + if callable(entry["code"]): + fn = entry["code"] + fn_file = os.path.basename(inspect.getfile(fn)) + trace = "\n// Generated (by {}:{}): {}".format(fn_file, fn.__name__, {k:v for (k,v) in entry.items() if k != "code"}) + fn(filename, sip, entry) + sip["code"] = trace + sip["code"] + else: + sip["code"] = entry["code"] + # + # Fetch/format the code. + # + sip["code"] = textwrap.dedent(sip["code"]).strip() + "\n" + fqn = filename + "::" + before["name"] + self._trace_result(fqn, before, sip) + + def dump_usage(self, fn): + """ Dump the usage counts.""" + for k in sorted(self.db.keys()): + v = self.db[k] + fn(str(self) + " for " + k, v["usage"]) + + class RuleSet(object): """ To implement your own binding, create a subclass of RuleSet, also called @@ -501,6 +735,24 @@ class RuleSet(object): """ raise NotImplemented(_("Missing subclass implementation")) + @abstractmethod + def methodcode_rules(self): + """ + Return a compiled list of rules for method-related code. + + :return: A MethodCodeDb instance + """ + raise NotImplemented(_("Missing subclass implementation")) + + @abstractmethod + def modulecode_rules(self): + """ + Return a compiled list of rules for module-related code. + + :return: A ModuleCodeDb instance + """ + raise NotImplemented(_("Missing subclass implementation")) + def dump_unused(self): """Usage statistics, to identify unused rules.""" def dumper(rule, usage): @@ -510,9 +762,23 @@ class RuleSet(object): logger.warn(_("Rule {} was not used".format(rule))) for db in [self.container_rules(), self.function_rules(), self.parameter_rules(), - self.variable_rules()]: + self.variable_rules(), self.methodcode_rules(), self.modulecode_rules()]: db.dump_usage(dumper) + @abstractmethod + def methodcode(self, container, function): + """ + Lookup %MethodCode. + """ + raise NotImplemented(_("Missing subclass implementation")) + + @abstractmethod + def modulecode(self, filename): + """ + Lookup %ModuleCode and friends. + """ + raise NotImplemented(_("Missing subclass implementation")) + def container_discard(container, sip, matcher): sip["name"] = "" diff --git a/find-modules/sip_generator.py b/find-modules/sip_generator.py index aae0b694..e5e807ed 100644 --- a/find-modules/sip_generator.py +++ b/find-modules/sip_generator.py @@ -186,6 +186,20 @@ class SipGenerator(object): return True SipGenerator._report_ignoring(container, member, text) + if container.kind.is_translation_unit(): + # + # Any module-related manual code (%ExportedHeaderCode, %ModuleCode, %ModuleHeaderCode or other + # module-level directives? + # + sip = { + "name": include_filename, + "decl": "" + } + self.rules.modulecode(include_filename, sip) + body = sip["code"] + else: + body = "" + sip = { "name": container.displayname, "annotations": set() @@ -195,7 +209,6 @@ class SipGenerator(object): if self.dump_privates: logger.debug("Ignoring private {}".format(SipGenerator.describe(container))) return "" - body = "" base_specifiers = [] template_type_parameters = [] had_copy_constructor = False @@ -486,6 +499,11 @@ class SipGenerator(object): modifying_rule = self.rules.function_rules().apply(container, function, sip) pad = " " * (level * 4) if sip["name"]: + # + # Any method-related code (%MethodCode, %VirtualCatcherCode, VirtualCallCode + # or other method-related directives)? + # + self.rules.methodcode(function, sip) decl = "" if modifying_rule: decl += "// Modified {} (by {}):\n".format(SipGenerator.describe(function), modifying_rule) + pad @@ -501,6 +519,7 @@ class SipGenerator(object): if sip["template_parameters"]: decl = pad + "template <" + sip["template_parameters"] + ">\n" + decl decl += ";\n" + decl += sip["code"] else: decl = pad + "// Discarded {} (by {})\n".format(SipGenerator.describe(function), modifying_rule) return decl |