From: Dmitry Selyutin Date: Tue, 19 Sep 2017 19:40:10 +0000 (+0300) Subject: refactor transitive closure into module table X-Git-Url: https://gitweb.git.savannah.gnu.org/gitweb/?a=commitdiff_plain;h=ba7383af06c6bccbb37b1196cc2c17b3b9b81846;p=gnulib.git refactor transitive closure into module table --- diff --git a/pygnulib/filesystem.py b/pygnulib/filesystem.py index 4165549917..36d01ee045 100644 --- a/pygnulib/filesystem.py +++ b/pygnulib/filesystem.py @@ -8,7 +8,7 @@ import os as _os_ from .error import type_assert as _type_assert_ -from .error import GnulibModuleNotFoundError as _GnulibModuleNotFoundError_ +from .error import UnknownModuleError as _UnknownModuleError_ from .config import Base as _BaseConfig_ from .module import Base as _BaseModule_ from .module import File as _FileModule_ @@ -129,7 +129,7 @@ class GnulibGit(Directory): try: return _FileModule_(path, name=name) if full else _BaseModule_(name) except FileNotFoundError: - raise _GnulibModuleNotFoundError_(name) + raise _UnknownModuleError_(name) def modules(self, full=True): @@ -149,67 +149,3 @@ class GnulibGit(Directory): path = _os_.path.join(root, name) name = path[len(prefix) + 1:] yield self.module(name, full) - - - def transitive_closure(self, config, modules, tests): - """ - GnulibGit.transitive_closure(config, modules, tests) -> - set(( - (module0, demander0, condition0), - (moduleN, demanderN, conditionN), - )) - - config is a gnulib configuration (e.g. pygnulib.Config.Base instance). - modules is an iterable, yielding a module (either name or instance). - tests designates whether to consider the corresponding test module and its dependencies. - - The returned value is a set, containing a sequence of tuples (module, demander, condition). - If condition is None, but demander is not, there is no special condition for this module. - If demander is None, the module is provided unconditionally (condition is always None). - The correct iteration over the resulting table is shown below: - - for (module, demander, condition) in gnulib.transitive_closure(modules): - if demander is not None: - if condition is not None: - fmt = "{0} is added as dependency from {1} under '{2}' condition" - args = (module.name, demander.name, condition) - else: - fmt = "{0} is added as dependency from {1} without specific condition" - args = (module.name, demander.name) - else: - fmt = "{0} is added unconditionally" - args = (module.name,) - print(fmt.format(args)) - """ - demanders = set() - (old, new) = (set(), set()) - for module in modules: - if isinstance(module, str): - module = self.module(module) - new.add((module, None, None)) - while old != new: - old.update(new) - for (demander, _, _) in old: - if demander in demanders: - continue - if tests: - try: - name = "{0}-tests".format(demander.name) - new.add((self.module(name), None, None)) - except _GnulibModuleNotFoundError_: - pass # ignore non-existent tests - for (dependency, condition) in demander.dependencies: - module = self.module(dependency) - exclude = ( - (not config.obsolete and module.obsolete), - (not config.cxx_tests and module.cxx_test), - (not config.longrunning_tests and module.longrunning_test), - (not config.privileged_tests and module.privileged_test), - (not config.unportable_tests and module.unportable_test), - ) - if any(exclude): - continue - condition = condition if condition.strip() else None - new.add((module, demander, condition)) - demanders.add(demander) - return new diff --git a/pygnulib/module.py b/pygnulib/module.py index 2d06e889da..8256d9ce80 100644 --- a/pygnulib/module.py +++ b/pygnulib/module.py @@ -12,6 +12,8 @@ import re as _re_ from .error import type_assert as _type_assert_ +from .error import UnknownModuleError as _UnknownModuleError_ +from .config import Option as _ConfigOption_ @@ -342,12 +344,16 @@ class Base: def __lt__(self, value): + if not isinstance(value, Base): + return True return self.name < value.name def __le__(self, value): return self.__lt__(value) or self.__eq__(value) def __eq__(self, value): + if not isinstance(value, Base): + return False return self.name == value.name def __ne__(self, value): @@ -432,3 +438,99 @@ class File(Base): def __exit__(self, exctype, excval, exctrace): self.close() + + + +class Table: + """module transitive closure result""" + def __init__(self, lookup, modules, options): + """ + Perform a transitive closure, generating a set of module dependencies. + Each iteration over the table yields a tuple of (module, demander, condition). + + If condition is None, but demander is not, there is no special condition for this module. + If demander is None, the module is provided unconditionally (condition is always None). + + lookup must be a callable which obtains a pygnulib module by its name. + modules is an iterable, yielding a module (either name or instance). + options may be any combination of gnulib configuration options. + """ + if not callable(lookup): + raise TypeError("lookup must be a callable") + _type_assert_("options", options, _ConfigOption_) + + obsolete = bool(options & _ConfigOption_.Obsolete) + tests = bool(options & _ConfigOption_.Tests) + cxx_tests = bool(options & _ConfigOption_.CXX) + longrunning_tests = bool(options & _ConfigOption_.Longrunning) + privileged_tests = bool(options & _ConfigOption_.Privileged) + unportable_tests = bool(options & _ConfigOption_.Unportable) + modules = set(lookup(module) for module in modules) + + def _transitive_closure_(tests): + queue = set() + previous = set() + current = set() + for module in modules: + current.add((module, None, None)) + while previous != current: + previous.update(current) + for (demander, _, _) in previous: + if demander in queue: + continue + if tests and not demander.name.endswith("-tests"): + try: + name = "{0}-tests".format(demander.name) + current.add((lookup(name), None, None)) + except _UnknownModuleError_: + pass # ignore non-existent tests + for (dependency, condition) in demander.dependencies: + module = lookup(dependency) + exclude = ( + (not obsolete and module.obsolete), + (not cxx_tests and module.cxx_test), + (not longrunning_tests and module.longrunning_test), + (not privileged_tests and module.privileged_test), + (not unportable_tests and module.unportable_test), + ) + if not any(exclude): + condition = condition if condition.strip() else None + current.add((module, demander, condition)) + queue.add(demander) + return current + + self.__with_tests = tests + self.__base = _transitive_closure_(False) + self.__full = _transitive_closure_(True) + + + def __iter__(self): + return iter(self.__full) + + + @property + def main(self): + """an iterator over main module set""" + return iter(set(module for (module, _, _) in self.__base)) + + + @property + def tests(self): + """an iterator over tests-related module set""" + final = self.final + if not self.__with_tests: + final = (module for module in final if module.applicability != "all") + main = (module for module in self.main if module.applicability != "all") + return iter(set(final) ^ set(main)) + + + @property + def final(self): + """an iterator over final module set""" + return self.all if self.__with_tests else self.main + + + @property + def all(self): + """an iterator over all modules""" + return iter(set(module for (module, _, _) in self.__full))