# 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 . from __future__ import annotations #=============================================================================== # Define global imports #=============================================================================== import os import re import subprocess as sp from .constants import ( DIRS, UTILS, MODES, TESTS, cleaner, joinpath, lines_to_multiline, movefile, copyfile, copyfile2, substart, subend, bold_escapes, relconcat, relativize, rmtree, ) from .functions import rewrite_file_name from .GLError import GLError from .GLConfig import GLConfig from .GLModuleSystem import GLModuleTable from .GLModuleSystem import GLModuleSystem from .GLFileSystem import GLFileSystem from .GLFileSystem import GLFileAssistant from .GLMakefileTable import GLMakefileTable from .GLEmiter import GLEmiter from .GLFileTable import GLFileTable #=============================================================================== # Define GLImport class #=============================================================================== class GLImport: '''GLImport class is used to provide methods for --import, --add-import, --remove-import and --update actions. This is a high-level class, so developers may have to use lower-level classes to create their own scripts. However, if user needs just to use power of gnulib-tool, this class is a very good choice.''' mode: int m4dirs: list[str] config: GLConfig cache: GLConfig emitter: GLEmiter modulesystem: GLModuleSystem moduletable: GLModuleTable makefiletable: GLMakefileTable def __init__(self, config: GLConfig, mode: int, m4dirs: list[str]) -> None: '''Create GLImport instance. config - must be a GLConfig object. mode - must be one of the values of the constants.MODES values. m4dirs - list of all directories that contain relevant .m4 files.''' if type(config) is not GLConfig: raise TypeError('config must have GLConfig type, not %s' % repr(config)) if not (type(mode) is int and MODES['import'] <= mode <= MODES['update']): raise TypeError('mode must be 0 <= mode <= 3, not %s' % repr(mode)) if type(m4dirs) is not list: raise TypeError('m4dirs must be a list of strings, not %s' % repr(m4dirs)) self.mode = mode self.m4dirs = m4dirs # config contains the configuration, as specified through command-line # parameters. # self.config records the configuration that we are building/deducing. # Initialize it with the properties from config. self.config = config.copy() # self.cache is the configuration extracted from some files on the # file system: configure.{ac,in}, gnulib-cache.m4, gnulib-comp.m4. self.cache = GLConfig() # Read configure.{ac,in}. with open(self.config.getAutoconfFile(), mode='r', newline='\n', encoding='utf-8') as file: data = file.read() # Get cached auxdir from configure.{ac,in}. self.cache.setAuxDir('.') pattern = re.compile(r'^AC_CONFIG_AUX_DIR\([\[ ]*([^]"$`\\)]+).*?$', re.MULTILINE) match = pattern.search(data) if match: self.cache.setAuxDir(match.group(1)) if self.config['auxdir'] == '': self.config.setAuxDir(self.cache['auxdir']) # Get libtool guess from configure.{ac,in}. # XXX This is not actually used. pattern = re.compile(r'A[CM]_PROG_LIBTOOL', re.M) guessed_libtool = bool(pattern.findall(data)) # Guess autoconf version. pattern = re.compile(r'.*AC_PREREQ\((.*)\)', re.M) versions = cleaner(pattern.findall(data)) if versions: version = max({ float(version) for version in versions }) self.config.setAutoconfVersion(version) if version < 2.64: raise GLError(4, version) # Get other cached variables. path = joinpath(self.config['m4base'], 'gnulib-cache.m4') if os.path.isfile(path): with open(path, mode='r', newline='\n', encoding='utf-8') as file: data = file.read() # gl_LGPL is special, because it can occur with or without # an argument list. pattern = re.compile(r'^gl_LGPL$', re.M) if pattern.search(data): self.cache.setLGPL(True) # Find gl_* macros without an argument list. if 'gl_LIBTOOL' in data: self.cache.setLibtool(True) data = data.replace('gl_LIBTOOL', '') if 'gl_CONDITIONAL_DEPENDENCIES' in data: self.cache.setCondDeps(True) data = data.replace('gl_CONDITIONAL_DEPENDENCIES', '') if 'gl_WITH_TESTS' in data: self.cache.enableInclTestCategory(TESTS['tests']) data = data.replace('gl_WITH_TESTS', '') if 'gl_WITH_OBSOLETE' in data: self.cache.setIncObsolete(True) data = data.replace('gl_WITH_OBSOLETE', '') if 'gl_WITH_CXX_TESTS' in data: self.cache.enableInclTestCategory(TESTS['c++-test']) data = data.replace('gl_WITH_CXX_TESTS', '') if 'gl_WITH_LONGRUNNING_TESTS' in data: self.cache.enableInclTestCategory(TESTS['longrunning-test']) data = data.replace('gl_WITH_LONGRUNNING_TESTS', '') if 'gl_WITH_PRIVILEGED_TESTS' in data: self.cache.enableInclTestCategory(TESTS['privileged-test']) data = data.replace('gl_WITH_PRIVILEGED_TESTS', '') if 'gl_WITH_UNPORTABLE_TESTS' in data: self.cache.enableInclTestCategory(TESTS['unportable-test']) data = data.replace('gl_WITH_UNPORTABLE_TESTS', '') if 'gl_WITH_ALL_TESTS' in data: self.cache.enableInclTestCategory(TESTS['all-test']) data = data.replace('gl_WITH_ALL_TESTS', '') if 'gl_AUTOMAKE_SUBDIR' in data: self.cache.setAutomakeSubdir(True) data = data.replace('gl_AUTOMAKE_SUBDIR', '') # Find gl_* macros with an argument list. pattern = re.compile(r'^(gl_[A-Z0-9_]*)\((.*?)\)$', re.S | re.M) keys = \ [ 'gl_LOCAL_DIR', 'gl_MODULES', 'gl_AVOID', 'gl_SOURCE_BASE', 'gl_M4_BASE', 'gl_PO_BASE', 'gl_DOC_BASE', 'gl_TESTS_BASE', 'gl_LIB', 'gl_LGPL', 'gl_GPL', 'gl_MAKEFILE_NAME', 'gl_TESTS_MAKEFILE_NAME', 'gl_MACRO_PREFIX', 'gl_PO_DOMAIN', 'gl_WITNESS_C_MACRO', 'gl_VC_FILES', ] result = dict(pattern.findall(data)) values = cleaner([ result.get(key, '') for key in keys ]) tempdict = dict(zip(keys, values)) if 'gl_LGPL' in tempdict: lgpl = cleaner(tempdict['gl_LGPL']) if lgpl != '': self.cache.setLGPL(lgpl) else: # if 'gl_LGPL' not in tempdict self.cache.setLGPL(None) if tempdict['gl_LIB']: self.cache.setLibName(cleaner(tempdict['gl_LIB'])) if tempdict['gl_GPL']: self.cache.setLibName(cleaner(tempdict['gl_GPL'])) if tempdict['gl_LOCAL_DIR']: self.cache.setLocalPath(cleaner(tempdict['gl_LOCAL_DIR']).split(':')) if tempdict['gl_MODULES']: self.cache.setModules(cleaner(tempdict['gl_MODULES'].split())) if tempdict['gl_AVOID']: self.cache.setAvoids(cleaner(tempdict['gl_AVOID'].split())) if tempdict['gl_SOURCE_BASE']: self.cache.setSourceBase(cleaner(tempdict['gl_SOURCE_BASE'])) if tempdict['gl_M4_BASE']: self.cache.setM4Base(cleaner(tempdict['gl_M4_BASE'])) if tempdict['gl_PO_BASE']: self.cache.setPoBase(cleaner(tempdict['gl_PO_BASE'])) if tempdict['gl_DOC_BASE']: self.cache.setDocBase(cleaner(tempdict['gl_DOC_BASE'])) if tempdict['gl_TESTS_BASE']: self.cache.setTestsBase(cleaner(tempdict['gl_TESTS_BASE'])) if tempdict['gl_MAKEFILE_NAME']: self.cache.setMakefileName(cleaner(tempdict['gl_MAKEFILE_NAME'])) if tempdict['gl_TESTS_MAKEFILE_NAME']: self.cache.setTestsMakefileName(cleaner(tempdict['gl_TESTS_MAKEFILE_NAME'])) if tempdict['gl_MACRO_PREFIX']: self.cache.setMacroPrefix(cleaner(tempdict['gl_MACRO_PREFIX'])) if tempdict['gl_PO_DOMAIN']: self.cache.setPoDomain(cleaner(tempdict['gl_PO_DOMAIN'])) if tempdict['gl_WITNESS_C_MACRO']: self.cache.setWitnessCMacro(cleaner(tempdict['gl_WITNESS_C_MACRO'])) if tempdict['gl_VC_FILES'] != '': self.cache.setVCFiles(cleaner(tempdict['gl_VC_FILES'])) # Get cached filelist from gnulib-comp.m4. destdir, m4base = self.config.getDestDir(), self.config.getM4Base() path = joinpath(destdir, m4base, 'gnulib-comp.m4') if os.path.isfile(path): with open(path, mode='r', newline='\n', encoding='utf-8') as file: data = file.read() regex = r'AC_DEFUN\(\[%s_FILE_LIST\], \[(.*?)\]\)' % self.cache['macro_prefix'] pattern = re.compile(regex, re.S | re.M) matches = pattern.findall(data) if matches: files = matches[-1].strip().split() else: files = [] self.cache.setFiles(files) # The self.config['localpath'] defaults to the cached one. Recall that # the cached one is relative to self.config['destdir'], whereas the one # we use is relative to . or absolute. if len(self.config['localpath']) == 0: if len(self.cache['localpath']) > 0: localpath = [ self.relative_to_currdir(localdir) for localdir in self.cache['localpath'] ] self.config.setLocalPath(localpath) if self.mode == MODES['import']: self.config.setModules(sorted(self.config.getModules())) else: if self.cache['m4base'] and (self.config['m4base'] != self.cache['m4base']): raise GLError(5, m4base) # Perform actions with modules. In --add-import, append each given module # to the list of cached modules; in --remove-import, remove each given # module from the list of cached modules; in --update, simply set # self.config['modules'] to its cached version. new, old = self.config.getModules(), self.cache.getModules() if self.mode == MODES['add-import']: modules = sorted(set(new + old)) elif self.mode == MODES['remove-import']: modules = [ module for module in old if not module in new ] elif self.mode == MODES['update']: modules = self.cache.getModules() # Merge the configuration found on disk. self.config.update(self.cache) self.config.setModules(modules) # Merge with the configuration from the command-line parameters; # they override the configuration found on disk. for key in config.keys(): if key not in ['modules', 'avoids', 'incl_test_categories', 'excl_test_categories']: value = config[key] if not config.isdefault(key, value): self.config.update_key(config, key) # Determine whether --automake-subdir/--automake-subdir-tests are supported. if self.config['automake_subdir'] or self.config['automake_subdir_tests']: found_subdir_objects = False if self.config['destdir']: with open(self.config['configure_ac'], mode='r', newline='\n', encoding='utf-8') as file: data = file.read() pattern = re.compile(r'^.*AM_INIT_AUTOMAKE\([\[ ]*([^])]*).*$', re.MULTILINE) configure_ac_automake_options = pattern.findall(data) if configure_ac_automake_options: automake_options = { x for y in configure_ac_automake_options for x in y.split() } found_subdir_objects = 'subdir-objects' in automake_options if not found_subdir_objects: if self.config['destdir']: base = self.config['destdir'] else: base = '.' if os.path.isfile(joinpath(base, 'Makefile.am')): with open(joinpath(base, 'Makefile.am'), mode='r', newline='\n', encoding='utf-8') as file: data = file.read() pattern = re.compile(r'^AUTOMAKE_OPTIONS[\t ]*=(.*)$', re.MULTILINE) automake_options = pattern.findall(data) if automake_options: automake_options = { x for y in automake_options for x in y.split() } found_subdir_objects = 'subdir-objects' in automake_options if not found_subdir_objects: raise GLError(21, None) # Define GLImport attributes. self.emitter = GLEmiter(self.config) self.modulesystem = GLModuleSystem(self.config) self.moduletable = GLModuleTable(self.config, self.config.checkInclTestCategory(TESTS['all-tests']), self.config.checkInclTestCategory(TESTS['all-tests'])) self.makefiletable = GLMakefileTable(self.config) def __repr__(self) -> str: '''x.__repr__ <==> repr(x)''' result = '' % hex(id(self)) return result def actioncmd(self) -> str: '''Return command-line invocation comment.''' modules = self.config.getModules() avoids = self.config.getAvoids() localpath = self.config.getLocalPath() auxdir = self.config.getAuxDir() sourcebase = self.config.getSourceBase() m4base = self.config.getM4Base() docbase = self.config.getDocBase() pobase = self.config.getPoBase() testsbase = self.config.getTestsBase() conddeps = self.config.checkCondDeps() libname = self.config.getLibName() lgpl = self.config.getLGPL() gpl = self.config.getGPL() gnu_make = self.config.getGnuMake() makefile_name = self.config.getMakefileName() tests_makefile_name = self.config.getTestsMakefileName() automake_subdir = self.config.getAutomakeSubdir() automake_subdir_tests = self.config.getAutomakeSubdirTests() libtool = self.config.checkLibtool() macro_prefix = self.config.getMacroPrefix() witness_c_macro = self.config.getWitnessCMacro() podomain = self.config.getPoDomain() vc_files = self.config.checkVCFiles() # Command-line invocation printed in a comment in generated gnulib-cache.m4. actioncmd = '# gnulib-tool --import' # Break the action command log into multiple lines. # Emacs puts some gnulib-tool log lines in its source repository, and # git send-email rejects patch lines longer than 998 characters. # Also, config.status uses awk, and the HP-UX 11.00 awk fails if a # line has length >= 3071. if len(localpath) > 0: actioncmd += ''.join([f' \\\n# --local-dir={x}' for x in localpath]) actioncmd += ' \\\n# --lib=%s' % libname actioncmd += ' \\\n# --source-base=%s' % sourcebase actioncmd += ' \\\n# --m4-base=%s' % m4base if pobase: actioncmd += ' \\\n# --po-base=%s' % pobase actioncmd += ' \\\n# --doc-base=%s' % docbase actioncmd += ' \\\n# --tests-base=%s' % testsbase actioncmd += ' \\\n# --aux-dir=%s' % auxdir if self.config.checkInclTestCategory(TESTS['tests']): actioncmd += ' \\\n# --with-tests' if self.config.checkIncObsolete(): actioncmd += ' \\\n# --with-obsolete' if self.config.checkInclTestCategory(TESTS['c++-test']): actioncmd += ' \\\n# --with-c++-tests' if self.config.checkInclTestCategory(TESTS['longrunning-test']): actioncmd += ' \\\n# --with-longrunning-tests' if self.config.checkInclTestCategory(TESTS['privileged-test']): actioncmd += ' \\\n# --with-privileged-tests' if self.config.checkInclTestCategory(TESTS['unportable-test']): actioncmd += ' \\\n# --with-unportable-tests' if self.config.checkInclTestCategory(TESTS['all-test']): actioncmd += ' \\\n# --with-all-tests' if lgpl: if lgpl == True: actioncmd += ' \\\n# --lgpl' else: # if lgpl != True actioncmd += ' \\\n# --lgpl=%s' % lgpl if gpl: actioncmd += ' \\\n# --gpl=%s' % gpl if gnu_make: actioncmd += ' \\\n# --gnu-make' if makefile_name: actioncmd += ' \\\n# --makefile-name=%s' % makefile_name if tests_makefile_name: actioncmd += ' \\\n# --tests-makefile-name=%s' % tests_makefile_name if automake_subdir: actioncmd += ' \\\n# --automake-subdir' if automake_subdir_tests: actioncmd += ' \\\n# --automake-subdir-tests' if conddeps == True: actioncmd += ' \\\n# --conditional-dependencies' else: # if conddeps == False or conddeps == None actioncmd += ' \\\n# --no-conditional-dependencies' if libtool == True: actioncmd += ' \\\n# --libtool' else: # if libtool == False or libtool == None actioncmd += ' \\\n# --no-libtool' actioncmd += ' \\\n# --macro-prefix=%s' % macro_prefix if podomain: actioncmd += ' \\\n# --po-domain=%s' % podomain if witness_c_macro: actioncmd += ' \\\n# --witness-c-macro=%s' % witness_c_macro if vc_files == True: actioncmd += ' \\\n# --vc-files' elif vc_files == False: actioncmd += ' \\\n# --no-vc-files' if len(avoids) > 0: actioncmd += ''.join([f' \\\n# --avoid={x}' for x in avoids]) if len(modules) > 0: actioncmd += ''.join([f' \\\n# {x}' for x in modules]) return actioncmd def relative_to_destdir(self, dir: str) -> str: '''Convert a filename that represents dir, relative to the current directory, to a filename relative to destdir. GLConfig: destdir.''' destdir = self.config['destdir'] if os.path.isabs(dir): return dir else: if os.path.isabs(destdir): # XXX This doesn't look right. return dir else: return relativize(destdir, dir) def relative_to_currdir(self, dir: str) -> str: '''The opposite of GLImport.relative_to_destdir: Convert a filename that represents dir, relative to destdir, to a filename relative to the current directory. GLConfig: destdir.''' destdir = self.config['destdir'] if os.path.isabs(dir): return dir else: if os.path.isabs(destdir): # XXX This doesn't look right. return joinpath(destdir, dir) else: return relconcat(destdir, dir) def gnulib_cache(self) -> str: '''Emit the contents of generated $m4base/gnulib-cache.m4 file. GLConfig: destdir, localpath, tests, sourcebase, m4base, pobase, docbase, testsbase, conddeps, libtool, macro_prefix, podomain, vc_files.''' emit = '' actioncmd = self.actioncmd() localpath = self.config['localpath'] sourcebase = self.config['sourcebase'] m4base = self.config['m4base'] pobase = self.config['pobase'] docbase = self.config['docbase'] testsbase = self.config['testsbase'] lgpl = self.config['lgpl'] gpl = self.config['gpl'] libname = self.config['libname'] makefile_name = self.config['makefile_name'] tests_makefile_name = self.config['tests_makefile_name'] automake_subdir = self.config['automake_subdir'] conddeps = self.config['conddeps'] libtool = self.config['libtool'] macro_prefix = self.config['macro_prefix'] podomain = self.config['podomain'] witness_c_macro = self.config['witness_c_macro'] vc_files = self.config['vc_files'] modules = self.config['modules'] avoids = self.config['avoids'] emit += self.emitter.copyright_notice() emit += '''# # This file represents the specification of how gnulib-tool is used. # It acts as a cache: It is written and read by gnulib-tool. # In projects that use version control, this file is meant to be put under # version control, like the configure.ac and various Makefile.am files. # Specification in the form of a command-line invocation: %s # Specification in the form of a few gnulib-tool.m4 macro invocations:\n''' % actioncmd # Store the localpath relative to destdir. relative_localpath = [ self.relative_to_destdir(localdir) for localdir in localpath ] emit += 'gl_LOCAL_DIR([%s])\n' % ':'.join(relative_localpath) emit += 'gl_MODULES([\n' emit += ' %s\n' % '\n '.join(modules) emit += '])\n' if self.config.checkIncObsolete(): emit += 'gl_WITH_OBSOLETE\n' if self.config.checkInclTestCategory(TESTS['cxx-tests']): emit += 'gl_WITH_CXX_TESTS\n' if self.config.checkInclTestCategory(TESTS['longrunning-tests']): emit += 'gl_WITH_LONGRUNNING_TESTS\n' if self.config.checkInclTestCategory(TESTS['privileged-tests']): emit += 'gl_WITH_PRIVILEGED_TESTS\n' if self.config.checkInclTestCategory(TESTS['unportable-tests']): emit += 'gl_WITH_UNPORTABLE_TESTS\n' if self.config.checkInclTestCategory(TESTS['all-tests']): emit += 'gl_WITH_ALL_TESTS\n' emit += 'gl_AVOID([%s])\n' % ' '.join(avoids) emit += 'gl_SOURCE_BASE([%s])\n' % sourcebase emit += 'gl_M4_BASE([%s])\n' % m4base emit += 'gl_PO_BASE([%s])\n' % pobase emit += 'gl_DOC_BASE([%s])\n' % docbase emit += 'gl_TESTS_BASE([%s])\n' % testsbase if self.config.checkInclTestCategory(TESTS['tests']): emit += 'gl_WITH_TESTS\n' emit += 'gl_LIB([%s])\n' % libname if lgpl != None: if lgpl == True: emit += 'gl_LGPL\n' else: # if lgpl != True emit += 'gl_LGPL([%s])\n' % lgpl if gpl != None: emit += 'gl_GPL([%s])\n' % gpl emit += 'gl_MAKEFILE_NAME([%s])\n' % makefile_name if tests_makefile_name: emit += 'gl_TESTS_MAKEFILE_NAME([%s])\n' % tests_makefile_name if automake_subdir: emit += 'gl_AUTOMAKE_SUBDIR\n' if conddeps: emit += 'gl_CONDITIONAL_DEPENDENCIES\n' if libtool: emit += 'gl_LIBTOOL\n' emit += 'gl_MACRO_PREFIX([%s])\n' % macro_prefix emit += 'gl_PO_DOMAIN([%s])\n' % podomain emit += 'gl_WITNESS_C_MACRO([%s])\n' % witness_c_macro if vc_files != None: # Convert Python bools to shell (True -> true). emit += 'gl_VC_FILES([%s])\n' % str(vc_files).lower() return emit def gnulib_comp(self, filetable: GLFileTable, gentests: bool) -> str: '''Emit the contents of generated $m4base/gnulib-comp.m4 file. GLConfig: destdir, localpath, tests, sourcebase, m4base, pobase, docbase, testsbase, conddeps, libtool, macro_prefix, podomain, vc_files. filetable is a GLFileTable containing file information for this import. gentests is True if a tests Makefile.am is being generated, False otherwise.''' if type(filetable) is not GLFileTable: raise TypeError(f'filetable should be a GLFileTable, not {type(filetable).__name__}') if type(gentests) is not bool: raise TypeError(f'gentests should be a bool, not {type(gentests).__name__}') emit = '' moduletable = self.moduletable auxdir = self.config['auxdir'] sourcebase = self.config['sourcebase'] m4base = self.config['m4base'] testsbase = self.config['testsbase'] libname = self.config['libname'] libtool = self.config['libtool'] macro_prefix = self.config['macro_prefix'] witness_c_macro = self.config['witness_c_macro'] configure_ac = self.config['configure_ac'] libtests = self.config['libtests'] emit += '# DO NOT EDIT! GENERATED AUTOMATICALLY!\n' emit += self.emitter.copyright_notice() emit += '''# # This file represents the compiled summary of the specification in # gnulib-cache.m4. It lists the computed macro invocations that need # to be invoked from configure.ac. # In projects that use version control, this file can be treated like # other built files. # This macro should be invoked from %s, in the section # "Checks for programs", right after AC_PROG_CC, and certainly before # any checks for libraries, header files, types and library functions. AC_DEFUN([%s_EARLY], [ m4_pattern_forbid([^gl_[A-Z]])dnl the gnulib macro namespace m4_pattern_allow([^gl_ES$])dnl a valid locale name m4_pattern_allow([^gl_LIBOBJS$])dnl a variable m4_pattern_allow([^gl_LTLIBOBJS$])dnl a variable\n''' % (configure_ac, macro_prefix) emit += self.emitter.preEarlyMacros(True, ' ', moduletable.getFinalModules()) for module in moduletable.getFinalModules(): emit += ' # Code from module %s:\n' % module.name snippet = module.getAutoconfEarlySnippet() lines = [ line for line in snippet.split('\n') if line != '' ] if lines: emit += ' %s\n' % '\n '.join(lines) emit += '])\n' emit += ''' # This macro should be invoked from %s, in the section # "Check for header files, types and library functions". AC_DEFUN([%s_INIT], [\n''' % (configure_ac, macro_prefix) # This AC_CONFIG_LIBOBJ_DIR invocation silences an error from the automake # front end: # error: required file './alloca.c' not found # It is needed because of the last remaining use of AC_LIBSOURCES in # _AC_LIBOBJ_ALLOCA, invoked from AC_FUNC_ALLOCA. # All the m4_pushdef/m4_popdef logic in func_emit_initmacro_start/_end # does not help to avoid this error. newfile_set = { pair[1] for pair in filetable.new_files } if 'lib/alloca.c' in newfile_set: emit += ' AC_CONFIG_LIBOBJ_DIR([%s])\n' % sourcebase elif 'tests=lib/alloca.c' in newfile_set: # alloca.c will be present in $testsbase. emit += ' AC_CONFIG_LIBOBJ_DIR([%s])\n' % testsbase if libtool: emit += ' AM_CONDITIONAL([GL_COND_LIBTOOL], [true])\n' emit += ' gl_cond_libtool=true\n' else: # if not libtool emit += ' AM_CONDITIONAL([GL_COND_LIBTOOL], [false])\n' emit += ' gl_cond_libtool=false\n' emit += ' gl_libdeps=\n' emit += ' gl_ltlibdeps=\n' replace_auxdir = False if auxdir != 'build-aux': replace_auxdir = True emit += ' gl_m4_base=\'%s\'\n' % m4base emit += self.emitter.initmacro_start(macro_prefix, False) emit += self.emitter.shellvars_init(False, sourcebase) if witness_c_macro: emit += ' m4_pushdef([gl_MODULE_INDICATOR_CONDITION], [%s])\n' % witness_c_macro # Emit main autoconf snippets. emit += self.emitter.autoconfSnippets(moduletable.getMainModules(), moduletable.getMainModules(), moduletable, lambda module: True, True, False, True, replace_auxdir) if witness_c_macro: emit += ' m4_popdef([gl_MODULE_INDICATOR_CONDITION])\n' emit += ' # End of code from modules\n' emit += self.emitter.initmacro_end(macro_prefix, False) emit += ' gltests_libdeps=\n' emit += ' gltests_ltlibdeps=\n' emit += self.emitter.initmacro_start('%stests' % macro_prefix, gentests) emit += self.emitter.shellvars_init(True, testsbase) # 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 . emit += 'changequote(,)dnl\n' emit += ' %stests_WITNESS=' % macro_prefix emit += 'IN_`echo "${PACKAGE-$PACKAGE_TARNAME}" | LC_ALL=C tr abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ | LC_ALL=C sed -e \'s/[^A-Z0-9_]/_/g\'`_GNULIB_TESTS\n' emit += 'changequote([, ])dnl\n' emit += ' AC_SUBST([%stests_WITNESS])\n' % macro_prefix emit += ' gl_module_indicator_condition=$%stests_WITNESS\n' % macro_prefix emit += ' m4_pushdef([gl_MODULE_INDICATOR_CONDITION], ' emit += '[$gl_module_indicator_condition])\n' # Emit tests autoconf snippets. emit += self.emitter.autoconfSnippets(moduletable.getTestsModules(), moduletable.getMainModules() + moduletable.getTestsModules(), moduletable, lambda module: True, True, True, True, replace_auxdir) emit += ' m4_popdef([gl_MODULE_INDICATOR_CONDITION])\n' emit += self.emitter.initmacro_end('%stests' % macro_prefix, gentests) emit += ' AC_REQUIRE([gl_CC_GNULIB_WARNINGS])\n' # _LIBDEPS and _LTLIBDEPS variables are not needed if this library is # created using libtool, because libtool already handles the dependencies. if not libtool: libname_upper = libname.upper().replace('-', '_') emit += ' %s_LIBDEPS="$gl_libdeps"\n' % libname_upper emit += ' AC_SUBST([%s_LIBDEPS])\n' % libname_upper emit += ' %s_LTLIBDEPS="$gl_ltlibdeps"\n' % libname_upper emit += ' AC_SUBST([%s_LTLIBDEPS])\n' % libname_upper if libtests: emit += ' LIBTESTS_LIBDEPS="$gltests_libdeps"\n' emit += ' AC_SUBST([LIBTESTS_LIBDEPS])\n' emit += '])\n' emit += self.emitter.initmacro_done(macro_prefix, sourcebase) emit += self.emitter.initmacro_done('%stests' % macro_prefix, testsbase) emit += ''' # This macro records the list of files which have been installed by # gnulib-tool and may be removed by future gnulib-tool invocations. AC_DEFUN([%s_FILE_LIST], [\n''' % macro_prefix emit += ' %s\n' % '\n '.join(filetable.all_files) emit += '])\n' return emit def _done_dir_(self, directory: str, files_added: list[str], files_removed: list[str]) -> None: '''This method is used to determine ignore argument for _update_ignorelist_ method and then call it.''' destdir = self.config['destdir'] if (os.path.isdir(joinpath(destdir, 'CVS')) or os.path.isdir(joinpath(destdir, directory, 'CVS')) or os.path.isfile(joinpath(destdir, directory, '.cvsignore'))): self._update_ignorelist_(directory, '.cvsignore', files_added, files_removed) if (os.path.isdir(joinpath(destdir, '.git')) or os.path.isfile(joinpath(destdir, '.gitignore')) or os.path.isfile(joinpath(destdir, directory, '.gitignore'))): self._update_ignorelist_(directory, '.gitignore', files_added, files_removed) def _update_ignorelist_(self, directory: str, ignore: str, files_added: list[str], files_removed: list[str]) -> None: '''Update .gitignore or .cvsignore files.''' destdir = self.config['destdir'] if ignore == '.gitignore': # In a .gitignore file, "foo" applies to the current directory and all # subdirectories, whereas "/foo" applies to the current directory only. anchor = '/' else: anchor = '' srcpath = joinpath(directory, ignore) backupname = '%s~' % srcpath if os.path.isfile(joinpath(destdir, srcpath)): if files_added or files_removed: with open(joinpath(destdir, srcpath), mode='r', newline='\n', encoding='utf-8') as file: original_lines = file.readlines() # Clean the newlines but not trailing whitespace. original_lines = [ line.rstrip('\n') for line in original_lines ] already_listed_filenames = { substart(anchor, '', filename) for filename in original_lines if filename.strip() } filenames_to_add = set(files_added).difference(already_listed_filenames) filenames_to_remove = set(files_removed) if filenames_to_add or filenames_to_remove: if not self.config['dryrun']: print('Updating %s (backup in %s)' % (srcpath, backupname)) copyfile2(joinpath(destdir, srcpath), joinpath(destdir, backupname)) new_lines = original_lines + [ f'{anchor}{filename}' for filename in sorted(filenames_to_add) ] if anchor != '': lines_to_remove = filenames_to_remove.union({ f'{anchor}{filename}' for filename in filenames_to_remove }) else: lines_to_remove = filenames_to_remove new_lines = [ line for line in new_lines if line not in lines_to_remove ] with open(joinpath(destdir, srcpath), mode='w', newline='\n', encoding='utf-8') as file: file.write(lines_to_multiline(new_lines)) else: # if self.config['dryrun'] print('Update %s (backup in %s)' % (srcpath, backupname)) else: # if not os.path.isfile(joinpath(destdir, srcpath)) if files_added: if not self.config['dryrun']: print('Creating %s' % srcpath) files_added = sorted(set(files_added)) files_added = [ '%s%s' % (anchor, f) for f in files_added ] if ignore == '.cvsignore': # Automake generates Makefile rules that create .dirstamp files. files_added = ['.deps', '.dirstamp'] + files_added with open(joinpath(destdir, srcpath), mode='w', newline='\n', encoding='utf-8') as file: file.write(lines_to_multiline(files_added)) else: # if self.config['dryrun'] print('Create %s' % srcpath) def prepare(self) -> tuple[GLFileTable, dict[str, tuple[re.Pattern, str] | None]]: '''Perform preperations before GLImport.execute(). Returns a filetable and the transformers passed to GLFileAssistant().''' destdir = self.config['destdir'] modules = list(self.config['modules']) m4base = self.config['m4base'] lgpl = self.config['lgpl'] gpl = self.config['gpl'] verbose = self.config['verbosity'] base_modules = set() for name in modules: module = self.modulesystem.find(name) if module is not None: base_modules.add(module) base_modules = sorted(base_modules) # Perform transitive closure. final_modules = self.moduletable.transitive_closure(base_modules) # Show final module list. if verbose >= 0: (bold_on, bold_off) = bold_escapes() print('Module list with included dependencies (indented):') for module in final_modules: if module.name in self.config.getModules(): print(' %s%s%s' % (bold_on, module.name, bold_off)) else: # if module.name not in self.config.getModules() print(' %s' % module.name) # Separate modules into main_modules and tests_modules. modules = \ self.moduletable.transitive_closure_separately(base_modules, final_modules) main_modules, tests_modules = modules # Print main_modules and tests_modules. if verbose >= 1: print('Main module list:') for module in main_modules: print(' %s' % module.name) print('Tests-related module list:') for module in tests_modules: print(' %s' % module.name) # Determine whether a $testsbase/libtests.a is needed. libtests = False for module in tests_modules: files = module.getFiles() for file in files: if file.startswith('lib/'): libtests = True break if libtests: self.config.setLibtests(True) # Add the dummy module to the main module list if needed. main_modules = self.moduletable.add_dummy(main_modules) if libtests: # if we need to use libtests.a # Add the dummy module to the tests-related module list if needed. tests_modules = self.moduletable.add_dummy(tests_modules) # Transmit base_modules, final_modules, main_modules and tests_modules. self.moduletable.setBaseModules(base_modules) self.moduletable.setFinalModules(final_modules) self.moduletable.setMainModules(main_modules) self.moduletable.setTestsModules(tests_modules) # Check license incompatibilities. listing = [] compatibilities = dict() compatibilities['all'] = ['GPLv2+ build tool', 'GPLed build tool', 'public domain', 'unlimited', 'unmodifiable license text'] compatibilities['GPLv3'] = ['LGPLv2+', 'LGPLv3+ or GPLv2+', 'LGPLv3+', 'LGPL', 'GPLv2+', 'GPLv3+', 'GPL'] compatibilities['GPLv2'] = ['LGPLv2+', 'LGPLv3+ or GPLv2+', 'GPLv2+'] compatibilities['LGPLv3'] = ['LGPLv2+', 'LGPLv3+ or GPLv2+', 'LGPLv3+', 'LGPL'] compatibilities['LGPLv3orGPLv2'] = ['LGPLv2+', 'LGPLv3+ or GPLv2+'] compatibilities['LGPLv2'] = ['LGPLv2+'] if lgpl or gpl: for module in main_modules: license = module.getLicense() if license not in compatibilities['all']: if lgpl == True: if license not in compatibilities['LGPLv3']: listing.append(tuple([module.name, license])) elif lgpl: if license not in compatibilities['LGPLv'+lgpl]: listing.append(tuple([module.name, license])) elif gpl: if license not in compatibilities['GPLv'+gpl]: listing.append(tuple([module.name, license])) if listing: raise GLError(11, listing) # Print notices from modules. if verbose >= -1: for module in main_modules: notice = module.getNotice().strip('\n') if notice: print('Notice from module %s:' % module.name) pattern = re.compile(r'^(.*?)$', re.S | re.M) notice = pattern.sub(r' \1', notice) print(notice) # Determine script to apply to imported library files. sed_transform_lib_file = None if 'config-h' in [ module.name for module in main_modules ]: sed_transform_lib_file = (re.compile(r'^#ifdef[\t ]*HAVE_CONFIG_H[\t ]*$', re.MULTILINE), r'#if 1') sed_transform_main_lib_file = sed_transform_lib_file # Determine script to apply to auxiliary files that go into $auxdir/. sed_transform_build_aux_file = None # Determine script to apply to library files that go into $testsbase/. sed_transform_testsrelated_lib_file = sed_transform_lib_file # Determine the final file lists. main_filelist, tests_filelist = \ self.moduletable.filelist_separately(main_modules, tests_modules) filelist = sorted(set(main_filelist + tests_filelist)) if not filelist: raise GLError(12, None) # Print list of files. if verbose >= 0: print('File list:') for file in filelist: if file.startswith('tests=lib/'): rest = file[10:] print(' lib/%s -> tests/%s' % (rest, rest)) else: print(' %s' % file) # Prepare basic filelist and basic old_files/new_files variables. filelist = sorted(set(filelist)) # Add m4/gnulib-tool.m4 to the file list. It is not part of any module. new_files = filelist + ['m4/gnulib-tool.m4'] old_files = list(self.cache['files']) path = joinpath(destdir, m4base, 'gnulib-tool.m4') if os.path.isfile(path): old_files.append(joinpath('m4', 'gnulib-tool.m4')) # old_files is the list of files according to the last gnulib-tool invocation. # new_files is the list of files after this gnulib-tool invocation. # Construct the transformers. transformers = dict() transformers['lib'] = sed_transform_lib_file transformers['aux'] = sed_transform_build_aux_file transformers['main'] = sed_transform_main_lib_file transformers['tests'] = sed_transform_testsrelated_lib_file # old_table is a table with two columns: (rewritten-file-name original-file-name), # representing the files according to the last gnulib-tool invocation. old_table = { (rewrite_file_name(file_name, self.cache, True), file_name) for file_name in old_files } # new_table is a table with two columns: (rewritten-file-name original-file-name), # representing the files after this gnulib-tool invocation. new_table = { (rewrite_file_name(file_name, self.config, True), file_name) for file_name in new_files } # Prepare the filetable. filetable = GLFileTable(sorted(set(filelist))) filetable.old_files = sorted(old_table, key=lambda pair: pair[0]) filetable.new_files = sorted(new_table, key=lambda pair: pair[0]) # Return the result. result = tuple([filetable, transformers]) return result def execute(self, filetable: GLFileTable, transformers: dict[str, tuple[re.Pattern, str] | None]) -> None: '''Perform operations on the lists of files, which are given in a special format except filelist argument. Such lists of files can be created using GLImport.prepare() function.''' if type(filetable) is not GLFileTable: raise TypeError('filetable must be a GLFileTable, not %s' % type(filetable).__name__) destdir = self.config['destdir'] auxdir = self.config['auxdir'] sourcebase = self.config['sourcebase'] m4base = self.config['m4base'] pobase = self.config['pobase'] docbase = self.config['docbase'] testsbase = self.config['testsbase'] libname = self.config['libname'] makefile_name = self.config['makefile_name'] tests_makefile_name = self.config['tests_makefile_name'] automake_subdir = self.config['automake_subdir'] macro_prefix = self.config['macro_prefix'] vc_files = self.config['vc_files'] configure_ac = self.config['configure_ac'] actioncmd = self.actioncmd() # Determine whether to put anything into $testsbase. testsfiles = [ file for file in filetable.all_files if file.startswith('tests/') or file.startswith('tests=lib/') ] gentests = len(testsfiles) > 0 # Create all necessary directories. dirs = [sourcebase, m4base] if pobase: dirs.append(pobase) if [ file for file in filetable.all_files if file.startswith('doc/') ]: dirs.append(docbase) if gentests: dirs.append(testsbase) dirs.append(auxdir) dirs += sorted([ os.path.dirname(pair[0]) for pair in filetable.new_files ]) dirs = [ os.path.join(destdir, d) for d in dirs ] for directory in dirs: if not os.path.isdir(directory): print('Creating directory %s' % directory) if not self.config['dryrun']: try: # Try to create directory os.makedirs(directory) except Exception as exc: raise GLError(13, directory) from exc else: # if self.config['dryrun'] print('Create directory %s' % directory) # Create GLFileAssistant instance to process files. assistant = GLFileAssistant(self.config, transformers) # Set of rewritten-file-names from filetable.old_files. old_rewritten_files = { pair[0] for pair in filetable.old_files } # Set of rewritten-file-names from filetable.new_files. new_rewritten_files = { pair[0] for pair in filetable.new_files } # Files which are in filetable.old_files and not in filetable.new_files. # They will be removed and added to filetable.removed_files list. pairs = [ pair for pair in filetable.old_files if pair[0] not in new_rewritten_files ] pairs = sorted(set(pairs), key=lambda pair: pair[0]) files = sorted(set(pair[0] for pair in pairs)) for file in files: path = joinpath(destdir, file) if os.path.isfile(path) or os.path.islink(path): if not self.config['dryrun']: backup = '%s~' % path print('Removing file %s (backup in %s)' % (path, backup)) try: # Try to move file if os.path.exists(backup): os.remove(backup) movefile(path, '%s~' % path) except Exception as exc: raise GLError(14, file) from exc else: # if self.config['dryrun'] print('Remove file %s (backup in %s~)' % (path, path)) filetable.removed_files.append(file) # Files which are in filetable.new_files and not in filetable.old_files. # They will be added/updated and added to filetable.added_files list. already_present = False pairs = [ pair for pair in filetable.new_files if pair[0] not in old_rewritten_files ] pairs = sorted(set(pairs)) for pair in pairs: original = pair[1] rewritten = pair[0] assistant.setOriginal(original) assistant.setRewritten(rewritten) assistant.add_or_update(already_present) # Files which are in filetable.new_files and in filetable.old_files. # They will be added/updated and added to filetable.added_files list. already_present = True pairs = [ pair for pair in filetable.new_files if pair[0] in old_rewritten_files ] pairs = sorted(set(pairs)) for pair in pairs: original = pair[1] rewritten = pair[0] assistant.setOriginal(original) assistant.setRewritten(rewritten) assistant.add_or_update(already_present) # Add files which were added to the list of filetable.added_files. filetable.added_files += assistant.getFiles() filetable.added_files = sorted(set(filetable.added_files)) # Default the source makefile name to Makefile.am. if makefile_name: source_makefile_am = makefile_name else: source_makefile_am = 'Makefile.am' # Default the tests makefile name to the source makefile name. if tests_makefile_name: tests_makefile_am = tests_makefile_name else: tests_makefile_am = source_makefile_am # Create normal Makefile.ams. for_test = False # Setup list of Makefile.am edits that are to be performed afterwards. # Some of these edits apply to files that we will generate; others are # under the responsibility of the developer. if source_makefile_am == 'Makefile.am': sourcebase_dir = os.path.dirname(sourcebase) sourcebase_base = os.path.basename(sourcebase) self.makefiletable.editor(sourcebase_dir, 'SUBDIRS', sourcebase_base) if pobase: pobase_dir = os.path.dirname(pobase) pobase_base = os.path.basename(pobase) self.makefiletable.editor(pobase_dir, 'SUBDIRS', pobase_base) if self.config.checkInclTestCategory(TESTS['tests']): if tests_makefile_am == 'Makefile.am': testsbase_dir = os.path.dirname(testsbase) testsbase_base = os.path.basename(testsbase) self.makefiletable.editor(testsbase_dir, 'SUBDIRS', testsbase_base, True) self.makefiletable.editor('', 'ACLOCAL_AMFLAGS', m4base) self.makefiletable.parent(gentests, source_makefile_am, tests_makefile_am) # Create po/ directory. filesystem = GLFileSystem(self.config) if pobase: # Create po makefile and auxiliary files. for file in ['Makefile.in.in', 'remove-potcdate.sin', 'remove-potcdate.sed']: tmpfile = assistant.tmpfilename(joinpath(pobase, file)) path = joinpath('build-aux', 'po', file) if os.path.exists(joinpath(DIRS['root'], path)): lookedup, flag = filesystem.lookup(path) copyfile(lookedup, tmpfile) basename = joinpath(pobase, file) filename, backup, flag = assistant.super_update(basename, tmpfile) if flag == 1: if not self.config['dryrun']: print('Updating %s (backup in %s)' % (filename, backup)) else: # if self.config['dryrun'] print('Update %s (backup in %s)' % (filename, backup)) elif flag == 2: if not self.config['dryrun']: print('Creating %s' % filename) else: # if self.config['dryrun']: print('Create %s' % filename) filetable.added_files.append(filename) if os.path.isfile(tmpfile): os.remove(tmpfile) # Create po makefile parameterization, part 1. basename = joinpath(pobase, 'Makevars') tmpfile = assistant.tmpfilename(basename) emit = self.emitter.po_Makevars() with open(tmpfile, mode='w', newline='\n', encoding='utf-8') as file: file.write(emit) filename, backup, flag = assistant.super_update(basename, tmpfile) if flag == 1: if not self.config['dryrun']: print('Updating %s (backup in %s)' % (filename, backup)) else: # if self.config['dryrun'] print('Update %s (backup in %s)' % (filename, backup)) elif flag == 2: if not self.config['dryrun']: print('Creating %s' % filename) else: # if self.config['dryrun']: print('Create %s' % filename) filetable.added_files.append(filename) if os.path.isfile(tmpfile): os.remove(tmpfile) # Create po makefile parameterization, part 2. basename = joinpath(pobase, 'POTFILES.in') tmpfile = assistant.tmpfilename(basename) with open(tmpfile, mode='w', newline='\n', encoding='utf-8') as file: file.write(self.emitter.po_POTFILES_in(filetable.all_files)) basename = joinpath(pobase, 'POTFILES.in') filename, backup, flag = assistant.super_update(basename, tmpfile) if flag == 1: if not self.config['dryrun']: print('Updating %s (backup in %s)' % (filename, backup)) else: # if self.config['dryrun'] print('Update %s (backup in %s)' % (filename, backup)) elif flag == 2: if not self.config['dryrun']: print('Creating %s' % filename) else: # if self.config['dryrun']: print('Create %s' % filename) filetable.added_files.append(filename) if os.path.isfile(tmpfile): os.remove(tmpfile) # Fetch PO files. TP_URL = 'https://translationproject.org/latest/' if not self.config['dryrun']: print('Fetching gnulib PO files from %s' % TP_URL) args = ['wget', '--no-verbose', '--mirror', '--level=1', '-nd', '-A.po', '-P', '.', '%sgnulib/' % TP_URL] sp.call(args, cwd=joinpath(destdir, pobase)) else: # if self.config['dryrun'] print('Fetch gnulib PO files from %s' % TP_URL) # Create po/LINGUAS. basename = joinpath(pobase, 'LINGUAS') if not self.config['dryrun']: tmpfile = assistant.tmpfilename(basename) data = '# Set of available languages.\n' files = sorted([ subend('.po', '', file) for file in os.listdir(joinpath(destdir, pobase)) if file.endswith('.po') ]) data += lines_to_multiline(files) with open(tmpfile, mode='w', newline='\n', encoding='utf-8') as file: file.write(data) filename, backup, flag = assistant.super_update(basename, tmpfile) if flag == 1: print('Updating %s (backup in %s)' % (filename, backup)) elif flag == 2: print('Creating %s' % filename) filetable.added_files.append(filename) if os.path.isfile(tmpfile): os.remove(tmpfile) else: # if not self.config['dryrun'] backupname = '%s~' % basename if os.path.isfile(joinpath(destdir, basename)): print('Update %s (backup in %s)' % (basename, backupname)) else: # if not os.path.isfile(joinpath(destdir, basename)) print('Create %s' % basename) # Create m4/gnulib-cache.m4. basename = joinpath(m4base, 'gnulib-cache.m4') tmpfile = assistant.tmpfilename(basename) emit = self.gnulib_cache() with open(tmpfile, mode='w', newline='\n', encoding='utf-8') as file: file.write(emit) filename, backup, flag = assistant.super_update(basename, tmpfile) if flag == 1: if not self.config['dryrun']: print('Updating %s (backup in %s)' % (filename, backup)) else: # if self.config['dryrun'] print('Update %s (backup in %s)' % (filename, backup)) elif flag == 2: if not self.config['dryrun']: print('Creating %s' % filename) else: # if self.config['dryrun']: print('Create %s' % filename) if emit[-2:] == '\r\n': emit = emit[:-2] elif emit[-1:] == '\n': emit = emit[:-1] print(emit) if os.path.isfile(tmpfile): os.remove(tmpfile) # Create m4/gnulib-comp.m4. basename = joinpath(m4base, 'gnulib-comp.m4') tmpfile = assistant.tmpfilename(basename) emit = self.gnulib_comp(filetable, gentests) with open(tmpfile, mode='w', newline='\n', encoding='utf-8') as file: file.write(emit) filename, backup, flag = assistant.super_update(basename, tmpfile) if flag == 1: if not self.config['dryrun']: print('Updating %s (backup in %s)' % (filename, backup)) else: # if self.config['dryrun'] print('Update %s (backup in %s)' % (filename, backup)) elif flag == 2: if not self.config['dryrun']: print('Creating %s' % filename) else: # if self.config['dryrun']: print('Create %s' % filename) if emit[-2:] == '\r\n': emit = emit[:-2] elif emit[-1:] == '\n': emit = emit[:-1] print(emit) if os.path.isfile(tmpfile): os.remove(tmpfile) if self.config['gnu_make']: # Regenerate aclocal.m4. # This is needed because the next step may run 'autoconf -t' and # the preceding steps may have added new *.m4 files (which need to # be reflected in aclocal.m4 before 'autoconf -t' is run). aclocal_args = [] for dir in self.m4dirs: aclocal_args.append('-I') aclocal_args.append(dir) sp.run([UTILS['aclocal']] + aclocal_args, cwd=destdir) # Create library makefile. # Do this after creating gnulib-comp.m4, because func_emit_lib_Makefile_am # can run 'autoconf -t', which reads gnulib-comp.m4. basename = joinpath(sourcebase, source_makefile_am) tmpfile = assistant.tmpfilename(basename) emit = self.emitter.lib_Makefile_am(basename, self.moduletable.getMainModules(), self.moduletable, self.makefiletable, actioncmd, for_test) if automake_subdir: emit = sp.run([joinpath(DIRS['root'], 'build-aux/prefix-gnulib-mk'), '--from-gnulib-tool', f'--lib-name={libname}', f'--prefix={sourcebase}/'], input=emit, text=True, capture_output=True).stdout with open(tmpfile, mode='w', newline='\n', encoding='utf-8') as file: file.write(emit) filename, backup, flag = assistant.super_update(basename, tmpfile) if flag == 1: if not self.config['dryrun']: print('Updating %s (backup in %s)' % (filename, backup)) else: # if self.config['dryrun'] print('Update %s (backup in %s)' % (filename, backup)) elif flag == 2: if not self.config['dryrun']: print('Creating %s' % filename) else: # if self.config['dryrun']: print('Create %s' % filename) filetable.added_files.append(filename) if os.path.isfile(tmpfile): os.remove(tmpfile) # Create tests Makefile. if gentests: basename = joinpath(testsbase, tests_makefile_am) tmpfile = assistant.tmpfilename(basename) emit = self.emitter.tests_Makefile_am(basename, self.moduletable.getTestsModules(), self.moduletable, self.makefiletable, '%stests_WITNESS' % macro_prefix, for_test) with open(tmpfile, mode='w', newline='\n', encoding='utf-8') as file: file.write(emit) filename, backup, flag = assistant.super_update(basename, tmpfile) if flag == 1: if not self.config['dryrun']: print('Updating %s (backup in %s)' % (filename, backup)) else: # if self.config['dryrun'] print('Update %s (backup in %s)' % (filename, backup)) elif flag == 2: if not self.config['dryrun']: print('Creating %s' % filename) else: # if self.config['dryrun']: print('Create %s' % filename) filetable.added_files.append(filename) if os.path.isfile(tmpfile): os.remove(tmpfile) if vc_files != False: # Update the .cvsignore and .gitignore files. ignorelist = [] # Treat gnulib-comp.m4 like an added file, even if it already existed. filetable.added_files.append(joinpath(m4base, 'gnulib-comp.m4')) filetable.added_files = sorted(set(filetable.added_files)) filetable.removed_files = sorted(set(filetable.removed_files)) for file in filetable.added_files: directory, basename = os.path.split(file) ignorelist.append(tuple([directory, '|A|', basename])) for file in filetable.removed_files: directory, basename = os.path.split(file) ignorelist.append(tuple([directory, '|R|', basename])) # Sort ignorelist by directory. ignorelist = sorted(ignorelist, key=lambda row: row[0]) last_dir = '' last_dir_files_added = [] last_dir_files_removed = [] for row in ignorelist: next_dir = row[0] operand = row[1] filename = row[2] if next_dir != last_dir: self._done_dir_(last_dir, last_dir_files_added, last_dir_files_removed) last_dir = next_dir last_dir_files_added = [] last_dir_files_removed = [] if operand == '|A|': last_dir_files_added.append(filename) elif operand == '|R|': last_dir_files_removed.append(filename) self._done_dir_(last_dir, last_dir_files_added, last_dir_files_removed) # Finish the work. print('Finished.\n') print('You may need to add #include directives for the following .h files.') # Intersect 'base' modules and 'main' modules # (since 'base' modules is not necessarily of subset of 'main' modules # - some may have been skipped through --avoid, and since the elements of # 'main' modules but not in 'base' modules can go away without explicit # notice - through changes in the module dependencies). modules = sorted(set(self.moduletable.getBaseModules()).intersection(self.moduletable.getMainModules())) # First the #include <...> directives without #ifs, sorted for convenience, # then the #include "..." directives without #ifs, sorted for convenience, # then the #include directives that are surrounded by #ifs. Not sorted. include_angles = [] include_quotes = [] include_if = [] for module in modules: include = module.getInclude() if '\n#if' in '\n'+include: include_if += [ f' {line}' for line in include.split('\n') if line.strip() ] else: include_angles += [ f' {line}' for line in include.split('\n') if 'include "' not in line and line.strip() ] include_quotes += [ f' {line}' for line in include.split('\n') if 'include "' in line and line.strip() ] includes = lines_to_multiline(sorted(set(include_angles))) includes += lines_to_multiline(sorted(set(include_quotes))) includes += lines_to_multiline(include_if) print(includes, end='') # Get link directives. links = [ module.getLink() for module in self.moduletable.getMainModules() ] lines = [ line for link in links for line in link.split('\n') if line != '' ] lines = sorted(set(lines)) if lines: print(''' You may need to use the following Makefile variables when linking. Use them in _LDADD when linking a program, or in _a_LDFLAGS or _la_LDFLAGS when linking a library.''') for line in lines: print(' %s' % line) # Print reminders. print('') print('Don\'t forget to') if source_makefile_am == 'Makefile.am': print(' - add "%s/Makefile" to AC_CONFIG_FILES in %s,' % (sourcebase, configure_ac)) else: # if makefile_am != 'Makefile.am' print(' - "include %s" from within "%s/Makefile.am",' % (source_makefile_am, sourcebase)) if pobase: print(' - add "%s/Makefile.in" to AC_CONFIG_FILES in %s,' % (pobase, configure_ac)) if gentests: if tests_makefile_am == 'Makefile.am': print(' - add "%s/Makefile" to AC_CONFIG_FILES in %s,' % (testsbase, configure_ac)) else: # if makefile_am != 'Makefile.am' print(' - "include %s" from within "%s/Makefile.am",' % (tests_makefile_am, testsbase)) # Print makefile edits. for current_edit in range(0, self.makefiletable.count()): dictionary = self.makefiletable[current_edit] if 'var' in dictionary: if dictionary['var'] == 'ACLOCAL_AMFLAGS': print(' - mention "-I %s" in %s in %s' % (dictionary['val'], dictionary['var'], joinpath(dictionary['dir'], 'Makefile.am'))) print(' or add an AC_CONFIG_MACRO_DIRS([%s]) invocation in %s,' % (dictionary['val'], configure_ac)) else: print(' - mention "%s" in %s in %s,' % (dictionary['val'], dictionary['var'], joinpath(dictionary['dir'], 'Makefile.am'))) # Detect position_early_after. with open(configure_ac, mode='r', newline='\n', encoding='utf-8') as file: data = file.read() match_result1 = re.compile(r'^ *AC_PROG_CC_STDC', re.MULTILINE).search(data) match_result2 = re.compile(r'^ *AC_PROG_CC_C99', re.MULTILINE).search(data) if match_result1: print(' - replace AC_PROG_CC_STDC with AC_PROG_CC in %s,' % (configure_ac)) position_early_after = 'AC_PROG_CC_STDC' elif match_result2: print(' - replace AC_PROG_CC_C99 with AC_PROG_CC in %s,' % (configure_ac)) position_early_after = 'AC_PROG_CC_C99' else: # if not any([match_result1, match_result2]) position_early_after = 'AC_PROG_CC' print(' - invoke %s_EARLY in %s, right after %s,' % (macro_prefix, configure_ac, position_early_after)) print(' - invoke %s_INIT in %s.' % (macro_prefix, configure_ac))