]> Savannah Git Hosting - gnulib.git/commitdiff
gnulib-tool.py: New options --extract-[recursive-]dependents.
authorBruno Haible <bruno@clisp.org>
Sun, 28 Jul 2024 23:26:51 +0000 (01:26 +0200)
committerBruno Haible <bruno@clisp.org>
Sun, 28 Jul 2024 23:26:51 +0000 (01:26 +0200)
* pygnulib/GLInfo.py (GLInfo.usage): Document --extract-dependents and
--extract-recursive-dependents options.
* pygnulib/GLModuleSystem.py (GLModule.getDependenciesRecursively): Move
method.
(getDependents, getDependentsRecursively): New methods.
* pygnulib/main.py (main): Add support for --extract-dependents and
--extract-recursive-dependents.

ChangeLog
pygnulib/GLInfo.py
pygnulib/GLModuleSystem.py
pygnulib/main.py

index 0a03f64c1bd0edf53e9857187f8de738942a87dd..7c19352aae4df497a65f2d5b5197e0902842035d 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,14 @@
+2024-07-28  Bruno Haible  <bruno@clisp.org>
+
+       gnulib-tool.py: New options --extract-[recursive-]dependents.
+       * pygnulib/GLInfo.py (GLInfo.usage): Document --extract-dependents and
+       --extract-recursive-dependents options.
+       * pygnulib/GLModuleSystem.py (GLModule.getDependenciesRecursively): Move
+       method.
+       (getDependents, getDependentsRecursively): New methods.
+       * pygnulib/main.py (main): Add support for --extract-dependents and
+       --extract-recursive-dependents.
+
 2024-07-28  Bruno Haible  <bruno@clisp.org>
 
        gnulib-tool.py: Avoid adding specific modules to a testdir.
index 9ed3ea61d40467bce71b2f96ed71ceaceae28147..83659b1c1f13e3dadfdf64bec4e14b27b43d654f 100644 (file)
@@ -134,6 +134,8 @@ Usage: gnulib-tool --list
        gnulib-tool --extract-filelist module
        gnulib-tool --extract-dependencies module
        gnulib-tool --extract-recursive-dependencies module
+       gnulib-tool --extract-dependents module
+       gnulib-tool --extract-recursive-dependents module
        gnulib-tool --extract-autoconf-snippet module
        gnulib-tool --extract-automake-snippet module
        gnulib-tool --extract-include-directive module
@@ -175,6 +177,11 @@ Operation modes:
       --extract-recursive-dependencies  extract the dependencies of the module
                                         and its dependencies, recursively, all
                                         together, but without the conditions
+      --extract-dependents         list the modules which depend on the given
+                                   module directly.  This is also known as the
+                                   "reverse dependencies".
+      --extract-recursive-dependents  list the modules which depend on the given
+                                      module directly or indirectly
       --extract-autoconf-snippet   extract the snippet for configure.ac
       --extract-automake-snippet   extract the snippet for library makefile
       --extract-include-directive  extract the #include directive
index 248d9ec80091459e196a2cb7c554785a16fc1017..e713b60fc9fce8270f66466485bf29dd7e7f5ef0 100644 (file)
@@ -23,6 +23,7 @@ import re
 import sys
 import hashlib
 import subprocess as sp
+import shlex
 from collections import defaultdict
 from typing import Any, ClassVar
 from .constants import (
@@ -330,33 +331,6 @@ class GLModule:
         result = self.name == 'libtextstyle-optional'
         return result
 
-    def getDependenciesRecursively(self) -> str:
-        '''Return a list of recursive dependencies of this module separated
-        by a newline.'''
-        handledmodules = set()
-        inmodules = set()
-        outmodules = set()
-
-        # 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.
-        inmodules.add(self)
-        while len(inmodules) > 0:
-            inmodules_this_round = inmodules
-            inmodules = set()  # Accumulator, queue for next round
-            for module in inmodules_this_round:
-                outmodules.add(module)
-                inmodules = inmodules.union(module.getDependenciesWithoutConditions())
-            handledmodules = handledmodules.union(inmodules_this_round)
-            # Remove handledmodules from inmodules.
-            inmodules = inmodules.difference(handledmodules)
-
-        module_names = sorted([ module.name
-                                for module in outmodules ])
-        return lines_to_multiline(module_names)
-
     def getLinkDirectiveRecursively(self) -> str:
         '''Return a list of the link directives of this module separated
         by a newline.'''
@@ -554,6 +528,107 @@ class GLModule:
             self.cache['dependenciesWithCond'] = result
         return self.cache['dependenciesWithCond']
 
+    def getDependenciesRecursively(self) -> str:
+        '''Return a list of recursive dependencies of this module separated
+        by a newline.'''
+        handledmodules = set()
+        inmodules = set()
+        outmodules = set()
+
+        # 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.
+        inmodules.add(self)
+        while len(inmodules) > 0:
+            inmodules_this_round = inmodules
+            inmodules = set()  # Accumulator, queue for next round
+            for module in inmodules_this_round:
+                outmodules.add(module)
+                inmodules = inmodules.union(module.getDependenciesWithoutConditions())
+            handledmodules = handledmodules.union(inmodules_this_round)
+            # Remove handledmodules from inmodules.
+            inmodules = inmodules.difference(handledmodules)
+
+        module_names = sorted([ module.name
+                                for module in outmodules ])
+        return lines_to_multiline(module_names)
+
+    def getDependents(self) -> list[GLModule]:
+        '''Return list of dependents (a.k.a. "reverse dependencies"),
+        as a list of GLModule objects.
+        GLConfig: localpath.'''
+        if 'dependents' not in self.cache:
+            localpath = self.config['localpath']
+            # Find a set of module candidates quickly.
+            # TODO: Optimize. This approach is fine for a single getDependents
+            #       invocation, but not for 100 or 1000 of them.
+            # Convert the module name to a POSIX basic regex.
+            # Needs to handle . [ \ * ^ $.
+            regex = self.name.replace('\\', '\\\\').replace('[', '\\[').replace('^', '\\^')
+            regex = re.compile(r'([.*$])').sub(r'[\1]', regex)
+            line_regex = '^' + regex
+            # We can't add a '$' to line_regex, because that would fail to match
+            # lines that denote conditional dependencies. We could invoke grep
+            # twice, once to search for  line_regex + '$'  and once to search
+            # for   line_regex + [ <TAB>]  but that would be twice as slow.
+            # Read module candidates from gnulib root directory.
+            command = "find modules -type f -print | xargs -n 100 grep -l %s /dev/null | sed -e 's,^modules/,,'" % shlex.quote(line_regex)
+            with sp.Popen(command, shell=True, cwd=DIRS['root'], stdout=sp.PIPE) as proc:
+                result = proc.stdout.read().decode('UTF-8')
+            # Read module candidates from local directories.
+            if localpath != None and len(localpath) > 0:
+                command = "find modules -type f -print | xargs -n 100 grep -l %s /dev/null | sed -e 's,^modules/,,' -e 's,\\.diff$,,'" % shlex.quote(line_regex)
+                for localdir in localpath:
+                    with sp.Popen(command, shell=True, cwd=localdir, stdout=sp.PIPE) as proc:
+                        result += proc.stdout.read().decode('UTF-8')
+            listing = [ line
+                        for line in result.split('\n')
+                        if line.strip() ]
+            # Remove modules/ prefix from each file name.
+            pattern = re.compile(r'^modules/')
+            listing = [ pattern.sub('', line)
+                        for line in listing ]
+            # Filter out undesired file names.
+            listing = [ line
+                        for line in listing
+                        if self.modulesystem.file_is_module(line) ]
+            candidates = sorted(set(listing))
+            result = []
+            for name in candidates:
+                module = self.modulesystem.find(name)
+                if module:  # Ignore module candidates that don't actually exist.
+                    if self in module.getDependenciesWithoutConditions():
+                        result.append(module)
+            self.cache['dependents'] = result
+        return self.cache['dependents']
+
+    def getDependentsRecursively(self) -> str:
+        '''Return a list of recursive dependents of this module,
+        as a list of GLModule objects.'''
+        handledmodules = set()
+        inmodules = set()
+        outmodules = set()
+
+        # 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.
+        inmodules.add(self)
+        while len(inmodules) > 0:
+            inmodules_this_round = inmodules
+            inmodules = set()  # Accumulator, queue for next round
+            for module in inmodules_this_round:
+                outmodules.add(module)
+                inmodules = inmodules.union(module.getDependents())
+            handledmodules = handledmodules.union(inmodules_this_round)
+            # Remove handledmodules from inmodules.
+            inmodules = inmodules.difference(handledmodules)
+
+        return outmodules
+
     def getAutoconfEarlySnippet(self) -> str:
         '''Return autoconf-early snippet.'''
         return self.sections.get('configure.ac-early', '')
index 6953a84917d67ce663c5b11406818da975a037f0..dbff9dc76aa1c485b4b2dd8b0ce47443eec0fca8 100644 (file)
@@ -213,6 +213,14 @@ def main(temp_directory: str) -> None:
                         dest='mode_xrecursive_dependencies',
                         default=None,
                         action='store_true')
+    parser.add_argument('--extract-dependents',
+                        dest='mode_xdependents',
+                        default=None,
+                        action='store_true')
+    parser.add_argument('--extract-recursive-dependents',
+                        dest='mode_xrecursive_dependents',
+                        default=None,
+                        action='store_true')
     parser.add_argument('--extract-autoconf-snippet',
                         dest='mode_xautoconf',
                         default=None,
@@ -558,6 +566,9 @@ def main(temp_directory: str) -> None:
         cmdargs.mode_xusability_in_testdir,
         cmdargs.mode_xfilelist,
         cmdargs.mode_xdependencies,
+        cmdargs.mode_xrecursive_dependencies,
+        cmdargs.mode_xdependents,
+        cmdargs.mode_xrecursive_dependents,
         cmdargs.mode_xautoconf,
         cmdargs.mode_xautomake,
         cmdargs.mode_xinclude,
@@ -648,6 +659,12 @@ def main(temp_directory: str) -> None:
     if cmdargs.mode_xrecursive_dependencies != None:
         mode = 'extract-recursive-dependencies'
         modules = list(cmdargs.non_option_arguments)
+    if cmdargs.mode_xdependents != None:
+        mode = 'extract-dependents'
+        modules = list(cmdargs.non_option_arguments)
+    if cmdargs.mode_xrecursive_dependents != None:
+        mode = 'extract-recursive-dependents'
+        modules = list(cmdargs.non_option_arguments)
     if cmdargs.mode_xinclude != None:
         mode = 'extract-include-directive'
         modules = list(cmdargs.non_option_arguments)
@@ -1241,6 +1258,38 @@ def main(temp_directory: str) -> None:
             if module:
                 sys.stdout.write(module.getDependenciesRecursively())
 
+    elif mode == 'extract-dependents':
+        if avoids:
+            message = '%s: *** ' % APP['name']
+            message += 'cannot combine --avoid and --extract-dependents\n'
+            message += '%s: *** Stop.\n' % APP['name']
+            sys.stderr.write(message)
+            sys.exit(1)
+        modulesystem = GLModuleSystem(config)
+        for name in modules:
+            module = modulesystem.find(name)
+            if module:
+                dependents = module.getDependents()
+                dependents_names = sorted([ m.name
+                                            for m in dependents ])
+                sys.stdout.write(lines_to_multiline(dependents_names))
+
+    elif mode == 'extract-recursive-dependents':
+        if avoids:
+            message = '%s: *** ' % APP['name']
+            message += 'cannot combine --avoid and --extract-recursive-dependents\n'
+            message += '%s: *** Stop.\n' % APP['name']
+            sys.stderr.write(message)
+            sys.exit(1)
+        modulesystem = GLModuleSystem(config)
+        for name in modules:
+            module = modulesystem.find(name)
+            if module:
+                dependents = module.getDependentsRecursively()
+                dependents_names = sorted([ m.name
+                                            for m in dependents ])
+                sys.stdout.write(lines_to_multiline(dependents_names))
+
     elif mode == 'extract-autoconf-snippet':
         modulesystem = GLModuleSystem(config)
         for name in modules: