Mercurial > gnulib
view pygnulib/module.py @ 39134:e7bca471a0b8
module: fix autoconf version check
author | Dmitry Selyutin <ghostmansd@gmail.com> |
---|---|
date | Thu, 05 Jul 2018 22:26:10 +0300 |
parents | 9b79e837bfd8 |
children | 61d46a751bfa |
line wrap: on
line source
#!/usr/bin/python # encoding: UTF-8 """gnulib module API""" import ast as _ast import codecs as _codecs import collections as _collections import hashlib as _hashlib import itertools as _itertools import json as _json import os as _os import re as _re import sys as _sys from .error import UnknownModuleError as _UnknownModuleError from .config import BaseConfig as _BaseConfig from .misc import Property as _Property from .misc import PathProperty as _PathProperty from .misc import StringListProperty as _StringListProperty from .misc import PathListProperty as _PathListProperty class BaseModule: """base module""" __slots__ = ("__options", "__flags") __PROPERTIES = { "name" : None, "description" : "", "comment" : "", "status" : "", "notice" : "", "applicability" : "", "files" : "", "dependencies" : tuple(), "early_autoconf_snippet" : "", "autoconf_snippet" : "", "conditional_automake_snippet" : "", "unconditional_automake_snippet" : None, "automake_snippet" : None, "include_directives" : "", "link_directives" : tuple(), "licenses" : tuple(), "maintainers" : tuple(), "test" : False, } __OPTIONS = { "name", "description", "comment", "status", "notice", "applicability", "files", "dependencies", "early_autoconf_snippet", "autoconf_snippet", "conditional_automake_snippet", "unconditional_automake_snippet", "automake_snippet", "include_directives", "link_directives", "licenses", "maintainers", "test", } __FLAGS = {} __LIB_SOURCES = _re.compile(r"^lib_SOURCES\s*\+\=\s*(.*?)$", _re.S | _re.M) def __init__(self, name, **kwargs): if not isinstance(name, str): raise TypeError("name: str expected") if not name.strip(): raise ValueError("name: invalid name") self.__flags = 0 self.__options = {} for key in BaseModule.__OPTIONS: value = BaseModule.__PROPERTIES[key] if value is not None: self.__set_option(key, value) for key in BaseModule.__FLAGS: state = BaseModule.__PROPERTIES[key] mask = getattr(self.__class__, key).mask self.__set_flags(mask, state) self.__set_option("name", name) for (key, value) in kwargs.items(): setattr(self, key, value) def __enter__(self): return self def __hash__(self): return hash(_json.dumps(dict(self.__options), sort_keys=True)) def __repr__(self): module = self.__class__.__module__ name = self.__class__.__name__ return f"{module}.{name}[{self.name}]" def __getitem__(self, key): if key not in BaseModule.__PROPERTIES: key = key.replace("-", "_") if key not in BaseModule.__PROPERTIES: raise KeyError(repr(key)) return getattr(self, key) def __setitem__(self, key, value): if key not in BaseModule.__PROPERTIES: key = key.replace("-", "_") if key not in BaseModule.__PROPERTIES: raise KeyError(repr(key)) return setattr(self, key, value) def __get_option(self, key): return self.__options[key] def __set_option(self, key, value): self.__options[key] = value @property def gnulib_package(self): """gnulib-compatible module textual representation""" def _gnulib(): yield "Description:" yield self.description yield "Comment:" yield self.comment yield "Status:" for status in sorted(self.status): yield status yield "Notice:" yield self.notice yield "Applicability:" yield self.applicability yield "Files:" for file in self.files: yield file yield "Depends-on:" for (module, condition) in self.dependencies: if condition: yield f"{module} {condition}" else: yield f"{module}" yield "configure.ac-early:" yield self.early_autoconf_snippet yield "configure.ac:" yield self.autoconf_snippet yield "Makefile.am:" yield self.conditional_automake_snippet yield "Include:" for include in self.include_directives: yield include yield "Link:" for link in self.link_directives: yield link yield "License:" for license in self.licenses: yield license yield "Maintainer:" for maintainer in maintainers: yield maintainer return "\n".join(_gnulib()) name = _Property( fget=lambda self: self.__get_option("name"), fset=lambda self, string: self.__set_option("name", string), check=lambda value: isinstance(value, str) and value, doc="name", ) description = _Property( fget=lambda self: self.__get_option("description"), fset=lambda self, string: self.__set_option("description", string), check=lambda value: isinstance(value, str), doc="description", ) comment = _Property( fget=lambda self: self.__get_option("comment"), fset=lambda self, string: self.__set_option("comment", string), check=lambda value: isinstance(value, str), doc="comment", ) status = _StringListProperty( sorted=True, unique=True, fget=lambda self: self.__get_option("comment"), fset=lambda self, string: self.__set_option("comment", string), doc="status list", ) obsolete = _Property( fget=lambda self: "obsolete" in self.status, doc="module is obsolete?", ) cxx_test = _Property( fget=lambda self: "c++-test" in self.status, doc="module is a C++ test?", ) longrunning_test = _Property( fget=lambda self: "longrunning-test" in self.status, doc="module is a longrunning test?", ) privileged_test = _Property( fget=lambda self: "privileged-test" in self.status, doc="module is a privileged test?", ) unportable_test = _Property( fget=lambda self: "unportable-test" in self.status, doc="module is an unportable test?", ) mask = _Property( fget=lambda self: ((0, (1 << 0))[self.obsolete] |(0, (1 << 1))[self.cxx_test] |(0, (1 << 2))[self.longrunning_test] |(0, (1 << 3))[self.privileged_test] |(0, (1 << 4))[self.unportable_test]), doc="module acceptibility mask", ) notice = _Property( fget=lambda self: self.__get_option("notice"), fset=lambda self, string: self.__set_option("notice", string), check=lambda value: isinstance(value, str), doc="module notice or disclaimer", ) applicability = _Property( fget=lambda self: self.__get_option("applicability"), fset=lambda self, string: self.__set_option("applicability", string), check=lambda value: isinstance(value, str) and value in {"main", "tests", "all"}, doc="applicability ('main', 'tests' or 'all')", ) files = _PathListProperty( sorted=True, unique=True, fget=lambda self: self.__get_option("files"), fset=lambda self, string: self.__set_option("files", string), doc="file dependencies", ) early_autoconf_snippet = _Property( fget=lambda self: self.__get_option("early_autoconf_snippet"), fset=lambda self, string: self.__set_option("early_autoconf_snippet", string), check=lambda value: isinstance(value, str), doc="early configure.ac snippet", ) autoconf_snippet = _Property( fget=lambda self: self.__get_option("autoconf_snippet"), fset=lambda self, string: self.__set_option("autoconf_snippet", string), check=lambda value: isinstance(value, str), doc="configure.ac snippet", ) conditional_automake_snippet = _Property( fget=lambda self: self.__get_option("conditional_automake_snippet"), fset=lambda self, string: self.__set_option("conditional_automake_snippet", string), check=lambda value: isinstance(value, str), doc="configure.ac snippet", ) automake_snippet = _Property( fget=lambda self: "\n".join((self.conditional_automake_snippet, self.unconditional_automake_snippet)), doc="full automake snippet (conditional + unconditional parts)", ) include_directives = _StringListProperty( fget=lambda self: self.__get_option("include_directives"), fset=lambda self, string: self.__set_option("include_directives", string), doc="include directive", ) link_directives = _StringListProperty( fget=lambda self: self.__get_option("link_directives"), fset=lambda self, string: self.__set_option("link_directives", string), doc="link directive", ) licenses = _StringListProperty( sorted=True, unique=True, fget=lambda self: self.__get_option("licenses"), fset=lambda self, name: self.__set_option("licenses", name), doc="acceptable licenses for modules", ) maintainers = _StringListProperty( sorted=False, unique=True, fget=lambda self: self.__get_option("maintainers"), fset=lambda self, name: self.__set_option("maintainers", name), doc="module maintainers list", ) test = _Property( fget=lambda self: self.__get_option("test"), fset=lambda self, name: self.__set_option("test", name), check=lambda value: isinstance(value, bool), doc="module is a test?", ) @_Property def dependencies(self): """dependencies iterator (name, condition)""" return self.__options["dependencies"] @dependencies.setter def dependencies(self, value): result = [] types = (list, tuple, set, frozenset, type({}.keys()), type({}.values())) if not isinstance(value, types): raise TypeError("value: iterable expected") for item in value: if not isinstance(value, (list, tuple)): raise TypeError("item: pair expected") (module, condition) = item if not isinstance(module, str): raise TypeError("module: str expected") if condition is not None and not isinstance(condition, str): raise TypeError("condition: str or None expected") condition = "" if condition is None else condition result.append((module, condition)) self.__options["dependencies"] = tuple(result) @_Property def unconditional_automake_snippet(self): """Makefile.am snippet that must stay outside of Automake conditionals""" result = "" files = self.files if self.test: # *-tests module live in tests/, not lib/. # Synthesize an EXTRA_DIST augmentation. test_files = tuple(file[len("tests/"):] for file in files if file.startswith("tests/")) if test_files: result += ("EXTRA_DIST += {}".format(" ".join(test_files)) + "\n") return result snippet = self.conditional_automake_snippet lib_SOURCES = False lines = list(snippet.splitlines()) for (index, line) in enumerate(lines): if BaseModule.__LIB_SOURCES.findall(line): (first, last) = (index, index) while line.endswith("\\"): line = lines[last] last += 1 lines = list(lines)[first:(last + 1)] lines[0] = BaseModule.__LIB_SOURCES.sub("\\1", lines[0]) lib_SOURCES = True break lines = tuple(lines) if lib_SOURCES else () lines = filter(lambda line: line.strip(), lines) lines = (line.replace("\\", "").strip() for line in lines) (all_files, mentioned_files) = (files, []) for line in lines: for file in line.split(): if file.strip(): mentioned_files += [file] mentioned_files = tuple(_collections.OrderedDict.fromkeys(mentioned_files)) lib_files = tuple(file[len("lib/"):] for file in all_files if file.startswith("lib/")) extra_files = tuple(file for file in lib_files if file not in mentioned_files) if extra_files: result += ("EXTRA_DIST += {}".format(" ".join(extra_files)) + "\n") # Synthesize also an EXTRA_lib_SOURCES augmentation. # This is necessary so that automake can generate the right list of # dependency rules. # A possible approach would be to use autom4te --trace of the redefined # AC_LIBOBJ and AC_REPLACE_FUNCS macros when creating the Makefile.am # (use autom4te --trace, not just grep, so that AC_LIBOBJ invocations # inside autoconf's built-in macros are not missed). # But it's simpler and more robust to do it here, based on the file list. # If some .c file exists and is not used with AC_LIBOBJ - for example, # a .c file is preprocessed into another .c file for BUILT_SOURCES -, # automake will generate a useless dependency; this is harmless. if self.name not in {"relocatable-prog-wrapper", "pt_chown"}: extra_files = tuple(file for file in extra_files if file.endswith(".c")) if extra_files: result += ("EXTRA_lib_SOURCES += {}".format(" ".join(sorted(extra_files))) + "\n") # Synthesize an EXTRA_DIST augmentation also for the files in build-aux/. prefix = "$(top_srcdir)/{auxdir}" buildaux_files = (file for file in all_files if file.startswith("build-aux/")) buildaux_files = tuple(_os.path.join(prefix, file[len("build-aux/"):]) for file in buildaux_files) if buildaux_files: result += ("EXTRA_DIST += {}".format(" ".join(sorted(buildaux_files))) + "\n") return result def shell_variable(self, macro_prefix="gl"): """Get the name of the shell variable set to true once m4 macros have been executed.""" name = self.name if any(filter(lambda rune: not (rune.isalnum() or rune == "_"), name)): name = (name + "\n").encode("UTF-8") name = _hashlib.md5(name).hexdigest() return f"{macro_prefix}_gnulib_enabled_{name}" def shell_function(self, macro_prefix="gl"): """Get the name of the shell function containing the m4 macros.""" name = self.name if any(filter(lambda rune: not (rune.isalnum() or rune == "_"), name)): name = (name + "\n").encode("UTF-8") name = _hashlib.md5(name).hexdigest() return f"func_{macro_prefix}_gnulib_m4code_{name}" def conditional_name(self, macro_prefix="gl"): """Get the automake conditional name.""" name = self.name if any(filter(lambda rune: not (rune.isalnum() or rune == "_"), name)): name = (name + "\n").encode("UTF-8") name = _hashlib.md5(name).hexdigest() return f"{macro_prefix}_GNULIB_ENABLED_{name}" def items(self): """a set-like object providing a view on module items""" for key in BaseModule.__PROPERTIES: yield (key, self[key]) @classmethod def keys(self): """a set-like object providing a view on module keys""" for key in BaseModule.__PROPERTIES: yield key def values(self): """a set-like object providing a view on module values""" for key in BaseModule.__PROPERTIES: yield self[key] def __lt__(self, value): if value is not None: return self.name < value.name return False def __le__(self, value): return self.__lt__(value) or self.__eq__(value) def __eq__(self, value): if value is not None: if self.name != value.name: return False for key in BaseModule.__PROPERTIES: if self[key] != value[key]: return False return True return False def __ne__(self, value): return not self.__eq__(value) def __ge__(self, value): return not value.__le__(self) def __gt__(self, value): return not value.__lt__(self) class FileModule(BaseModule): """text-based module""" __slots__ = ("__path") __DEPENDENCY = _re.compile(r"(\S+)(?:\s+\[(.*?)\])?$", _re.M) __STRING = lambda text: text.strip() __MULTILINE = lambda text: tuple(filter( lambda line: line.strip() and not line.strip().startswith("#"), [line.strip() for line in text.strip().splitlines()], )) __INCLUDE_DIRECTIVES = lambda text: tuple(filter( lambda line: line.strip(), [line.strip() for line in text.strip().splitlines()], )) __DEPENDENCIES = lambda text: FileModule.__DEPENDENCY.findall("\n".join(FileModule.__MULTILINE(text))) __MAINTAINERS = lambda text: tuple(filter( lambda line: line.strip() and not line.strip().startswith("#"), {line.strip() for line in text.split((",", "\n")["\n" in text.strip()])}, )) __LICENSES = lambda text: set(_itertools.chain.from_iterable( line.split(" or ") for line in FileModule.__MULTILINE(text) )) __TABLE = { "Description": ( "description", __STRING, ), "Comment": ( "comment", __STRING, ), "Status": ( "status", __MULTILINE, ), "Notice": ( "notice", __STRING, ), "Applicability": ( "applicability", __STRING, ), "Files": ( "files", __MULTILINE, ), "Depends-on": ( "dependencies", __DEPENDENCIES, ), "configure.ac-early": ( "early_autoconf_snippet", __STRING, ), "configure.ac": ( "autoconf_snippet", __STRING, ), "Makefile.am": ( "conditional_automake_snippet", __STRING, ), "Include": ( "include_directives", __INCLUDE_DIRECTIVES, ), "Link": ( "link_directives", __MULTILINE, ), "License": ( "licenses", __LICENSES, ), "Maintainer": ( "maintainers", __MAINTAINERS, ), } __PATTERN = _re.compile("({}):".format("|".join(__TABLE))) path = _Property( fget=lambda self: self.__path, doc="module file path", ) def __init__(self, path, name, **kwargs): if not isinstance(path, str): raise TypeError("path: str expected") if not isinstance(name, str): raise TypeError("name: str expected") with _codecs.open(path, "rb", "UTF-8") as stream: match = FileModule.__PATTERN.split(stream.read())[1:] for (group, text) in zip(match[::2], match[1::2]): (key, hook) = FileModule.__TABLE[group] kwargs.setdefault(key, hook(text)) super().__init__(name=name, **kwargs) self.__path = path class _DummyModuleMeta(type): __INSTANCE = None __PROPERTIES = { "description": "A dummy file, to make sure the library is non-empty.", "comment": "", "status": tuple(), "notice": "", "applicability": "main", "files": tuple({"lib/dummy.c"}), "dependencies": tuple(), "early_autoconf_snippet": "", "autoconf_snippet": "", "include_directives": "", "link_directives": "", "licenses": tuple({"public domain"}), "maintainers": tuple({"all"}), "automake_snippet": "lib_SOURCES += dummy.c", "conditional_automake_snippet": "lib_SOURCES += dummy.c", "unconditional_automake_snippet": "", } def __new__(mcs, name, parents, attributes): for (key, value) in _DummyModuleMeta.__PROPERTIES.items(): fget=lambda self, value=value: value doc = getattr(BaseModule, key).__doc__ attributes[key] = _Property(fget=fget, doc=doc) return super().__new__(mcs, name, parents, attributes) def __call__(cls, *args, **kwargs): if _DummyModuleMeta.__INSTANCE is None: _DummyModuleMeta.__INSTANCE = super().__call__(*args, **kwargs) return _DummyModuleMeta.__INSTANCE class DummyModule(BaseModule, metaclass=_DummyModuleMeta): """dummy module singleton""" def __init__(self): super().__init__(name="dummy") def __repr__(self): return "pygnulib.module.DummyModule" class _GnulibModuleMeta(type): def __new__(mcs, name, parents, attributes): for key in BaseModule.keys(): if key in attributes: continue fget = lambda self, key=key: self.__getitem__(key) doc = getattr(BaseModule, key).__doc__ attributes[key] = _Property(fget=fget, doc=doc) return super().__new__(mcs, name, parents, attributes) class GnulibModule(FileModule, metaclass=_GnulibModuleMeta): """read-only gnulib standard module""" __slots__ = ("__cache", "__hash", "__mask", "__path", "__test") __OBSOLETE = (1 << 0) __CXX_TEST = (1 << 1) __LONGRUNNING_TEST = (1 << 2) __PRIVILEGED_TEST = (1 << 3) __UNPORTABLE_TEST = (1 << 4) def __init__(self, path, name): super(FileModule, self).__init__(name=name) try: module = FileModule(path=path, name=name, test=name.endswith("-tests")) except FileNotFoundError: raise _UnknownModuleError(name) self.__cache = {_sys.intern(k):v for (k,v) in module.items()} self.__hash = super().__hash__() self.__mask = module.mask self.__path = module.path self.__test = module.test def __repr__(self): return f"{self.name}" module = self.__class__.__module__ name = self.__class__.__name__ return f"{module}.{name}{{{self.name}}}" def __getitem__(self, key): return self.__cache[key] def __hash__(self): return self.__hash def __eq__(self, other): if isinstance(other, GnulibModule): return self.__hash == hash(other) return super().__eq__(other) obsolete = _Property( fget=lambda self: bool(self.__mask & GnulibModule.__OBSOLETE), doc="module is obsolete?", ) cxx_test = _Property( fget=lambda self: bool(self.__mask & GnulibModule.__CXX_TEST), doc="module is a C++ test?", ) longrunning_test = _Property( fget=lambda self: bool(self.__mask & GnulibModule.__LONGRUNNING_TEST), doc="module is a longrunning test?", ) privileged_test = _Property( fget=lambda self: bool(self.__mask & GnulibModule.__PRIVILEGED_TEST), doc="module is a privileged test?", ) unportable_test = _Property( fget=lambda self: bool(self.__mask & GnulibModule.__UNPORTABLE_TEST), doc="module is an unportable test?", ) mask = _Property( fget=lambda self: self.__mask, doc="module acceptibility mask", ) test = _Property( fget=lambda self: self.name.endswith("-tests"), doc="module is tests-related?", ) path = _Property( fget=lambda self: self.__path, doc="module file path", ) @_Property def applicability(self): """WAGH applicability (usually "main" or "tests")""" default = "tests" if self.test else "main" current = self.__cache["applicability"] return current if current else default class TransitiveClosure: """transitive closure table""" __slots__ = ("__lookup", "__dependencies", "__demanders", "__paths", "__conditional") __AUTOMAKE_CONDITION = _re.compile("^if\\s+", _re.S | _re.M) def __init__(self, lookup, modules, mask, gnumake, conditionals, tests=False, error=True): if not callable(lookup): raise TypeError("lookup: callable expected") table = {None: None} def _lookup(module): return table.setdefault(module, lookup(module)) current = set() previous = set() demanders = _collections.defaultdict(dict) dependencies = _collections.defaultdict(dict) def _update(demander, dependency, condition): table[dependency.name] = dependency if dependency.mask == mask: # A module whose Makefile.am snippet contains a reference to an # automake conditional. If we were to use it conditionally, we # would get an error # configure: error: conditional "..." was never defined. # because automake 1.11.1 does not handle nested conditionals # correctly. As a workaround, make the module unconditional. snippet = dependency.automake_snippet pattern = TransitiveClosure.__AUTOMAKE_CONDITION if condition and pattern.findall(snippet): condition = None demander = None if not condition: condition = None if demander is not None: demander = demander.name dependency = dependency.name demanders[demander][dependency] = condition dependencies[dependency][demander] = condition current.add(dependency) for module in modules: dependency = lookup(module) _update(None, dependency, None) while True: modules = current.difference(previous) if not modules: break previous.update(current) for demander in modules: demander = _lookup(demander) if tests and not demander.test: dependency = _lookup(demander.name + "-tests") if dependency is not None: _update(None, dependency, bool(demanders[demander])) for (dependency, condition) in demander.dependencies: dependency = _lookup(dependency) _update(demander, dependency, condition) self.__lookup = _lookup self.__paths = dict() self.__conditional = dict() self.__demanders = dict(demanders) self.__dependencies = dict(dependencies) def __iter__(self): for dependency in self.__dependencies: yield self.__lookup(dependency) def paths(self, module): if module in self.__paths: return self.__paths[module] graph = self.__dependencies module = self.__lookup(module).name def _paths(): path = [module] seen = {module} def search(): dead_end = True for neighbour in graph.get(path[-1], []): if neighbour not in seen: dead_end = False seen.add(neighbour) path.append(neighbour) yield from search() path.pop() seen.remove(neighbour) if dead_end: yield path yield from search() self.__paths[module] = (tuple(path[:-1]) for path in _paths()) return tuple(self.__paths[module]) def conditional(self, module): """ Test whether module is a conditional dependency. Note that this check also takes all parent modules into account. """ if module in self.__conditional: return self.__conditional[module] for path in self.paths(module): unconditional = list() for (dependency, demander) in zip(path, path[1:]): unconditional.append(not self.__dependencies[dependency][demander]) if all(unconditional): self.__conditional[module] = False return False self.__conditional[module] = True return True def dump(self, indent=" "): """Export transitive closure result into string.""" def _dump(): unconditional = set() storage = _collections.defaultdict(dict) yield "{{".format() for (dependency, entries) in self.__dependencies.items(): for (demander, condition) in entries.items(): if condition is None: condition = "" if not demander and not condition: unconditional.add(dependency) condition = condition.replace("\"", "\\\"") storage[dependency][demander] = condition for dependency in sorted(storage): if dependency in unconditional: yield "{}\"{}\": {{}},".format(indent, dependency) continue yield "{}\"{}\": {{".format(indent, dependency) for demander in sorted(storage[dependency]): condition = storage[dependency][demander] yield "{}\"{}\": \"{}\",".format((indent * 2), demander, condition) yield "{}}},".format(indent) yield "}}".format() if not self.__dependencies: return "{{}}".format() return _os.linesep.join(_dump()) def load(self, string): """Import transitive closure result from string.""" demanders = _collections.defaultdict(dict) dependencies = _collections.defaultdict(dict) collection = _ast.literal_eval(string) for key in collection: value = collection[key] for (subkey, subvalue) in value.items(): (dependency, demander, condition) = (key, subkey, subvalue) if not condition: condition = None demanders[demander][dependency] = condition dependencies[dependency][demander] = condition self.__demanders = dict(demanders) self.__dependencies = dict(dependencies) def demanders(self, module): """For each demander which requires the module yield the demander and the corresponding condition.""" module = self.__lookup(module).name if module in self.__dependencies: for (demander, condition) in self.__dependencies.get(module, {}).items(): yield (self.__lookup(demander), condition) def dependencies(self, module): """For each dependency of the module yield this dependency and the corresponding condition.""" module = self.__lookup(module).name if module in self.__demanders: for (dependency, condition) in self.__demanders.get(module, {}).items(): yield (self.__lookup(dependency), condition) class Database: """gnulib module database""" __DUMMY_PATTERN = _re.compile(r"^lib_SOURCES\s*\+\=\s*(.*?)$", _re.S | _re.M) def __init__(self, lookup, config): if not callable(lookup): raise TypeError("lookup: callable expected") mask = config.mask gnumake = config.gnumake lookup = lambda module, lookup=lookup: module if isinstance(module, BaseModule) else lookup(module) def _applicability(module): return module.applicability in ({"main", "all"}, {"main"})[config.tests] def _dummy(modules): if "dummy" in config.avoids: return False for module in modules: snippet = module.conditional_automake_snippet for match in Database.__DUMMY_PATTERN.findall(snippet): files = {file.strip() for file in match.split("#", 1)[0].split(" ") if file.strip()} if {file for file in files if not file.endswith(".h")}: return False return True def _files(modules): files = set() for module in modules: files.update(module.files) files.add("m4/00gnulib.m4") files.add("m4/gnulib-common.m4") if config.ac_version == "2.59": files.add("m4/onceonly.m4") return files def _libtests(modules): for module in modules: for file in module.files: if file.startswith("lib/"): return True return False # Perform a transitive closure for modules from the configuration. # The result of this transitive closure is a set of main modules. conditionals = config.conditionals modules = explicit_modules = {lookup(module) for module in config.modules} base_closure = TransitiveClosure(lookup, modules, mask, gnumake, conditionals) modules = map(lambda module: lookup(module.name + "-tests"), set(base_closure)) modules = set(filter(lambda module: module is not None, modules)) full_closure = TransitiveClosure(lookup, (explicit_modules | modules), mask, gnumake, conditionals, True) # Once the full transitive closure is completed, populate the database. main_modules = set(base_closure) final_modules = set(full_closure) if config.tests else main_modules test_modules = (final_modules - set(filter(_applicability, sorted(main_modules)))) libtests = _libtests(test_modules) if _dummy(main_modules): main_modules.add(DummyModule()) if _dummy(test_modules) and libtests: test_modules.add(DummyModule()) main_files = _files(main_modules) test_files = _files(test_modules) self.__libtests = libtests self.__closure = full_closure self.__main_modules = tuple(sorted(main_modules)) self.__test_modules = tuple(sorted(test_modules)) self.__final_modules = tuple(sorted(final_modules)) self.__explicit_modules = tuple(sorted(explicit_modules)) self.__main_files = tuple(sorted(main_files)) self.__test_files = tuple(sorted(test_files)) def __iter__(self): return iter(self.__closure) def paths(self, module): return self.__closure.paths(module) def conditional(self, module): """ Test whether module is a conditional dependency. Note that this check also takes all parent modules into account. Any module with an unconditional demander is also unconditional. """ return self.__closure.conditional(module) def unconditional(self, module): """ Test whether module is an unconditional dependency. Note that this check also takes all parent modules into account. Any module with an unconditional demander is also unconditional. """ return not self.conditional(module) def demanders(self, module): """For each demander which requires the module yield the demander and the corresponding condition.""" return sorted(self.__closure.demanders(module)) def dependencies(self, module): """For each dependency of the module yield this dependency and the corresponding condition.""" return sorted(self.__closure.dependencies(module)) @property def final_modules(self): """ The final module list is the transitive closure of the specified modules, including or ignoring tests modules (depending on the tests configuration option). """ return self.__final_modules @property def main_modules(self): """ The main module list is the transitive closure of the specified modules, ignoring tests modules. Its lib/* sources go into {source_base}. If --lgpl is specified, it will consist only of LGPLed source. """ return self.__main_modules @property def main_files(self): """The full set of the files required for modules in the main modules list.""" return self.__main_files @property def test_modules(self): """ The tests-related module list is the transitive closure of the specified modules, including tests modules, minus the main module list excluding modules of applicability 'all'. Its lib/* sources (brought in through dependencies of *-tests modules) go into {tests_base}. It may contain GPLed source, even if --lgpl is specified. """ return self.__test_modules @property def explicit_modules(self): """ The list of modules which were explicitly required to be imported. This list does not include direct or indirect dependencies at all. """ return self.__explicit_modules @property def test_files(self): """The full set of the files required for modules in the test modules list.""" return self.__test_files @property def libtests(self): """If libtests.a is required, this variable yields true.""" return self.__libtests