view pygnulib/generator.py @ 39136:25eedd702b46

more fixes regarding --lgpl option
author Dmitry Selyutin <ghostmansd@gmail.com>
date Thu, 05 Jul 2018 21:57:52 +0300
parents 01e983399117
children 6289200390d5
line wrap: on
line source

#!/usr/bin/python
# encoding: UTF-8
"""gnulib generators API"""



import os as _os
import re as _re
import codecs as _codecs
import subprocess as _sp
from datetime import datetime as _datetime


from .config import BaseConfig as _BaseConfig
from .config import LGPLv2_LICENSE as _LGPLv2_LICENSE
from .config import LGPLv3_LICENSE as _LGPLv3_LICENSE
from .config import GPLv2_LICENSE as _GPLv2_LICENSE
from .config import LGPL_LICENSES as _LGPL_LICENSES
from .misc import Executable as _Executable
from .module import BaseModule as _BaseModule
from .module import Database as _Database



_LGPL = {
    tuple(sorted(_LGPLv2_LICENSE)): "2",
    tuple(sorted(_LGPLv3_LICENSE)): "3",
    tuple(sorted(_LGPL_LICENSES)): "yes",
    tuple(sorted(_GPLv2_LICENSE | _LGPLv3_LICENSE)): "3orGPLv2",
}
__DISCLAIMER = (
    "## DO NOT EDIT! GENERATED AUTOMATICALLY!",
    "#",
    "# This file is free software; you can redistribute it and/or modify",
    "# it under the terms of the GNU General Public License as published by",
    "# the Free Software Foundation; either version 3 of the License, or",
    "# (at your option) any later version.",
    "#",
    "# This file is distributed in the hope that it will be useful,",
    "# but WITHOUT ANY WARRANTY; without even the implied warranty of",
    "# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the",
    "# GNU General Public License for more details.",
    "#",
    "# You should have received a copy of the GNU General Public License",
    "# along with this file.  If not, see <http://www.gnu.org/licenses/>.",
    "#",
    "# As a special exception to the GNU General Public License,",
    "# this file may be distributed as part of a program that",
    "# contains a configuration script generated by Autoconf, under",
    "# the same distribution terms as the rest of that program.",
    "#",
    "# Generated by gnulib-tool.",
)
_ITERABLES = frozenset((list, tuple, set, frozenset, type({}.keys()), type({}.values())))



__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 po_make_vars(config, **override):
    """Generate PO Makefile parameterization."""
    config = _BaseConfig(**config)
    for (key, value) in override.items():
        config[key] = value

    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



def POTFILES(config, files, **override):
    """Generate file list to be passed to xgettext."""
    config = _BaseConfig(**config)
    for (key, value) in override.items():
        config[key] = value

    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:])



def autoconf_snippet(config, module, toplevel, no_libtool, no_gettext, **override):
    """
    Generate autoconf snippet for a standalone module."""
    config = _BaseConfig(**config)
    for (key, value) in override.items():
        config[key] = value

    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 = snippet.replace(r"${gl_include_guard_prefix}", include_guard_prefix)
        if no_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)
        if no_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 config.libtool and not no_libtool:
            yield "changequote(,)dnl"
            yield "LTALLOCA=`echo \"$ALLOCA\" | sed -e 's/\\.[^.]* /.lo /g;s/\\.[^.]*$/.lo/'`"
            yield "changequote([, ])dnl"
            yield "AC_SUBST([LTALLOCA])"



def autoconf_snippets(config, database, modules, toplevel, no_libtool, no_gettext, **override):
    """Generate an autoconf snippet for multiple modules."""
    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:
            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:
                    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:
                    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}])"



__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]))",

    # 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]))",

    # 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_header(config, **override):
    """Generate the first few statements of the gl_INIT macro."""
    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)



__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])",
    "  ])",
)

def init_macro_footer(config, **override):
    """Generate the last few statements of the gl_INIT macro."""
    config = _BaseConfig(**config)
    for (key, value) in override.items():
        config[key] = value
    macro_prefix = config.macro_prefix

    for line in __INIT_MACRO_FOOTER:
        yield line.format(**config)



__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, [ ])",
    "    ])",
    "  ])",
    "])",
)

def init_macro_done(config, **override):
    """
    Generate few statements after the gl_INIT macro.

    [macro_prefix] the prefix of the macros 'gl_EARLY' and 'gl_INIT'
    """
    config = _BaseConfig(**config)
    for (key, value) in override.items():
        config[key] = value

    for line in __INIT_MACRO_DONE:
        yield line.format(**config)



__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 = (
    ("makefile_name", lambda k, v, d: "" if v is None else f"--makefile-name={v}"),
    ("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),
    ("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 command_line(config, explicit, **override):
    """Generate gnulib command-line invocation."""
    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 f"--avoid={module}"
    licenses = tuple(sorted(config.licenses))
    if licenses in _LGPL:
        lgpl = _LGPL[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 config.modules:
        yield f"{module}"



__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\s(.*?)$", _re.S | _re.M)


def _lib_makefile_callback(database, macro_prefix, conditionals, gnumake):

    def _automake_conditional(module, conditional, unconditional):
        yield ""
        if database.conditional(module):
            yield "if {}".format(module.conditional_name(macro_prefix))
            yield conditional
            yield "endif"
        else:
            yield conditional
        yield unconditional

    def _automake_unconditional(module, conditional, unconditional):
        yield ""
        yield conditional
        yield unconditional

    def _gnumake_conditional(module, conditional, unconditional):
        yield "ifeq (,$(OMIT_GNULIB_MODULE_{}))".format(module.name)
        yield ""
        if database.conditional(module):
            yield "ifneq (,$({}))".format(module.conditional_name(macro_prefix))
            yield __MAKEFILE_GNUMAKE.sub("ifneq (,$(\\1))", conditional)
            yield "endif"
        else:
            yield __MAKEFILE_GNUMAKE.sub("ifneq (,$(\\1))", conditional)
        yield "endif"
        yield __MAKEFILE_GNUMAKE.sub("ifneq (,$(\\1))", unconditional)

    def _gnumake_unconditional(module, conditional, unconditional):
        yield ""
        yield __MAKEFILE_GNUMAKE.sub("ifneq (,$(\\1))", conditional)
        yield __MAKEFILE_GNUMAKE.sub("ifneq (,$(\\1))", unconditional)

    callbacks = (
        (_automake_unconditional, _gnumake_unconditional),
        (_automake_conditional, _gnumake_conditional),
    )
    return callbacks[conditionals][gnumake]


def lib_makefile(path, config, explicit, database, mkedits, testing, autoconf, **override):
    """Generate library Makefile.am file."""
    if not isinstance(autoconf, _Executable):
        raise TypeError("autoconf: executable expected")

    date = _datetime.now()
    libname = config.libname
    po_domain = config.po_domain
    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 f"# Copyright (C) 2002-{date.year} Free Software Foundation, Inc."
    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 f"# Reproduce by: {actioncmd}"
    yield ""

    callback = _lib_makefile_callback(database, macro_prefix, 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(f"{libname}_{libext}_\\1", 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(f"{libname}_{libext}_\\1", unconditional)
            if (conditional + unconditional).strip():
                lines.append(f"## begin gnulib module {module.name}")
                if module.name == "alloca":
                    lines.append(f"{libname}_{libext}_LIBADD += @{perhaps_LT}ALLOCA@")
                    lines.append(f"{libname}_{libext}_DEPENDENCIES += @{perhaps_LT}ALLOCA@")
                lines += list(callback(module, conditional, unconditional))
                lines.append(f"## end   gnulib module {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 f"MOSTLYCLEANFILES {assign} core *.stackdump"
    if "makefile_name" not in explicit:
        yield "MOSTLYCLEANDIRS ="
        yield "CLEANFILES ="
        yield "DISTCLEANFILES ="
        yield "MAINTAINERCLEANFILES ="

    if config.gnumake:
        yield "# Start of GNU Make output."
        with autoconf("-t", "AC_SUBST:$1 = @$1@", config.ac_file) as process:
            (stdout, stderr) = process.communicate()
            stdout = stdout.decode("UTF-8")
            stderr = stderr.decode("UTF-8")
            if process.returncode == 0:
                for line in sorted(set(stdout.splitlines())):
                    yield line
            else:
                yield "== gnulib-tool GNU Make output failed as follows =="
                for line in stderr.splitlines():
                    yield f"# stderr: {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:
            yield f"{key} += {value}"
            del (directory, 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 f"AM_CPPFLAGS ={cppflags}"
        yield "AM_CFLAGS ="
    elif "".join(cppflags):
        yield ""
        yield f"AM_CPPFLAGS +={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.
    pattern = _re.compile(f"^[a-zA-Z0-9_]*_{perhaps_LT}LIBRARIES\\s*\\+?\\=\\s*{libname}\\.{libext}$", _re.S)
    if not pattern.findall(snippet):
        yield f"noinst_{perhaps_LT}LIBRARIES += {libname}.{libext}"

    yield ""
    yield f"{libname}_{libext}_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"{libname}_{libext}_LIBADD = $({macro_prefix}_{perhaps_LT}LIBOBJS)"
    yield f"{libname}_{libext}_DEPENDENCIES = $({macro_prefix}_{perhaps_LT}LIBOBJS)"
    yield f"EXTRA_{libname}_{libext}_SOURCES ="
    if config.libtool:
        yield f"{libname}_{libext}_LDFLAGS = $(AM_LDFLAGS)"
        yield f"{libname}_{libext}_LDFLAGS += -no-undefined"
        # Synthesize an ${libname}_${libext}_LDFLAGS augmentation by combining
        # the link dependencies of all modules.
        def _directives(modules):
            for module in modules:
                for directive in module.link_directives:
                    index = directive.find("when linking with libtool")
                    if index != -1:
                        directive = directive[:index].strip(" ")
                    yield directive
        for directive in sorted(set(_directives(database.main_modules))):
            yield f"{libname}_{libext}_LDFLAGS += {directive}"
    yield ""

    if "po_base" in explicit:
        yield f"AM_CPPFLAGS += -DDEFAULT_TEXT_DOMAIN=\\\"{po_domain}-gnulib\\\""
        yield ""

    for line in lines:
        src = "$(top_srcdir)/build-aux/"
        dst = _os.path.join("$(top_srcdir)", config.auxdir)
        dst += _os.path.sep
        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 _tests_makefile_callback(gnumake):

    def _automake(module, snippet):
        yield ""
        yield snippet

    def _gnumake(module, snippet):
        yield f"ifeq (,$(OMIT_GNULIB_MODULE_{module.name}))"
        yield ""
        yield snippet
        yield "endif"

    return _gnumake if gnumake else _automake

def tests_makefile(path, config, explicit, modules, mkedits, testing, libtests):
    """Generate tests Makefile.am file."""
    single_configure = config.single_configure
    if testing and not 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")
    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 f"# Copyright (C) 2002-{date.year} Free Software Foundation, Inc."
    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 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(f"## begin gnulib module {module.name}")
                lines += list(callback(module, snippet))
                lines.append(f"## end   gnulib module {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 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 libtests else ""
    local_ldadd_after = "  libtests.a $(LIBTESTS_LIBDEPS)" if libtests else ""
    yield f"LDADD ={local_ldadd_before} {tests_base_inverse}/{source_base}/{libname}.{libext}{local_ldadd_after}"
    yield ""

    if 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)
        dst += _os.path.sep
        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:"



__GNULIB_CACHE_OPTIONS = (
    ("obsolete", "gl_WITH_OBSOLETE"),
    ("cxx_tests", "gl_WITH_CXX_TESTS"),
    ("longrunning", "gl_WITH_LONGRUNNING_TESTS"),
    ("privileged", "gl_WITH_PRIVILEGED_TESTS"),
)

def gnulib_cache(config, explicit):
    """
    Generate gnulib-cache.m4 file.
    """
    date = _datetime.now()
    yield "## DO NOT EDIT! GENERATED AUTOMATICALLY!"
    yield "## Process this file with automake to produce Makefile.in."
    yield f"# Copyright (C) 2002-{date.year} Free Software Foundation, Inc."
    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 "#   " + " ".join(command_line(config, explicit))
    yield ""
    yield "# Specification in the form of a few gnulib-tool.m4 macro invocations:"
    yield "gl_LOCAL_DIR([{}])".format(" ".join(config.overrides))
    yield "gl_MODULES(["
    for module in sorted(config.modules):
        yield f"  {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 f"gl_SOURCE_BASE([{config.source_base}])"
    yield f"gl_M4_BASE([{config.m4_base}])"
    if config.po_base:
        yield f"gl_PO_BASE([{config.po_base}])"
    else:
        yield f"gl_PO_BASE([])"
    yield f"gl_DOC_BASE([{config.doc_base}])"
    yield f"gl_TESTS_BASE([{config.tests_base}])"
    if config.tests:
        yield "gl_WITH_TESTS"
    yield "gl_LIB([{}])".format(config.libname)
    licenses = tuple(sorted(config.licenses))
    if licenses in _LGPL:
        lgpl = _LGPL[licenses]
        if lgpl != "yes":
            yield f"gl_LGPL([{lgpl}])"
        else:
            yield "gl_LGPL([])"
    if config.makefile_name:
        yield f"gl_MAKEFILE_NAME([{config.makefile_name}])"
    else:
        yield f"gl_MAKEFILE_NAME([])"
    if config.conditionals:
        yield "gl_CONDITIONAL_DEPENDENCIES"
    if config.libtool:
        yield "gl_LIBTOOL"
    yield f"gl_MACRO_PREFIX([{config.macro_prefix}])"
    yield f"gl_PO_DOMAIN([{config.po_domain}])"
    yield f"gl_WITNESS_C_MACRO([{config.witness_c_macro}])"
    if config.vc_files:
        yield "gl_VC_FILES([{true}])"



def gnulib_comp(config, explicit, database, **override):
    """gnulib-comp.m4 generator"""
    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

    subdirs = False
    for module in database.main_modules:
        subdirs |= any(__MAKEFILE_SUBDIRS.match(file) for file in module.files)

    date = _datetime.now()
    ac_file = config.ac_file
    yield "# DO NOT EDIT! GENERATED AUTOMATICALLY!"
    yield f"# Copyright (C) 2002-{date.year} Free Software Foundation, Inc."
    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 f"# This macro should be invoked from {ac_file}, in the section"
    yield "# \"Checks for programs\", right after AC_PROG_CC, and certainly before"
    yield "# any checks for libraries, header files, types and library functions."
    yield f"AC_DEFUN([{macro_prefix}_EARLY],"
    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 f"  # Code from module {module.name}:"
        lines = module.early_autoconf_snippet.split("\n")
        for line in filter(lambda line: line.strip(), lines):
            yield f"  {line}"
    yield "])"
    yield ""
    yield f"# This macro should be invoked from {ac_file}, in the section"
    yield "# \"Check for header files, types and library functions\"."
    yield f"AC_DEFUN([{macro_prefix}_INIT],"
    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 f"  gl_m4_base='{config.m4_base}'"
    for line in init_macro_header(config, macro_prefix=macro_prefix):
        yield line
    yield f"  gl_source_base='{config.source_base}'"
    if "witness_c_macro" in explicit:
        yield f"  m4_pushdef([gl_MODULE_INDICATOR_CONDITION], [{config.witness_c_macro}])"
    for line in autoconf_snippets(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])"
    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 f"  gl_source_base='{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((
        f"  {macro_prefix}tests_WITNESS=IN_`",
        "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 f"  AC_SUBST([{macro_prefix}tests_WITNESS])"
    yield f"  gl_module_indicator_condition=${macro_prefix}tests_WITNESS"
    yield "  m4_pushdef([gl_MODULE_INDICATOR_CONDITION], [$gl_module_indicator_condition])"
    for line in autoconf_snippets(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 f"  {libname}_LIBDEPS=\"$gl_libdeps\""
        yield f"  AC_SUBST([{libname}_LIBDEPS])"
        yield f"  {libname}_LTLIBDEPS=\"$gl_ltlibdeps\""
        yield f"  AC_SUBST([{libname}_LTLIBDEPS])"
    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 f"AC_DEFUN([{macro_prefix}_FILE_LIST], ["
    test_files = set()
    main_files = set(database.main_files)
    for file in database.test_files:
        if file.startswith("lib/"):
            file = ("tests=lib/" + file[len("lib/"):])
        test_files.add(file)
    for file in sorted(main_files | test_files):
        yield f"  {file}"
    yield "])"