>From 62fa8fc7b2c9db14d8c24d6ec5beedecb27b4802 Mon Sep 17 00:00:00 2001 From: Bruno Haible Date: Wed, 10 Aug 2022 00:51:59 +0200 Subject: [PATCH 5/5] gnulib-tool.py: Finish implementing option --conditional-dependencies. * gnulib-tool.py (main) Accept options --conditional-dependencies, --no-conditional-dependencies. * pygnulib/GLModuleSystem.py (GLModuleTable.addConditional): Use str(module), not module, as key. Fix logic bug. (GLModuleTable.getCondition): Simplify. (GLModuleTable.transitive_closure): Show a warning when there are duplicate dependencies. Fix logic bug. (GLModuleTable.transitive_closure_separately): Simplify. (GLModuleTable.add_dummy): Ignore tests modules. Cope with multiple lib_SOURCES augmentation lines. Cope with comments at the end of a lib_SOURCES augmentation line. Add the dummy module at the end of the modules list. * pygnulib/GLTestDir.py (GLTestDir.execute): Remove the code that forces the dummy module to the end of the list. * pygnulib/GLEmiter.py (GLEmiter.autoconfSnippets): Add code to terminate the shell functions. Add code for the dependencies from the unconditional to the conditional modules. Don't emit AM_CONDITIONAL for unconditional modules. --- ChangeLog | 20 ++++++++ gnulib-tool.py | 13 ++++- gnulib-tool.py.TODO | 7 +-- pygnulib/GLEmiter.py | 44 +++++++++++++--- pygnulib/GLImport.py | 3 +- pygnulib/GLModuleSystem.py | 100 +++++++++++++++++++++++++++---------- pygnulib/GLTestDir.py | 24 +-------- 7 files changed, 148 insertions(+), 63 deletions(-) diff --git a/ChangeLog b/ChangeLog index 642a7363e3..0dfcb7cdd7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,25 @@ 2022-08-09 Bruno Haible + gnulib-tool.py: Finish implementing option --conditional-dependencies. + * gnulib-tool.py (main) Accept options --conditional-dependencies, + --no-conditional-dependencies. + * pygnulib/GLModuleSystem.py (GLModuleTable.addConditional): Use + str(module), not module, as key. Fix logic bug. + (GLModuleTable.getCondition): Simplify. + (GLModuleTable.transitive_closure): Show a warning when there are + duplicate dependencies. Fix logic bug. + (GLModuleTable.transitive_closure_separately): Simplify. + (GLModuleTable.add_dummy): Ignore tests modules. Cope with multiple + lib_SOURCES augmentation lines. Cope with comments at the end of a + lib_SOURCES augmentation line. Add the dummy module at the end of the + modules list. + * pygnulib/GLTestDir.py (GLTestDir.execute): Remove the code that forces + the dummy module to the end of the list. + * pygnulib/GLEmiter.py (GLEmiter.autoconfSnippets): Add code to + terminate the shell functions. Add code for the dependencies from the + unconditional to the conditional modules. Don't emit AM_CONDITIONAL for + unconditional modules. + gnulib-tool.py: Don't do license replacements in the autoconf snippets. * pygnulib/GLEmiter.py (GLEmiter.autoconfSnippet): Remove fileassistant argument. Don't invoke the 'aux' transformer here. Don't produce Windows diff --git a/gnulib-tool.py b/gnulib-tool.py index 9988c84de9..71ef1c4f2c 100755 --- a/gnulib-tool.py +++ b/gnulib-tool.py @@ -298,6 +298,15 @@ def main(): default=None, action='append', nargs=1) + # conditional-dependencies + parser.add_argument('--conditional-dependencies', + dest='cond_dependencies', + default=None, + action="store_true") + parser.add_argument('--no-conditional-dependencies', + dest='cond_dependencies', + default=None, + action="store_false") # libtool parser.add_argument('--libtool', dest='libtool', @@ -659,6 +668,7 @@ def main(): lgpl = lgpl[-1] if lgpl == None: lgpl = True + cond_dependencies = cmdargs.cond_dependencies libtool = cmdargs.libtool makefile_name = cmdargs.makefile_name if makefile_name != None: @@ -682,7 +692,6 @@ def main(): lsymlink = cmdargs.lsymlink == True single_configure = cmdargs.single_configure docbase = None - conddeps = None # Create pygnulib configuration. config = classes.GLConfig( @@ -702,7 +711,7 @@ def main(): lgpl=lgpl, makefile_name=makefile_name, libtool=libtool, - conddeps=conddeps, + conddeps=cond_dependencies, macro_prefix=macro_prefix, podomain=podomain, witness_c_macro=witness_c_macro, diff --git a/gnulib-tool.py.TODO b/gnulib-tool.py.TODO index be6a2e2b10..da6df2ab90 100644 --- a/gnulib-tool.py.TODO +++ b/gnulib-tool.py.TODO @@ -15,15 +15,9 @@ The following commits to gnulib-tool have not yet been reflected in -------------------------------------------------------------------------------- -Inline all 'sed' invocations. - --------------------------------------------------------------------------------- - Implement the options: --extract-recursive-dependencies --extract-recursive-link-directive - --conditional-dependencies - --no-conditional-dependencies --gnu-make --tests-makefile-name --automake-subdir @@ -37,6 +31,7 @@ Remove exit() in GLImport.py. Optimize: - os.chdir around subprocess creation -> cwd=... argument instead. + - Inline all 'sed' invocations. -------------------------------------------------------------------------------- diff --git a/pygnulib/GLEmiter.py b/pygnulib/GLEmiter.py index 9b54d5009e..c0fd053077 100644 --- a/pygnulib/GLEmiter.py +++ b/pygnulib/GLEmiter.py @@ -202,7 +202,6 @@ class GLEmiter(object): AM_GNU_GETTEXT invocations. replace_auxdir is a bool variable; it tells whether to replace 'build-aux' directory in AC_CONFIG_FILES.''' - emit = '' for module in modules: if type(module) is not GLModule: raise TypeError('each module must be a GLModule instance') @@ -229,6 +228,7 @@ class GLEmiter(object): auxdir = self.config['auxdir'] 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: @@ -290,7 +290,39 @@ class GLEmiter(object): # Intersect dependencies with the modules list. depmodules = [ dep for dep in depmodules - if dep in modules ] + if dep in modules ] # TODO should this be basemodules or modules? + for depmodule in depmodules: + if moduletable.isConditional(depmodule): + shellfunc = depmodule.getShellFunc() + condition = moduletable.getCondition(module, depmodule) + if condition != None: + emit += ' if %s; then\n' % condition + emit += ' %s\n' % shellfunc + emit += ' fi\n' + else: # if condition == None + 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 verifier == 0: + solution = True + elif verifier == 1: + solution = module.isNonTests() + elif verifier == 2: + solution = module.isTests() + if solution: + if not moduletable.isConditional(module): + depmodules = module.getDependenciesWithoutConditions() + # Intersect dependencies with the modules list. + depmodules = [ dep + for dep in depmodules + if dep in modules ] # TODO should this be basemodules or modules? for depmodule in depmodules: if moduletable.isConditional(depmodule): shellfunc = depmodule.getShellFunc() @@ -316,14 +348,14 @@ class GLEmiter(object): elif verifier == 2: solution = module.isTests() if solution: - condname = module.getConditionalName() - shellvar = module.getShellVar() - emit += ' AM_CONDITIONAL([%s], [$%s])\n' % (condname, shellvar) + 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 = '%s\n' % '\n'.join(lines) - emit = constants.nlconvert(emit) return emit def preEarlyMacros(self, require, indentation, modules): diff --git a/pygnulib/GLImport.py b/pygnulib/GLImport.py index 114605dd8a..5dd73fa8bd 100644 --- a/pygnulib/GLImport.py +++ b/pygnulib/GLImport.py @@ -831,9 +831,10 @@ AC_DEFUN([%s_FILE_LIST], [\n''' % macro_prefix if libtests: self.config.setLibtests(True) - # Add dummy package if it is needed. + # 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) # Check license incompatibilities. diff --git a/pygnulib/GLModuleSystem.py b/pygnulib/GLModuleSystem.py index 9bfd5bd266..f9b47a64a5 100644 --- a/pygnulib/GLModuleSystem.py +++ b/pygnulib/GLModuleSystem.py @@ -705,6 +705,18 @@ class GLModuleTable(object): inc_all_indirect_tests = True if all kinds of problematic unit tests among the unit tests of the dependencies should be included + + Methods for conditional dependencies: + - addUnconditional(B) + notes the presence of B as an unconditional module. + - addConditional(A, B. cond) + notes the presence of a conditional dependency from module A to module B, + subject to the condition that A is enabled and cond is true. + - isConditional(B) + tests whether module B is conditional. + - getCondition(A, B) + returns the condition when B should be enabled as a dependency of A, + once the m4 code for A has been executed. ''' self.dependers = dict() # Dependencies self.conditionals = dict() # Conditional modules @@ -765,9 +777,11 @@ class GLModuleTable(object): raise TypeError('condition must be a string or True, not %s' % type(condition).__name__) if not str(module) in self.unconditionals: + # No unconditional dependency to the given module is known at this point. if str(module) not in self.dependers: - self.dependers[module] = list() - self.dependers[module] += [module] + self.dependers[str(module)] = list() + if str(parent) not in self.dependers[str(module)]: + self.dependers[str(module)].append(str(parent)) key = '%s---%s' % (str(parent), str(module)) self.conditionals[key] = condition @@ -778,9 +792,9 @@ class GLModuleTable(object): if type(module) is not GLModule: raise TypeError('module must be a GLModule, not %s' % type(module).__name__) + self.unconditionals[str(module)] = True if str(module) in self.dependers: self.dependers.pop(str(module)) - self.unconditionals[str(module)] = True def isConditional(self, module): '''GLModuleTable.isConditional(module) -> bool @@ -804,9 +818,7 @@ class GLModuleTable(object): raise TypeError('module must be a GLModule, not %s' % type(module).__name__) key = '%s---%s' % (str(parent), str(module)) - result = None - if key in self.conditionals: - result = self.conditionals[key] + result = self.conditionals.get(key, None) return result def transitive_closure(self, modules): @@ -823,6 +835,11 @@ class GLModuleTable(object): for module in modules: if type(module) is not GLModule: raise TypeError('each module must be a GLModule instance') + # In order to process every module only once (for speed), process an + # "input list" of modules, producing an "output list" of modules. During + # each round, more modules can be queued in the input list. Once a + # module on the input list has been processed, it is added to the + # "handled list", so we can avoid to process it again. inc_all_tests = self.inc_all_direct_tests handledmodules = list() inmodules = modules @@ -833,7 +850,7 @@ class GLModuleTable(object): self.addUnconditional(module) while inmodules: inmodules_this_round = inmodules - inmodules = list() + inmodules = list() # Accumulator, queue for next round for module in inmodules_this_round: if module not in self.avoids: outmodules += [module] @@ -842,6 +859,13 @@ class GLModuleTable(object): module.getAutomakeSnippet_Conditional() pattern = re.compile('^if', re.M) if not pattern.findall(automake_snippet): + # A module whose Makefile.am snippet contains a + # reference to an automake conditional. If we were + # to use it conditionally, we would get an error + # configure: error: conditional "..." was never defined. + # because automake 1.11.1 does not handle nested + # conditionals correctly. As a workaround, make the + # module unconditional. self.addUnconditional(module) conditional = self.isConditional(module) dependencies = module.getDependenciesWithConditions() @@ -849,6 +873,16 @@ class GLModuleTable(object): for pair in dependencies ] conditions = [ pair[1] for pair in dependencies ] + # Duplicate dependencies are harmless, but Jim wants a warning. + duplicate_depmodules = [ depmodule + for depmodule in set(depmodules) + if depmodules.count(depmodule) > 1 ] + if duplicate_depmodules: + duplicate_depmodule_names = [ str(depmodule) + for depmodule in duplicate_depmodules ] + message = ('gnulib-tool: warning: module %s has duplicated dependencies: %s\n' + % (module, duplicate_depmodule_names)) + sys.stderr.write(message) if self.config.checkInclTestCategory(TESTS['tests']): testsname = module.getTestsName() if self.modulesystem.exists(testsname): @@ -856,6 +890,7 @@ class GLModuleTable(object): depmodules += [testsmodule] conditions += [None] for depmodule in depmodules: + # Determine whether to include the dependency or tests module. include = True statuses = depmodule.getStatuses() for word in statuses: @@ -890,15 +925,15 @@ class GLModuleTable(object): if self.config['conddeps']: index = depmodules.index(depmodule) condition = conditions[index] + if condition == True: + condition = None if condition: self.addConditional(module, depmodule, condition) else: # if condition if conditional: self.addConditional(module, depmodule, True) else: # if not conditional - self.addUnconditional(module) - listing = list() # Create empty list - inmodules = sorted(set(inmodules)) + self.addUnconditional(depmodule) handledmodules = sorted(set(handledmodules + inmodules_this_round)) # Remove handledmodules from inmodules. inmodules = [module @@ -934,17 +969,24 @@ class GLModuleTable(object): for module in finalmodules: if type(module) is not GLModule: raise TypeError('each module must be a GLModule instance') + # Determine main module list. saved_inctests = self.config.checkInclTestCategory(TESTS['tests']) self.config.disableInclTestCategory(TESTS['tests']) main_modules = self.transitive_closure(basemodules) self.config.setInclTestCategory(TESTS['tests'], saved_inctests) + # Determine tests-related module list. tests_modules = \ [ m for m in finalmodules - if m not in main_modules ] \ - + [ m - for m in main_modules - if m.getApplicability() != 'main' ] + if not (m in main_modules and m.getApplicability() == 'main') ] + # Note: Since main_modules is (hopefully) a subset of finalmodules, this + # ought to be the same as + # [ m + # for m in finalmodules + # if m not in main_modules ] \ + # + [ m + # for m in main_modules + # if m.getApplicability() != 'main' ] tests_modules = sorted(set(tests_modules)) result = tuple([main_modules, tests_modules]) return result @@ -957,24 +999,30 @@ class GLModuleTable(object): GLConfig: auxdir, ac_version.''' auxdir = self.config['auxdir'] ac_version = self.config['ac_version'] - have_lib_sources = False for module in modules: if type(module) is not GLModule: raise TypeError('each module must be a GLModule instance') - snippet = module.getAutomakeSnippet() - snippet = constants.remove_backslash_newline(snippet) - pattern = re.compile('^lib_SOURCES[\t ]*\\+=[\t ]*(.*)$', re.M) - files = pattern.findall(snippet) - if files: # if source files were found - files = files[-1].split(' ') - for file in files: - if not file.endswith('.h'): - have_lib_sources = True - break + # Determine whether any module provides a lib_SOURCES augmentation. + have_lib_sources = False + for module in modules: + if not module.isTests(): + snippet = module.getAutomakeSnippet() + # Extract the value of "lib_SOURCES += ...". + snippet = constants.remove_backslash_newline(snippet) + pattern = re.compile('^lib_SOURCES[\t ]*\\+=([^#]*).*$', re.M) + for matching_rhs in pattern.findall(snippet): + files = matching_rhs.split(' ') + for file in files: + # Ignore .h files since they are not compiled. + if not file.endswith('.h'): + have_lib_sources = True + break + # Add the dummy module, to make sure the library will be non-empty. if not have_lib_sources: dummy = self.modulesystem.find('dummy') if dummy not in self.avoids: - modules = sorted(set(modules + [dummy])) + if dummy not in modules: + modules = sorted(set(modules)) + [dummy] return list(modules) def filelist(self, modules): diff --git a/pygnulib/GLTestDir.py b/pygnulib/GLTestDir.py index 3c4ad0b9dc..bf00099283 100644 --- a/pygnulib/GLTestDir.py +++ b/pygnulib/GLTestDir.py @@ -265,33 +265,13 @@ class GLTestDir(object): self.config.setLibtests(True) if single_configure: - # Add dummy package if it is needed. + # Add the dummy module to the main module list if needed. main_modules = moduletable.add_dummy(main_modules) - if 'dummy' in [ str(module) - for module in main_modules ]: - main_modules = [ m - for m in main_modules - if str(m) != 'dummy' ] - dummy = self.modulesystem.find('dummy') - main_modules = sorted(set(main_modules)) + [dummy] if libtests: # if we need to use libtests.a + # Add the dummy module to the tests-related module list if needed. tests_modules = moduletable.add_dummy(tests_modules) - if 'dummy' in [ str(module) - for module in tests_modules ]: - tests_modules = [ m - for m in tests_modules - if str(m) != 'dummy' ] - dummy = self.modulesystem.find('dummy') - tests_modules = sorted(set(tests_modules)) + [dummy] else: # if not single_configure modules = moduletable.add_dummy(modules) - if 'dummy' in [ str(module) - for module in modules ]: - modules = [ m - for m in modules - if str(m) != 'dummy' ] - dummy = self.modulesystem.find('dummy') - modules = sorted(set(modules)) + [dummy] # Show banner notice of every module. if single_configure: -- 2.34.1