# Copyright (C) 2002-2025 Free Software Foundation, Inc.
#
# This program 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 program 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 program.  If not, see <https://www.gnu.org/licenses/>.

from __future__ import annotations

#===============================================================================
# Define global imports
#===============================================================================
import os
import re
import subprocess as sp
from collections.abc import Callable
from .constants import (
    UTILS,
    joinpath,
    lines_to_multiline,
    combine_lines_matching,
    substart,
    relinverse,
)
from .GLInfo import GLInfo
from .GLConfig import GLConfig
from .GLModuleSystem import GLModule
from .GLModuleSystem import GLModuleTable
from .GLMakefileTable import GLMakefileTable


# Regular expressions used to convert Automake conditionals to GNU Make syntax.
# Each tuple is the arguments given to re.sub in the correct order.
_CONVERT_TO_GNU_MAKE = [
    (re.compile(r'^if (.*)', re.MULTILINE), r'ifneq (,$(\1_CONDITION))'),
    (re.compile(r'%reldir%/'), r''),
    (re.compile(r'%reldir%'), r'.')
]


def _convert_to_gnu_make(snippet: str) -> str:
    '''Convert a Automake snippet to GNU Make syntax.'''
    if type(snippet) is not str:
        raise TypeError(f'snippet must be a str, not {type(snippet).__name__}')
    for regexp in _CONVERT_TO_GNU_MAKE:
        snippet = re.sub(regexp[0], regexp[1], snippet)
    return snippet


def _eliminate_NMD_from_line(line: str, automake_subdir: bool) -> str | None:
    '''Eliminate occurrences of @NMD@ and @!NMD@ from the given line. The
    modified line is returned or None if the line should be removed.

    line is the current line in the snippet being operated on.
    automake_subdir is a bool that is True if --automake-subdir is in use,
      else False.'''
    if automake_subdir:
        clean = '@NMD@'
        eliminate = '@!NMD@'
    else:
        clean = '@!NMD@'
        eliminate = '@NMD@'
    # Check if we should eliminate the line from the output.
    if line.startswith(eliminate):
        return None
    # Check if we should clean the mark but keep the line.
    if line.startswith(clean):
        return line.replace(clean, '')
    return line


def _eliminate_NMD(snippet: str, automake_subdir: bool) -> str:
    '''Return the Automake snippet with occurrences of @NMD@ and @!NMD@
    removed.

    snippet is the Automake snippet being operated on.
    automake_subdir is a bool that is True if --automake-subdir is in use,
      else False.'''
    result = []
    for line in snippet.splitlines():
        line = _eliminate_NMD_from_line(line, automake_subdir)
        if line != None:
            result.append(line)
    return lines_to_multiline(result)


#===============================================================================
# Define GLEmiter class
#===============================================================================
class GLEmiter:
    '''This class is used to emit the contents of necessary files.'''

    info: GLInfo
    config: GLConfig

    def __init__(self, config: GLConfig) -> None:
        '''Create GLEmiter instance.'''
        self.info = GLInfo()
        if type(config) is not GLConfig:
            raise TypeError('config must be a GLConfig, not %s'
                            % type(config).__name__)
        self.config = config

    def __repr__(self) -> str:
        '''x.__repr__() <==> repr(x)'''
        result = '<pygnulib.GLEmiter %s>' % hex(id(self))
        return result

    def copyright_notice(self) -> str:
        '''Emit a header for a generated file.'''
        emit = '# %s' % self.info.copyright_range()
        emit += '''
#
# 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 <https://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.\n'''
        return emit

    def shellvars_init(self, gentests: bool, base: str) -> str:
        '''Emits some shell variable assignments.

        gentests is a bool that is True if a tests Makefile.am is being
          generated, False otherwise.
        base is a string representing the base directory relative to the
          top-level directory.'''
        if type(gentests) is not bool:
            raise TypeError(f'gentests must be a bool, not {type(gentests).__name__}')
        if type(base) is not str:
            raise TypeError(f'base must be a str, not {type(base).__name__}')
        sourcebase = self.config['sourcebase']
        testsbase = self.config['testsbase']
        automake_subdir = self.config['automake_subdir']
        automake_subdir_tests = self.config['automake_subdir_tests']
        # Define the base directory, relative to the top-level directory.
        emit = f'  gl_source_base=\'{base}\'\n'
        # Define the prefix for the file name of generated files.
        if gentests and automake_subdir_tests:
            # When tests share the same Makefile as the whole project, they
            # share the same base prefix.
            if base == testsbase:
                emit += f'  gl_source_base_prefix=\'$(top_build_prefix){sourcebase}/\'\n'
            else:
                emit += f'  gl_source_base_prefix=\'$(top_build_prefix){base}/\'\n'
        elif not gentests and automake_subdir:
            emit += f'  gl_source_base_prefix=\'$(top_build_prefix){base}/\'\n'
        else:
            emit += '  gl_source_base_prefix=\n'
        return emit

    def autoconfSnippet(self, module: GLModule, toplevel: bool, disable_libtool: bool,
                        disable_gettext: bool, replace_auxdir: bool, indentation: str) -> str:
        '''Emit the autoconf snippet of a module.
        GLConfig: include_guard_prefix.

        module is a GLModule instance, which is processed.
        toplevel is a bool variable, False means a subordinate use of pygnulib.
        disable_libtool is a bool variable; it tells whether to disable libtool
          handling even if it has been specified through the GLConfig class.
        disable_gettext is a bool variable; it tells whether to disable
          AM_GNU_GETTEXT invocations.
        replace_auxdir is a bool variable; it tells whether to replace
          'build-aux' directory in AC_CONFIG_FILES.
        indentation is a string which contain spaces to prepend on each line.'''
        if type(module) is not GLModule:
            raise TypeError('module must be a GLModule, not %s'
                            % type(module).__name__)
        if type(toplevel) is not bool:
            raise TypeError('toplevel must be a bool, not %s'
                            % type(toplevel).__name__)
        if type(disable_libtool) is not bool:
            raise TypeError('disable_libtool must be a bool, not %s'
                            % type(disable_libtool).__name__)
        if type(disable_gettext) is not bool:
            raise TypeError('disable_gettext must be a bool, not %s'
                            % type(disable_gettext).__name__)
        if type(indentation) is not str:
            raise TypeError('indentation must be a string, not %s'
                            % type(indentation).__name__)
        if not indentation.isspace():
            raise ValueError('indentation must contain only whitespaces')
        auxdir = self.config['auxdir']
        libtool = self.config['libtool']
        include_guard_prefix = self.config['include_guard_prefix']
        emit = ''
        if module.name in ['gnumakefile', 'maintainer-makefile']:
            # These modules are meant to be used only in the top-level directory.
            flag = toplevel
        else:  # if module.name not in ['gnumakefile', 'maintainer-makefile']
            flag = True
        if flag:
            snippet = module.getAutoconfSnippet()
            snippet = snippet.replace('${gl_include_guard_prefix}',
                                      include_guard_prefix)
            lines = [ f'{indentation}{line}'
                      for line in snippet.split('\n')
                      if line.strip() ]
            snippet = lines_to_multiline(lines)
            if disable_libtool:
                snippet = snippet.replace('$gl_cond_libtool', 'false')
                snippet = snippet.replace('gl_libdeps', 'gltests_libdeps')
                snippet = snippet.replace('gl_ltlibdeps', 'gltests_ltlibdeps')
            if disable_gettext:
                snippet = snippet.replace('AM_GNU_GETTEXT([external])',
                                          'dnl you must add AM_GNU_GETTEXT([external]) or similar to configure.ac.')
            else:
                # Don't indent AM_GNU_GETTEXT_VERSION line, as that confuses
                # autopoint through at least GNU gettext version 0.18.2.
                snippet = re.compile(r'^ *AM_GNU_GETTEXT_VERSION', re.M).sub(r'AM_GNU_GETTEXT_VERSION', snippet)
            emit += snippet
            if module.name == 'alloca' and libtool and not disable_libtool:
                emit += 'changequote(,)dnl\n'
                emit += "LTALLOCA=`echo \"$ALLOCA\" | sed -e 's/\\.[^.]* /.lo /g;s/\\.[^.]*$/.lo/'`\n"
                emit += 'changequote([, ])dnl\n'
                emit += 'AC_SUBST([LTALLOCA])'
            if replace_auxdir:
                regex = r'AC_CONFIG_FILES\(\[(.*)\:build-aux/(.*)\]\)'
                repl = r'AC_CONFIG_FILES([\1:%s/\2])' % auxdir
                pattern = re.compile(regex, re.M)
                emit = pattern.sub(repl, emit)
        lines = [ line
                  for line in emit.split('\n')
                  if line.strip() ]
        emit = lines_to_multiline(lines)
        return emit

    def autoconfSnippets(self, modules: list[GLModule], referenceable_modules: list[GLModule],
                         moduletable: GLModuleTable, module_filter: Callable[[GLModule], bool], toplevel: bool,
                         disable_libtool: bool, disable_gettext: bool, replace_auxdir: bool) -> str:
        '''Collect and emit the autoconf snippets of a set of modules.
        GLConfig: conddeps.

        basemodules argument represents list of modules; every module in this list
          must be a GLModule instance; this list of modules is used to sort all
          modules after they were processed.
        modules argument represents list of modules; every module in this list must
          be a GLModule instance.
        referenceable_modules is the list of modules which may be referenced as
          dependencies.
        moduletable is a GLModuleTable instance, which contains necessary
          information about dependencies of the modules.
        module_filter is a function that accepts a GLModule and returns a bool describing
          whether or not Autoconf snippets should be emitted for it.
        toplevel is a bool variable, False means a subordinate use of pygnulib.
        disable_libtool is a bool variable; it tells whether to disable libtool
          handling even if it has been specified through the GLConfig class.
        disable_gettext is a bool variable; it tells whether to disable
          AM_GNU_GETTEXT invocations.
        replace_auxdir is a bool variable; it tells whether to replace
          'build-aux' directory in AC_CONFIG_FILES.'''
        for module in modules:
            if type(module) is not GLModule:
                raise TypeError('each module must be a GLModule instance')
        for module in referenceable_modules:
            if type(module) is not GLModule:
                raise TypeError('each referenceable module must be a GLModule instance')
        if type(moduletable) is not GLModuleTable:
            raise TypeError('moduletable must be a GLModuleTable, not %s'
                            % type(moduletable).__name__)
        if not callable(module_filter):
            raise TypeError('module_filter must be a function, not %s'
                            % type(module_filter).__name__)
        if type(toplevel) is not bool:
            raise TypeError('toplevel must be a bool, not %s'
                            % type(toplevel).__name__)
        if type(disable_libtool) is not bool:
            raise TypeError('disable_libtool must be a bool, not %s'
                            % type(disable_libtool).__name__)
        if type(disable_gettext) is not bool:
            raise TypeError('disable_gettext must be a bool, not %s'
                            % type(disable_gettext).__name__)
        if type(replace_auxdir) is not bool:
            raise TypeError('replace_auxdir must be a bool, not %s'
                            % type(replace_auxdir).__name__)
        conddeps = self.config['conddeps']
        macro_prefix = self.config['macro_prefix']
        emit = ''
        if not conddeps:
            # Ignore the conditions, and enable all modules unconditionally.
            for module in modules:
                if module_filter(module):
                    emit += self.autoconfSnippet(module, toplevel,
                                                 disable_libtool, disable_gettext, replace_auxdir, '  ')
        else:  # if conddeps
            # Emit the autoconf code for the unconditional modules.
            for module in modules:
                if module_filter(module):
                    if not moduletable.isConditional(module):
                        emit += self.autoconfSnippet(module, toplevel,
                                                     disable_libtool, disable_gettext, replace_auxdir, '  ')
            # Initialize the shell variables indicating that the modules are enabled.
            for module in modules:
                if module_filter(module):
                    if moduletable.isConditional(module):
                        emit += '  %s=false\n' % module.getShellVar()
            # Emit the autoconf code for the conditional modules, each in a separate
            # function. This makes it possible to support cycles among conditional
            # modules.
            for module in modules:
                if module_filter(module):
                    if moduletable.isConditional(module):
                        shellfunc = module.getShellFunc()
                        shellvar = module.getShellVar()
                        emit += '  %s ()\n' % shellfunc
                        emit += '  {\n'
                        emit += '    if $%s; then :; else\n' % shellvar
                        emit += self.autoconfSnippet(module, toplevel,
                                                     disable_libtool, disable_gettext, replace_auxdir, '      ')
                        emit += '      %s=true\n' % shellvar
                        depmodules = module.getDependenciesWithoutConditions()
                        # Intersect dependencies with the modules list.
                        depmodules = sorted(set(depmodules).intersection(referenceable_modules))
                        for depmodule in depmodules:
                            if moduletable.isConditional(depmodule):
                                shellfunc = depmodule.getShellFunc()
                                condition = moduletable.getCondition(module, depmodule)
                                if condition != None and condition != True:
                                    emit += '      if %s; then\n' % condition
                                    emit += '        %s\n' % shellfunc
                                    emit += '      fi\n'
                                else:  # if condition == None or condition == True
                                    emit += '      %s\n' % shellfunc
                            # if not moduletable.isConditional(depmodule)
                            else:
                                # The autoconf code for $dep has already been emitted above and
                                # therefore is already executed when this code is run.
                                pass
                        emit += '    fi\n'
                        emit += '  }\n'
            # Emit the dependencies from the unconditional to the conditional modules.
            for module in modules:
                if module_filter(module):
                    if not moduletable.isConditional(module):
                        depmodules = module.getDependenciesWithoutConditions()
                        # Intersect dependencies with the modules list.
                        depmodules = sorted(set(depmodules).intersection(referenceable_modules))
                        for depmodule in depmodules:
                            if moduletable.isConditional(depmodule):
                                shellfunc = depmodule.getShellFunc()
                                condition = moduletable.getCondition(module, depmodule)
                                if condition != None and condition != True:
                                    emit += '  if %s; then\n' % condition
                                    emit += '    %s\n' % shellfunc
                                    emit += '  fi\n'
                                else:  # if condition == None or condition == True
                                    emit += '  %s\n' % shellfunc
                            # if not moduletable.isConditional(depmodule)
                            else:
                                # The autoconf code for $dep has already been emitted above and
                                # therefore is already executed when this code is run.
                                pass
            # Define the Automake conditionals.
            emit += '  m4_pattern_allow([^%s_GNULIB_ENABLED_])\n' % macro_prefix
            for module in modules:
                if module_filter(module):
                    if moduletable.isConditional(module):
                        condname = module.getConditionalName()
                        shellvar = module.getShellVar()
                        emit += '  AM_CONDITIONAL([%s], [$%s])\n' % (condname, shellvar)
        lines = [ line
                  for line in emit.split('\n')
                  if line.strip() ]
        emit = lines_to_multiline(lines)
        return emit

    def preEarlyMacros(self, require: bool, indentation: str, modules: list[GLModule]) -> str:
        '''Collect and emit the pre-early section.

        require parameter can be True (AC_REQUIRE) or False (direct call).
        indentation parameter is a string.
        modules argument represents list of modules; every module in this list must
          be a GLModule instance.'''
        emit = '\n' + indentation + '# Pre-early section.\n'
        # We need to call gl_USE_SYSTEM_EXTENSIONS before gl_PROG_AR_RANLIB.
        # Doing AC_REQUIRE in configure-ac.early is not early enough.
        if any(module.name == 'extensions' for module in modules):
            if require:
                emit += indentation + 'AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS])\n'
            else:
                emit += indentation + 'gl_USE_SYSTEM_EXTENSIONS\n'
        if require:
            emit += indentation + 'AC_REQUIRE([gl_PROG_AR_RANLIB])\n'
        else:
            emit += indentation + 'gl_PROG_AR_RANLIB\n'
        emit += '\n'
        return emit

    def po_Makevars(self) -> str:
        '''Emit the contents of po/ makefile parameterization.
        GLConfig: pobase, podomain.'''
        pobase = self.config['pobase']
        podomain = self.config['podomain']
        emit = ''
        emit += '## DO NOT EDIT! GENERATED AUTOMATICALLY!\n'
        emit += '%s\n' % self.copyright_notice()
        emit += '# Usually the message domain is the same as the package name.\n'
        emit += "# But here it has a '-gnulib' suffix.\n"
        emit += 'DOMAIN = %s-gnulib\n\n' % podomain
        emit += '# These two variables depend on the location of this directory.\n'
        emit += 'subdir = %s\n' % pobase
        emit += 'top_builddir = %s\n' % relinverse(pobase)
        emit += '''
# 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_lite: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\n'''
        return emit

    def po_POTFILES_in(self, files: list[str]) -> str:
        '''Emit the file list to be passed to xgettext.
        GLConfig: sourcebase.'''
        sourcebase = self.config['sourcebase'] + os.path.sep
        emit = ''
        emit += '## DO NOT EDIT! GENERATED AUTOMATICALLY!\n'
        emit += '%s\n' % self.copyright_notice()
        emit += '# List of files which contain translatable strings.\n'
        for file in files:
            if file.startswith('lib/'):
                emit += '%s\n' % substart('lib/', sourcebase, file)
        return emit

    def initmacro_start(self, macro_prefix_arg: str, gentests: bool) -> str:
        '''Emit the first few statements of the gl_INIT macro.

        macro_prefix_arg is the prefix of gl_EARLY and gl_INIT macros to use.
        gentests is True if a tests Makefile.am is being generated, False
          otherwise.'''
        if type(macro_prefix_arg) is not str:
            raise TypeError('macro_prefix_arg must be a string, not %s'
                            % type(macro_prefix_arg).__name__)
        if type(gentests) is not bool:
            raise TypeError('gentests must be a bool, not %s'
                            % type(gentests).__name__)
        module_indicator_prefix = self.config.getModuleIndicatorPrefix()
        emit = ''
        # 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.
        emit += '  m4_pushdef([AC_LIBOBJ], m4_defn([%s_LIBOBJ]))\n' % macro_prefix_arg
        emit += '  m4_pushdef([AC_REPLACE_FUNCS], m4_defn([%s_REPLACE_FUNCS]))\n' % macro_prefix_arg
        # 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.
        emit += '  m4_pushdef([AC_LIBSOURCES], m4_defn([%s_LIBSOURCES]))\n' % macro_prefix_arg
        # 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.
        emit += '  m4_pushdef([%s_LIBSOURCES_LIST], [])\n' % macro_prefix_arg
        emit += '  m4_pushdef([%s_LIBSOURCES_DIR], [])\n' % macro_prefix_arg
        # Scope for m4 macros.
        emit += '  m4_pushdef([GL_MACRO_PREFIX], [%s])\n' % macro_prefix_arg
        # Scope the GNULIB_<modulename> variables.
        emit += '  m4_pushdef([GL_MODULE_INDICATOR_PREFIX], [%s])\n' % module_indicator_prefix
        emit += '  gl_COMMON\n'
        if gentests:
            emit += '  AC_REQUIRE([gl_CC_ALLOW_WARNINGS])\n'
            emit += '  AC_REQUIRE([gl_CXX_ALLOW_WARNINGS])\n'
        return emit

    def initmacro_end(self, macro_prefix_arg: str, gentests: bool) -> str:
        '''Emit the last few statements of the gl_INIT macro.

        macro_prefix_arg is the prefix of gl_EARLY, gl_INIT macros to use.
        gentests is a bool that is True if a tests Makefile.am is being
          generated, False otherwise.'''
        if type(macro_prefix_arg) is not str:
            raise TypeError('macro_prefix_arg must be a string, not %s'
                            % type(macro_prefix_arg).__name__)
        if type(gentests) is not bool:
            raise TypeError('gentests must be a bool, not %s'
                            % type(gentests).__name__)
        sourcebase = self.config['sourcebase']
        testsbase = self.config['testsbase']
        automake_subdir = self.config['automake_subdir']
        automake_subdir_tests = self.config['automake_subdir_tests']
        libname = self.config['libname']
        libtool = self.config['libtool']
        emit = ''
        # 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.
        if automake_subdir and not gentests and sourcebase != '' and sourcebase != '.':
            subdir = f'{sourcebase}/'
        elif automake_subdir_tests and gentests and testsbase != '' and testsbase != '.':
            subdir = f'{testsbase}/'
        else:
            subdir = ''
        if libtool:
            libobjdeps_line1 = f'{macro_prefix_arg}_libobjdeps="${macro_prefix_arg}_libobjdeps {subdir}$i_dir\\$(DEPDIR)/$i_base.Plo"'
            libobjdeps_line2 = f'{macro_prefix_arg}_{libname}_libobjdeps="${macro_prefix_arg}_{libname}_libobjdeps {subdir}$i_dir\\$(DEPDIR)/{libname}_la-$i_base.Plo"'
        else:
            libobjdeps_line1 = f'{macro_prefix_arg}_libobjdeps="${macro_prefix_arg}_libobjdeps {subdir}$i_dir\\$(DEPDIR)/$i_base.Po"'
            libobjdeps_line2 = f'{macro_prefix_arg}_{libname}_libobjdeps="${macro_prefix_arg}_{libname}_libobjdeps {subdir}$i_dir\\$(DEPDIR)/{libname}_a-$i_base.Po"'
        emit += fr'''  m4_ifval({macro_prefix_arg}_LIBSOURCES_LIST, [
    m4_syscmd([test ! -d ]m4_defn([{macro_prefix_arg}_LIBSOURCES_DIR])[ ||
      for gl_file in ]{macro_prefix_arg}_LIBSOURCES_LIST[ ; do
        if test ! -r ]m4_defn([{macro_prefix_arg}_LIBSOURCES_DIR])[/$gl_file ; then
          echo "missing file ]m4_defn([{macro_prefix_arg}_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([GL_MODULE_INDICATOR_PREFIX])
  m4_popdef([GL_MACRO_PREFIX])
  m4_popdef([{macro_prefix_arg}_LIBSOURCES_DIR])
  m4_popdef([{macro_prefix_arg}_LIBSOURCES_LIST])
  m4_popdef([AC_LIBSOURCES])
  m4_popdef([AC_REPLACE_FUNCS])
  m4_popdef([AC_LIBOBJ])
  AC_CONFIG_COMMANDS_PRE([
    {macro_prefix_arg}_libobjs=
    {macro_prefix_arg}_ltlibobjs=
    {macro_prefix_arg}_libobjdeps=
    {macro_prefix_arg}_{libname}_libobjs=
    {macro_prefix_arg}_{libname}_ltlibobjs=
    {macro_prefix_arg}_{libname}_libobjdeps=
    if test -n "${macro_prefix_arg}_LIBOBJS"; then
      # Remove the extension.
changequote(,)dnl
      sed_drop_objext='s/\.o$//;s/\.obj$//'
      sed_dirname1='s,//*,/,g'
      sed_dirname2='s,\(.\)/$,\1,'
      sed_dirname3='s,[^/]*$,,'
      sed_basename1='s,.*/,,'
changequote([, ])dnl
      for i in `for i in ${macro_prefix_arg}_LIBOBJS; do echo "$i"; done | sed -e "$sed_drop_objext" | sort | uniq`; do
        {macro_prefix_arg}_libobjs="${macro_prefix_arg}_libobjs {subdir}$i.$ac_objext"
        {macro_prefix_arg}_ltlibobjs="${macro_prefix_arg}_ltlibobjs {subdir}$i.lo"
        i_dir=`echo "$i" | sed -e "$sed_dirname1" -e "$sed_dirname2" -e "$sed_dirname3"`
        i_base=`echo "$i" | sed -e "$sed_basename1"`
        {macro_prefix_arg}_{libname}_libobjs="${macro_prefix_arg}_{libname}_libobjs {subdir}$i_dir""{libname}_a-$i_base.$ac_objext"
        {macro_prefix_arg}_{libname}_ltlibobjs="${macro_prefix_arg}_{libname}_ltlibobjs {subdir}$i_dir""{libname}_la-$i_base.lo"
        {libobjdeps_line1}
        {libobjdeps_line2}
      done
    fi
    AC_SUBST([{macro_prefix_arg}_LIBOBJS], [${macro_prefix_arg}_libobjs])
    AC_SUBST([{macro_prefix_arg}_LTLIBOBJS], [${macro_prefix_arg}_ltlibobjs])
    AC_SUBST([{macro_prefix_arg}_LIBOBJDEPS], [${macro_prefix_arg}_libobjdeps])
    AC_SUBST([{macro_prefix_arg}_{libname}_LIBOBJS], [${macro_prefix_arg}_{libname}_libobjs])
    AC_SUBST([{macro_prefix_arg}_{libname}_LTLIBOBJS], [${macro_prefix_arg}_{libname}_ltlibobjs])
    AC_SUBST([{macro_prefix_arg}_{libname}_LIBOBJDEPS], [${macro_prefix_arg}_{libname}_libobjdeps])
  ])
'''
        return emit

    def initmacro_done(self, macro_prefix_arg: str, sourcebase_arg: str) -> str:
        '''Emit a few statements after the gl_INIT macro.
        GLConfig: sourcebase.'''
        if type(macro_prefix_arg) is not str:
            raise TypeError('macro_prefix_arg must be a string, not %s'
                            % type(macro_prefix_arg).__name__)
        if type(sourcebase_arg) is not str:
            raise TypeError('sourcebase_arg must be a string, not %s'
                            % type(sourcebase_arg).__name__)
        emit = ''
        emit += '''\

# Like AC_LIBOBJ, except that the module name goes
# into %V1%_LIBOBJS instead of into LIBOBJS.
AC_DEFUN([%V1%_LIBOBJ], [
  AS_LITERAL_IF([$1], [%V1%_LIBSOURCES([$1.c])])dnl
  %V1%_LIBOBJS="$%V1%_LIBOBJS $1.$ac_objext"
])

# Like AC_REPLACE_FUNCS, except that the module name goes
# into %V1%_LIBOBJS instead of into LIBOBJS.
AC_DEFUN([%V1%_REPLACE_FUNCS], [
  m4_foreach_w([gl_NAME], [$1], [AC_LIBSOURCES(gl_NAME[.c])])dnl
  AC_CHECK_FUNCS([$1], , [%V1%_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([%V1%_LIBSOURCES], [
  m4_foreach([_gl_NAME], [$1], [
    m4_if(_gl_NAME, [alloca.c], [], [
      m4_define([%V1%_LIBSOURCES_DIR], [%V2%])
      m4_append([%V1%_LIBSOURCES_LIST], _gl_NAME, [ ])
    ])
  ])
])\n'''
        emit = emit.replace('%V1%', macro_prefix_arg)
        emit = emit.replace('%V2%', sourcebase_arg)
        return emit

    def lib_Makefile_am(self, destfile: str, modules: list[GLModule], moduletable: GLModuleTable,
                        makefiletable: GLMakefileTable, actioncmd: str, for_test: bool) -> str:
        '''Emit the contents of the library Makefile. Returns it as a string.
        GLConfig: localpath, sourcebase, libname, pobase, auxdir, makefile_name, libtool,
        macro_prefix, podomain, conddeps, witness_c_macro.

        destfile is a filename relative to destdir of Makefile being generated.
        modules is a list of GLModule instances.
        moduletable is a GLModuleTable instance.
        makefiletable is a GLMakefileTable instance.
        actioncmd is a string variable, which represents the actioncmd; it can be
          an empty string e.g. when user wants to generate files for GLTestDir.
        for_test is a bool variable; it must be set to True if creating a package
          for testing, False otherwise.'''
        if type(destfile) is not str:
            raise TypeError('destfile must be a string, not %s'
                            % type(destfile).__name__)
        for module in modules:
            if type(module) is not GLModule:
                raise TypeError('each module must be a GLModule instance')
        if type(moduletable) is not GLModuleTable:
            raise TypeError('moduletable must be a GLModuleTable, not %s'
                            % type(moduletable).__name__)
        if type(makefiletable) is not GLMakefileTable:
            raise TypeError('makefiletable must be a GLMakefileTable, not %s'
                            % type(makefiletable).__name__)
        if type(actioncmd) is not str:
            raise TypeError('actioncmd must be a string, not %s'
                            % type(actioncmd).__name__)
        if type(for_test) is not bool:
            raise TypeError('for_test must be a bool, not %s'
                            % type(for_test).__name__)
        sourcebase = self.config['sourcebase']
        libname = self.config['libname']
        pobase = self.config['pobase']
        auxdir = self.config['auxdir']
        gnu_make = self.config['gnu_make']
        makefile_name = self.config['makefile_name']
        automake_subdir = self.config['automake_subdir']
        libtool = self.config['libtool']
        macro_prefix = self.config['macro_prefix']
        podomain = self.config['podomain']
        conddeps = self.config['conddeps']
        witness_c_macro = self.config['witness_c_macro']
        include_guard_prefix = self.config['include_guard_prefix']
        module_indicator_prefix = self.config.getModuleIndicatorPrefix()
        destfile = os.path.normpath(destfile)
        emit = ''

        # When using GNU make, or when creating an includable Makefile.am snippet,
        # augment variables with += instead of assigning them.
        if gnu_make or makefile_name:
            assign = '+='
        else:  # if not makefile_name
            assign = '='
        if libtool:
            libext = 'la'
            objext = 'lo'
            perhapsLT = 'LT'
            eliminate_LDFLAGS = False
        else:  # if not libtool
            libext = 'a'
            objext = 'o'
            perhapsLT = ''
            eliminate_LDFLAGS = True
        if for_test:
            # 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.
            edit_check_PROGRAMS = True
        else:  # if not for_test
            edit_check_PROGRAMS = False
        emit += '## DO NOT EDIT! GENERATED AUTOMATICALLY!\n'
        if not gnu_make:
            emit += '## Process this file with automake to produce Makefile.in.\n'
        emit += self.copyright_notice()
        if actioncmd:
            emit += '# Reproduce by:\n%s\n' % actioncmd
        emit += '\n'
        uses_subdirs = False

        # Compute allsnippets variable.
        allsnippets = ''
        for module in modules:
            if module.isNonTests():
                # Get conditional snippet, edit it and save to amsnippet1.
                amsnippet1 = module.getAutomakeSnippet_Conditional()
                amsnippet1 = amsnippet1.replace('lib_LIBRARIES', 'lib%_LIBRARIES')
                amsnippet1 = amsnippet1.replace('lib_LTLIBRARIES', 'lib%_LTLIBRARIES')
                if eliminate_LDFLAGS:
                    pattern = re.compile(r'^(lib_LDFLAGS[\t ]*\+=.*$\n)', re.M)
                    amsnippet1 = pattern.sub(r'', amsnippet1)
                # Replace NMD, so as to remove redundant "$(MKDIR_P) '.'" invocations.
                # The logic is similar to how we define gl_source_base_prefix.
                amsnippet1 = _eliminate_NMD(amsnippet1, automake_subdir)
                # Replace @LT@, @la@, @lo@, depending on libtool.
                amsnippet1 = amsnippet1.replace('@LT@', perhapsLT)
                amsnippet1 = amsnippet1.replace('@la@', libext)
                amsnippet1 = amsnippet1.replace('@lo@', objext)
                pattern = re.compile(r'lib_([A-Z]+)', re.M)
                amsnippet1 = pattern.sub(r'%s_%s_\1' % (libname, libext),
                                         amsnippet1)
                amsnippet1 = amsnippet1.replace('$(GNULIB_', '$(' + module_indicator_prefix + '_GNULIB_')
                amsnippet1 = amsnippet1.replace('lib%_LIBRARIES', 'lib_LIBRARIES')
                amsnippet1 = amsnippet1.replace('lib%_LTLIBRARIES', 'lib_LTLIBRARIES')
                if edit_check_PROGRAMS:
                    amsnippet1 = amsnippet1.replace('check_PROGRAMS', 'noinst_PROGRAMS')
                amsnippet1 = amsnippet1.replace('${gl_include_guard_prefix}',
                                                include_guard_prefix)
                if module.name == 'alloca':
                    amsnippet1 += '%s_%s_LIBADD += @%sALLOCA@\n' % (libname, libext, perhapsLT)
                    amsnippet1 += '%s_%s_DEPENDENCIES += @%sALLOCA@\n' % (libname, libext, perhapsLT)
                amsnippet1 = combine_lines_matching(re.compile(r'%s_%s_SOURCES' % (libname, libext)),
                                                    amsnippet1)

                # Get unconditional snippet, edit it and save to amsnippet2.
                amsnippet2 = module.getAutomakeSnippet_Unconditional()
                pattern = re.compile(r'lib_([A-Z]+)', re.M)
                amsnippet2 = pattern.sub(r'%s_%s_\1' % (libname, libext),
                                         amsnippet2)
                amsnippet2 = amsnippet2.replace('$(GNULIB_',
                                                '$(' + module_indicator_prefix + '_GNULIB_')
                # Skip the contents if it's entirely empty.
                if (amsnippet1 + amsnippet2).strip() != '':
                    allsnippets += '## begin gnulib module %s\n' % module.name
                    if gnu_make:
                        allsnippets += 'ifeq (,$(OMIT_GNULIB_MODULE_%s))\n' % module.name
                    allsnippets += '\n'
                    if conddeps:
                        if moduletable.isConditional(module):
                            name = module.getConditionalName()
                            if gnu_make:
                                allsnippets += 'ifneq (,$(%s_CONDITION))\n' % name
                            else:
                                allsnippets += 'if %s\n' % name
                    if gnu_make:
                        allsnippets += _convert_to_gnu_make(amsnippet1)
                    else:
                        allsnippets += amsnippet1
                    if conddeps:
                        if moduletable.isConditional(module):
                            allsnippets += 'endif\n'
                    if gnu_make:
                        allsnippets += _convert_to_gnu_make(amsnippet2)
                    else:
                        allsnippets += amsnippet2
                    if gnu_make:
                        allsnippets += 'endif\n'
                    allsnippets += '## end   gnulib module %s\n\n' % module.name

                    # Test whether there are some source files in subdirectories.
                    for file in module.getFiles():
                        if (file.startswith('lib/')
                                and file.endswith('.c')
                                and file.count('/') > 1):
                            uses_subdirs = True
                            break

        if not makefile_name:
            subdir_options = ''
            # If there are source files in subdirectories, prevent collision of the
            # object files (example: hash.c and libxml/hash.c).
            if uses_subdirs:
                subdir_options = ' subdir-objects'
            emit += 'AUTOMAKE_OPTIONS = 1.14 gnits%s\n' % subdir_options
        emit += '\n'
        if not makefile_name:
            emit += 'SUBDIRS =\n'
            emit += 'noinst_HEADERS =\n'
            emit += 'noinst_LIBRARIES =\n'
            emit += 'noinst_LTLIBRARIES =\n'
            emit += 'pkgdata_DATA =\n'
            emit += 'EXTRA_DIST =\n'
            emit += 'BUILT_SOURCES =\n'
            emit += 'SUFFIXES =\n'
        emit += 'MOSTLYCLEANFILES %s core *.stackdump\n' % assign
        if not makefile_name:
            emit += 'MOSTLYCLEANDIRS =\n'
            emit += 'CLEANFILES =\n'
            emit += 'DISTCLEANFILES =\n'
            emit += 'MAINTAINERCLEANFILES =\n'

        if gnu_make:
            emit += '# Start of GNU Make output.\n'
            result = sp.run([UTILS['autoconf'], '-t', 'AC_SUBST:$1 = @$1@',
                             joinpath(self.config['destdir'], 'configure.ac')],
                            capture_output=True)
            if result.returncode == 0:
                # sort -u
                emit += lines_to_multiline(sorted({ x.strip()
                                                    for x in result.stdout.decode(encoding='utf-8').splitlines() }))
            else:
                emit += '== gnulib-tool GNU Make output failed as follows ==\n'
                emit += ['# stderr: ' + x + '\n' for x in
                         result.stderr.decode(encoding='utf-8').splitlines()]
            emit += '# End of GNU Make output.\n'
        else:
            emit += '# No GNU Make output.\n'

        # Execute edits that apply to the Makefile.am being generated.
        for current_edit in range(0, makefiletable.count()):
            dictionary = makefiletable[current_edit]
            if 'var' in dictionary:
                if destfile == joinpath(dictionary['dir'], 'Makefile.am'):
                    val = dictionary['val']
                    if dictionary['var'] == 'SUBDIRS' and dictionary['dotfirst']:
                        # The added subdirectory ${val} needs to be mentioned after '.'.
                        # Since we don't have '.' among SUBDIRS so far, add it now.
                        val = f'. {val}'
                    emit += '%s += %s\n' % (dictionary['var'], val)
                    del dictionary['var']

        # Define two parts of cppflags variable.
        cppflags_part1 = ''
        if witness_c_macro:
            cppflags_part1 = ' -D%s=1' % witness_c_macro
        cppflags_part2 = ''
        if for_test:
            cppflags_part2 = ' -DGNULIB_STRICT_CHECKING=1'
        cppflags = '%s%s' % (cppflags_part1, cppflags_part2)
        if not makefile_name:
            emit += '\n'
            emit += 'AM_CPPFLAGS =%s\n' % cppflags
            emit += 'AM_CFLAGS =\n'
        else:  # if makefile_name
            if cppflags:
                emit += '\n'
                emit += 'AM_CPPFLAGS +=%s\n' % cppflags
        emit += '\n'

        # Test if 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.
        # First test if allsnippets already specify an installation location.
        lib_gets_installed = False
        regex = r'^[a-zA-Z0-9_]*_%sLIBRARIES *\+{0,1}= *%s\.%s' % (perhapsLT, libname, libext)
        pattern = re.compile(regex, re.M)
        if pattern.findall(allsnippets):
            lib_gets_installed = True
        else:
            # Then test if $sourcebase/Makefile.am (if it exists) specifies it.
            if makefile_name:
                path = joinpath(sourcebase, 'Makefile.am')
                if os.path.isfile(path):
                    with open(path, mode='r', newline='\n', encoding='utf-8') as file:
                        data = file.read()
                    if pattern.findall(data):
                        lib_gets_installed = True
        if not lib_gets_installed:
            # By default, the generated library should not be installed.
            emit += 'noinst_%sLIBRARIES += %s.%s\n' % (perhapsLT, libname, libext)

        emit += '\n'
        emit += '%s_%s_SOURCES =\n' % (libname, libext)
        # Insert a '-Wno-error' option in the compilation commands emitted by
        # Automake, between $(AM_CPPFLAGS) and before the reference to @CFLAGS@.
        # Why?
        # - Because every package maintainer has their preferred set of warnings
        #   that they may want to enforce in the main source code of their package,
        #   and some of these warnings (such as '-Wswitch-enum') complain about code
        #   that is just perfect.
        #   Gnulib source code is maintained according to Gnulib coding style.
        #   Package maintainers have no right to force their coding style upon Gnulib.
        # Why before @CFLAGS@?
        # - Because "the user is always right": If a user adds '-Werror' to their
        #   CFLAGS, they have asked for errors, they will get errors. But they have
        #   no right to complain about these errors, because Gnulib does not support
        #   '-Werror'.
        if not for_test:
            emit += '%s_%s_CFLAGS = $(AM_CFLAGS) $(GL_CFLAG_GNULIB_WARNINGS) $(GL_CFLAG_ALLOW_WARNINGS)\n' % (libname, libext)
        # Here we use $(LIBOBJS), not @LIBOBJS@. The value is the same. However,
        # automake during its analysis looks for $(LIBOBJS), not for @LIBOBJS@.
        if not for_test:
            # When there is a {libname}_{libext}_CFLAGS or {libname}_{libext}_CPPFLAGS
            # definition, Automake emits rules for creating object files prefixed with
            # "{libname}_{libext}-".
            emit += '%s_%s_LIBADD = $(%s_%s_%sLIBOBJS)\n' % (libname, libext, macro_prefix, libname, perhapsLT)
            emit += '%s_%s_DEPENDENCIES = $(%s_%s_%sLIBOBJS)\n' % (libname, libext, macro_prefix, libname, perhapsLT)
        else:
            emit += '%s_%s_LIBADD = $(%s_%sLIBOBJS)\n' % (libname, libext, macro_prefix, perhapsLT)
            emit += '%s_%s_DEPENDENCIES = $(%s_%sLIBOBJS)\n' % (libname, libext, macro_prefix, perhapsLT)
        emit += 'EXTRA_%s_%s_SOURCES =\n' % (libname, libext)
        if libtool:
            emit += '%s_%s_LDFLAGS = $(AM_LDFLAGS)\n' % (libname, libext)
            emit += '%s_%s_LDFLAGS += -no-undefined\n' % (libname, libext)
            # Synthesize an ${libname}_${libext}_LDFLAGS augmentation by combining
            # the link dependencies of all modules.
            links = [ module.getLink()
                      for module in modules
                      if module.isNonTests() ]
            lines = [ line
                      for link in links
                      for line in link.split('\n')
                      if line != '' ]
            pattern = re.compile(r' when linking with libtool.*')
            lines = [ pattern.sub(r'', line)
                      for line in lines ]
            lines = sorted(set(lines))
            for line in lines:
                emit += '%s_%s_LDFLAGS += %s\n' % (libname, libext, line)
        emit += '\n'
        if pobase:
            emit += 'AM_CPPFLAGS += -DDEFAULT_TEXT_DOMAIN=\\"%s-gnulib\\"\n' % podomain
            emit += '\n'
        allsnippets = allsnippets.replace('$(top_srcdir)/build-aux/',
                                          '$(top_srcdir)/%s/' % auxdir)
        emit += allsnippets
        emit += '\n'
        emit += 'mostlyclean-local: mostlyclean-generic\n'
        emit += '\t@for dir in \'\' $(MOSTLYCLEANDIRS); do \\\n'
        emit += '\t  if test -n "$$dir" && test -d $$dir; then \\\n'
        emit += '\t    echo "rmdir $$dir"; rmdir $$dir; \\\n'
        emit += '\t  fi; \\\n'
        emit += '\tdone; \\\n'
        emit += '\t:\n'
        # Emit rules to erase .Po and .Plo files for AC_LIBOBJ invocations.
        # Extend the 'distclean' rule.
        emit += 'distclean-local: distclean-gnulib-libobjs\n'
        emit += 'distclean-gnulib-libobjs:\n'
        if not for_test:
            # When there is a {libname}_{libext}_CFLAGS or {libname}_{libext}_CPPFLAGS
            # definition, Automake emits rules for creating object files prefixed with
            # "{libname}_{libext}-".
            emit += '\t-rm -f @%s_%s_LIBOBJDEPS@\n' % (macro_prefix, libname)
        else:
            emit += '\t-rm -f @%s_LIBOBJDEPS@\n' % (macro_prefix)
        # Extend the 'maintainer-clean' rule.
        emit += 'maintainer-clean-local: distclean-gnulib-libobjs\n'
        return emit

    def tests_Makefile_am(self, destfile: str, modules: list[GLModule], moduletable: GLModuleTable,
                          makefiletable: GLMakefileTable, witness_macro: str, for_test: bool) -> str:
        '''Emit the contents of the tests Makefile. Returns it as a string.
        GLConfig: localpath, modules, libname, auxdir, makefile_name, libtool,
        sourcebase, m4base, testsbase, macro_prefix, witness_c_macro,
        single_configure, libtests.

        destfile is a filename relative to destdir of Makefile being generated.
        witness_macro is a string which represents witness_c_macro with the suffix.
        modules is a list of GLModule instances.
        moduletable is a GLModuleTable instance.
        makefiletable is a GLMakefileTable instance.
        actioncmd is a string variable, which represents the actioncmd; it can be
          an empty string e.g. when user wants to generate files for GLTestDir.
        for_test is a bool variable; it must be set to True if creating a package
          for testing, False otherwise.'''
        if type(destfile) is not str:
            raise TypeError('destfile must be a string, not %s'
                            % type(destfile).__name__)
        for module in modules:
            if type(module) is not GLModule:
                raise TypeError('each module must be a GLModule instance')
        if type(makefiletable) is not GLMakefileTable:
            raise TypeError('makefiletable must be a GLMakefileTable, not %s'
                            % type(makefiletable).__name__)
        if type(witness_macro) is not str:
            raise TypeError('witness_macro must be a string, not %s'
                            % type(witness_macro).__name__)
        if type(for_test) is not bool:
            raise TypeError('for_test must be a bool, not %s'
                            % type(for_test).__name__)
        auxdir = self.config['auxdir']
        sourcebase = self.config['sourcebase']
        libname = self.config['libname']
        m4base = self.config['m4base']
        testsbase = self.config['testsbase']
        gnu_make = self.config['gnu_make']
        libtool = self.config['libtool']
        macro_prefix = self.config['macro_prefix']
        conddeps = self.config['conddeps']
        witness_c_macro = self.config['witness_c_macro']
        include_guard_prefix = self.config['include_guard_prefix']
        module_indicator_prefix = self.config.getModuleIndicatorPrefix()
        libtests = self.config['libtests']
        single_configure = self.config['single_configure']
        emit = ''

        if libtool:
            libext = 'la'
            objext = 'lo'
            perhapsLT = 'LT'
            eliminate_LDFLAGS = False
        else:  # if not libtool
            libext = 'a'
            objext = 'o'
            perhapsLT = ''
            eliminate_LDFLAGS = True
        if for_test:
            # 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.
            edit_check_PROGRAMS = True
        else:  # if not for_test
            edit_check_PROGRAMS = False

        # Compute testsbase_inverse
        testsbase_inverse = relinverse(testsbase)

        # Begin the generation.
        emit += '## DO NOT EDIT! GENERATED AUTOMATICALLY!\n'
        emit += '## Process this file with automake to produce Makefile.in.\n'
        emit += '%s\n' % self.copyright_notice()

        uses_subdirs = False
        main_snippets = ''
        longrun_snippets = ''
        for module in modules:
            if for_test and not single_configure:
                if module.repeatModuleInTests():
                    accept = True
                else:
                    accept = module.isTests()
            else:  # if for_test and not single_configure
                accept = True
            if accept:
                amsnippet1 = module.getAutomakeSnippet_Conditional()
                amsnippet1 = amsnippet1.replace('lib_LIBRARIES', 'lib%_LIBRARIES')
                amsnippet1 = amsnippet1.replace('lib_LTLIBRARIES', 'lib%_LTLIBRARIES')
                if eliminate_LDFLAGS:
                    pattern = re.compile(r'^(lib_LDFLAGS[\t ]*\+=.*$\n)', re.M)
                    amsnippet1 = pattern.sub(r'', amsnippet1)
                # Replace NMD, so as to remove redundant "$(MKDIR_P) '.'" invocations.
                # The logic is similar to how we define gl_source_base_prefix.
                amsnippet1 = _eliminate_NMD(amsnippet1, False)
                # Replace @LT@, @la@, @lo@, depending on libtool.
                amsnippet1 = amsnippet1.replace('@LT@', perhapsLT)
                amsnippet1 = amsnippet1.replace('@la@', libext)
                amsnippet1 = amsnippet1.replace('@lo@', objext)
                pattern = re.compile(r'lib_([A-Z]+)', re.M)
                amsnippet1 = pattern.sub(r'libtests_a_\1', amsnippet1)
                amsnippet1 = amsnippet1.replace('$(GNULIB_', '$(' + module_indicator_prefix + '_GNULIB_')
                amsnippet1 = amsnippet1.replace('lib%_LIBRARIES', 'lib_LIBRARIES')
                amsnippet1 = amsnippet1.replace('lib%_LTLIBRARIES', 'lib_LTLIBRARIES')
                if edit_check_PROGRAMS:
                    amsnippet1 = amsnippet1.replace('check_PROGRAMS', 'noinst_PROGRAMS')
                amsnippet1 = amsnippet1.replace('${gl_include_guard_prefix}',
                                                include_guard_prefix)
                # Check if module is 'alloca'.
                if libtests and module.name == 'alloca':
                    amsnippet1 += 'libtests_a_LIBADD += @ALLOCA@\n'
                    amsnippet1 += 'libtests_a_DEPENDENCIES += @ALLOCA@\n'

                amsnippet1 = combine_lines_matching(re.compile(r'libtests_a_SOURCES'),
                                                    amsnippet1)

                # Get unconditional snippet, edit it and save to amsnippet2.
                amsnippet2 = module.getAutomakeSnippet_Unconditional()
                pattern = re.compile(r'lib_([A-Z]+)', re.M)
                amsnippet2 = pattern.sub(r'libtests_a_\1', amsnippet2)
                amsnippet2 = amsnippet2.replace('$(GNULIB_',
                                                '$(' + module_indicator_prefix + '_GNULIB_')
                # Skip the contents if it's entirely empty.
                if (amsnippet1 + amsnippet2).strip() != '':
                    snippet = '## begin gnulib module %s\n' % module.name
                    if gnu_make:
                        snippet += 'ifeq (,$(OMIT_GNULIB_MODULE_%s))\n' % module.name
                    snippet += '\n'
                    if conddeps:
                        if moduletable.isConditional(module):
                            name = module.getConditionalName()
                            if gnu_make:
                                snippet += 'ifneq (,$(%s_CONDITION))\n' % name
                            else:
                                snippet += 'if %s\n' % name
                    if gnu_make:
                        snippet += _convert_to_gnu_make(amsnippet1)
                    else:
                        snippet += amsnippet1
                    if conddeps:
                        if moduletable.isConditional(module):
                            snippet += 'endif\n'
                    if gnu_make:
                        snippet += _convert_to_gnu_make(amsnippet2)
                    else:
                        snippet += amsnippet2
                    if gnu_make:
                        snippet += 'endif\n'
                    snippet += '## end   gnulib module %s\n\n' % module.name
                    # Mention long-running tests at the end.
                    if 'longrunning-test' in module.getStatuses():
                        longrun_snippets += snippet
                    else:
                        main_snippets += snippet

                    # Test whether there are some source files in subdirectories.
                    for file in module.getFiles():
                        if ((file.startswith('lib/') or file.startswith('tests/'))
                                and file.endswith('.c')
                                and file.count('/') > 1):
                            uses_subdirs = True
                            break

        # 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 = ''
        if uses_subdirs:
            subdir_options = ' subdir-objects'
        emit += 'AUTOMAKE_OPTIONS = 1.14 foreign%s\n\n' % subdir_options
        if for_test and not single_configure:
            emit += 'ACLOCAL_AMFLAGS = -I %s/%s\n\n' % (testsbase_inverse, m4base)

        # 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"
        emit += 'SUBDIRS = .\n'
        emit += 'TESTS =\n'
        emit += 'XFAIL_TESTS =\n'
        emit += 'TESTS_ENVIRONMENT =\n'
        emit += 'noinst_PROGRAMS =\n'
        if not for_test:
            emit += 'check_PROGRAMS =\n'
        emit += 'EXTRA_PROGRAMS =\n'
        emit += 'noinst_HEADERS =\n'
        emit += 'noinst_LIBRARIES =\n'
        if libtests:
            if for_test:
                emit += 'noinst_LIBRARIES += libtests.a\n'
            else:  # if not for_test
                emit += 'check_LIBRARIES = libtests.a\n'
        emit += 'pkgdata_DATA =\n'
        emit += 'EXTRA_DIST =\n'
        emit += 'BUILT_SOURCES =\n'
        emit += 'SUFFIXES =\n'
        emit += 'MOSTLYCLEANFILES = core *.stackdump\n'
        emit += 'MOSTLYCLEANDIRS =\n'
        emit += 'CLEANFILES =\n'
        emit += 'DISTCLEANFILES =\n'
        emit += 'MAINTAINERCLEANFILES =\n'

        # Execute edits that apply to the Makefile.am being generated.
        for current_edit in range(0, makefiletable.count()):
            dictionary = makefiletable[current_edit]
            if 'var' in dictionary:
                if destfile == joinpath(dictionary['dir'], 'Makefile.am'):
                    val = dictionary['val']
                    if dictionary['var'] == 'SUBDIRS' and dictionary['dotfirst']:
                        # The added subdirectory ${val} needs to be mentioned after '.'.
                        # But we have '.' among SUBDIRS already, so do nothing.
                        pass
                    emit += '%s += %s\n' % (dictionary['var'], val)
                    del dictionary['var']

        emit += '\n'

        # Insert a '-Wno-error' option in the compilation commands emitted by
        # Automake, between $(AM_CPPFLAGS) and before the reference to @CFLAGS@.
        # Why?
        # 1) Because parts of the Gnulib tests exercise corner cases (invalid
        #    arguments, endless recursions, etc.) that a compiler may warn about,
        #    even with just the normal '-Wall' option.
        # 2) Because every package maintainer has their preferred set of warnings
        #    that they may want to enforce in the main source code of their package,
        #    and some of these warnings (such as '-Wswitch-enum') complain about code
        #    that is just perfect.
        #    But Gnulib tests are maintained in Gnulib and don't end up in binaries
        #    that that package installs; therefore it does not make sense for
        #    package maintainers to enforce the absence of warnings on these tests.
        # Why before @CFLAGS@?
        # - Because "the user is always right": If a user adds '-Werror' to their
        #   CFLAGS, they have asked for errors, they will get errors. But they have
        #   no right to complain about these errors, because Gnulib does not support
        #   '-Werror'.
        cflags_for_gnulib_code = ''
        if not for_test:
            # Enable or disable warnings as suitable for the Gnulib coding style.
            cflags_for_gnulib_code = ' $(GL_CFLAG_GNULIB_WARNINGS)'
        emit += 'CFLAGS = @GL_CFLAG_ALLOW_WARNINGS@%s @CFLAGS@\n' % (cflags_for_gnulib_code)
        emit += 'CXXFLAGS = @GL_CXXFLAG_ALLOW_WARNINGS@ @CXXFLAGS@\n'
        emit += '\n'

        emit += 'AM_CPPFLAGS = \\\n'
        if for_test:
            emit += '  -DGNULIB_STRICT_CHECKING=1 \\\n'
        if witness_c_macro:
            emit += '  -D%s=1 \\\n' % witness_c_macro
        if witness_macro:
            emit += '  -D@%s@=1 \\\n' % witness_macro
        emit += '  -I. -I$(srcdir) \\\n'
        emit += '  -I%s -I$(srcdir)/%s \\\n' % (testsbase_inverse, testsbase_inverse)
        emit += '  -I%s/%s -I$(srcdir)/%s/%s\n' % (testsbase_inverse, sourcebase, testsbase_inverse, sourcebase)
        emit += '\n'

        if libtests:
            # 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
            # 'version-etc' module whose dependency to 'version-etc-fsf' is
            # voluntarily omitted).
            # The LIBTESTS_LIBDEPS can be passed to the linker once or twice, it
            # does not matter.
            emit += ('LDADD = libtests.a %s/%s/%s.%s libtests.a %s/%s/%s.%s libtests.a $(LIBTESTS_LIBDEPS)\n'
                     % (testsbase_inverse, sourcebase, libname, libext,
                        testsbase_inverse, sourcebase, libname, libext))
        else:
            emit += ('LDADD = %s/%s/%s.%s\n'
                     % (testsbase_inverse, sourcebase, libname, libext))
        emit += '\n'
        if libtests:
            emit += 'libtests_a_SOURCES =\n'
            # Here we use $(LIBOBJS), not @LIBOBJS@. The value is the same. However,
            # automake during its analysis looks for $(LIBOBJS), not for @LIBOBJS@.
            emit += 'libtests_a_LIBADD = $(%stests_LIBOBJS)\n' % macro_prefix
            emit += 'libtests_a_DEPENDENCIES = $(%stests_LIBOBJS)\n' % macro_prefix
            emit += 'EXTRA_libtests_a_SOURCES =\n'
            # The circular dependency in LDADD requires this.
            emit += 'AM_LIBTOOLFLAGS = --preserve-dup-deps\n\n'
        # Many test scripts use ${EXEEXT} or ${srcdir}.
        # EXEEXT is defined by AC_PROG_CC through autoconf.
        # srcdir is defined by autoconf and automake.
        emit += "TESTS_ENVIRONMENT += EXEEXT='@EXEEXT@' srcdir='$(srcdir)'\n"
        # Omit logs of skipped tests from test-suite.log, if Automake ≥ 1.17 is used.
        emit += 'IGNORE_SKIPPED_LOGS = 1\n\n'
        all_snippets = main_snippets + longrun_snippets
        all_snippets = all_snippets.replace('$(top_srcdir)/build-aux/',
                                            '$(top_srcdir)/%s/' % auxdir)
        emit += all_snippets
        # Arrange to print a message before compiling the files in this directory.
        emit += 'all: all-notice\n'
        emit += 'all-notice:\n'
        emit += '\t@echo \'## ---------------------------------------------------- ##\'\n'
        emit += '\t@echo \'## ------------------- Gnulib tests ------------------- ##\'\n'
        emit += '\t@echo \'## You can ignore compiler warnings in this directory.  ##\'\n'
        emit += '\t@echo \'## ---------------------------------------------------- ##\'\n'
        emit += '\n'
        # Arrange to print a message before executing the tests in this directory.
        emit += 'check-am: check-notice\n'
        emit += 'check-notice:\n'
        emit += '\t@echo \'## ---------------------------------------------------------------------- ##\'\n'
        emit += '\t@echo \'## ---------------------------- Gnulib tests ---------------------------- ##\'\n'
        emit += '\t@echo \'## Please report test failures in this directory to <bug-gnulib@gnu.org>. ##\'\n'
        emit += '\t@echo \'## ---------------------------------------------------------------------- ##\'\n'
        emit += '\n'
        emit += '# Clean up after Solaris cc.\n'
        emit += 'clean-local:\n'
        emit += '\trm -rf SunWS_cache\n\n'
        emit += 'mostlyclean-local: mostlyclean-generic\n'
        emit += '\t@for dir in \'\' $(MOSTLYCLEANDIRS); do \\\n'
        emit += '\t  if test -n "$$dir" && test -d $$dir; then \\\n'
        emit += '\t    echo "rmdir $$dir"; rmdir $$dir; \\\n'
        emit += '\t  fi; \\\n'
        emit += '\tdone; \\\n'
        emit += '\t:\n'
        return emit
