]> Savannah Git Hosting - gnulib.git/commitdiff
gnulib-tool.py: Finish implementing option --conditional-dependencies.
authorBruno Haible <bruno@clisp.org>
Tue, 9 Aug 2022 22:51:59 +0000 (00:51 +0200)
committerBruno Haible <bruno@clisp.org>
Tue, 9 Aug 2022 22:53:44 +0000 (00:53 +0200)
* 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
gnulib-tool.py
gnulib-tool.py.TODO
pygnulib/GLEmiter.py
pygnulib/GLImport.py
pygnulib/GLModuleSystem.py
pygnulib/GLTestDir.py

index 642a7363e3ad828f02270f09a14449544bcd2b12..0dfcb7cdd7db7fa3c46777bec8930387c2274431 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,25 @@
 2022-08-09  Bruno Haible  <bruno@clisp.org>
 
+       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
index 9988c84de9111b809cf61598e0da6990309738ad..71ef1c4f2c4c033826c55d414a8ad61654887ada 100755 (executable)
@@ -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,
index be6a2e2b1091e1a61b882ee0ab63134d940d5eb9..da6df2ab90ed5ce95e78dc383c18b35aa0d9398d 100644 (file)
@@ -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.
 
 --------------------------------------------------------------------------------
 
index 9b54d5009ee7ad667f3bf8a4e01695719d584f2e..c0fd053077ce5db78021fa439f8e69926dc7ab91 100644 (file)
@@ -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):
index 114605dd8a1409080860bde7904545e9cedc0fca..5dd73fa8bd781d7d972c528438d6092755db5469 100644 (file)
@@ -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.
index 9bfd5bd266ee3c5f61fe8ba832a2a7d81a692bef..f9b47a64a5414384bc8e27355cd6a22697954bee 100644 (file)
@@ -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):
index 3c4ad0b9dc19965c950987c158659dd7bc35539f..bf0009928319ba95a391560d3ca07601c1cc5307 100644 (file)
@@ -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: