changeset 39101:915fae496f3c

refactored generators; bugfix; tests Makefile.am
author Dmitry Selyutin <ghostmansd@gmail.com>
date Mon, 29 Jan 2018 19:07:42 +0300
parents 9085df917421
children 677c1423b34d
files pygnulib.py pygnulib/config.py pygnulib/generator.py pygnulib/module.py pygnulib/tools.py pygnulib/vfs.py
diffstat 6 files changed, 1206 insertions(+), 1021 deletions(-) [+]
line wrap: on
line diff
--- a/pygnulib.py	Sun Jan 21 20:43:31 2018 +0300
+++ b/pygnulib.py	Mon Jan 29 19:07:42 2018 +0300
@@ -12,23 +12,26 @@
 import traceback
 import subprocess as sp
 
+
 from pygnulib.error import CommandLineError
 from pygnulib.error import UnknownModuleError
 
 from pygnulib.config import BaseConfig
 from pygnulib.config import CachedConfig
 
-from pygnulib.generator import CommandLineGenerator
-from pygnulib.generator import GnulibCacheGenerator
-from pygnulib.generator import LibMakefileGenerator
-from pygnulib.generator import POMakevarsGenerator
-from pygnulib.generator import GnulibCompGenerator
+from pygnulib.generator import gnulib_cache
+from pygnulib.generator import gnulib_comp
+from pygnulib.generator import lib_makefile
+from pygnulib.generator import po_make_vars
+from pygnulib.generator import tests_makefile
 
 from pygnulib.module import DummyModule
 from pygnulib.module import Database
 
 from pygnulib.parser import CommandLine as CommandLineParser
 
+from pygnulib.tools import Executable
+
 from pygnulib.vfs import BaseVFS
 from pygnulib.vfs import GnulibGitVFS
 from pygnulib.vfs import backup as vfs_backup
@@ -44,6 +47,23 @@
 
 
 
+class GnulibExecutable(Executable):
+    def __init__(self, name, encoding=None, shell_name=None, shell_path=None):
+        path = None
+        if shell_name is None:
+            shell_name = shell_name.upper()
+        if shell_path is None:
+            shell_path = "{}PATH".format(shell_name)
+        environ = dict(ENVIRON)
+        environ.update(os.environ)
+        if shell_name in environ:
+            path = shell_name
+        elif shell_path in environ:
+            path = "{}{}".format(shell_path)
+        super().__init__(name, path)
+
+
+
 AC_VERSION_PATTERN = re.compile(r"AC_PREREQ\(\[(.*?)\]\)", re.S | re.M)
 IGNORED_LICENSES = {
     "GPLed build tool",
@@ -56,7 +76,7 @@
     "hardlink": vfs_hardlink,
     "symlink": vfs_symlink,
 }
-SUBSTITUTION_RULES = {
+SUBSTITUTION = {
     "build-aux": "auxdir",
     "doc": "doc_base",
     "lib": "source_base",
@@ -96,7 +116,12 @@
 def import_hook(script, gnulib, namespace, explicit, verbosity, options, *args, **kwargs):
     (_, _) = (args, kwargs)
     config = BaseConfig(**namespace)
-    cache = CachedConfig(root=config.root)
+    try:
+        cache = CachedConfig(root=config.root)
+    except FileNotFoundError:
+        cache = BaseConfig(**config)
+        cache.files = set()
+        cache.ac_version = "2.59"
     for key in {"ac_version", "files"}:
         if key not in namespace:
             config[key] = cache[key]
@@ -165,16 +190,19 @@
 
     table = {}
     overrides = []
-    for (tbl_key, cfg_key) in SUBSTITUTION_RULES.items():
-        table[tbl_key] = config[cfg_key] if cfg_key else ""
     project = BaseVFS(config.root, **table)
     overrides = {BaseVFS(override, **table) for override in config.overrides}
+    for (name, key) in SUBSTITUTION.items():
+        project[name] = config[key] if key else ""
+    for override in overrides:
+        for (name, key) in SUBSTITUTION.items():
+            override[name] = config[key] if key else ""
 
     table = {}
     old_files = set(cache.files)
     if "m4/gnulib-tool.m4" in project:
         old_files |= set(["m4/gnulib-tool.m4"])
-    for (tbl_key, cfg_key) in SUBSTITUTION_RULES.items():
+    for (tbl_key, cfg_key) in SUBSTITUTION.items():
         table[tbl_key] = cache[cfg_key] if cfg_key else ""
     new_files = frozenset(files | set(["m4/gnulib-tool.m4"]))
 
@@ -271,7 +299,8 @@
     # Generate the contents of library makefile.
     path = os.path.join(config.source_base, config.makefile_name)
     with tempfile.NamedTemporaryFile("w", encoding="UTF-8", delete=False) as tmp:
-        for line in LibMakefileGenerator(path, config, explicit, database, mkedits, False):
+        modules = database.main_modules
+        for line in lib_makefile(path, config, explicit, database, modules, mkedits, False):
             print(line, file=tmp)
     (src, dst) = (tmp.name, path)
     present = vfs_exists(project, dst)
@@ -297,7 +326,7 @@
             action(bool(match), vfs, src, project, dst, present)
         # Create po makefile parameterization, part 1.
         with tempfile.NamedTemporaryFile("w", encoding="UTF-8", delete=False) as tmp:
-            for line in POMakevarsGenerator(config):
+            for line in po_make_vars(config):
                 print(line, file=tmp)
         (src, dst) = (tmp.name, "po/Makevars")
         present = vfs_exists(project, dst)
@@ -308,7 +337,7 @@
         os.unlink(tmp.name)
         # Create po makefile parameterization, part 2.
         with tempfile.NamedTemporaryFile("w", encoding="UTF-8", delete=False) as tmp:
-            for line in POMakevarsGenerator(config):
+            for line in po_make_vars(config):
                 print(line, file=tmp)
         (src, dst) = (tmp.name, "po/POTFILESGenerator.in")
         present = vfs_exists(project, dst)
@@ -353,7 +382,7 @@
 
     # Create m4/gnulib-cache.m4.
     with tempfile.NamedTemporaryFile("w", encoding="UTF-8", delete=False) as tmp:
-        for line in GnulibCacheGenerator(config):
+        for line in gnulib_cache(config):
             print(line, file=tmp)
     (src, dst) = (tmp.name, "m4/gnulib-cache.m4")
     present = vfs_exists(project, dst)
@@ -365,7 +394,7 @@
 
     # Create m4/gnulib-comp.m4.
     with tempfile.NamedTemporaryFile("w", encoding="UTF-8", delete=False) as tmp:
-        for line in GnulibCompGenerator(config, explicit, database):
+        for line in gnulib_comp(config, explicit, database, True):
             print(line, file=tmp)
     (src, dst) = (tmp.name, "m4/gnulib-comp.m4")
     present = vfs_exists(project, dst)
@@ -375,6 +404,21 @@
     action(False, None, src, project, dst, present)
     os.unlink(tmp.name)
 
+    # Generate the contents of tests makefile.
+    if config.tests:
+        path = os.path.join(config.tests_base, config.makefile_name)
+        with tempfile.NamedTemporaryFile("w", encoding="UTF-8", delete=False) as tmp:
+            modules = database.test_modules
+            for line in tests_makefile(path, config, explicit, database, modules, mkedits, False):
+                print(line, file=tmp)
+        (src, dst) = (tmp.name, path)
+        present = vfs_exists(project, dst)
+        if present:
+            added_files.add(dst)
+        action = update_file if present else add_file
+        action(False, None, src, project, dst, present)
+        os.unlink(tmp.name)
+
     return os.EX_OK
 
 
@@ -420,6 +464,7 @@
 
 def main(script, gnulib, program, arguments, environ):
     gnulib = GnulibGitVFS(gnulib)
+    gnulib["tests=lib"] = "lib"
     parser = CommandLineParser(program)
     try:
         (namespace, mode, verbosity, options) = parser.parse(arguments)
--- a/pygnulib/config.py	Sun Jan 21 20:43:31 2018 +0300
+++ b/pygnulib/config.py	Mon Jan 29 19:07:42 2018 +0300
@@ -41,6 +41,45 @@
 
 
 
+KEYS = frozenset({
+    "root",
+    "overrides",
+    "source_base",
+    "m4_base",
+    "po_base",
+    "doc_base",
+    "tests_base",
+    "auxdir",
+    "libname",
+    "makefile_name",
+    "macro_prefix",
+    "po_domain",
+    "witness_c_macro",
+    "licenses",
+    "ac_version",
+    "ac_file",
+    "modules",
+    "avoids",
+    "files",
+    "copymode",
+    "local_copymode",
+    "tests",
+    "obsolete",
+    "cxx_tests",
+    "longrunning_tests",
+    "privileged_tests",
+    "unportable_tests",
+    "libtool",
+    "conditionals",
+    "copyrights",
+    "gnumake",
+    "single_configure",
+    "vc_files",
+    "all_tests",
+})
+
+
+
 class BaseConfig:
     """gnulib generic configuration"""
     _TABLE = {
@@ -424,7 +463,7 @@
     @property
     def obsolete(self):
         """include obsolete modules when they occur among the modules?"""
-        return bool(self.__options & BaseConfig._Option.Tests)
+        return bool(self.__options & BaseConfig._Option.Obsolete)
 
     @obsolete.setter
     def obsolete(self, value):
@@ -586,17 +625,17 @@
 
 
     def __getitem__(self, key):
-        if key not in BaseConfig._TABLE:
+        if key not in KEYS:
             key = key.replace("-", "_")
-            if key not in BaseConfig._TABLE:
+            if key not in KEYS:
                 raise KeyError("unsupported option: {0}".format(key))
         return getattr(self, key)
 
 
     def __setitem__(self, key, value):
-        if key not in BaseConfig._TABLE:
+        if key not in KEYS:
             key = key.replace("_", "-")
-            if key not in BaseConfig._TABLE:
+            if key not in KEYS:
                 raise KeyError("unsupported option: {0}".format(key))
         return setattr(self, key, value)
 
@@ -657,6 +696,7 @@
         if ac_file is None:
             ac_file = "configure.ac"
         _type_assert("ac_file", ac_file, str)
+
         ac_path = _os.path.join(root, ac_file)
         if not _os.path.exists(ac_path):
             ac_file = "configure.in"
--- a/pygnulib/generator.py	Sun Jan 21 20:43:31 2018 +0300
+++ b/pygnulib/generator.py	Mon Jan 29 19:07:42 2018 +0300
@@ -29,11 +29,7 @@
     _LGPL_LICENSE: "yes",
     (_GPLv2_LICENSE | _LGPLv3_LICENSE): "3orGPLv2",
 }
-_ITERABLES = frozenset((list, tuple, set, frozenset, type({}.keys()), type({}.values())))
-
-
-
-_DISCLAIMER = (
+__DISCLAIMER = (
     "## DO NOT EDIT! GENERATED AUTOMATICALLY!",
     "#",
     "# This file is free software; you can redistribute it and/or modify",
@@ -56,1053 +52,1086 @@
     "#",
     "# Generated by gnulib-tool.",
 )
-
-
-
-class BaseGenerator:
-    """gnulib file content generator"""
-    def __repr__(self):
-        module = self.__class__.__module__
-        name = self.__class__.__name__
-        return "{0}.{1}".format(module, name)
-
-
-    def __str__(self):
-        return "\n".join([_ for _ in self])
-
-
-    def __enter__(self):
-        return self
-
-
-    def __exit__(self, exctype, excval, exctrace):
-        pass
-
-
-    def __iter__(self):
-        yield
-
-
-
-class POMakevarsGenerator(BaseGenerator):
-    """PO Makefile parameterization"""
-    _TEMPLATE = (
-        "# These options get passed to xgettext.",
-        "XGETTEXT_OPTIONS = \\",
-        "  --keyword=_ --flag=_:1:pass-c-format \\",
-        "  --keyword=N_ --flag=N_:1:pass-c-format \\",
-        "  --keyword='proper_name:1,\"This is a proper name." # comma omitted
-        " See the gettext manual, section Names.\"' \\",
-        "  --keyword='proper_name_utf8:1,\"This is a proper name." # comma omitted
-        " See the gettext manual, section Names.\"' \\",
-        "  --flag=error:3:c-format --flag=error_at_line:5:c-format",
-        "",
-        "# This is the copyright holder that gets inserted into the header of the",
-        "# $(DOMAIN).pot file.  gnulib is copyrighted by the FSF.",
-        "COPYRIGHT_HOLDER = Free Software Foundation, Inc.",
-        "",
-        "# This is the email address or URL to which the translators shall report",
-        "# bugs in the untranslated strings:",
-        "# - Strings which are not entire sentences, see the maintainer guidelines",
-        "#   in the GNU gettext documentation, section 'Preparing Strings'.",
-        "# - Strings which use unclear terms or require additional context to be",
-        "#   understood.",
-        "# - Strings which make invalid assumptions about notation of date, time or",
-        "#   money.",
-        "# - Pluralisation problems.",
-        "# - Incorrect English spelling.",
-        "# - Incorrect formatting.",
-        "# It can be your email address, or a mailing list address where translators",
-        "# can write to without being subscribed, or the URL of a web page through",
-        "# which the translators can contact you.",
-        "MSGID_BUGS_ADDRESS = bug-gnulib@gnu.org",
-        "",
-        "# This is the list of locale categories, beyond LC_MESSAGES, for which the",
-        "# message catalogs shall be used.  It is usually empty.",
-        "EXTRA_LOCALE_CATEGORIES =",
-        "",
-        "# This tells whether the $(DOMAIN).pot file contains messages with an 'msgctxt'",
-        "# context.  Possible values are \"yes\" and \"no\".  Set this to yes if the",
-        "# package uses functions taking also a message context, like pgettext(), or",
-        "# if in $(XGETTEXT_OPTIONS) you define keywords with a context argument.",
-        "USE_MSGCTXT = no"
-    )
-
-
-    def __init__(self, config):
-        _type_assert("config", config, _BaseConfig)
-        super().__init__()
-        self.__config = config
-
-
-    @property
-    def po_base(self):
-        """directory relative to ROOT where *.po files are placed; defaults to 'po'"""
-        return self.__config.po_base
-
-
-    @property
-    def po_domain(self):
-        """the prefix of the i18n domain"""
-        return self.__config.po_domain
-
-
-    def __repr__(self):
-        module = self.__class__.__module__
-        name = self.__class__.__name__
-        fmt = "{}.{}{po_base={}, po_domain={}}"
-        return fmt.format(module, name, repr(self.__config.po_base), repr(self.__config.po_domain))
-
-
-    def __iter__(self):
-        for line in _DISCLAIMER:
-            yield line
-        yield "# Usually the message domain is the same as the package name."
-        yield "# But here it has a '-gnulib' suffix."
-        yield "DOMAIN = {}-gnulib".format(self.po_domain)
-        yield ""
-        yield "# These two variables depend on the location of this directory."
-        yield "subdir = {}".format(self.po_domain)
-        yield "top_subdir = {}".format("/".join(".." for _ in self.po_base.split(_os.path.sep)))
-        for line in POMakevarsGenerator._TEMPLATE:
-            yield line
+_ITERABLES = frozenset((list, tuple, set, frozenset, type({}.keys()), type({}.values())))
 
 
 
-class POTFILESGenerator(BaseGenerator):
-    """file list to be passed to xgettext"""
-    def __init__(self, config, files):
-        _type_assert("config", config, _BaseConfig)
-        super().__init__()
-        self.__config = config
-        self.__files = tuple(files)
-
-
-    @property
-    def files(self):
-        """list of files"""
-        return tuple(self.files)
+__PO_MAKE_VARS = (
+    "# These options get passed to xgettext.",
+    "XGETTEXT_OPTIONS = \\",
+    "  --keyword=_ --flag=_:1:pass-c-format \\",
+    "  --keyword=N_ --flag=N_:1:pass-c-format \\",
+    "  --keyword='proper_name:1,\"This is a proper name. See the gettext manual, section Names.\"' \\",
+    "  --keyword='proper_name_utf8:1,\"This is a proper name. See the gettext manual, section Names.\"' \\",
+    "  --flag=error:3:c-format --flag=error_at_line:5:c-format",
+    "",
+    "# This is the copyright holder that gets inserted into the header of the",
+    "# $(DOMAIN).pot file.  gnulib is copyrighted by the FSF.",
+    "COPYRIGHT_HOLDER = Free Software Foundation, Inc.",
+    "",
+    "# This is the email address or URL to which the translators shall report",
+    "# bugs in the untranslated strings:",
+    "# - Strings which are not entire sentences, see the maintainer guidelines",
+    "#   in the GNU gettext documentation, section 'Preparing Strings'.",
+    "# - Strings which use unclear terms or require additional context to be",
+    "#   understood.",
+    "# - Strings which make invalid assumptions about notation of date, time or",
+    "#   money.",
+    "# - Pluralisation problems.",
+    "# - Incorrect English spelling.",
+    "# - Incorrect formatting.",
+    "# It can be your email address, or a mailing list address where translators",
+    "# can write to without being subscribed, or the URL of a web page through",
+    "# which the translators can contact you.",
+    "MSGID_BUGS_ADDRESS = bug-gnulib@gnu.org",
+    "",
+    "# This is the list of locale categories, beyond LC_MESSAGES, for which the",
+    "# message catalogs shall be used.  It is usually empty.",
+    "EXTRA_LOCALE_CATEGORIES =",
+    "",
+    "# This tells whether the $(DOMAIN).pot file contains messages with an 'msgctxt'",
+    "# context.  Possible values are \"yes\" and \"no\".  Set this to yes if the",
+    "# package uses functions taking also a message context, like pgettext(), or",
+    "# if in $(XGETTEXT_OPTIONS) you define keywords with a context argument.",
+    "USE_MSGCTXT = no"
+)
 
-
-    def __repr__(self):
-        module = self.__class__.__module__
-        name = self.__class__.__name__
-        fmt = "{}.{}{files={}}"
-        return fmt.format(module, name, repr(self.__files))
+def po_make_vars(config, **override):
+    """Generate PO Makefile parameterization."""
+    _type_assert("config", config, _BaseConfig)
+    config = _BaseConfig(**config)
+    for (key, value) in override.items():
+        config[key] = value
 
-
-    def __iter__(self):
-        for line in _DISCLAIMER:
-            yield line
-        yield "# List of files which contain translatable strings."
-        for file in [_ for _ in self.files if _.startswith("lib/")]:
-            yield _os.path.join(self.__config.source_base, file[4:])
+    po_base = config.po_base
+    po_domain = comnfig.po_domain
+    for line in __DISCLAIMER:
+        yield line
+    yield "# Usually the message domain is the same as the package name."
+    yield "# But here it has a '-gnulib' suffix."
+    yield f"DOMAIN = {po_domain}-gnulib"
+    yield ""
+    yield "# These two variables depend on the location of this directory."
+    yield f"subdir = {po_base}"
+    top_builddir = "/".join(".." for _ in config.po_base.split(_os.path.sep))
+    yield f"top_builddir = {top_builddir}"
+    for line in __PO_MAKE_VARS:
+        yield line
 
 
 
-class AutoconfSnippetGenerator(BaseGenerator):
-    """autoconf snippet generator for standalone module"""
-    def __init__(self, config, module, toplevel, no_libtool, no_gettext):
-        """
-        config: gnulib configuration
-        module: gnulib module instance
-        toplevel: make a subordinate use of gnulib if False
-        no_libtool: disable libtool (regardless of configuration)
-        no_gettext: disable AM_GNU_GETTEXT invocations if True
-        """
-        _type_assert("config", config, _BaseConfig)
-        _type_assert("module", module, _BaseModule)
-        _type_assert("toplevel", toplevel, bool)
-        _type_assert("no_libtool", no_libtool, bool)
-        _type_assert("no_gettext", no_gettext, bool)
-        super().__init__()
-        self.__config = config
-        self.__module = module
-        self.__toplevel = toplevel
-        self.__no_libtool = no_libtool
-        self.__no_gettext = no_gettext
+def POTFILES(config, files, **override):
+    """Generate file list to be passed to xgettext."""
+    _type_assert("config", config, _BaseConfig)
+    config = _BaseConfig(**config)
+    for (key, value) in override.items():
+        config[key] = value
 
-
-    @property
-    def toplevel(self):
-        """top level indicator; subordinate use of pygnulib"""
-        return self.__toplevel
-
+    source_base = config.source_base
+    for line in __DISCLAIMER:
+        yield line
+    yield "# List of files which contain translatable strings."
+    for file in filter(lambda file: file.startswith("lib/"), files):
+        yield _os.path.join(source_base, file[4:])
 
-    @property
-    def libtool(self):
-        """libtool switch, disabling libtool configuration parameter"""
-        return self.__config.libtool and not self.__no_libtool
-
-    @property
-    def gettext(self):
-        """gettext switch, disabling AM_GNU_GETTEXT invocations"""
-        return not self.__no_gettext
 
 
-    def __repr__(self):
-        flags = []
-        module = self.__class__.__module__
-        name = self.__class__.__name__
-        if self.toplevel:
-            flags += ["toplevel"]
-        if self.libtool:
-            flags += ["libtool"]
-        if self.gettext:
-            flags += ["gettext"]
-        include_guard_prefix = self.__config.include_guard_prefix
-        flags = "|".join(flags)
-        fmt = "{}.{}{include_guard_prefix={}, flags={}}"
-        return fmt.format(module, name, repr(include_guard_prefix), flags)
+def autoconf_snippet(config, module, toplevel, no_libtool, no_gettext, **override):
+    """
+    Generate autoconf snippet for a standalone module.
 
+    <config> gnulib configuration
+    <module> gnulib module instance
+    <toplevel> make a subordinate use of gnulib if False
+    <no_libtool> disable libtool (regardless of configuration)
+    <no_gettext> disable AM_GNU_GETTEXT invocations if True
+    """
+    _type_assert("config", config, _BaseConfig)
+    _type_assert("module", module, _BaseModule)
+    _type_assert("toplevel", toplevel, bool)
+    _type_assert("no_libtool", no_libtool, bool)
+    _type_assert("no_gettext", no_gettext, bool)
+    config = _BaseConfig(**config)
+    for (key, value) in override.items():
+        config[key] = value
 
-    def __iter__(self):
-        module = self.__module
-        if module.name not in ("gnumakefile", "maintainer-makefile") or self.toplevel:
-            snippet = module.autoconf_snippet
-            include_guard_prefix = self.__config.include_guard_prefix
-            snippet.replace(r"${gl_include_guard_prefix}", include_guard_prefix)
-            if not self.libtool:
-                table = (
-                    (r"$gl_cond_libtool", "false"),
-                    ("gl_libdeps", "gltests_libdeps"),
-                    ("gl_ltlibdeps", "gltests_ltlibdeps"),
-                )
-                for (src, dst) in table:
-                    snippet = snippet.replace(src, dst)
-            if not self.gettext:
-                src = "AM_GNU_GETTEXT([external])"
-                dst = "dnl you must add AM_GNU_GETTEXT([external]) or similar to configure.ac.'"
+    libtool = config.libtool and not no_libtool
+    gettext = not no_gettext
+    if module.name not in ("gnumakefile", "maintainer-makefile") or toplevel:
+        snippet = module.autoconf_snippet
+        include_guard_prefix = config.include_guard_prefix
+        snippet.replace(r"${gl_include_guard_prefix}", include_guard_prefix)
+        if not config.libtool:
+            table = (
+                (r"$gl_cond_libtool", "false"),
+                (r"gl_libdeps", "gltests_libdeps"),
+                (r"gl_ltlibdeps", "gltests_ltlibdeps"),
+            )
+            for (src, dst) in table:
                 snippet = snippet.replace(src, dst)
-            else:
-                # Don't indent AM_GNU_GETTEXT_VERSION line, as that confuses
-                # autopoint through at least GNU gettext version 0.18.2.
-                snippet = snippet.lstrip()
-            lines = (_ for _ in snippet.split("\n") if _)
-            for line in lines:
-                yield line
-            if module.name == "alloca" and self.libtool:
-                yield "changequote(,)dnl"
-                yield "LTALLOCA=`echo \"$ALLOCA\" | sed -e 's/\\.[^.]* /.lo /g;s/\\.[^.]*$/.lo/'`"
-                yield "changequote([, ])dnl"
-                yield "AC_SUBST([LTALLOCA])"
+        if not gettext:
+            src = "AM_GNU_GETTEXT([external])"
+            dst = "dnl you must add AM_GNU_GETTEXT([external]) or similar to configure.ac.'"
+            snippet = snippet.replace(src, dst)
+        else:
+            # Don't indent AM_GNU_GETTEXT_VERSION line, as that confuses
+            # autopoint through at least GNU gettext version 0.18.2.
+            snippet = snippet.lstrip()
+        lines = filter(lambda line: line.strip(), snippet.split("\n"))
+        for line in lines:
+            yield line
+        if module.name == "alloca" and libtool:
+            yield "changequote(,)dnl"
+            yield "LTALLOCA=`echo \"$ALLOCA\" | sed -e 's/\\.[^.]* /.lo /g;s/\\.[^.]*$/.lo/'`"
+            yield "changequote([, ])dnl"
+            yield "AC_SUBST([LTALLOCA])"
 
 
 
-class AutoconfMultisnippetGenerator(BaseGenerator):
-    """multi-module autoconf snippets generator"""
-    def __init__(self, config, database, modules, toplevel, no_libtool, no_gettext, macro_prefix=None):
-        _type_assert("config", config, _BaseConfig)
-        _type_assert("database", database, _Database)
-        _type_assert("modules", modules, _ITERABLES)
-        _type_assert("toplevel", toplevel, bool)
-        _type_assert("no_libtool", no_libtool, bool)
-        _type_assert("no_gettext", no_gettext, bool)
-        if macro_prefix is None:
-            macro_prefix = config.macro_prefix
-        _type_assert("macro_prefix", macro_prefix, str)
-        super().__init__()
-        self.__config = config
-        self.__database = database
-        self.__modules = set()
+def autoconf_snippet_sequence(config, database, modules, toplevel, no_libtool, no_gettext, **override):
+    """
+    Generate an autoconf snippet for multiple modules.
+
+    <config> gnulib configuration
+    <module> gnulib module instance
+    <toplevel> make a subordinate use of gnulib if False
+    <no_libtool> disable libtool (regardless of configuration)
+    <no_gettext> disable AM_GNU_GETTEXT invocations if True
+    [macro_prefix] the prefix of the macros 'gl_EARLY' and 'gl_INIT'
+    """
+    _type_assert("config", config, _BaseConfig)
+    _type_assert("database", database, _Database)
+    _type_assert("modules", modules, _ITERABLES)
+    _type_assert("toplevel", toplevel, bool)
+    _type_assert("no_libtool", no_libtool, bool)
+    _type_assert("no_gettext", no_gettext, bool)
+    config = _BaseConfig(**config)
+    for (key, value) in override.items():
+        config[key] = value
+    macro_prefix = config.macro_prefix
+
+    arguments = {
+        "config": config,
+        "module": None,
+        "toplevel": toplevel,
+        "no_libtool": no_libtool,
+        "no_gettext": no_gettext,
+    }
+    if not config.conditionals:
         for module in modules:
-            _type_assert("module", module, _BaseModule)
-            self.__modules.add(module)
-        self.__modules = sorted(self.__modules)
-        self.__toplevel = toplevel
-        self.__no_libtool = no_libtool
-        self.__no_gettext = no_gettext
-        self.__macro_prefix = macro_prefix
+            arguments["module"] = module
+            for line in autoconf_snippet(**arguments):
+                yield f"  {line}"
+        return
+
+    conditional = set()
+    unconditional = set()
+    for dependency in modules:
+        if database.conditional(dependency):
+            conditional.add(dependency)
+        else:
+            unconditional.add(dependency)
+    conditional = sorted(conditional)
+    unconditional = sorted(unconditional)
+
+    # Emit the autoconf code for the unconditional modules.
+    for module in unconditional:
+        arguments["module"] = module
+        for line in autoconf_snippet(**arguments):
+            yield f"  {line}"
+
+    # Initialize the shell variables indicating that the modules are enabled.
+    for module in conditional:
+        shellvar = module.shell_variable(macro_prefix)
+        yield f"  {shellvar}=false"
+
+    # Emit the autoconf code for the conditional modules, each in a separate
+    # function. This makes it possible to support cycles among conditional
+    # modules.
+    for demander in conditional:
+        arguments["module"] = demander
+        shellvar = demander.shell_variable(macro_prefix)
+        shellfunc = demander.shell_function(macro_prefix)
+        yield f"  {shellfunc} ()"
+        yield f"  {{"
+        yield f"    if ! ${shellvar}; then"
+        for line in autoconf_snippet(**arguments):
+            yield f"      {line}"
+        yield f"      {shellvar}=true"
+        for (dependency, condition) in sorted(database.dependencies(demander)):
+            if database.conditional(dependency):
+                shellfunc = dependency.shell_function(macro_prefix)
+                if condition is not None:
+                    yield f"      if {condition}; then"
+                    yield f"        {shellfunc}"
+                    yield f"      fi"
+                else:
+                    yield f"      {shellfunc}"
+        yield f"    fi"
+        yield f"  }}"
+
+    # Emit the dependencies from the unconditional to the conditional modules.
+    for demander in unconditional:
+        for (dependency, condition) in sorted(database.dependencies(demander)):
+            if dependency in modules and database.conditional(dependency):
+                condname = dependency.conditional_name(macro_prefix)
+                shellfunc = dependency.shell_function(macro_prefix)
+                if condition is not None:
+                    yield f"  if {condition}; then"
+                    yield f"    {shellfunc}"
+                    yield f"  fi"
+                    continue
+                else:
+                    yield f"  {shellfunc}"
+
+    # Define the Automake conditionals.
+    yield f"  m4_pattern_allow([^{macro_prefix}_GNULIB_ENABLED_])"
+    for module in conditional:
+        condname = module.conditional_name(macro_prefix)
+        shellvar = module.shell_variable(macro_prefix)
+        yield f"  AM_CONDITIONAL([{condname}], [${shellvar}])"
 
 
-    def __iter__(self):
-        config = self.__config
-        database = self.__database
-        modules = self.__modules
-        macro_prefix = self.__macro_prefix
-
-        arguments = {
-            "config": config,
-            "module": None,
-            "toplevel": self.__toplevel,
-            "no_libtool": self.__no_libtool,
-            "no_gettext": self.__no_gettext,
-        }
-        if not config.conditionals:
-            for module in sorted(modules):
-                arguments["module"] = module
-                for line in AutoconfSnippetGenerator(**arguments):
-                    yield "  {}".format(line)
-            return
 
-        conditional = set()
-        unconditional = set()
-        for dependency in modules:
-            if database.conditional(dependency):
-                conditional.add(dependency)
-            else:
-                unconditional.add(dependency)
-        conditional = sorted(conditional)
-        unconditional = sorted(unconditional)
+__INIT_MACRO_HEADER = (
+    # Overriding AC_LIBOBJ and AC_REPLACE_FUNCS has the effect of storing
+    # platform-dependent object files in ${macro_prefix_arg}_LIBOBJS instead
+    # of LIBOBJS. The purpose is to allow several gnulib instantiations under
+    # a single configure.ac file. (AC_CONFIG_LIBOBJ_DIR does not allow this
+    # flexibility).
+    # Furthermore it avoids an automake error like this when a Makefile.am
+    # that uses pieces of gnulib also uses $(LIBOBJ):
+    #   automatically discovered file `error.c' should not be explicitly
+    #   mentioned.
+    "  m4_pushdef([AC_LIBOBJ], m4_defn([{macro_prefix}_LIBOBJ]))",
+    "  m4_pushdef([AC_REPLACE_FUNCS], m4_defn([{macro_prefix}_REPLACE_FUNCS]))",
 
-        # Emit the autoconf code for the unconditional modules.
-        for module in unconditional:
-            arguments["module"] = module
-            for line in AutoconfSnippetGenerator(**arguments):
-                yield "  {}".format(line)
-
-        # Initialize the shell variables indicating that the modules are enabled.
-        for module in conditional:
-            shellvar = module.shell_variable(macro_prefix)
-            yield "  {}=false".format(shellvar)
+    # Overriding AC_LIBSOURCES has the same purpose of avoiding the automake
+    # error when a Makefile.am that uses pieces of gnulib also uses $(LIBOBJ):
+    #   automatically discovered file `error.c' should not be explicitly
+    #   mentioned
+    # We let automake know about the files to be distributed through the
+    # EXTRA_lib_SOURCES variable.
+    "  m4_pushdef([AC_LIBSOURCES], m4_defn([{macro_prefix}_LIBSOURCES]))",
 
-        # Emit the autoconf code for the conditional modules, each in a separate
-        # function. This makes it possible to support cycles among conditional
-        # modules.
-        for demander in conditional:
-            shellvar = demander.shell_variable(macro_prefix)
-            shellfunc = demander.shell_function(macro_prefix)
-            yield "  {} ()".format(shellfunc)
-            yield "  {"
-            yield "    if ! ${}; then".format(shellvar)
-            arguments["module"] = demander
-            for line in AutoconfSnippetGenerator(**arguments):
-                yield "      {}".format(line)
-            yield "      {}=true".format(shellvar)
-            for (dependency, condition) in sorted(database.dependencies(demander)):
-                if database.conditional(dependency):
-                    shellfunc = dependency.shell_function(macro_prefix)
-                    if condition is not None:
-                        yield "      if {}; then".format(condition)
-                        yield "        {}".format(shellfunc)
-                        yield "      fi"
-                    else:
-                        yield "      {}".format(shellfunc)
-            yield "    fi"
-            yield "  }"
+    # Create data variables for checking the presence of files that are
+    # mentioned as AC_LIBSOURCES arguments. These are m4 variables, not shell
+    # variables, because we want the check to happen when the configure file is
+    # created, not when it is run. ${macro_prefix_arg}_LIBSOURCES_LIST is the
+    # list of files to check for. ${macro_prefix_arg}_LIBSOURCES_DIR is the
+    # subdirectory in which to expect them.
+    "  m4_pushdef([{macro_prefix}_LIBSOURCES_LIST], [])",
+    "  m4_pushdef([{macro_prefix}_LIBSOURCES_DIR], [])",
+    "  gl_COMMON",
+)
 
-        # Emit the dependencies from the unconditional to the conditional modules.
-        for demander in unconditional:
-            for (dependency, condition) in sorted(database.dependencies(demander)):
-                if dependency in modules and database.conditional(dependency):
-                    condname = dependency.conditional_name(macro_prefix)
-                    shellfunc = dependency.shell_function(macro_prefix)
-                    if condition is not None:
-                        yield "  if {}; then".format(condition)
-                        yield "    {}".format(shellfunc)
-                        yield "  fi"
-                    else:
-                        yield "  {}".format(shellfunc)
+def init_macro_header(config, **override):
+    """
+    Generate the first few statements of the gl_INIT macro.
 
-        # Define the Automake conditionals.
-        yield "  m4_pattern_allow([^{}_GNULIB_ENABLED_])".format(macro_prefix)
-        for module in conditional:
-            condname = module.conditional_name(macro_prefix)
-            shellvar = module.shell_variable(macro_prefix)
-            yield "  AM_CONDITIONAL([{}], [${}])".format(condname, shellvar)
+    [macro_prefix] the prefix of the macros 'gl_EARLY' and 'gl_INIT'
+    """
+    _type_assert("config", config, _BaseConfig)
+    config = _BaseConfig(**config)
+    for (key, value) in override.items():
+        config[key] = value
+    macro_prefix = config.macro_prefix
+
+    for line in __INIT_MACRO_HEADER:
+        yield line.format(**config)
 
 
 
-class InitMacroGenerator(BaseGenerator):
-    """basic gl_INIT macro generator"""
-    def __init__(self, config, macro_prefix=None):
-        """
-        config: gnulib configuration
-        macro_prefix: macro prefix; if None, consider configuration
-        """
-        _type_assert("config", config, _BaseConfig)
-        if macro_prefix is None:
-            macro_prefix = config.macro_prefix
-        _type_assert("macro_prefix", macro_prefix, str)
-        super().__init__()
-        self.__macro_prefix = macro_prefix
+__INIT_MACRO_FOOTER = (
+    # Check the presence of files that are mentioned as AC_LIBSOURCES
+    # arguments. The check is performed only when autoconf is run from the
+    # directory where the configure.ac resides; if it is run from a different
+    # directory, the check is skipped.
+    "  m4_ifval({macro_prefix}_LIBSOURCES_LIST, [",
+    "    m4_syscmd([test ! -d ]m4_defn([{macro_prefix}_LIBSOURCES_DIR])[ ||",
+    "      for gl_file in ]{macro_prefix}_LIBSOURCES_LIST[ ; do",
+    "        if test ! -r ]m4_defn([{macro_prefix}_LIBSOURCES_DIR])[/$gl_file ; then",
+    "          echo \"missing file ]m4_defn([{macro_prefix}_LIBSOURCES_DIR])[/$gl_file\" >&2",
+    "          exit 1",
+    "        fi",
+    "      done])dnl",
+    "      m4_if(m4_sysval, [0], [],",
+    "        [AC_FATAL([expected source file, required through AC_LIBSOURCES, not found])])",
+    "  ])",
+    "  m4_popdef([{macro_prefix}_LIBSOURCES_DIR])",
+    "  m4_popdef([{macro_prefix}_LIBSOURCES_LIST])",
+    "  m4_popdef([AC_LIBSOURCES])",
+    "  m4_popdef([AC_REPLACE_FUNCS])",
+    "  m4_popdef([AC_LIBOBJ])",
+    "  AC_CONFIG_COMMANDS_PRE([",
+    "    {macro_prefix}_libobjs=",
+    "    {macro_prefix}_ltlibobjs=",
+    "    if test -n \"${macro_prefix}_LIBOBJS\"; then",
+    "      # Remove the extension.",
+    "      sed_drop_objext='s/\\.o$//;s/\\.obj$//'",
+    "      for i in `for i in ${macro_prefix}_LIBOBJS; " # comma omitted
+    "do echo \"$i\"; done | sed -e \"$sed_drop_objext\" | sort | uniq`; do",
+    "        {macro_prefix}_libobjs=\"${macro_prefix}_libobjs $i.$ac_objext\"",
+    "        {macro_prefix}_ltlibobjs=\"${macro_prefix}_ltlibobjs $i.lo\"",
+    "      done",
+    "    fi",
+    "    AC_SUBST([{macro_prefix}_LIBOBJS], [${macro_prefix}_libobjs])",
+    "    AC_SUBST([{macro_prefix}_LTLIBOBJS], [${macro_prefix}_ltlibobjs])",
+    "  ])",
+)
 
-
-    @property
-    def macro_prefix(self):
-        """the prefix of the macros 'gl_EARLY' and 'gl_INIT'"""
-        return self.__macro_prefix
+def init_macro_footer(config, **override):
+    """Generate the last few statements of the gl_INIT macro."""
+    _type_assert("config", config, _BaseConfig)
+    config = _BaseConfig(**config)
+    for (key, value) in override.items():
+        config[key] = value
+    macro_prefix = config.macro_prefix
 
-
-    def __repr__(self):
-        module = self.__class__.__module__
-        name = self.__class__.__name__
-        fmt = "{}.{}{macro_prefix={}}"
-        return fmt.format(module, name, repr(self.__macro_prefix))
+    for line in __INIT_MACRO_FOOTER:
+        yield line.format(**config)
 
 
 
-class InitMacroHeaderGenerator(InitMacroGenerator):
-    """the first few statements of the gl_INIT macro"""
-    _TEMPLATE = (
-        # Overriding AC_LIBOBJ and AC_REPLACE_FUNCS has the effect of storing
-        # platform-dependent object files in ${macro_prefix_arg}_LIBOBJS instead
-        # of LIBOBJS. The purpose is to allow several gnulib instantiations under
-        # a single configure.ac file. (AC_CONFIG_LIBOBJ_DIR does not allow this
-        # flexibility).
-        # Furthermore it avoids an automake error like this when a Makefile.am
-        # that uses pieces of gnulib also uses $(LIBOBJ):
-        #   automatically discovered file `error.c' should not be explicitly
-        #   mentioned.
-        "  m4_pushdef([AC_LIBOBJ], m4_defn([{macro_prefix}_LIBOBJ]))",
-        "  m4_pushdef([AC_REPLACE_FUNCS], m4_defn([{macro_prefix}_REPLACE_FUNCS]))",
-
-        # Overriding AC_LIBSOURCES has the same purpose of avoiding the automake
-        # error when a Makefile.am that uses pieces of gnulib also uses $(LIBOBJ):
-        #   automatically discovered file `error.c' should not be explicitly
-        #   mentioned
-        # We let automake know about the files to be distributed through the
-        # EXTRA_lib_SOURCES variable.
-        "  m4_pushdef([AC_LIBSOURCES], m4_defn([{macro_prefix}_LIBSOURCES]))",
+__INIT_MACRO_DONE = (
+    "",
+    "# Like AC_LIBOBJ, except that the module name goes",
+    "# into {macro_prefix}_LIBOBJS instead of into LIBOBJS.",
+    "AC_DEFUN([{macro_prefix}_LIBOBJ], [",
+    "  AS_LITERAL_IF([$1], [{macro_prefix}_LIBSOURCES([$1.c])])dnl",
+    "  {macro_prefix}_LIBOBJS=\"${macro_prefix}_LIBOBJS $1.$ac_objext\"",
+    "])",
+    "",
+    "# Like AC_REPLACE_FUNCS, except that the module name goes",
+    "# into {macro_prefix}_LIBOBJS instead of into LIBOBJS.",
+    "AC_DEFUN([{macro_prefix}_REPLACE_FUNCS], [",
+    "  m4_foreach_w([gl_NAME], [$1], [AC_LIBSOURCES(gl_NAME[.c])])dnl",
+    "  AC_CHECK_FUNCS([$1], , [{macro_prefix}_LIBOBJ($ac_func)])",
+    "])",
+    "",
+    "# Like AC_LIBSOURCES, except the directory where the source file is",
+    "# expected is derived from the gnulib-tool parameterization,",
+    "# and alloca is special cased (for the alloca-opt module).",
+    "# We could also entirely rely on EXTRA_lib..._SOURCES.",
+    "AC_DEFUN([{macro_prefix}_LIBSOURCES], [",
+    "  m4_foreach([_gl_NAME], [$1], [",
+    "    m4_if(_gl_NAME, [alloca.c], [], [",
+    "      m4_define([{macro_prefix}_LIBSOURCES_DIR], [{source_base}])",
+    "      m4_append([{macro_prefix}_LIBSOURCES_LIST], _gl_NAME, [ ])",
+    "    ])",
+    "  ])",
+    "])",
+)
 
-        # Create data variables for checking the presence of files that are
-        # mentioned as AC_LIBSOURCES arguments. These are m4 variables, not shell
-        # variables, because we want the check to happen when the configure file is
-        # created, not when it is run. ${macro_prefix_arg}_LIBSOURCES_LIST is the
-        # list of files to check for. ${macro_prefix_arg}_LIBSOURCES_DIR is the
-        # subdirectory in which to expect them.
-        "  m4_pushdef([{macro_prefix}_LIBSOURCES_LIST], [])",
-        "  m4_pushdef([{macro_prefix}_LIBSOURCES_DIR], [])",
-        "  gl_COMMON",
-    )
-
+def init_macro_done(config, **override):
+    """
+    Generate few statements after the gl_INIT macro.
 
-    def __init__(self, config, macro_prefix=None):
-        """
-        config: gnulib configuration
-        macro_prefix: macro prefix; if None, consider configuration
-        """
-        super().__init__(config=config, macro_prefix=macro_prefix)
+    [macro_prefix] the prefix of the macros 'gl_EARLY' and 'gl_INIT'
+    """
+    _type_assert("config", config, _BaseConfig)
+    config = _BaseConfig(**config)
+    for (key, value) in override.items():
+        config[key] = value
 
-
-    def __iter__(self):
-        macro_prefix = self.macro_prefix
-        for line in InitMacroHeaderGenerator._TEMPLATE:
-            yield line.format(macro_prefix=macro_prefix)
+    for line in __INIT_MACRO_DONE:
+        yield line.format(**config)
 
 
 
-class InitMacroFooterGenerator(InitMacroGenerator):
-    """the last few statements of the gl_INIT macro"""
-    _TEMPLATE = (
-        "  m4_ifval({macro_prefix}_LIBSOURCES_LIST, [",
-        "    m4_syscmd([test ! -d ]m4_defn([{macro_prefix}_LIBSOURCES_DIR])[ ||",
-        "      for gl_file in ]{macro_prefix}_LIBSOURCES_LIST[ ; do",
-        "        if test ! -r ]m4_defn([{macro_prefix}_LIBSOURCES_DIR])[/$gl_file ; then",
-        "          echo \"missing file ]m4_defn([{macro_prefix}_LIBSOURCES_DIR])[/$gl_file\" >&2",
-        "          exit 1",
-        "        fi",
-        "      done])dnl",
-        "      m4_if(m4_sysval, [0], [],",
-        "        [AC_FATAL([expected source file, required through AC_LIBSOURCES, not found])])",
-        "  ])",
-        "  m4_popdef([{macro_prefix}_LIBSOURCES_DIR])",
-        "  m4_popdef([{macro_prefix}_LIBSOURCES_LIST])",
-        "  m4_popdef([AC_LIBSOURCES])",
-        "  m4_popdef([AC_REPLACE_FUNCS])",
-        "  m4_popdef([AC_LIBOBJ])",
-        "  AC_CONFIG_COMMANDS_PRE([",
-        "    {macro_prefix}_libobjs=",
-        "    {macro_prefix}_ltlibobjs=",
-        "    if test -n \"${macro_prefix}_LIBOBJS\"; then",
-        "      # Remove the extension.",
-        "      sed_drop_objext='s/\\.o$//;s/\\.obj$//'",
-        "      for i in `for i in ${macro_prefix}_LIBOBJS; " # comma omitted
-        "do echo \"$i\"; done | sed -e \"$sed_drop_objext\" | sort | uniq`; do",
-        "        {macro_prefix}_libobjs=\"${macro_prefix}_libobjs $i.$ac_objext\"",
-        "        {macro_prefix}_ltlibobjs=\"${macro_prefix}_ltlibobjs $i.lo\"",
-        "      done",
-        "    fi",
-        "    AC_SUBST([{macro_prefix}_LIBOBJS], [${macro_prefix}_libobjs])",
-        "    AC_SUBST([{macro_prefix}_LTLIBOBJS], [${macro_prefix}_ltlibobjs])",
-        "  ])",
-    )
+__COMMAND_LINE_PATHS = (
+    ("libname", lambda k, v, d: f"--lib={v}"),
+    ("source_base", lambda k, v, d: f"--source_base={v}"),
+    ("m4_base", lambda k, v, d: f"--m4-base={v}"),
+    ("po_base", lambda k, v, d: f"--po-base={v}" if k in d else None),
+    ("doc_base", lambda k, v, d: f"--doc-base={v}"),
+    ("tests_base", lambda k, v, d: f"--tests-base={v}"),
+    ("auxdir", lambda k, v, d: f"--aux-dir={v}"),
+)
+__COMMAND_LINE_TESTS = (
+    ("tests", lambda v: "--with-tests" if v else None),
+    ("cxx_tests", lambda v: "--with-c++-tests" if v else None),
+    ("longrunning_tests", lambda v: "--with-longrunning-tests" if v else None),
+    ("privileged_tests", lambda v: "--with-privileged-tests" if v else None),
+    ("unportable_tests", lambda v: "--with-unportable-tests" if v else None),
+    ("all_tests", lambda v: "--with-all-tests" if v else None),
+)
+__COMMAND_LINE_MISC = (
+    ("conditionals", lambda k, v, d: ("--no", "--")[v] + "conditional-dependencies"),
+    ("libtool", lambda k, v, d: ("--no", "--")[v] + "libtool"),
+    ("macro_prefix", lambda k, v, d: f"--macro-prefix={v}"),
+    ("gnumake", lambda k, v, d: "--gnu-make" if v else None),
+    ("makefile_name", lambda k, v, d: f"--makefile-name={v}"),
+    ("po_domain", lambda k, v, d: f"--po-domain={v}" if k in d else None),
+    ("witness_c_macro", lambda k, v, d: f"--witness-c-macro={v}" if k in d else None),
+    ("vc_files", lambda k, v, d: ("--no", "--")[v] + "vc-files" if k in d else None),
+)
 
-
-    def __init__(self, config, macro_prefix=None):
-        """
-        config: gnulib configuration
-        macro_prefix: macro prefix; if None, consider configuration
-        """
-        super().__init__(config=config, macro_prefix=macro_prefix)
+def command_line(config, explicit, **override):
+    """Generate gnulib command-line invocation."""
+    _type_assert("config", config, _BaseConfig)
+    _type_assert("explicit", explicit, _ITERABLES)
 
-
-    def __iter__(self):
-        # Check the presence of files that are mentioned as AC_LIBSOURCES
-        # arguments. The check is performed only when autoconf is run from the
-        # directory where the configure.ac resides; if it is run from a different
-        # directory, the check is skipped.
-        for line in InitMacroFooterGenerator._TEMPLATE:
-            yield line.format(macro_prefix=self.macro_prefix)
+    yield "gnulib-tool --import"
+    for path in config.overrides:
+        yield f"--local-dir={path}"
+    for (key, hook) in __COMMAND_LINE_PATHS:
+        value = config[key]
+        option = hook(key, value, explicit)
+        if option is not None:
+            yield option
+    for (key, hook) in __COMMAND_LINE_TESTS:
+        value = config[key]
+        option = hook(value)
+        if option is not None:
+            yield option
+    for module in config.avoids:
+        yield "--avoid={module.name}"
+    if config.licenses in _LGPL:
+        lgpl = _LGPL[config.licenses]
+        if lgpl != "yes":
+            yield f"--lgpl={lgpl}"
+        else:
+            yield "--lgpl"
+    for (key, hook) in __COMMAND_LINE_MISC:
+        value = config[key]
+        option = hook(key, value, explicit)
+        if option is not None:
+            yield option
+    for module in sorted(config.modules):
+        yield "{}".format(module)
 
 
 
-class InitMacroDoneGenerator(InitMacroGenerator):
-    """few statements AFTER the gl_INIT macro"""
-    _TEMPLATE = (
-        "",
-        "# Like AC_LIBOBJ, except that the module name goes",
-        "# into {macro_prefix}_LIBOBJS instead of into LIBOBJS.",
-        "AC_DEFUN([{macro_prefix}_LIBOBJ], [",
-        "  AS_LITERAL_IF([$1], [{macro_prefix}_LIBSOURCES([$1.c])])dnl",
-        "  {macro_prefix}_LIBOBJS=\"${macro_prefix}_LIBOBJS $1.$ac_objext\"",
-        "])",
-        "",
-        "# Like AC_REPLACE_FUNCS, except that the module name goes",
-        "# into {macro_prefix}_LIBOBJS instead of into LIBOBJS.",
-        "AC_DEFUN([{macro_prefix}_REPLACE_FUNCS], [",
-        "  m4_foreach_w([gl_NAME], [$1], [AC_LIBSOURCES(gl_NAME[.c])])dnl",
-        "  AC_CHECK_FUNCS([$1], , [{macro_prefix}_LIBOBJ($ac_func)])",
-        "])",
-        "",
-        "# Like AC_LIBSOURCES, except the directory where the source file is",
-        "# expected is derived from the gnulib-tool parameterization,",
-        "# and alloca is special cased (for the alloca-opt module).",
-        "# We could also entirely rely on EXTRA_lib..._SOURCES.",
-        "AC_DEFUN([{macro_prefix}_LIBSOURCES], [",
-        "  m4_foreach([_gl_NAME], [$1], [",
-        "    m4_if(_gl_NAME, [alloca.c], [], [",
-        "      m4_define([{macro_prefix}_LIBSOURCES_DIR], [{source_base}])",
-        "      m4_append([{macro_prefix}_LIBSOURCES_LIST], _gl_NAME, [ ])",
-        "    ])",
-        "  ])",
-        "])",
-    )
+__MAKEFILE_PKGDATA = _re.compile(r"^pkgdata_DATA\s+\=")
+__MAKEFILE_SUBDIRS = _re.compile(r"lib/.*/.*\.c", _re.S)
+__MAKEFILE_LDFLAGS = _re.compile(r"^lib_LDFLAGS\s*\+\=.*?$", _re.S)
+__MAKEFILE_LIBNAME = _re.compile(r"lib_([A-Z][A-Z]*)", _re.S)
+__MAKEFILE_GNUMAKE = _re.compile(r"^if (.*?)$", _re.S)
 
 
-    def __init__(self, config, source_base=None, macro_prefix=None):
-        if source_base is None:
-            source_base = config.source_base
-        _type_assert("source_base", source_base, str)
-        super().__init__(config=config, macro_prefix=macro_prefix)
-        self.__source_base = source_base
+def _lib_makefile_callback(conditionals, gnumake):
 
+    def _automake_conditional(module, conditional, unconditional, macro_prefix):
+        yield ""
+        yield "if {}".format(module.conditional_name(macro_prefix))
+        yield conditional
+        yield "endif"
+        yield unconditional
+
+    def _automake_unconditional(module, conditional, unconditional, macro_prefix):
+        yield ""
+        yield conditional
+        yield unconditional
 
-    @property
-    def source_base(self):
-        """directory relative to ROOT where source code is placed; defaults to 'lib'"""
-        return self.__source_base
+    def _gnumake_conditional(module, conditional, unconditional, macro_prefix):
+        yield "ifeq (,$(OMIT_GNULIB_MODULE_{}))".format(module.name)
+        yield ""
+        yield "ifneq (,$({}))".format(module.conditional_name(macro_prefix))
+        yield __MAKEFILE_GNUMAKE.sub("ifneq (,$(\\1))", conditional)
+        yield "endif"
+        yield "endif"
+        yield __MAKEFILE_GNUMAKE.sub("ifneq (,$(\\1))", unconditional)
 
+    def _gnumake_unconditional(module, conditional, unconditional, macro_prefix):
+        yield ""
+        yield __MAKEFILE_GNUMAKE.sub("ifneq (,$(\\1))", conditional)
+        yield __MAKEFILE_GNUMAKE.sub("ifneq (,$(\\1))", unconditional)
 
-    def __iter__(self):
-        for line in InitMacroDoneGenerator._TEMPLATE:
-            yield line.format(source_base=self.__source_base, macro_prefix=self.macro_prefix)
-
+    callbacks = (
+        (_automake_unconditional, _gnumake_unconditional),
+        (_automake_conditional, _gnumake_conditional),
+    )
+    return callbacks[conditionals][gnumake]
 
 
-class CommandLineGenerator(BaseGenerator):
-    """gnulib command-line invocation generator"""
-    _TESTS = {
-        "tests": "tests",
-        "obsolete": "obsolete",
-        "cxx_tests": "c++-tests",
-        "longrunning_tests": "longrunning-tests",
-        "privileged_tests": "privileged-tests",
-        "unportable_tests": "unportable-tests",
+def lib_makefile(path, config, explicit, database, modules, mkedits, testing, **override):
+    """Generate library Makefile.am file."""
+    _type_assert("path", path, str)
+    _type_assert("config", config, _BaseConfig)
+    _type_assert("explicit", explicit, _ITERABLES)
+    _type_assert("database", database, _Database)
+    _type_assert("modules", modules, _ITERABLES)
+    _type_assert("mkedits", mkedits, _ITERABLES)
+    _type_assert("testing", testing, bool)
+
+    date = _datetime.now()
+    kwargs = {
+        "libname": config.libname,
+        "macro_prefix": config.macro_prefix,
+        "libext": "la" if config.libtool else "a",
+        "perhaps_LT": "LT" if config.libtool else "",
     }
+    assign = "+=" if config.gnumake or "makefile_name" in explicit else "="
+    eliminate_LDFLAGS = True if config.libtool else False
 
+    # When creating a package for testing: Attempt to provoke failures,
+    # especially link errors, already during "make" rather than during
+    # "make check", because "make check" is not possible in a cross-compiling
+    # situation. Turn check_PROGRAMS into noinst_PROGRAMS.
+    transform_check_PROGRAMS = True if testing else False
+
+    yield "## DO NOT EDIT! GENERATED AUTOMATICALLY!"
+    yield "## Process this file with automake to produce Makefile.in."
+    yield "# Copyright (C) 2002-{} Free Software Foundation, Inc.".format(date.year)
+    for line in __DISCLAIMER[1:]:
+        yield line
+    yield ""
+
+    # The maximum line length (excluding the terminating newline) of any file
+    # that is to be preprocessed by config.status is 3070.  config.status uses
+    # awk, and the HP-UX 11.00 awk fails if a line has length >= 3071;
+    # similarly, the IRIX 6.5 awk fails if a line has length >= 3072.
+    actioncmd = " ".join(command_line(config, explicit))
+    if len(actioncmd) <= 3000:
+        yield "# Reproduce by: {}".format(actioncmd)
+    yield ""
+
+    callback = _lib_makefile_callback(config.conditionals, config.gnumake)
+    def _snippet():
+        lines = []
+        subdirs = False
+        for module in database.main_modules:
+            if module.test:
+                continue
+            conditional = module.conditional_automake_snippet
+            conditional = conditional.replace("lib_LIBRARIES", "lib%_LIBRARIES")
+            conditional = conditional.replace("lib_LTLIBRARIES", "lib%_LTLIBRARIES")
+            if eliminate_LDFLAGS:
+                conditional = __MAKEFILE_LDFLAGS.sub("", conditional)
+            conditional = __MAKEFILE_LIBNAME.sub("{libname}_{libext}_\\1".format(**kwargs), conditional)
+            conditional = conditional.replace("lib%_LIBRARIES", "lib_LIBRARIES")
+            conditional = conditional.replace("lib%_LTLIBRARIES", "lib_LTLIBRARIES")
+            if transform_check_PROGRAMS:
+                conditional = conditional.replace("check_PROGRAMS", "noinst_PROGRAMS")
+            conditional = conditional.replace(r"${gl_include_guard_prefix}", config.include_guard_prefix)
+            unconditional = module.unconditional_automake_snippet.format(auxdir=config.auxdir)
+            unconditional = __MAKEFILE_LIBNAME.sub("{libname}_{libext}_\\1".format(**kwargs), unconditional)
+            if (conditional + unconditional).strip():
+                lines.append("## begin gnulib module {}".format(module.name))
+                if module.name == "alloca":
+                    lines.append("{libname}_{libext}_LIBADD += @{perhaps_LT}ALLOCA@".format(**kwargs))
+                    lines.append("{libname}_{libext}_DEPENDENCIES += @{perhaps_LT}ALLOCA@".format(**kwargs))
+                lines += list(callback(module, conditional, unconditional, config.macro_prefix))
+                lines.append("## end   gnulib module {}".format(module.name))
+                lines.append("")
+            subdirs |= any(__MAKEFILE_SUBDIRS.match(file) for file in module.files)
+        return (subdirs, lines)
+
+    (subdirs, lines) = _snippet()
+    if "makefile_name" not in explicit:
+        # If there are source files in subdirectories, prevent collision of the
+        # object files (example: hash.c and libxml/hash.c).
+        yield "AUTOMAKE_OPTIONS = 1.9.6 gnits{}".format(" subdir-objects" if subdirs else "")
+    yield ""
+    if "makefile_name" not in explicit:
+        yield "SUBDIRS ="
+        yield "noinst_HEADERS ="
+        yield "noinst_LIBRARIES ="
+        yield "noinst_LTLIBRARIES ="
+        # Automake versions < 1.11.4 create an empty pkgdatadir at
+        # installation time if you specify pkgdata_DATA to empty.
+        # See automake bugs #10997 and #11030:
+        #  * http://debbugs.gnu.org/10997
+        #  * http://debbugs.gnu.org/11030
+        # So we need this workaround.
+        if __MAKEFILE_PKGDATA.match("\n".join(lines)):
+            yield "pkgdata_DATA ="
+        yield "EXTRA_DIST ="
+        yield "BUILT_SOURCES ="
+        yield "SUFFIXES ="
+    yield "MOSTLYCLEANFILES {} core *.stackdump".format(assign)
+    if "makefile_name" not in explicit:
+        yield "MOSTLYCLEANDIRS ="
+        yield "CLEANFILES ="
+        yield "DISTCLEANFILES ="
+        yield "MAINTAINERCLEANFILES ="
 
-    def __init__(self, config, explicit):
-        _type_assert("config", config, _BaseConfig)
-        _type_assert("explicit", explicit, _ITERABLES)
-        super().__init__()
-        self.__config = config
-        self.__explicit = explicit
+    if config.gnumake:
+        yield "# Start of GNU Make output."
+        autoconf = "autoconf"
+        cmdargs = (autoconf, "-t", "AC_SUBST:$1 = @$1@", config.ac_file)
+        with _sp.Popen(cmdargs, stdout=_sp.PIPE, stderr=_sp.PIPE) as process:
+            (stdout, stderr) = process.communicate()
+            stdout = stdout.decode("UTF-8")
+            stderr = stderr.decode("UTF-8")
+            if process.returncode == 0:
+                for line in sorted(stdout.splitlines()):
+                    yield line
+            else:
+                yield "== gnulib-tool GNU Make output failed as follows =="
+                for line in stderr.splitlines():
+                    yield "# stderr: {}".format(line)
+        yield "# End of GNU Make output."
+    else:
+        yield "# No GNU Make output."
+
+    for (directory, key, value) in mkedits:
+        if key and _os.path.join(directory, "Makefile.am") == path:
+            del (directory, key, value)
+            yield f"{key} += {value}"
+
+    cppflags = "".join((
+        " -D{}=1".format(config.witness_c_macro) if "witness_c_macro" in explicit else "",
+        " -DGNULIB_STRICT_CHECKING=1" if testing else "",
+    ))
+    if "makefile_name" not in explicit:
+        yield ""
+        yield "AM_CPPFLAGS ={}".format(cppflags)
+        yield "AM_CFLAGS ="
+    elif "".join(cppflags):
+        yield ""
+        yield "AM_CPPFLAGS +={}".format(cppflags)
+    yield ""
+
+    snippet = "\n".join(lines)
+    if "makefile_name" in explicit:
+        makefile = _os.path.join(config.source_base, "Makefile.am")
+        if _os.path.exists(makefile):
+            with _codecs.open(makefile, "rb", "UTF-8") as stream:
+                snippet += ("\n" + stream.read())
+    # One of the snippets or the user's Makefile.am already specifies an
+    # installation location for the library. Don't confuse automake by saying
+    # it should not be installed.
+    # By default, the generated library should not be installed.
+    regex = "^[a-zA-Z0-9_]*_{perhaps_LT}LIBRARIES\\s*\\+?\\=\\s*{libname}\\.{libext}$"
+    pattern = _re.compile(regex.format(**kwargs), _re.S)
+    if not pattern.findall(snippet):
+        yield "noinst_{perhaps_LT}LIBRARIES += {libname}.{libext}".format(**kwargs)
+
+    yield ""
+    yield "{libname}_{libext}_SOURCES =".format(**kwargs)
+    # Here we use $(LIBOBJS), not @LIBOBJS@. The value is the same. However,
+    # automake during its analysis looks for $(LIBOBJS), not for @LIBOBJS@.
+    yield "{libname}_{libext}_LIBADD = $({macro_prefix}_{perhaps_LT}LIBOBJS)".format(**kwargs)
+    yield "{libname}_{libext}_DEPENDENCIES = $({macro_prefix}_{perhaps_LT}LIBOBJS)".format(**kwargs)
+    yield "EXTRA_{libname}_{libext}_SOURCES =".format(**kwargs)
+    if config.libtool:
+        yield "{libname}_{libext}_LDFLAGS = $(AM_LDFLAGS)".format(**kwargs)
+        yield "{libname}_{libext}_LDFLAGS += -no-undefined".format(**kwargs)
+        # Synthesize an ${libname}_${libext}_LDFLAGS augmentation by combining
+        # the link dependencies of all modules.
+        def _directives(modules):
+            directives = (module.link_directive for module in sorted(modules))
+            for directive in filter(lambda directive: directive.strip(), directives):
+                index = directive.find("when linking with libtool")
+                if index != -1:
+                    directive = directive[:index].strip(" ")
+                yield directive
+        for directive in _directives(database.main_modules):
+            yield "{libname}_{libext}_LDFLAGS += {directive}".format(directive=directive, **kwargs)
+    yield ""
+
+    if "po_base" in explicit:
+        yield "AM_CPPFLAGS += -DDEFAULT_TEXT_DOMAIN=\\\"{}-gnulib\\\"".format(config.po_domain)
+        yield ""
+
+    for line in lines:
+        src = "$(top_srcdir)/build-aux/"
+        dst = _os.path.join("$(top_srcdir)", config.auxdir)
+        yield line.replace(src, dst)
+    yield ""
+    yield "mostlyclean-local: mostlyclean-generic"
+    yield "\t@for dir in '' $(MOSTLYCLEANDIRS); do \\"
+    yield "\t  if test -n \"$$dir\" && test -d $$dir; then \\"
+    yield "\t    echo \"rmdir $$dir\"; rmdir $$dir; \\"
+    yield "\t  fi; \\"
+    yield "\tdone; \\"
+    yield "\t:"
 
 
-    def __iter__(self):
-        config = self.__config
-        explicit = self.__explicit
-        yield "gnulib-tool --import"
-        for path in config.overrides:
-            yield "--local-dir={}".format(path)
-        yield "--lib={}".format(config.libname)
-        yield "--source-base={}".format(config.source_base)
-        yield "--m4-base={}".format(config.m4_base)
-        if "po_base" in explicit:
-            yield "--po-base={}".format(config.po_base)
-        yield "--doc-base={}".format(config.doc_base)
-        yield "--tests-base={}".format(config.tests_base)
-        yield "--aux-dir={}".format(config.auxdir)
-        for (key, value) in CommandLineGenerator._TESTS.items():
-            if config[key]:
-                yield "--with-{}".format(value)
-        if config.all_tests:
-            yield "--with-all-tests"
-        for module in config.avoids:
-            yield "--avoid={}".format(module)
-        if config.licenses in _LGPL:
-            lgpl = _LGPL[config.licenses]
-            yield "--lgpl={}".format(lgpl) if lgpl != "yes" else "--lgpl"
-        if config.gnumake:
-            yield "--gnu-make"
-        if "makefile_name" in explicit:
-            yield "--makefile-name={}".format(config.makefile_name)
-        yield "--{}conditional-dependencies".format("" if config.conditionals else "no-")
-        yield "--{}libtool".format("" if config.libtool else "no-")
-        yield "--macro-prefix={}".format(config.macro_prefix)
-        if "po_domain" in explicit:
-            yield "--po-domain={}".format(config.po_domain)
-        if "witness_c_macro" in explicit:
-            yield "--witness-c-macro={}".format(config.witness_c_macro)
-        if "vc_files" in explicit:
-            yield "--{}vc-files".format("" if config.vc_files else "no-")
-        for module in sorted(config.modules):
-            yield "{}".format(module)
+def _tests_makefile_callback(gnumake):
+
+    def _automake(module, snippet):
+        yield ""
+        yield snippet
+
+    def _gnumake(module, snippet):
+        yield "ifeq (,$(OMIT_GNULIB_MODULE_{}))".format(module.name)
+        yield ""
+        yield snippet
+        yield "endif"
+
+    return _gnumake if gnumake else _automake
+
+def tests_makefile(path, config, explicit, database, modules, mkedits, testing):
+    """Generate tests Makefile.am file."""
+    _type_assert("path", path, str)
+    _type_assert("config", config, _BaseConfig)
+    _type_assert("explicit", explicit, _ITERABLES)
+    _type_assert("database", database, _Database)
+    _type_assert("modules", modules, _ITERABLES)
+    _type_assert("mkedits", mkedits, _ITERABLES)
+    _type_assert("testing", testing, bool)
+
+    if testing and not config.single_configure:
+        modules = sorted(filter(lambda module: module.test, modules))
+
+    date = _datetime.now()
+    tests_base = config.tests_base
+    source_base = config.source_base
+    m4_base = config.m4_base
+    macro_prefix = config.macro_prefix
+    witness_c_macro = config.witness_c_macro
+    tests_base_inverse = "/".join(".." for _ in config.tests_base.split(_os.path.sep))
+    (libname, libext) = (config.libname, "la" if config.libtool else "a")
+    kwargs = {
+        "libname": config.libname,
+        "macro_prefix": config.macro_prefix,
+        "libext": "la" if config.libtool else "a",
+        "perhaps_LT": "LT" if config.libtool else "",
+    }
+    assign = "+=" if config.gnumake or "makefile_name" in explicit else "="
+    eliminate_LDFLAGS = True if config.libtool else False
+
+    # When creating a package for testing: Attempt to provoke failures,
+    # especially link errors, already during "make" rather than during
+    # "make check", because "make check" is not possible in a cross-compiling
+    # situation. Turn check_PROGRAMS into noinst_PROGRAMS.
+    transform_check_PROGRAMS = True if testing else False
+
+    yield "## DO NOT EDIT! GENERATED AUTOMATICALLY!"
+    yield "## Process this file with automake to produce Makefile.in."
+    yield "# Copyright (C) 2002-{} Free Software Foundation, Inc.".format(date.year)
+    for line in __DISCLAIMER[1:]:
+        yield line
+    yield ""
+
+    callback = _tests_makefile_callback(config.gnumake)
+    def _snippet():
+        main = []
+        longrunning = []
+        subdirs = False
+        for module in modules:
+            lines = []
+            snippet = module.automake_snippet
+            snippet = snippet.replace("lib_LIBRARIES", "lib%_LIBRARIES")
+            snippet = snippet.replace("lib_LTLIBRARIES", "lib%_LTLIBRARIES")
+            if eliminate_LDFLAGS:
+                snippet = __MAKEFILE_LDFLAGS.sub("", snippet)
+            snippet = __MAKEFILE_LIBNAME.sub("libtests_a_\\1", snippet)
+            snippet = snippet.replace("lib%_LIBRARIES", "lib_LIBRARIES")
+            snippet = snippet.replace("lib%_LTLIBRARIES", "lib_LTLIBRARIES")
+            if transform_check_PROGRAMS:
+                snippet = snippet.replace("check_PROGRAMS", "noinst_PROGRAMS")
+            snippet = snippet.replace(r"${gl_include_guard_prefix}", config.include_guard_prefix)
+            if database.libtests and module.name == "alloca":
+                lines += ["libtests_a_LIBADD += @ALLOCA@"]
+                lines += ["libtests_a_DEPENDENCIES += @ALLOCA@"]
+            subdirs |= any(__MAKEFILE_SUBDIRS.match(file) for file in module.files)
+            if snippet.strip():
+                lines.append("## begin gnulib module {}".format(module.name))
+                lines += list(callback(module, snippet))
+                lines.append("## end   gnulib module {}".format(module.name))
+                lines.append("")
+            if module.longrunning_test:
+                longrunning += lines
+            else:
+                main += lines
+        lines = (main + longrunning)
+        return (subdirs, lines)
+
+    (subdirs, lines) = _snippet()
+
+    # Generate dependencies here, since it eases the debugging of test failures.
+    # If there are source files in subdirectories, prevent collision of the
+    # object files (example: hash.c and libxml/hash.c).
+    subdir_options = " subdir-objects" if subdirs else ""
+    yield f"AUTOMAKE_OPTIONS = 1.9.6 foreign{subdir_options}"
+    yield ""
+    if testing and not single_configure:
+        yield f"ACLOCAL_AMFLAGS = -I {tests_base_inverse}/{m4_base}"
+        yield ""
+
+    # Nothing is being added to SUBDIRS; nevertheless the existence of this
+    # variable is needed to avoid an error from automake:
+    #   "AM_GNU_GETTEXT used but SUBDIRS not defined"
+    yield "SUBDIRS = ."
+    yield "TESTS ="
+    yield "XFAIL_TESTS ="
+    yield "TESTS_ENVIRONMENT ="
+    yield "noinst_PROGRAMS ="
+    if not testing:
+        yield "check_PROGRAMS ="
+    yield "EXTRA_PROGRAMS ="
+    yield "noinst_HEADERS ="
+    yield "noinst_LIBRARIES ="
+    if database.libtests:
+        if testing:
+            yield "noinst_LIBRARIES += libtests.a"
+        else:
+            yield "check_LIBRARIES = libtests.a"
+
+    # Automake versions < 1.11.4 create an empty pkgdatadir at
+    # installation time if you specify pkgdata_DATA to empty.
+    # See automake bugs #10997 and #11030:
+    #  * http://debbugs.gnu.org/10997
+    #  * http://debbugs.gnu.org/11030
+    # So we need this workaround.
+    if __MAKEFILE_PKGDATA.match("\n".join(lines)):
+        yield "pkgdata_DATA ="
+
+    yield "EXTRA_DIST ="
+    yield "BUILT_SOURCES ="
+    yield "SUFFIXES ="
+    yield "MOSTLYCLEANFILES = core *.stackdump"
+    yield "MOSTLYCLEANDIRS ="
+    yield "CLEANFILES ="
+    yield "DISTCLEANFILES ="
+    yield "MAINTAINERCLEANFILES ="
+
+    # Execute edits that apply to the Makefile.am being generated.
+    for (directory, key, value) in mkedits:
+        if key and _os.path.join(directory, "Makefile.am") == path:
+            del (directory, key, value)
+            yield f"{key} += {value}"
+
+    yield ""
+    yield "AM_CPPFLAGS = \\"
+    if testing:
+        yield "  -DGNULIB_STRICT_CHECKING=1 \\"
+    if "witness_c_macro" in explicit:
+        yield "  -D{witness_c_macro}=1 \\"
+        yield "  -D@{witness_c_macro}@=1 \\"
+    yield f"  -I. -I$(srcdir) \\"
+    yield f"  -I{tests_base_inverse} -I$(srcdir)/{tests_base_inverse} \\"
+    yield f"  -I{tests_base_inverse}/{source_base} -I$(srcdir)/{tests_base_inverse}/{source_base}"
+    yield f""
+
+    # All test programs need to be linked with libtests.a.
+    # It needs to be passed to the linker before ${libname}.${libext}, since
+    # the tests-related modules depend on the main modules.
+    # It also needs to be passed to the linker after ${libname}.${libext}
+    # because the latter might contain incomplete modules (such as the 'error'
+    # module whose dependency to 'progname' is voluntarily omitted).
+    # The LIBTESTS_LIBDEPS can be passed to the linker once or twice, it does
+    # not matter.
+    local_ldadd_before = " libtests.a" if database.libtests else ""
+    local_ldadd_after = "  libtests.a $(LIBTESTS_LIBDEPS)" if database.libtests else ""
+    yield f"LDADD ={local_ldadd_before} {tests_base_inverse}/{source_base}/{libname}.{libext}{local_ldadd_after}"
+    yield ""
+
+    if database.libtests:
+        yield "libtests_a_SOURCES ="
+        # Here we use $(LIBOBJS), not @LIBOBJS@. The value is the same. However,
+        # automake during its analysis looks for $(LIBOBJS), not for @LIBOBJS@.
+        yield f"libtests_a_LIBADD = $({macro_prefix}tests_LIBOBJS)"
+        yield f"libtests_a_DEPENDENCIES = $({macro_prefix}tests_LIBOBJS)"
+        yield "EXTRA_libtests_a_SOURCES ="
+        # The circular dependency in LDADD requires this.
+        yield "AM_LIBTOOLFLAGS = --preserve-dup-deps"
+        yield ""
+
+    # Many test scripts use ${EXEEXT} or ${srcdir}.
+    # EXEEXT is defined by AC_PROG_CC through autoconf.
+    # srcdir is defined by autoconf and automake.
+    yield "TESTS_ENVIRONMENT += EXEEXT='@EXEEXT@' srcdir='$(srcdir)'"
+    yield ""
+
+    for line in lines:
+        src = "$(top_srcdir)/build-aux/"
+        dst = _os.path.join("$(top_srcdir)", config.auxdir)
+        yield line.replace(src, dst)
+    yield ""
+    yield "# Clean up after Solaris cc."
+    yield "clean-local:"
+    yield "\trm -rf SunWS_cache"
+    yield ""
+    yield "mostlyclean-local: mostlyclean-generic"
+    yield "\t@for dir in '' $(MOSTLYCLEANDIRS); do \\"
+    yield "\t  if test -n \"$$dir\" && test -d $$dir; then \\"
+    yield "\t    echo \"rmdir $$dir\"; rmdir $$dir; \\"
+    yield "\t  fi; \\"
+    yield "\tdone; \\"
+    yield "\t:"
 
 
 
-class LibMakefileGenerator(BaseGenerator):
-    """library Makefile.am generator"""
-    _LDFLAGS = _re.compile(r"^lib_LDFLAGS\s*\+\=.*?$", _re.S)
-    _LIBNAME = _re.compile(r"lib_([A-Z][A-Z]*)", _re.S)
-    _GNUMAKE = _re.compile(r"^if (.*?)$", _re.S)
-
-
-    def __init__(self, path, config, explicit, database, mkedits, testing):
-        _type_assert("path", path, str)
-        _type_assert("config", config, _BaseConfig)
-        _type_assert("explicit", explicit, _ITERABLES)
-        _type_assert("database", database, _Database)
-        _type_assert("mkedits", mkedits, _ITERABLES)
-        _type_assert("testing", testing, bool)
-        super().__init__()
-        self.__config = config
-        self.__explicit = explicit
-        self.__database = database
-        self.__path = path
-        self.__mkedits = mkedits
-        self.__testing = testing
-
-
-    def __iter__(self):
-        date = _datetime.now()
-        config = self.__config
-        explicit = self.__explicit
-        testing = self.__testing
-        database = self.__database
-
-        gnumake = config.gnumake
-        libtool = config.libtool
-        kwargs = {
-            "libname": config.libname,
-            "macro_prefix": config.macro_prefix,
-            "libext": "la" if libtool else "a",
-            "perhaps_LT": "LT" if libtool else "",
-        }
-        assign = "+=" if gnumake or "makefile_name" in explicit else "="
-        eliminate_LDFLAGS = True if libtool else False
+__GNULIB_CACHE_OPTIONS = (
+    ("obsolete", "gl_WITH_OBSOLETE"),
+    ("cxx_tests", "gl_WITH_CXX_TESTS"),
+    ("longrunning", "gl_WITH_LONGRUNNING_TESTS"),
+    ("privileged", "gl_WITH_PRIVILEGED_TESTS"),
+)
 
-        # When creating a package for testing: Attempt to provoke failures,
-        # especially link errors, already during "make" rather than during
-        # "make check", because "make check" is not possible in a cross-compiling
-        # situation. Turn check_PROGRAMS into noinst_PROGRAMS.
-        transform_check_PROGRAMS = True if testing else False
-
-        yield "## DO NOT EDIT! GENERATED AUTOMATICALLY!"
-        yield "## Process this file with automake to produce Makefile.in."
-        yield "# Copyright (C) 2002-{} Free Software Foundation, Inc.".format(date.year)
-        for line in _DISCLAIMER:
-            yield line
-
-        # The maximum line length (excluding the terminating newline) of any file
-        # that is to be preprocessed by config.status is 3070.  config.status uses
-        # awk, and the HP-UX 11.00 awk fails if a line has length >= 3071;
-        # similarly, the IRIX 6.5 awk fails if a line has length >= 3072.
-        actioncmd = " ".join(CommandLineGenerator(config, explicit))
-        if len(actioncmd) <= 3000:
-            yield "# Reproduce by: {}".format(actioncmd)
-        yield ""
-
-        def _snippet():
-            lines = []
-
-            def _common_conditional(module, conditional, unconditional):
-                yield ""
-                yield "if {}".format(module.conditional_name(config.macro_prefix))
-                yield conditional
-                yield "endif"
-                yield unconditional
-
-            def _common_unconditional(module, conditional, unconditional):
-                yield ""
-                yield conditional
-                yield unconditional
-
-            def _gnumake_conditional(module, conditional, unconditional):
-                yield "ifeq (,$(OMIT_GNULIB_MODULE_{}))".format(module.name)
-                yield ""
-                yield "ifneq (,$({}))".format(module.conditional_name(config.macro_prefix))
-                yield LibMakefileGenerator._GNUMAKE.sub("ifneq (,$(\\1))", conditional)
-                yield "endif"
-                yield "endif"
-                yield LibMakefileGenerator._GNUMAKE.sub("ifneq (,$(\\1))", unconditional)
-
-            def _gnumake_unconditional(module, conditional, unconditional):
-                yield ""
-                yield LibMakefileGenerator._GNUMAKE.sub("ifneq (,$(\\1))", conditional)
-                yield LibMakefileGenerator._GNUMAKE.sub("ifneq (,$(\\1))", unconditional)
+def gnulib_cache(config):
+    """
+    Generate gnulib-cache.m4 file.
+    """
+    _type_assert("config", config, _BaseConfig)
 
-            uses_subdirs = False
-            process = (
-                (_common_unconditional, _gnumake_unconditional),
-                (_common_conditional, _gnumake_conditional),
-            )[config.conditionals][gnumake]
-            for module in database.main_modules:
-                if module.test:
-                    continue
-                conditional = module.conditional_automake_snippet
-                conditional = conditional.replace("lib_LIBRARIES", "lib%_LIBRARIES")
-                conditional = conditional.replace("lib_LTLIBRARIES", "lib%_LTLIBRARIES")
-                if eliminate_LDFLAGS:
-                    conditional = LibMakefileGenerator._LDFLAGS.sub("", conditional)
-                conditional = LibMakefileGenerator._LIBNAME.sub("{libname}_{libext}_\\1".format(**kwargs), conditional)
-                conditional = conditional.replace("lib%_LIBRARIES", "lib_LIBRARIES")
-                conditional = conditional.replace("lib%_LTLIBRARIES", "lib_LTLIBRARIES")
-                if transform_check_PROGRAMS:
-                    conditional = conditional.replace("check_PROGRAMS", "noinst_PROGRAMS")
-                conditional = conditional.replace(r"${gl_include_guard_prefix}", config.include_guard_prefix)
-                unconditional = module.unconditional_automake_snippet.format(auxdir=config.auxdir)
-                unconditional = LibMakefileGenerator._LIBNAME.sub("{libname}_{libext}_\\1".format(**kwargs), unconditional)
-                if (conditional + unconditional).strip():
-                    lines.append("## begin gnulib module {}".format(module.name))
-                    if module.name == "alloca":
-                        lines.append("{libname}_{libext}_LIBADD += @{perhaps_LT}ALLOCA@".format(**kwargs))
-                        lines.append("{libname}_{libext}_DEPENDENCIES += @{perhaps_LT}ALLOCA@".format(**kwargs))
-                    lines += list(process(module, conditional, unconditional))
-                    lines.append("## end   gnulib module {}".format(module.name))
-                    lines.append("")
-            return (uses_subdirs, lines)
-
-        (uses_subdirs, overall_snippet) = _snippet()
-        if "makefile_name" not in explicit:
-            # If there are source files in subdirectories, prevent collision of the
-            # object files (example: hash.c and libxml/hash.c).
-            yield "AUTOMAKE_OPTIONS = 1.9.6 gnits{}".format(" subdir-objects" if uses_subdirs else "")
-        yield ""
-        if "makefile_name" not in explicit:
-            yield "SUBDIRS ="
-            yield "noinst_HEADERS ="
-            yield "noinst_LIBRARIES ="
-            yield "noinst_LTLIBRARIES ="
-            # Automake versions < 1.11.4 create an empty pkgdatadir at
-            # installation time if you specify pkgdata_DATA to empty.
-            # See automake bugs #10997 and #11030:
-            #  * http://debbugs.gnu.org/10997
-            #  * http://debbugs.gnu.org/11030
-            # So we need this workaround.
-            if {line for line in overall_snippet if line.startswith()}:
-                yield "pkgdata_DATA ="
-            yield "EXTRA_DIST ="
-            yield "BUILT_SOURCES ="
-            yield "SUFFIXES ="
-        yield "MOSTLYCLEANFILES {} core *.stackdump".format(assign)
-        if "makefile_name" not in explicit:
-            yield "MOSTLYCLEANDIRS ="
-            yield "CLEANFILES ="
-            yield "DISTCLEANFILES ="
-            yield "MAINTAINERCLEANFILES ="
-
-        if gnumake:
-            yield "# Start of GNU Make output."
-            self.__autoconf = "autoconf"
-            self.__configure_ac = "configure.ac"
-            cmdargs = (self.__autoconf, "-t", "AC_SUBST:$1 = @$1@", self.__configure_ac)
-            with _sp.Popen(cmdargs, stdout=_sp.PIPE, stderr=_sp.PIPE) as process:
-                (stdout, stderr) = process.communicate()
-                stdout = stdout.decode("UTF-8")
-                stderr = stderr.decode("UTF-8")
-                if process.returncode == 0:
-                    for line in sorted(stdout.splitlines()):
-                        yield line
-                else:
-                    yield "== gnulib-tool GNU Make output failed as follows =="
-                    for line in stderr.splitlines():
-                        yield "# stderr: {}".format(line)
-            yield "# End of GNU Make output."
-        else:
-            yield "# No GNU Make output."
-
-        for (directory, key, value) in self.__mkedits:
-            if key and _os.path.join(directory, "Makefile.am") == self.__path:
-                yield f"{key} += {value}"
-
-        cppflags = "".join((
-            " -D{}=1".format(config.witness_c_macro) if "witness_c_macro" in explicit else "",
-            " -DGNULIB_STRICT_CHECKING=1" if testing else "",
-        ))
-        if "makefile_name" not in explicit:
-            yield ""
-            yield "AM_CPPFLAGS ={}".format(cppflags)
-            yield "AM_CFLAGS ="
-        elif "".join(cppflags):
-            yield ""
-            yield "AM_CPPFLAGS +={}".format(cppflags)
-        yield ""
-
-        snippet = "\n".join(overall_snippet)
-        if "makefile_name" in explicit:
-            makefile = _os.path.join(config.source_base, "Makefile.am")
-            if _os.path.exists(makefile):
-                with _codecs.open(makefile, "rb", "UTF-8") as stream:
-                    snippet += ("\n" + stream.read())
-        # One of the snippets or the user's Makefile.am already specifies an
-        # installation location for the library. Don't confuse automake by saying
-        # it should not be installed.
-        # By default, the generated library should not be installed.
-        regex = "^[a-zA-Z0-9_]*_{perhaps_LT}LIBRARIES\\s*\\+?\\=\\s*{libname}\\.{libext}$"
-        pattern = _re.compile(regex.format(**kwargs), _re.S)
-        if not pattern.findall(snippet):
-            yield "noinst_{perhaps_LT}LIBRARIES += {libname}.{libext}".format(**kwargs)
-
-        yield ""
-        yield "{libname}_{libext}_SOURCES =".format(**kwargs)
-        # Here we use $(LIBOBJS), not @LIBOBJS@. The value is the same. However,
-        # automake during its analysis looks for $(LIBOBJS), not for @LIBOBJS@.
-        yield "{libname}_{libext}_LIBADD = $({macro_prefix}_{perhaps_LT}LIBOBJS)".format(**kwargs)
-        yield "{libname}_{libext}_DEPENDENCIES = $({macro_prefix}_{perhaps_LT}LIBOBJS)".format(**kwargs)
-        yield "EXTRA_{libname}_{libext}_SOURCES =".format(**kwargs)
-        if libtool:
-            yield "{libname}_{libext}_LDFLAGS = $(AM_LDFLAGS)".format(**kwargs)
-            yield "{libname}_{libext}_LDFLAGS += -no-undefined".format(**kwargs)
-            # Synthesize an ${libname}_${libext}_LDFLAGS augmentation by combining
-            # the link dependencies of all modules.
-            def _directives(modules):
-                directives = (module.link_directive for module in sorted(modules))
-                for directive in filter(lambda directive: directive.strip(), directives):
-                    index = directive.find("when linking with libtool")
-                    if index != -1:
-                        directive = directive[:index].strip(" ")
-                    yield directive
-            for directive in _directives(database.main_modules):
-                yield ("{libname}_{libext}_LDFLAGS += {directive}".format(directive=directive, **kwargs))
-        yield ""
-
-        if "po_base" in explicit:
-            yield "AM_CPPFLAGS += -DDEFAULT_TEXT_DOMAIN=\\\"{}-gnulib\\\"".format(config.po_domain)
-            yield ""
-
-        for line in overall_snippet:
-            yield line.replace("$(top_srcdir)/build-aux/", _os.path.join("$(top_srcdir)", config.auxdir))
-        yield ""
-        yield "mostlyclean-local: mostlyclean-generic"
-        yield "\t@for dir in '' $(MOSTLYCLEANDIRS); do \\"
-        yield "\t  if test -n \"$$dir\" && test -d $$dir; then \\"
-        yield "\t    echo \"rmdir $$dir\"; rmdir $$dir; \\"
-        yield "\t  fi; \\"
-        yield "\tdone; \\"
-        yield "\t:"
+    date = _datetime.now()
+    yield "## DO NOT EDIT! GENERATED AUTOMATICALLY!"
+    yield "## Process this file with automake to produce Makefile.in."
+    yield "# Copyright (C) 2002-{} Free Software Foundation, Inc.".format(date.year)
+    for line in __DISCLAIMER:
+        yield line
+    yield "#"
+    yield "# This file represents the specification of how gnulib-tool is used."
+    yield "# It acts as a cache: It is written and read by gnulib-tool."
+    yield "# In projects that use version control, this file is meant to be put under"
+    yield "# version control, like the configure.ac and various Makefile.am files."
+    yield ""
+    yield ""
+    yield "# Specification in the form of a command-line invocation:"
+    yield "gl_LOCAL_DIR([$relative_local_gnulib_path])"
+    yield "gl_MODULES(["
+    for module in sorted(config.modules):
+        yield "  {}".format(module)
+    yield "])"
+    for key in ("obsolete", "cxx_tests", "longrunning_tests", "privileged_tests", "unportable_tests"):
+        if config[key]:
+            yield "gl_WITH_{}".format(key.upper())
+    if config.all_tests:
+        yield "gl_WITH_ALL_TESTS"
+    yield "gl_AVOID([{}])".format(" ".join(sorted(config.avoids)))
+    yield "gl_SOURCE_BASE([{}])".format(config.source_base)
+    yield "gl_M4_BASE([{}])".format(config.m4_base)
+    yield "gl_PO_BASE([{}])".format(config.po_base)
+    yield "gl_DOC_BASE([{}])".format(config.doc_base)
+    yield "gl_TESTS_BASE([{}])".format(config.tests_base)
+    if config.tests:
+        yield "gl_WITH_TESTS"
+    yield "gl_LIB([{}])".format(config.libname)
+    if config.licenses in _LGPL:
+        lgpl = _LGPL[config.licenses]
+        yield "gl_LGPL([{}])".format(lgpl) if lgpl != "yes" else "gl_LGPL"
+    yield "gl_MAKEFILE_NAME([{}])".format(config.makefile_name)
+    if config.conditionals:
+        yield "gl_CONDITIONAL_DEPENDENCIES"
+    if config.libtool:
+        yield "gl_LIBTOOL"
+    yield "gl_MACRO_PREFIX([{}])".format(config.macro_prefix)
+    yield "gl_PO_DOMAIN([{}])".format(config.po_domain)
+    yield "gl_WITNESS_C_MACRO([{}])".format(config.witness_c_macro)
+    if config.vc_files:
+        yield "gl_VC_FILES([{}])".format(" ".join(sorted(config.vc_files)))
 
 
 
-class GnulibCacheGenerator(BaseGenerator):
-    """gnulib-cache.m4 generator"""
-    _OPTIONS = (
-        ("obsolete", "gl_WITH_OBSOLETE"),
-        ("cxx_tests", "gl_WITH_CXX_TESTS"),
-        ("longrunning", "gl_WITH_LONGRUNNING_TESTS"),
-        ("privileged", "gl_WITH_PRIVILEGED_TESTS"),
-    )
-
-
-    def __init__(self, config):
-        _type_assert("config", config, _BaseConfig)
-        super().__init__()
-        self.__config = config
-
+def gnulib_comp(config, explicit, database, subdirs, **override):
+    """gnulib-comp.m4 generator"""
+    _type_assert("config", config, _BaseConfig)
+    _type_assert("explicit", explicit, _ITERABLES)
+    _type_assert("database", database, _Database)
+    config = _BaseConfig(**config)
+    for (key, value) in override.items():
+        config[key] = value
+    macro_prefix = config.macro_prefix
+    main_modules = database.main_modules
+    test_modules = database.test_modules
 
-    def __iter__(self):
-        date = _datetime.now()
-        config = self.__config
-        yield "## DO NOT EDIT! GENERATED AUTOMATICALLY!"
-        yield "## Process this file with automake to produce Makefile.in."
-        yield "# Copyright (C) 2002-{} Free Software Foundation, Inc.".format(date.year)
-        for line in _DISCLAIMER:
-            yield line
-        yield "#"
-        yield "# This file represents the specification of how gnulib-tool is used."
-        yield "# It acts as a cache: It is written and read by gnulib-tool."
-        yield "# In projects that use version control, this file is meant to be put under"
-        yield "# version control, like the configure.ac and various Makefile.am files."
-        yield ""
-        yield ""
-        yield "# Specification in the form of a command-line invocation:"
-        yield "gl_LOCAL_DIR([$relative_local_gnulib_path])"
-        yield "gl_MODULES(["
-        for module in sorted(config.modules):
-            yield "  {}".format(module)
-        yield "])"
-        for key in ("obsolete", "cxx_tests", "longrunning_tests", "privileged_tests", "unportable_tests"):
-            if config[key]:
-                yield "gl_WITH_{}".format(key.upper())
-        if config.all_tests:
-            yield "gl_WITH_ALL_TESTS"
-        yield "gl_AVOID([{}])".format(" ".join(sorted(config.avoids)))
-        yield "gl_SOURCE_BASE([{}])".format(config.source_base)
-        yield "gl_M4_BASE([{}])".format(config.m4_base)
-        yield "gl_PO_BASE([{}])".format(config.po_base)
-        yield "gl_DOC_BASE([{}])".format(config.doc_base)
-        yield "gl_TESTS_BASE([{}])".format(config.tests_base)
-        if config.tests:
-            yield "gl_WITH_TESTS"
-        yield "gl_LIB([{}])".format(config.libname)
-        if config.licenses in _LGPL:
-            lgpl = _LGPL[config.licenses]
-            yield "gl_LGPL([{}])".format(lgpl) if lgpl != "yes" else "gl_LGPL"
-        yield "gl_MAKEFILE_NAME([{}])".format(config.makefile_name)
-        if config.conditionals:
-            yield "gl_CONDITIONAL_DEPENDENCIES"
-        if config.libtool:
-            yield "gl_LIBTOOL"
-        yield "gl_MACRO_PREFIX([{}])".format(config.macro_prefix)
-        yield "gl_PO_DOMAIN([{}])".format(config.po_domain)
-        yield "gl_WITNESS_C_MACRO([{}])".format(config.witness_c_macro)
-        if config.vc_files:
-            yield "gl_VC_FILES([{}])".format(" ".join(sorted(config.vc_files)))
-
-
-
-class GnulibCompGenerator(BaseGenerator):
-    """gnulib-comp.m4 generator"""
-    def __init__(self, config, explicit, database, macro_prefix=None):
-        _type_assert("config", config, _BaseConfig)
-        _type_assert("explicit", explicit, _ITERABLES)
-        _type_assert("database", database, _Database)
-        if macro_prefix is None:
-            macro_prefix = config.macro_prefix
-        _type_assert("macro_prefix", macro_prefix, str)
-        super().__init__()
-        self.__config = config
-        self.__explicit = explicit
-        self.__database = database
-        self.__macro_prefix = macro_prefix
-        self.__uses_subdirs = True
-
-
-    def __iter__(self):
-        config = self.__config
-        explicit = self.__explicit
-        database = self.__database
-        main_modules = database.main_modules
-        test_modules = database.test_modules
-        macro_prefix = self.__macro_prefix
-
-        date = _datetime.now()
-        yield "# DO NOT EDIT! GENERATED AUTOMATICALLY!"
-        yield "# Copyright (C) 2002-{} Free Software Foundation, Inc.".format(date.year)
-        iterable = super().__iter__()
-        try:
-            next(iterable)
-        except StopIteration:
-            pass
-        for line in iterable:
-            yield line
+    date = _datetime.now()
+    yield "# DO NOT EDIT! GENERATED AUTOMATICALLY!"
+    yield "# Copyright (C) 2002-{} Free Software Foundation, Inc.".format(date.year)
+    for line in __DISCLAIMER[1:]:
+        yield line
 
-        yield "#"
-        yield "# This file represents the compiled summary of the specification in"
-        yield "# gnulib-cache.m4. It lists the computed macro invocations that need"
-        yield "# to be invoked from configure.ac."
-        yield "# In projects that use version control, this file can be treated like"
-        yield "# other built files."
-        yield ""
-        yield ""
-        yield "# This macro should be invoked from {}, in the section".format(config.ac_file)
-        yield "# \"Checks for programs\", right after AC_PROG_CC, and certainly before"
-        yield "# any checks for libraries, header files, types and library functions."
-        yield "AC_DEFUN([{}_EARLY],".format(config.macro_prefix)
-        yield "["
-        yield "  m4_pattern_forbid([^gl_[A-Z]])dnl the gnulib macro namespace"
-        yield "  m4_pattern_allow([^gl_ES$])dnl a valid locale name"
-        yield "  m4_pattern_allow([^gl_LIBOBJS$])dnl a variable"
-        yield "  m4_pattern_allow([^gl_LTLIBOBJS$])dnl a variable"
-        yield ""
-        yield "  # Pre-early section."
-        if "externsions" not in (module.name for module in database.final_modules):
-            yield "  AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS])"
-        yield "  AC_REQUIRE([gl_PROG_AR_RANLIB])"
-        yield ""
-        if not config.gnumake and self.__uses_subdirs:
-            yield "  AC_REQUIRE([AM_PROG_CC_C_O])"
-        for module in database.final_modules:
-            yield "  # Code from module {}:".format(module.name)
-            lines = module.early_autoconf_snippet.split("\n")
-            for line in filter(lambda line: line.strip(), lines):
-                yield "  {}".format(line)
-        yield "])"
-        yield ""
-        yield "# This macro should be invoked from {}, in the section".format(config.ac_file)
-        yield "# \"Check for header files, types and library functions\"."
-        yield "AC_DEFUN([{}_INIT],".format(macro_prefix)
-        yield "["
-        if config.libtool:
-            yield "  AM_CONDITIONAL([GL_COND_LIBTOOL], [true])"
-            yield "  gl_cond_libtool=true"
-        else:
-            yield "  AM_CONDITIONAL([GL_COND_LIBTOOL], [false])"
-            yield "  gl_cond_libtool=false"
-            yield "  gl_libdeps="
-            yield "  gl_ltlibdeps="
-        yield "  gl_m4_base='{}'".format(config.m4_base)
-        for line in InitMacroHeaderGenerator(config, macro_prefix):
-            yield line
-        yield "  gl_source_base='{}'".format(config.source_base)
-        if "witness_c_macro" in explicit:
-            yield "  m4_pushdef([gl_MODULE_INDICATOR_CONDITION], [{}])".format(config.witness_c_macro)
-        for line in AutoconfMultisnippetGenerator(config, database, main_modules, True, False, True, macro_prefix):
-            yield line
-        if "witness_c_macro" in explicit:
-            yield "  m4_popdef([gl_MODULE_INDICATOR_CONDITION])"
-        yield "  # End of code from modules"
-        for line in InitMacroFooterGenerator(config, macro_prefix):
-            yield line
-        yield "  gltests_libdeps="
-        yield "  gltests_ltlibdeps="
-        for line in InitMacroHeaderGenerator(config, (macro_prefix + "tests")):
-            yield line
-        yield "  gl_source_base='{}'".format(config.tests_base)
-        # Define a tests witness macro that depends on the package.
-        # PACKAGE is defined by AM_INIT_AUTOMAKE, PACKAGE_TARNAME is defined by AC_INIT.
-        # See <http://lists.gnu.org/archive/html/automake/2009-05/msg00145.html>.
-        yield "changequote(,)dnl"
-        yield "".join((
-            "  {}tests_WITNESS=IN_`".format(macro_prefix),
-            "echo \"${PACKAGE-$PACKAGE_TARNAME}\"",
-            " | ",
-            "LC_ALL=C tr abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ",
-            " | ",
-            "LC_ALL=C sed -e 's/[^A-Z0-9_]/_/g'",
-            "`_GNULIB_TESTS",
-        ))
-        yield "changequote([, ])dnl"
-        yield "  AC_SUBST([{}tests_WITNESS])".format(macro_prefix)
-        yield "  gl_module_indicator_condition=${}tests_WITNESS".format(macro_prefix)
-        yield "  m4_pushdef([gl_MODULE_INDICATOR_CONDITION], [$gl_module_indicator_condition])"
-        for line in AutoconfMultisnippetGenerator(config, database, test_modules, True, True, True, macro_prefix):
-            yield line
+    yield "#"
+    yield "# This file represents the compiled summary of the specification in"
+    yield "# gnulib-cache.m4. It lists the computed macro invocations that need"
+    yield "# to be invoked from configure.ac."
+    yield "# In projects that use version control, this file can be treated like"
+    yield "# other built files."
+    yield ""
+    yield ""
+    yield "# This macro should be invoked from {}, in the section".format(config.ac_file)
+    yield "# \"Checks for programs\", right after AC_PROG_CC, and certainly before"
+    yield "# any checks for libraries, header files, types and library functions."
+    yield "AC_DEFUN([{}_EARLY],".format(config.macro_prefix)
+    yield "["
+    yield "  m4_pattern_forbid([^gl_[A-Z]])dnl the gnulib macro namespace"
+    yield "  m4_pattern_allow([^gl_ES$])dnl a valid locale name"
+    yield "  m4_pattern_allow([^gl_LIBOBJS$])dnl a variable"
+    yield "  m4_pattern_allow([^gl_LTLIBOBJS$])dnl a variable"
+    yield ""
+    yield "  # Pre-early section."
+    if "externsions" not in (module.name for module in database.final_modules):
+        yield "  AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS])"
+    yield "  AC_REQUIRE([gl_PROG_AR_RANLIB])"
+    yield ""
+    if not config.gnumake and subdirs:
+        yield "  AC_REQUIRE([AM_PROG_CC_C_O])"
+    for module in database.final_modules:
+        yield "  # Code from module {}:".format(module.name)
+        lines = module.early_autoconf_snippet.split("\n")
+        for line in filter(lambda line: line.strip(), lines):
+            yield "  {}".format(line)
+    yield "])"
+    yield ""
+    yield "# This macro should be invoked from {}, in the section".format(config.ac_file)
+    yield "# \"Check for header files, types and library functions\"."
+    yield "AC_DEFUN([{}_INIT],".format(macro_prefix)
+    yield "["
+    if config.libtool:
+        yield "  AM_CONDITIONAL([GL_COND_LIBTOOL], [true])"
+        yield "  gl_cond_libtool=true"
+    else:
+        yield "  AM_CONDITIONAL([GL_COND_LIBTOOL], [false])"
+        yield "  gl_cond_libtool=false"
+        yield "  gl_libdeps="
+        yield "  gl_ltlibdeps="
+    yield "  gl_m4_base='{}'".format(config.m4_base)
+    for line in init_macro_header(config, macro_prefix=macro_prefix):
+        yield line
+    yield "  gl_source_base='{}'".format(config.source_base)
+    if "witness_c_macro" in explicit:
+        yield "  m4_pushdef([gl_MODULE_INDICATOR_CONDITION], [{}])".format(config.witness_c_macro)
+    for line in autoconf_snippet_sequence(config, database, main_modules, True, False, True, macro_prefix=macro_prefix):
+        yield line
+    if "witness_c_macro" in explicit:
         yield "  m4_popdef([gl_MODULE_INDICATOR_CONDITION])"
-        for line in InitMacroFooterGenerator(config, (macro_prefix + "tests")):
-            yield line
+    yield "  # End of code from modules"
+    for line in init_macro_footer(config, macro_prefix=macro_prefix):
+        yield line
+    yield "  gltests_libdeps="
+    yield "  gltests_ltlibdeps="
+    for line in init_macro_header(config, macro_prefix=(macro_prefix + "tests")):
+        yield line
+    yield "  gl_source_base='{}'".format(config.tests_base)
+    # Define a tests witness macro that depends on the package.
+    # PACKAGE is defined by AM_INIT_AUTOMAKE, PACKAGE_TARNAME is defined by AC_INIT.
+    # See <http://lists.gnu.org/archive/html/automake/2009-05/msg00145.html>.
+    yield "changequote(,)dnl"
+    yield "".join((
+        "  {}tests_WITNESS=IN_`".format(macro_prefix),
+        "echo \"${PACKAGE-$PACKAGE_TARNAME}\"",
+        " | ",
+        "LC_ALL=C tr abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ",
+        " | ",
+        "LC_ALL=C sed -e 's/[^A-Z0-9_]/_/g'",
+        "`_GNULIB_TESTS",
+    ))
+    yield "changequote([, ])dnl"
+    yield "  AC_SUBST([{}tests_WITNESS])".format(macro_prefix)
+    yield "  gl_module_indicator_condition=${}tests_WITNESS".format(macro_prefix)
+    yield "  m4_pushdef([gl_MODULE_INDICATOR_CONDITION], [$gl_module_indicator_condition])"
+    for line in autoconf_snippet_sequence(config, database, test_modules, True, True, True, macro_prefix=macro_prefix):
+        yield line
+    yield "  m4_popdef([gl_MODULE_INDICATOR_CONDITION])"
+    for line in init_macro_footer(config, macro_prefix=(macro_prefix + "tests")):
+        yield line
 
-        # _LIBDEPS and _LTLIBDEPS variables are not needed if this library is
-        # created using libtool, because libtool already handles the dependencies.
-        if not config.libtool:
-            libname = config.libname.upper()
-            yield "  {}_LIBDEPS=\"$gl_libdeps\"".format(libname)
-            yield "  AC_SUBST([{}_LIBDEPS])".format(libname)
-            yield "  {}_LTLIBDEPS=\"$gl_ltlibdeps\"".format(libname)
-            yield "  AC_SUBST([{}_LTLIBDEPS])".format(libname)
-        if database.libtests:
-            yield "  LIBTESTS_LIBDEPS=\"$gltests_libdeps\""
-            yield "  AC_SUBST([LIBTESTS_LIBDEPS])"
-        yield "])"
-        for line in InitMacroDoneGenerator(config, source_base=config.source_base, macro_prefix=macro_prefix):
-            yield line
-        for line in InitMacroDoneGenerator(config, source_base=config.tests_base, macro_prefix=(macro_prefix + "tests")):
-            yield line
-        yield ""
-        yield "# This macro records the list of files which have been installed by"
-        yield "# gnulib-tool and may be removed by future gnulib-tool invocations."
-        yield "AC_DEFUN([{}_FILE_LIST], [".format(macro_prefix)
-        for file in sorted(set(database.main_files + database.test_files)):
-            yield "  {}".format(file)
-        yield "])"
+    # _LIBDEPS and _LTLIBDEPS variables are not needed if this library is
+    # created using libtool, because libtool already handles the dependencies.
+    if not config.libtool:
+        libname = config.libname.upper()
+        yield "  {}_LIBDEPS=\"$gl_libdeps\"".format(libname)
+        yield "  AC_SUBST([{}_LIBDEPS])".format(libname)
+        yield "  {}_LTLIBDEPS=\"$gl_ltlibdeps\"".format(libname)
+        yield "  AC_SUBST([{}_LTLIBDEPS])".format(libname)
+    if database.libtests:
+        yield "  LIBTESTS_LIBDEPS=\"$gltests_libdeps\""
+        yield "  AC_SUBST([LIBTESTS_LIBDEPS])"
+    yield "])"
+    for line in init_macro_done(config, source_base=config.source_base, macro_prefix=macro_prefix):
+        yield line
+    for line in init_macro_done(config, source_base=config.tests_base, macro_prefix=(macro_prefix + "tests")):
+        yield line
+    yield ""
+    yield "# This macro records the list of files which have been installed by"
+    yield "# gnulib-tool and may be removed by future gnulib-tool invocations."
+    yield "AC_DEFUN([{}_FILE_LIST], [".format(macro_prefix)
+    for file in sorted(set(database.main_files + database.test_files)):
+        yield "  {}".format(file)
+    yield "])"
--- a/pygnulib/module.py	Sun Jan 21 20:43:31 2018 +0300
+++ b/pygnulib/module.py	Mon Jan 29 19:07:42 2018 +0300
@@ -347,9 +347,9 @@
         if self.test:
             # *-tests module live in tests/, not lib/.
             # Synthesize an EXTRA_DIST augmentation.
-            test_files = {file for file in files if file.startswith("tests/")}
+            test_files = tuple(file[len("tests/"):] for file in files if file.startswith("tests/"))
             if test_files:
-                result += ("EXTRA_DIST += {}".format(" ".join(sorted(test_files))) + "\n")
+                result += ("EXTRA_DIST += {}".format(" ".join(test_files)) + "\n")
             return result
         snippet = self.conditional_automake_snippet
         lib_SOURCES = False
@@ -376,7 +376,7 @@
         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(sorted(extra_files))) + "\n")
+            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
@@ -406,7 +406,7 @@
     @property
     def automake_snippet(self):
         """full automake snippet (conditional + unconditional parts)"""
-        return self.conditional_automake_snippet + self.unconditional_automake_snippet
+        return "\n".join((self.conditional_automake_snippet, self.unconditional_automake_snippet))
 
 
     @property
@@ -706,7 +706,7 @@
                         pass # ignore non-existent tests
                 for (dependency, condition) in demander.dependencies:
                     dependency = lookup(dependency)
-                    if config.gnumake and condition.startswith("if "):
+                    if config.gnumake and condition and condition.startswith("if "):
                         # 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
@@ -903,7 +903,7 @@
         test_files = set()
         for file in _files(test_modules):
             if file.startswith("lib/"):
-                file = ("lib=tests/" + file[len("lib/"):])
+                file = ("tests=lib/" + file[len("lib/"):])
             test_files.add(file)
 
         self.__libtests = libtests
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pygnulib/tools.py	Mon Jan 29 19:07:42 2018 +0300
@@ -0,0 +1,64 @@
+#!/usr/bin/python
+# encoding: UTF-8
+"""gnulib command-line tools"""
+
+
+
+import os as _os
+import subprocess as _sp
+
+
+from .error import type_assert as _type_assert
+
+
+
+class _PipeMeta(type):
+    __INSTANCE = None
+    def __call__(cls, *args, **kwargs):
+        if _PipeMeta.__INSTANCE is None:
+            _PipeMeta.__INSTANCE = super(_PipeMeta, cls).__call__(*args, **kwargs)
+        return _PipeMeta.__INSTANCE
+
+
+class Pipe(metaclass=_PipeMeta):
+    """pipe handle singleton"""
+    pass
+
+
+
+class Executable:
+    """command-line program or script"""
+    def __init__(self, name, path=None, encoding=None):
+        _type_assert("name", name, str)
+        if path is not None:
+            _type_assert("path", path, str)
+        if encoding is not None:
+            _type_assert("encoding", encoding, str)
+        self.__name = name
+        self.__path = path
+        self.__encoding = encoding
+
+
+    @property
+    def name(self):
+        """executable name"""
+        return self.__name
+
+
+    @property
+    def path(self):
+        """executable path"""
+        return self.__path if self.__path else self.name
+
+
+    def __call__(self, *args, **kwargs):
+        """
+        Invoke command-line tool with the given arguments.
+        Upon execution subprocess.Popen instance is returned.
+        """
+        args = ([self.path] + list(args))
+        for key in ("stdin", "stdout", "stderr"):
+            if isinstance(kwargs.get(key, Pipe()), Pipe):
+                kwargs[key] = _sp.PIPE
+        kwargs.setdefault("encoding", self.__encoding)
+        return _sp.Popen(args, **kwargs)
--- a/pygnulib/vfs.py	Sun Jan 21 20:43:31 2018 +0300
+++ b/pygnulib/vfs.py	Mon Jan 29 19:07:42 2018 +0300
@@ -56,10 +56,10 @@
         _type_assert("name", name, str)
         parts = []
         replaced = False
-        path = _os.path.normpath(name)
-        if _os.path.isabs(path):
+        name = _os.path.normpath(name)
+        if _os.path.isabs(name):
             raise ValueError("name cannot be an absolute path")
-        for part in path.split(_os.path.sep):
+        for part in name.split(_os.path.sep):
             if part == "..":
                 parts += [part]
                 continue
@@ -67,8 +67,18 @@
                 part = self.__table[part]
                 replaced = True
             parts += [part]
-        path = _os.path.sep.join(parts)
-        return _os.path.normpath(path)
+        name = _os.path.sep.join(parts)
+        return _os.path.normpath(name)
+
+
+    def __setitem__(self, src, dst):
+        for name in (src, dst):
+            _type_assert("name", name, str)
+            if _os.path.isabs(name):
+                raise ValueError("name cannot be an absoule path")
+        src = _os.path.normpath(src)
+        dst = _os.path.normpath(dst)
+        self.__table[src] = dst
 
 
     @property
@@ -291,7 +301,7 @@
 
     def __init__(self, prefix, **table):
         super().__init__(prefix, **table)
-        self.__cache = {}
+        self.__cache = {"dummy": _DummyModule()}
         if not _os.path.exists(self.absolute):
             raise FileNotFoundError(self.absolute)
         if not _os.path.isdir(self.absolute):
@@ -307,10 +317,7 @@
             return self.__cache[name]
         path = _os.path.join(self.absolute, self["modules"], name)
         try:
-            if name != "dummy":
-                self.__cache[name] = _GnulibModule(path=path, name=name)
-            else:
-                self.__cache[name] = _DummyModule()
+            self.__cache[name] = _GnulibModule(path=path, name=name)
             return self.__cache[name]
         except FileNotFoundError:
             raise _UnknownModuleError(name)