Today's changes:

2022-08-03  Bruno Haible  <br...@clisp.org>

        gnulib-tool.py: Implement option --single-configure.
        * gnulib-tool.py (main): Accept option --single-configure. Pass its
        value to the GLConfig constructor.
        * pygnulib/GLTestDir.py (GLTestDir.execute): Remove debugging output.

        gnulib-tool.py: Implement options --without-c++-tests etc.
        * gnulib-tool.py (main): Accept options --without-c++-tests,
        --without-longrunning-tests, --without-privileged-tests,
        --without-unportable-tests.
        Improve error message for --copy-file with invalid number of arguments.
        Check for invalid options given in --import, --add-import,
        --remove-import, --update modes.
        Pass both sets of test categories to the GLConfig constructor.
        * pygnulib/GLConfig.py (GLConfig.__init__): Accept incl_test_categories
        and excl_test_categories instead of testflags.
        (checkInclTestCategory): Renamed from checkTestFlag.
        (enableInclTestCategory): Renamed from enableTestFlag.
        (disableInclTestCategory): Renamed from disableTestFlag.
        (getInclTestCategories): Renamed from getTestFlags.
        (setInclTestCategories): Renamed from setTestFlags.
        (resetInclTestCategories): Renamed from resetTestFlags.
        (setInclTestCategory, checkExclTestCategory, enableExclTestCategory,
        disableExclTestCategory, getExclTestCategories, setExclTestCategories,
        resetExclTestCategories): New methods.
        * pygnulib/GLModuleSystem.py (GLModuleTable.__init__): Accept two
        booleans as second and third constructor arguments.
        (transitive_closure): Correct the determination of whether to include
        each module, depending on the with-* and without-* options.
        (transitive_closure_separately): Update.
        * pygnulib/GLMakefileTable.py: Update.
        * pygnulib/GLImport.py (__init__, actioncmd, gnulib_cache, execute):
        Update.
        * pygnulib/GLTestDir.py (GLTestDir.__init__, GLTestDir.execute,
        GLMegaTestDir.__init__): Update.

        gnulib-tool.py: Implement option --without-tests.
        * gnulib-tool.py (main): Accept option --without-tests.

        gnulib-tool.py: Fix broken 'for' loop.
        * gnulib-tool.py (main): Canonicalize inctests before creating the
        GLConfig. Rewrite a broken 'for' loop.
        * pygnulib/GLConfig.py (GLConfig.setTestFlags): Remove unused statement.

        gnulib-tool.py: Follow gnulib-tool changes, part 23.
        Follow gnulib-tool changes
        2016-11-11  Bruno Haible  <br...@clisp.org>
        gnulib-tool: Support for the dual "LGPLv3+ or GPLv2" license.
        2016-12-02  Nikos Mavrogiannopoulos <n...@gnutls.org>
        gnulib-tool (func_import): Adhere to the license guideline ...
        2016-12-02  Daiki Ueno  <u...@gnu.org>
        gnulib-tool (func_import): Relax the regex ...
        * gnulib-tool.py: For --lgpl, accept value 3orGPLv2.
        * pygnulib/GLInfo.py (GLInfo.usage): Update.
        * pygnulib/GLConfig.py (GLConfig.setLGPL): Update argument check.
        * pygnulib/GLImport.py (GLImport.__init__, GLImport.gnulib_cache):
        Update gl_LGPL handling.
        (GLImport.prepare): Update license compatibility checks and license
        header rewriting.
        * pygnulib/GLTestDir.py (GLTestDir.execute): Update license
        compatibility checks. Handle also the licenses GPLv3+, GPL, LGPLv3+.

        gnulib-tool.py: Fix unjustified "incompatible license" warnings.
        * pygnulib/GLTestDir.py (GLTestDir.execute): Don't emit a warning when
        the dependency module has a license such as "public domain" or
        "unlimited".

        gnulib-tool.py: Follow gnulib-tool changes, part 22.
        Follow gnulib-tool change
        2016-10-15  Bruno Haible  <br...@clisp.org>
        Avoid gnulib-tool warnings about the dependencies of 'parse-datetime'.
        * pygnulib/GLModuleSystem.py (GLModule.getLicense): Special-case the
        'parse-datetime' module.

        gnulib-tool.py: Follow gnulib-tool changes, part 21.
        Follow gnulib-tool change
        2016-10-16  Bruno Haible  <br...@clisp.org>
        gnulib-tool: Make --create-testdir on all modules work again.
        * pygnulib/GLTestDir.py (GLTestDir.execute): Don't include the
        non-recursive-gnulib-prefix-hack module.

        gnulib-tool.py: Follow gnulib-tool changes, part 20.
        Follow gnulib-tool changes
        2016-01-15  Paul Eggert  <egg...@cs.ucla.edu>
        gnulib-tool: don't assume ln -s works
        2016-01-24  Paul Eggert  <egg...@cs.ucla.edu>
        gnulib-tool: don't give up on ln -s so easily
        2017-06-08  Bruno Haible  <br...@clisp.org>
        gnulib-tool: Fix bug in func_ln_s, from 2016-01-15.
        * pygnulib/constants.py (symlink_relative): New function.
        (link_relative): Use it instead of os.symlink.

        gnulib-tool.py: Avoid errors when writing to a VFAT file system, part 2.
        * pygnulib/constants.py (movefile): New function.
        * pygnulib/*.py: Use it instead of shutil.

        gnulib-tool.py: Avoid errors when writing to a VFAT file system.
        * pygnulib/constants.py (copyfile, copyfile2): New functions.
        * gnulib-tool.py: Use them instead of shutil.
        * pygnulib/*.py: Likewise.

        gnulib-tool.py: Fix typo.
        * pygnulib/GLImport.py (GLImport.__init__): Use the relative auxdir as
        second, not as first argument of joinpath.

>From f88aeeb42a4926bc2f7bcb7758ca511e75404593 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Wed, 3 Aug 2022 00:24:29 +0200
Subject: [PATCH 01/12] gnulib-tool.py: Fix typo.

* pygnulib/GLImport.py (GLImport.__init__): Use the relative auxdir as
second, not as first argument of joinpath.
---
 ChangeLog            | 6 ++++++
 pygnulib/GLImport.py | 2 +-
 2 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/ChangeLog b/ChangeLog
index 59f595310b..419ea2fbd5 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+2022-08-03  Bruno Haible  <br...@clisp.org>
+
+	gnulib-tool.py: Fix typo.
+	* pygnulib/GLImport.py (GLImport.__init__): Use the relative auxdir as
+	second, not as first argument of joinpath.
+
 2022-07-31  Bruno Haible  <br...@clisp.org>
 
 	gnulib-tool.py: Fix typo.
diff --git a/pygnulib/GLImport.py b/pygnulib/GLImport.py
index 2b16ce0ea4..5009917e76 100644
--- a/pygnulib/GLImport.py
+++ b/pygnulib/GLImport.py
@@ -102,7 +102,7 @@ class GLImport(object):
         match = pattern.findall(data)
         if match:
             result = cleaner(match)[0]
-            self.cache.setAuxDir(joinpath(result, self.config['destdir']))
+            self.cache.setAuxDir(joinpath(self.config['destdir'], result))
         pattern = compiler(r'A[CM]_PROG_LIBTOOL', re.S | re.M)
         guessed_libtool = bool(pattern.findall(data))
         if self.config['auxdir'] == None:
-- 
2.34.1

>From dbc9a4b304185e2cb0d9f425866f71161c9a1255 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Wed, 3 Aug 2022 11:27:21 +0200
Subject: [PATCH 02/12] gnulib-tool.py: Avoid errors when writing to a VFAT
 file system.

* pygnulib/constants.py (copyfile, copyfile2): New functions.
* gnulib-tool.py: Use them instead of shutil.
* pygnulib/*.py: Likewise.
---
 ChangeLog                |  5 +++++
 gnulib-tool.py           |  3 ++-
 pygnulib/GLFileSystem.py |  7 ++++---
 pygnulib/GLImport.py     |  3 ++-
 pygnulib/GLTestDir.py    |  5 +++--
 pygnulib/constants.py    | 22 ++++++++++++++++++++++
 6 files changed, 38 insertions(+), 7 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 419ea2fbd5..fe8225b5df 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,10 @@
 2022-08-03  Bruno Haible  <br...@clisp.org>
 
+	gnulib-tool.py: Avoid errors when writing to a VFAT file system.
+	* pygnulib/constants.py (copyfile, copyfile2): New functions.
+	* gnulib-tool.py: Use them instead of shutil.
+	* pygnulib/*.py: Likewise.
+
 	gnulib-tool.py: Fix typo.
 	* pygnulib/GLImport.py (GLImport.__init__): Use the relative auxdir as
 	second, not as first argument of joinpath.
diff --git a/gnulib-tool.py b/gnulib-tool.py
index 302aaf3405..2ac522aa05 100755
--- a/gnulib-tool.py
+++ b/gnulib-tool.py
@@ -54,6 +54,7 @@ MODES = constants.MODES
 TESTS = constants.TESTS
 compiler = constants.compiler
 joinpath = constants.joinpath
+copyfile = constants.copyfile
 isabs = os.path.isabs
 isdir = os.path.isdir
 isfile = os.path.isfile
@@ -959,7 +960,7 @@ def main():
         # Copy the file.
         assistant = classes.GLFileAssistant(config)
         tmpfile = assistant.tmpfilename(destpath)
-        shutil.copy(lookedup, tmpfile)
+        copyfile(lookedup, tmpfile)
         assistant.setOriginal(srcpath)
         assistant.config.setDestDir(destdir)
         assistant.setRewritten(destpath)
diff --git a/pygnulib/GLFileSystem.py b/pygnulib/GLFileSystem.py
index dfb1bb903f..5368f3c1b8 100644
--- a/pygnulib/GLFileSystem.py
+++ b/pygnulib/GLFileSystem.py
@@ -42,6 +42,7 @@ __copyright__ = constants.__copyright__
 #===============================================================================
 DIRS = constants.DIRS
 joinpath = constants.joinpath
+copyfile = constants.copyfile
 isdir = os.path.isdir
 isfile = os.path.isfile
 
@@ -118,7 +119,7 @@ class GLFileSystem(object):
                     pass  # Skip errors if directory exists
                 if isfile(tempFile):
                     os.remove(tempFile)
-                shutil.copy(lookedupFile, tempFile)
+                copyfile(lookedupFile, tempFile)
                 for diff_in_localdir in reversed(lookedupPatches):
                     command = 'patch -s "%s" < "%s" >&2' % (tempFile, diff_in_localdir)
                     try:  # Try to apply patch
@@ -317,7 +318,7 @@ class GLFileAssistant(object):
                     try:  # Try to move file
                         if os.path.exists(basepath):
                             os.remove(basepath)
-                        shutil.copy(tmpfile, rewritten)
+                        copyfile(tmpfile, rewritten)
                     except Exception as error:
                         raise GLError(17, original)
             else:  # if self.config['dryrun']
@@ -352,7 +353,7 @@ class GLFileAssistant(object):
         sed_transform_testsrelated_lib_file = self.transformers.get(
             'tests', '')
         try:  # Try to copy lookedup file to tmpfile
-            shutil.copy(lookedup, tmpfile)
+            copyfile(lookedup, tmpfile)
         except Exception as error:
             raise GLError(15, lookedup)
         # Don't process binary files with sed.
diff --git a/pygnulib/GLImport.py b/pygnulib/GLImport.py
index 5009917e76..cbee0c4f33 100644
--- a/pygnulib/GLImport.py
+++ b/pygnulib/GLImport.py
@@ -52,6 +52,7 @@ TESTS = constants.TESTS
 compiler = constants.compiler
 joinpath = constants.joinpath
 cleaner = constants.cleaner
+copyfile2 = constants.copyfile2
 isabs = os.path.isabs
 isdir = os.path.isdir
 isfile = os.path.isfile
@@ -734,7 +735,7 @@ AC_DEFUN([%s_FILE_LIST], [\n''' % macro_prefix
                     if not self.config['dryrun']:
                         print('Updating %s (backup in %s)' %
                               (srcpath, backupname))
-                        shutil.copy2(srcpath, backupname)
+                        copyfile2(srcpath, backupname)
                         result = ''
                         with codecs.open(srcpath, 'ab', 'UTF-8') as file:
                             file.write(destdata)
diff --git a/pygnulib/GLTestDir.py b/pygnulib/GLTestDir.py
index 36aa5d2117..c230fbc744 100644
--- a/pygnulib/GLTestDir.py
+++ b/pygnulib/GLTestDir.py
@@ -53,6 +53,7 @@ UTILS = constants.UTILS
 TESTS = constants.TESTS
 compiler = constants.compiler
 joinpath = constants.joinpath
+copyfile = constants.copyfile
 isdir = os.path.isdir
 isfile = os.path.isfile
 normpath = os.path.normpath
@@ -333,12 +334,12 @@ class GLTestDir(object):
             if isfile(destpath):
                 os.remove(destpath)
             if flag:
-                shutil.copy(lookedup, destpath)
+                copyfile(lookedup, destpath)
             else:  # if not flag
                 if self.filesystem.shouldLink(src, lookedup) == CopyAction.Symlink:
                     constants.link_relative(lookedup, destpath)
                 else:
-                    shutil.copy(lookedup, destpath)
+                    copyfile(lookedup, destpath)
 
         # Create $sourcebase/Makefile.am.
         for_test = True
diff --git a/pygnulib/constants.py b/pygnulib/constants.py
index 91a2958eb3..cf6f05eb98 100644
--- a/pygnulib/constants.py
+++ b/pygnulib/constants.py
@@ -23,6 +23,7 @@ import re
 import os
 import sys
 import platform
+import shutil
 import tempfile
 import subprocess as sp
 import __main__ as interpreter
@@ -303,6 +304,27 @@ def relconcat(dir1, dir2):
     return os.path.normpath(os.path.join(dir1, dir2))
 
 
+def copyfile(src, dest):
+    '''Copy file src to file dest. Like shutil.copy, but ignore errors e.g. on
+    VFAT file systems.'''
+    shutil.copyfile(src, dest)
+    try:
+        shutil.copymode(src, dest)
+    except PermissionError:
+        pass
+
+
+def copyfile2(src, dest):
+    '''Copy file src to file dest, preserving modification time. Like
+    shutil.copy2, but ignore errors e.g. on VFAT file systems. This function
+    is to be used for backup files.'''
+    shutil.copyfile(src, dest)
+    try:
+        shutil.copystat(src, dest)
+    except PermissionError:
+        pass
+
+
 def link_relative(src, dest):
     '''Like ln -s, except that src is given relative to the current directory
     (or absolute), not given relative to the directory of dest.'''
-- 
2.34.1

>From ed7b3d9bb825644533235fcc636134bc8bc368f2 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Wed, 3 Aug 2022 14:27:51 +0200
Subject: [PATCH 03/12] gnulib-tool.py: Avoid errors when writing to a VFAT
 file system, part 2.

* pygnulib/constants.py (movefile): New function.
* pygnulib/*.py: Use it instead of shutil.
---
 ChangeLog                |  4 ++++
 pygnulib/GLFileSystem.py | 11 ++++++-----
 pygnulib/GLImport.py     |  5 +++--
 pygnulib/GLTestDir.py    |  5 +++--
 pygnulib/constants.py    | 13 +++++++++++++
 5 files changed, 29 insertions(+), 9 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index fe8225b5df..aeec6717fc 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,9 @@
 2022-08-03  Bruno Haible  <br...@clisp.org>
 
+	gnulib-tool.py: Avoid errors when writing to a VFAT file system, part 2.
+	* pygnulib/constants.py (movefile): New function.
+	* pygnulib/*.py: Use it instead of shutil.
+
 	gnulib-tool.py: Avoid errors when writing to a VFAT file system.
 	* pygnulib/constants.py (copyfile, copyfile2): New functions.
 	* gnulib-tool.py: Use them instead of shutil.
diff --git a/pygnulib/GLFileSystem.py b/pygnulib/GLFileSystem.py
index 5368f3c1b8..1f7a9f2343 100644
--- a/pygnulib/GLFileSystem.py
+++ b/pygnulib/GLFileSystem.py
@@ -43,6 +43,7 @@ __copyright__ = constants.__copyright__
 DIRS = constants.DIRS
 joinpath = constants.joinpath
 copyfile = constants.copyfile
+movefile = constants.movefile
 isdir = os.path.isdir
 isfile = os.path.isfile
 
@@ -266,7 +267,7 @@ class GLFileAssistant(object):
                     lookedup, joinpath(destdir, rewritten))
             else:  # if any of these conditions is not met
                 try:  # Try to move file
-                    shutil.move(tmpfile, joinpath(destdir, rewritten))
+                    movefile(tmpfile, joinpath(destdir, rewritten))
                 except Exception as error:
                     raise GLError(17, original)
         else:  # if self.config['dryrun']
@@ -308,7 +309,7 @@ class GLFileAssistant(object):
                 if isfile(backuppath):
                     os.remove(backuppath)
                 try:  # Try to replace the given file
-                    shutil.move(basepath, backuppath)
+                    movefile(basepath, backuppath)
                 except Exception as error:
                     raise GLError(17, original)
                 if self.filesystem.shouldLink(original, lookedup) == CopyAction.Symlink \
@@ -405,8 +406,8 @@ class GLFileAssistant(object):
                 if not self.config['dryrun']:
                     if isfile(backuppath):
                         os.remove(backuppath)
-                    shutil.move(basepath, backuppath)
-                    shutil.move(tmpfile, basepath)
+                    movefile(basepath, backuppath)
+                    movefile(tmpfile, basepath)
                 else:  # if self.config['dryrun']
                     os.remove(tmpfile)
         else:  # if not isfile(basepath)
@@ -414,7 +415,7 @@ class GLFileAssistant(object):
             if not self.config['dryrun']:
                 if isfile(basepath):
                     os.remove(basepath)
-                shutil.move(tmpfile, basepath)
+                movefile(tmpfile, basepath)
             else:  # if self.config['dryrun']
                 os.remove(tmpfile)
         result = tuple([basename, backupname, result_flag])
diff --git a/pygnulib/GLImport.py b/pygnulib/GLImport.py
index cbee0c4f33..046cf4745d 100644
--- a/pygnulib/GLImport.py
+++ b/pygnulib/GLImport.py
@@ -53,6 +53,7 @@ compiler = constants.compiler
 joinpath = constants.joinpath
 cleaner = constants.cleaner
 copyfile2 = constants.copyfile2
+movefile = constants.movefile
 isabs = os.path.isabs
 isdir = os.path.isdir
 isfile = os.path.isfile
@@ -1043,7 +1044,7 @@ AC_DEFUN([%s_FILE_LIST], [\n''' % macro_prefix
                     try:  # Try to move file
                         if os.path.exists(backup):
                             os.remove(backup)
-                        shutil.move(path, '%s~' % path)
+                        movefile(path, '%s~' % path)
                     except Exception as error:
                         raise GLError(14, file)
                 else:  # if self.config['dryrun']
@@ -1143,7 +1144,7 @@ AC_DEFUN([%s_FILE_LIST], [\n''' % macro_prefix
                 tmpfile = self.assistant.tmpfilename(joinpath(pobase, file))
                 path = joinpath('build-aux', 'po', file)
                 lookedup, flag = filesystem.lookup(path)
-                shutil.move(lookedup, tmpfile)
+                movefile(lookedup, tmpfile)
                 basename = joinpath(pobase, file)
                 filename, backup, flag = self.assistant.super_update(
                     basename, tmpfile)
diff --git a/pygnulib/GLTestDir.py b/pygnulib/GLTestDir.py
index c230fbc744..9065e462fa 100644
--- a/pygnulib/GLTestDir.py
+++ b/pygnulib/GLTestDir.py
@@ -54,6 +54,7 @@ TESTS = constants.TESTS
 compiler = constants.compiler
 joinpath = constants.joinpath
 copyfile = constants.copyfile
+movefile = constants.movefile
 isdir = os.path.isdir
 isfile = os.path.isfile
 normpath = os.path.normpath
@@ -658,7 +659,7 @@ class GLTestDir(object):
                     dest = src[:-1]
                     if isfile(dest):
                         os.remove(dest)
-                    shutil.move(src, dest)
+                    movefile(src, dest)
         # libtoolize
         if libtool:
             args = [UTILS['libtoolize'], '--copy']
@@ -692,7 +693,7 @@ class GLTestDir(object):
                         dest = src[:-1]
                         if isfile(dest):
                             os.remove(dest)
-                        shutil.move(src, dest)
+                        movefile(src, dest)
             # aclocal
             args = [UTILS['aclocal'], '-I', joinpath('..', m4base)]
             constants.execute(args, verbose)
diff --git a/pygnulib/constants.py b/pygnulib/constants.py
index cf6f05eb98..34c17aec46 100644
--- a/pygnulib/constants.py
+++ b/pygnulib/constants.py
@@ -325,6 +325,19 @@ def copyfile2(src, dest):
         pass
 
 
+def movefile(src, dest):
+    '''Move/rename file src to file dest. Like shutil.move, but gracefully
+    handle common errors.'''
+    try:
+        shutil.move(src, dest)
+    except PermissionError:
+        # shutil.move invokes os.rename, catches the resulting OSError for
+        # errno=EXDEV, attempts a copy instead, and encounters a PermissionError
+        # while doing that.
+        copyfile2(src, dest)
+        os.remove(src)
+
+
 def link_relative(src, dest):
     '''Like ln -s, except that src is given relative to the current directory
     (or absolute), not given relative to the directory of dest.'''
-- 
2.34.1

>From 22ad26c0b2d86acb21a1eab665ac9d25a2a58d21 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Wed, 3 Aug 2022 14:37:12 +0200
Subject: [PATCH 04/12] gnulib-tool.py: Follow gnulib-tool changes, part 20.

Follow gnulib-tool changes
2016-01-15  Paul Eggert  <egg...@cs.ucla.edu>
gnulib-tool: don't assume ln -s works
2016-01-24  Paul Eggert  <egg...@cs.ucla.edu>
gnulib-tool: don't give up on ln -s so easily
2017-06-08  Bruno Haible  <br...@clisp.org>
gnulib-tool: Fix bug in func_ln_s, from 2016-01-15.

* pygnulib/constants.py (symlink_relative): New function.
(link_relative): Use it instead of os.symlink.
---
 ChangeLog             | 11 +++++++++++
 gnulib-tool.py.TODO   | 42 ++++--------------------------------------
 pygnulib/constants.py | 26 +++++++++++++++++++++++---
 3 files changed, 38 insertions(+), 41 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index aeec6717fc..2b47dc08af 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,16 @@
 2022-08-03  Bruno Haible  <br...@clisp.org>
 
+	gnulib-tool.py: Follow gnulib-tool changes, part 20.
+	Follow gnulib-tool changes
+	2016-01-15  Paul Eggert  <egg...@cs.ucla.edu>
+	gnulib-tool: don't assume ln -s works
+	2016-01-24  Paul Eggert  <egg...@cs.ucla.edu>
+	gnulib-tool: don't give up on ln -s so easily
+	2017-06-08  Bruno Haible  <br...@clisp.org>
+	gnulib-tool: Fix bug in func_ln_s, from 2016-01-15.
+	* pygnulib/constants.py (symlink_relative): New function.
+	(link_relative): Use it instead of os.symlink.
+
 	gnulib-tool.py: Avoid errors when writing to a VFAT file system, part 2.
 	* pygnulib/constants.py (movefile): New function.
 	* pygnulib/*.py: Use it instead of shutil.
diff --git a/gnulib-tool.py.TODO b/gnulib-tool.py.TODO
index eeecd5ed8a..bfb613fb70 100644
--- a/gnulib-tool.py.TODO
+++ b/gnulib-tool.py.TODO
@@ -15,6 +15,10 @@ The following commits to gnulib-tool have not yet been reflected in
 
 --------------------------------------------------------------------------------
 
+Inline all 'sed' invocations.
+
+--------------------------------------------------------------------------------
+
 Implement the options:
   --find
   --extract-recursive-dependencies
@@ -37,8 +41,6 @@ Implement the options:
   --witness-c-macro
   --vc-files
   --no-vc-files
-  -s | --symbolic
-  --local-symlink
   -h | --hardlink
   --local-hardlink
   -S | --more-symlinks
@@ -1076,42 +1078,6 @@ Date:   Sun Oct 16 14:11:18 2016 +0200
 
 --------------------------------------------------------------------------------
 
-commit c09c24932066ecee81756adf2fca840b7c146e9d
-Author: Bruno Haible <br...@clisp.org>
-Date:   Thu Jun 8 14:45:39 2017 +0200
-
-    gnulib-tool: Fix bug in func_ln_s, from 2016-01-15.
-
-    * gnulib-tool (func_ln_s): Determine cp_src correctly.
-
-commit d9958eb1eb951f950f9b321419965001b1368a38
-Author: Paul Eggert <egg...@cs.ucla.edu>
-Date:   Sun Jan 24 14:24:35 2016 -0800
-
-    gnulib-tool: don't give up on ln -s so easily
-
-    * gnulib-tool (func_ln_s): Don't give up on a later ln -s merely
-    because an earlier one failed.  The targets could be on different
-    file systems.  Problem reported by KO Myung-Hun in:
-    http://lists.gnu.org/archive/html/bug-gnulib/2016-01/msg00081.html
-
-commit 350f2c6fb569f42f0a8ff47fd5b7442f24f0e658
-Author: Paul Eggert <egg...@cs.ucla.edu>
-Date:   Fri Jan 15 10:12:41 2016 -0800
-
-    * gnulib-tool: fix stray debug line in previous patch
-
-commit 0e50dd0071be89825810dbf4c2310663dcb77767
-Author: Paul Eggert <egg...@cs.ucla.edu>
-Date:   Wed May 1 13:39:22 2013 +0900
-
-    gnulib-tool: don't assume ln -s works
-
-    * gnulib-tool (func_ln_s): New function.
-    (func_ln): Use it.
-
---------------------------------------------------------------------------------
-
 commit 9bdf6c8a0cdeb13c12e4b65dee9538c5468dbe1d
 Author: Bruno Haible <br...@clisp.org>
 Date:   Sun Aug 19 14:06:50 2012 +0200
diff --git a/pygnulib/constants.py b/pygnulib/constants.py
index 34c17aec46..ca5e3f79aa 100644
--- a/pygnulib/constants.py
+++ b/pygnulib/constants.py
@@ -338,6 +338,26 @@ def movefile(src, dest):
         os.remove(src)
 
 
+def symlink_relative(src, dest):
+    '''Like ln -s, except use cp -p if ln -s fails.
+    src is either absolute or relative to the directory of dest.'''
+    try:
+        os.symlink(src, dest)
+    except PermissionError:
+        sys.stderr.write('%s: ln -s failed; falling back on cp -p\n' % APP['name'])
+        if src.startswith('/') or (len(src) >= 2 and src[1] == ':'):
+            # src is absolute.
+            cp_src = src
+        else:
+            # src is relative to the directory of dest.
+            last_slash = dest.rfind('/')
+            if last_slash >= 0:
+                cp_src = joinpath(dest[0:last_slash-1], src)
+            else:
+                cp_src = src
+        copyfile2(cp_src, dest)
+
+
 def link_relative(src, dest):
     '''Like ln -s, except that src is given relative to the current directory
     (or absolute), not given relative to the directory of dest.'''
@@ -348,17 +368,17 @@ def link_relative(src, dest):
         raise TypeError(
             'dest must be a string, not %s' % (type(dest).__name__))
     if src.startswith('/') or (len(src) >= 2 and src[1] == ':'):
-        os.symlink(src, dest)
+        symlink_relative(src, dest)
     else:  # if src is not absolute
         if dest.startswith('/') or (len(dest) >= 2 and dest[1] == ':'):
             cwd = os.getcwd()
-            os.symlink(joinpath(cwd, src), dest)
+            symlink_relative(joinpath(cwd, src), dest)
         else:  # if dest is not absolute
             destdir = os.path.dirname(dest)
             if not destdir:
                 destdir = '.'
             src = relativize(destdir, src)
-            os.symlink(src, dest)
+            symlink_relative(src, dest)
 
 
 def link_if_changed(src, dest):
-- 
2.34.1

>From 10aa0eb755d137e3af13c779da2127a3d0af1c6b Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Wed, 3 Aug 2022 14:43:40 +0200
Subject: [PATCH 05/12] gnulib-tool.py: Follow gnulib-tool changes, part 21.

Follow gnulib-tool change
2016-10-16  Bruno Haible  <br...@clisp.org>
gnulib-tool: Make --create-testdir on all modules work again.

* pygnulib/GLTestDir.py (GLTestDir.execute): Don't include the
non-recursive-gnulib-prefix-hack module.
---
 ChangeLog             |  7 +++++++
 gnulib-tool.py.TODO   | 11 -----------
 pygnulib/GLTestDir.py |  7 +++++--
 3 files changed, 12 insertions(+), 13 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 2b47dc08af..e2f3e0f127 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,12 @@
 2022-08-03  Bruno Haible  <br...@clisp.org>
 
+	gnulib-tool.py: Follow gnulib-tool changes, part 21.
+	Follow gnulib-tool change
+	2016-10-16  Bruno Haible  <br...@clisp.org>
+	gnulib-tool: Make --create-testdir on all modules work again.
+	* pygnulib/GLTestDir.py (GLTestDir.execute): Don't include the
+	non-recursive-gnulib-prefix-hack module.
+
 	gnulib-tool.py: Follow gnulib-tool changes, part 20.
 	Follow gnulib-tool changes
 	2016-01-15  Paul Eggert  <egg...@cs.ucla.edu>
diff --git a/gnulib-tool.py.TODO b/gnulib-tool.py.TODO
index bfb613fb70..b71b7200ab 100644
--- a/gnulib-tool.py.TODO
+++ b/gnulib-tool.py.TODO
@@ -1067,17 +1067,6 @@ Date:   Sat Oct 15 15:51:20 2016 +0200
 
 --------------------------------------------------------------------------------
 
-commit 932a1ae7ba56a9a3da52287ac028017323269d44
-Author: Bruno Haible <br...@clisp.org>
-Date:   Sun Oct 16 14:11:18 2016 +0200
-
-    gnulib-tool: Make --create-testdir on all modules work again.
-
-    * gnulib-tool (func_create_testdir): Don't include the
-    non-recursive-gnulib-prefix-hack module.
-
---------------------------------------------------------------------------------
-
 commit 9bdf6c8a0cdeb13c12e4b65dee9538c5468dbe1d
 Author: Bruno Haible <br...@clisp.org>
 Date:   Sun Aug 19 14:06:50 2012 +0200
diff --git a/pygnulib/GLTestDir.py b/pygnulib/GLTestDir.py
index 9065e462fa..f0a1a43883 100644
--- a/pygnulib/GLTestDir.py
+++ b/pygnulib/GLTestDir.py
@@ -168,11 +168,14 @@ class GLTestDir(object):
             base_modules = [self.modulesystem.find(m) for m in base_modules]
         # All modules together.
         # Except config-h, which breaks all modules which use HAVE_CONFIG_H.
+        # Except non-recursive-gnulib-prefix-hack, which represents a
+        # nonstandard way of using Automake.
         # Except ftruncate, mountlist, which abort the configuration on mingw.
         # Except lib-ignore, which leads to link errors when Sun C++ is used.
         base_modules = sorted(set(base_modules))
-        base_modules = [module for module in base_modules if str(module) not in
-                        ['config-h', 'ftruncate', 'mountlist', 'lib-ignore']]
+        base_modules = [module
+                        for module in base_modules
+                        if str(module) not in ['config-h', 'non-recursive-gnulib-prefix-hack', 'ftruncate', 'mountlist', 'lib-ignore']]
 
         # When computing transitive closures, don't consider $module to depend on
         # $module-tests. Need this because tests are implicitly GPL and may depend
-- 
2.34.1

>From 65349591fb099e8076f477d16704b05ef8233e1c Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Wed, 3 Aug 2022 14:51:20 +0200
Subject: [PATCH 06/12] gnulib-tool.py: Follow gnulib-tool changes, part 22.

Follow gnulib-tool change
2016-10-15  Bruno Haible  <br...@clisp.org>
Avoid gnulib-tool warnings about the dependencies of 'parse-datetime'.

* pygnulib/GLModuleSystem.py (GLModule.getLicense): Special-case the
'parse-datetime' module.
---
 ChangeLog                  |  7 +++++++
 gnulib-tool.py.TODO        | 11 -----------
 pygnulib/GLModuleSystem.py | 24 +++++++++++++++---------
 3 files changed, 22 insertions(+), 20 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index e2f3e0f127..2f88a72f90 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,12 @@
 2022-08-03  Bruno Haible  <br...@clisp.org>
 
+	gnulib-tool.py: Follow gnulib-tool changes, part 22.
+	Follow gnulib-tool change
+	2016-10-15  Bruno Haible  <br...@clisp.org>
+	Avoid gnulib-tool warnings about the dependencies of 'parse-datetime'.
+	* pygnulib/GLModuleSystem.py (GLModule.getLicense): Special-case the
+	'parse-datetime' module.
+
 	gnulib-tool.py: Follow gnulib-tool changes, part 21.
 	Follow gnulib-tool change
 	2016-10-16  Bruno Haible  <br...@clisp.org>
diff --git a/gnulib-tool.py.TODO b/gnulib-tool.py.TODO
index b71b7200ab..d5030c8231 100644
--- a/gnulib-tool.py.TODO
+++ b/gnulib-tool.py.TODO
@@ -1056,17 +1056,6 @@ Date:   Sun Nov 13 04:12:26 2016 +0100
 
 --------------------------------------------------------------------------------
 
-commit ff9debcf75301805b1db925cdcdfb248541c576d
-Author: Bruno Haible <br...@clisp.org>
-Date:   Sat Oct 15 15:51:20 2016 +0200
-
-    Avoid gnulib-tool warnings about the dependencies of 'parse-datetime'.
-
-    * gnulib-tool (func_get_license): Special-case the 'parse-datetime'
-    module.
-
---------------------------------------------------------------------------------
-
 commit 9bdf6c8a0cdeb13c12e4b65dee9538c5468dbe1d
 Author: Bruno Haible <br...@clisp.org>
 Date:   Sun Aug 19 14:06:50 2012 +0200
diff --git a/pygnulib/GLModuleSystem.py b/pygnulib/GLModuleSystem.py
index 319175dee8..25c34f8756 100644
--- a/pygnulib/GLModuleSystem.py
+++ b/pygnulib/GLModuleSystem.py
@@ -796,16 +796,22 @@ Include:|Link:|License:|Maintainer:)'
         '''GLModule.getLicense(self) -> str
 
         Get license and warn user if module lacks a license.'''
-        license = self.getLicense_Raw()
-        if not self.isTests():
+        if str(self) == 'parse-datetime':
+            # This module is under a weaker license only for the purpose of some
+            # users who hand-edit it and don't use gnulib-tool. For the regular
+            # gnulib users they are under a stricter license.
+            return 'GPL'
+        else:
+            license = self.getLicense_Raw()
+            if not self.isTests():
+                if not license:
+                    if self.config['errors']:
+                        raise GLError(18, str(self))
+                    else:  # if not self.config['errors']
+                        sys.stderr.write('gnulib-tool: warning: module %s lacks a license\n' % str(self))
             if not license:
-                if self.config['errors']:
-                    raise GLError(18, str(self))
-                else:  # if not self.config['errors']
-                    sys.stderr.write('gnulib-tool: warning: module %s lacks a license\n' % str(self))
-        if not license:
-            license = 'GPL'
-        return license
+                license = 'GPL'
+            return license
 
     def getLicense_Raw(self):
         '''GLModule.getLicense_Raw() -> str
-- 
2.34.1

>From 65301e0739f8c86cf96403d6426551df3c3f3ac3 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Wed, 3 Aug 2022 15:51:35 +0200
Subject: [PATCH 07/12] gnulib-tool.py: Fix unjustified "incompatible license"
 warnings.

* pygnulib/GLTestDir.py (GLTestDir.execute): Don't emit a warning when
the dependency module has a license such as "public domain" or
"unlimited".
---
 ChangeLog             |  5 +++++
 gnulib-tool.py        |  2 +-
 pygnulib/GLTestDir.py | 35 +++++++++++++++++++----------------
 3 files changed, 25 insertions(+), 17 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 2f88a72f90..c334581d4b 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,10 @@
 2022-08-03  Bruno Haible  <br...@clisp.org>
 
+	gnulib-tool.py: Fix unjustified "incompatible license" warnings.
+	* pygnulib/GLTestDir.py (GLTestDir.execute): Don't emit a warning when
+	the dependency module has a license such as "public domain" or
+	"unlimited".
+
 	gnulib-tool.py: Follow gnulib-tool changes, part 22.
 	Follow gnulib-tool change
 	2016-10-15  Bruno Haible  <br...@clisp.org>
diff --git a/gnulib-tool.py b/gnulib-tool.py
index 2ac522aa05..0e5914a402 100755
--- a/gnulib-tool.py
+++ b/gnulib-tool.py
@@ -24,7 +24,7 @@
 # - Line length is not limited to 79 characters.
 # - Line breaking before or after binary operators? Better before, like in GNU.
 # You can use this command to check the style:
-#   $ pycodestyle --max-line-length=128 --ignore=E265,W503,E241,E711,E712,E201,E202 gnulib-tool.py pygnulib/*.py
+#   $ pycodestyle --max-line-length=136 --ignore=E265,W503,E241,E711,E712,E201,E202 gnulib-tool.py pygnulib/*.py
 
 
 #===============================================================================
diff --git a/pygnulib/GLTestDir.py b/pygnulib/GLTestDir.py
index f0a1a43883..83555faad7 100644
--- a/pygnulib/GLTestDir.py
+++ b/pygnulib/GLTestDir.py
@@ -184,25 +184,28 @@ class GLTestDir(object):
         self.config.disableTestFlag(TESTS['tests'])
         for requested_module in base_modules:
             requested_licence = requested_module.getLicense()
-            # Here we use self.moduletable.transitive_closure([module]), not just
-            # module.getDependencies, so that we also detect weird situations like
-            # an LGPL module which depends on a GPLed build tool module which depends
-            # on a GPL module.
             if requested_licence != 'GPL':
-                modules = self.moduletable.transitive_closure(
-                    [requested_module])
+                # Here we use self.moduletable.transitive_closure([module]), not
+                # just module.getDependencies, so that we also detect weird
+                # situations like an LGPL module which depends on a GPLed build
+                # tool module which depends on a GPL module.
+                modules = self.moduletable.transitive_closure([requested_module])
                 for module in modules:
                     license = module.getLicense()
-                    errormsg = 'module %s depends on a module ' % requested_module
-                    errormsg += 'with an incompatible license: %s\n' % module
-                    if requested_licence == 'GPLv2+':
-                        if license not in ['GPLv2+', 'LGPLv2+']:
-                            sys.stderr.write(errormsg)
-                    elif requested_licence in ['LGPL']:
-                        if license not in ['LGPL', 'LGPLv2+']:
-                            sys.stderr.write(errormsg)
-                    elif requested_licence in ['LGPLv2+']:
-                        if license not in ['LGPLv2+']:
+                    if license not in ['GPLv2+ build tool', 'GPLed build tool',
+                                       'public domain', 'unlimited', 'unmodifiable license text']:
+                        incompatible = False
+                        if requested_licence == 'GPLv2+':
+                            if license not in ['GPLv2+', 'LGPLv2+']:
+                                incompatible = True
+                        elif requested_licence in ['LGPL']:
+                            if license not in ['LGPL', 'LGPLv2+']:
+                                incompatible = True
+                        elif requested_licence in ['LGPLv2+']:
+                            if license not in ['LGPLv2+']:
+                                incompatible = True
+                        if incompatible:
+                            errormsg = 'module %s depends on a module with an incompatible license: %s\n' % (requested_module, module)
                             sys.stderr.write(errormsg)
         self.config.setTestFlags(saved_testflags)
 
-- 
2.34.1

>From e2d15e5c893e42b32a840a6641d5fa8cef7b4f7c Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Wed, 3 Aug 2022 16:49:15 +0200
Subject: [PATCH 08/12] gnulib-tool.py: Follow gnulib-tool changes, part 23.

Follow gnulib-tool changes
2016-11-11  Bruno Haible  <br...@clisp.org>
gnulib-tool: Support for the dual "LGPLv3+ or GPLv2" license.
2016-12-02  Nikos Mavrogiannopoulos <n...@gnutls.org>
gnulib-tool (func_import): Adhere to the license guideline ...
2016-12-02  Daiki Ueno  <u...@gnu.org>
gnulib-tool (func_import): Relax the regex ...

* gnulib-tool.py: For --lgpl, accept value 3orGPLv2.
* pygnulib/GLInfo.py (GLInfo.usage): Update.
* pygnulib/GLConfig.py (GLConfig.setLGPL): Update argument check.
* pygnulib/GLImport.py (GLImport.__init__, GLImport.gnulib_cache):
Update gl_LGPL handling.
(GLImport.prepare): Update license compatibility checks and license
header rewriting.
* pygnulib/GLTestDir.py (GLTestDir.execute): Update license
compatibility checks. Handle also the licenses GPLv3+, GPL, LGPLv3+.
---
 ChangeLog             | 18 +++++++++++++++
 gnulib-tool.py        | 13 ++++++-----
 gnulib-tool.py.TODO   | 41 ----------------------------------
 pygnulib/GLConfig.py  | 12 +++++-----
 pygnulib/GLImport.py  | 52 +++++++++++++++++++++++++++++--------------
 pygnulib/GLInfo.py    |  3 ++-
 pygnulib/GLTestDir.py | 16 ++++++++-----
 7 files changed, 80 insertions(+), 75 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index c334581d4b..b0d9b08a62 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,23 @@
 2022-08-03  Bruno Haible  <br...@clisp.org>
 
+	gnulib-tool.py: Follow gnulib-tool changes, part 23.
+	Follow gnulib-tool changes
+	2016-11-11  Bruno Haible  <br...@clisp.org>
+	gnulib-tool: Support for the dual "LGPLv3+ or GPLv2" license.
+	2016-12-02  Nikos Mavrogiannopoulos <n...@gnutls.org>
+	gnulib-tool (func_import): Adhere to the license guideline ...
+	2016-12-02  Daiki Ueno  <u...@gnu.org>
+	gnulib-tool (func_import): Relax the regex ...
+	* gnulib-tool.py: For --lgpl, accept value 3orGPLv2.
+	* pygnulib/GLInfo.py (GLInfo.usage): Update.
+	* pygnulib/GLConfig.py (GLConfig.setLGPL): Update argument check.
+	* pygnulib/GLImport.py (GLImport.__init__, GLImport.gnulib_cache):
+	Update gl_LGPL handling.
+	(GLImport.prepare): Update license compatibility checks and license
+	header rewriting.
+	* pygnulib/GLTestDir.py (GLTestDir.execute): Update license
+	compatibility checks. Handle also the licenses GPLv3+, GPL, LGPLv3+.
+
 	gnulib-tool.py: Fix unjustified "incompatible license" warnings.
 	* pygnulib/GLTestDir.py (GLTestDir.execute): Don't emit a warning when
 	the dependency module has a license such as "public domain" or
diff --git a/gnulib-tool.py b/gnulib-tool.py
index 0e5914a402..0864d0d8c2 100755
--- a/gnulib-tool.py
+++ b/gnulib-tool.py
@@ -24,7 +24,7 @@
 # - Line length is not limited to 79 characters.
 # - Line breaking before or after binary operators? Better before, like in GNU.
 # You can use this command to check the style:
-#   $ pycodestyle --max-line-length=136 --ignore=E265,W503,E241,E711,E712,E201,E202 gnulib-tool.py pygnulib/*.py
+#   $ pycodestyle --max-line-length=136 --ignore=E265,W503,E241,E711,E712,E201,E202,E221 gnulib-tool.py pygnulib/*.py
 
 
 #===============================================================================
@@ -330,8 +330,9 @@ def main():
     # lgpl
     parser.add_argument('--lgpl',
                         dest='lgpl',
-                        default=False,
-                        type=int,
+                        default=None,
+                        action='append',
+                        choices=['2', '3orGPLv2', '3'],
                         nargs='?')
     # makefile
     parser.add_argument("--makefile-name",
@@ -545,10 +546,12 @@ def main():
                 elif index == 6:
                     testflags += [constants.TESTS['all-tests']]
     lgpl = cmdargs.lgpl
+    if lgpl != None:
+        lgpl = lgpl[-1]
+        if lgpl == None:
+            lgpl = True
     libtool = cmdargs.libtool
     makefile = cmdargs.makefile
-    if lgpl == None:
-        lgpl = True
     avoids = cmdargs.avoids
     if avoids != None:
         avoids = [ module
diff --git a/gnulib-tool.py.TODO b/gnulib-tool.py.TODO
index d5030c8231..d6ba01657e 100644
--- a/gnulib-tool.py.TODO
+++ b/gnulib-tool.py.TODO
@@ -1015,47 +1015,6 @@ Date:   Sun Feb 19 15:15:11 2017 +0100
 
 --------------------------------------------------------------------------------
 
-commit 31a08abd323ebffea3d4fb2d5a66f801fe8b3031
-Author: Daiki Ueno <u...@gnu.org>
-Date:   Fri Dec 2 17:16:50 2016 +0100
-
-    gnulib-tool: fix the previous change
-
-    * gnulib-tool (func_import): Relax the regex used for "LGPLv3+ or
-    GPLv2" rewriting.
-
-commit 27d1d32a202b6b2115d5c2e287e3f5f1090032e5
-Author: Daiki Ueno <u...@gnu.org>
-Date:   Fri Dec 2 16:56:11 2016 +0100
-
-    gnulib-tool: fix the previous change
-
-    * gnulib-tool (func_import): Relax the regex for the end marker of
-    original license text.
-
-commit 1aa6e23bd2487a7c3bd07cf693e6d968f74d951a
-Author: Nikos Mavrogiannopoulos <n...@gnutls.org>
-Date:   Mon Nov 21 21:15:25 2016 +0100
-
-    gnulib-tool: properly list the LGPL3orGPLv2 license
-
-    * gnulib-tool (func_import): Adhere to the license guideline when
-    rewriting the license text to "LGPLv3+ or GPLv2":
-    https://www.gnu.org/prep/maintain/maintain.html#Licensing-of-GNU-Packages
-
-commit 567bbf7b7b14b31385801b1fee47acfc5b6d2b01
-Author: Bruno Haible <br...@clisp.org>
-Date:   Sun Nov 13 04:12:26 2016 +0100
-
-    gnulib-tool: Support for the dual "LGPLv3+ or GPLv2" license.
-
-    * gnulib-tool (--lgpl): Accept value 3orGPLv2.
-    (func_import): Extend determination of license_incompatibilities.
-    (func_create_testdir): Extend table of license compatibility. Handle
-    also the licenses GPLv3+, GPL, LGPLv3+.
-
---------------------------------------------------------------------------------
-
 commit 9bdf6c8a0cdeb13c12e4b65dee9538c5468dbe1d
 Author: Bruno Haible <br...@clisp.org>
 Date:   Sun Aug 19 14:06:50 2012 +0200
diff --git a/pygnulib/GLConfig.py b/pygnulib/GLConfig.py
index dc0cce3490..d126b35dbe 100644
--- a/pygnulib/GLConfig.py
+++ b/pygnulib/GLConfig.py
@@ -784,21 +784,21 @@ class GLConfig(object):
     # Define lgpl methods.
     def getLGPL(self):
         '''Check for abort if modules aren't available under the LGPL.
-        Default value is False, which means that lgpl is disabled.'''
+        Default value is None, which means that lgpl is disabled.'''
         return self.table['lgpl']
 
     def setLGPL(self, lgpl):
         '''Abort if modules aren't available under the LGPL.
-        Default value is False, which means that lgpl is disabled.'''
-        if (type(lgpl) is int and 2 <= lgpl <= 3) or type(lgpl) is bool:
+        Default value is None, which means that lgpl is disabled.'''
+        if lgpl in [None, True, '2', '3orGPLv2', '3']:
             self.table['lgpl'] = lgpl
-        else:  # if lgpl is not False, 2 or 3
+        else:
             raise TypeError('invalid LGPL version: %s' % repr(lgpl))
 
     def resetLGPL(self):
         '''Disable abort if modules aren't available under the LGPL.
-        Default value is False, which means that lgpl is disabled.'''
-        self.table['lgpl'] = False
+        Default value is None, which means that lgpl is disabled.'''
+        self.table['lgpl'] = None
 
     def getIncludeGuardPrefix(self):
         '''Return include_guard_prefix to use inside GLEmiter class.'''
diff --git a/pygnulib/GLImport.py b/pygnulib/GLImport.py
index 046cf4745d..04b3fddaab 100644
--- a/pygnulib/GLImport.py
+++ b/pygnulib/GLImport.py
@@ -175,10 +175,10 @@ class GLImport(object):
             tempdict = dict(zip(keys, values))
             if 'gl_LGPL' in tempdict:
                 lgpl = cleaner(tempdict['gl_LGPL'])
-                if lgpl.isdecimal():
-                    self.cache.setLGPL(int(self.cache['lgpl']))
+                if lgpl != '':
+                    self.cache.setLGPL(lgpl)
             else:  # if 'gl_LGPL' not in tempdict
-                self.cache.setLGPL(False)
+                self.cache.setLGPL(None)
             if tempdict['gl_LIB']:
                 self.cache.setLibName(cleaner(tempdict['gl_LIB']))
             if tempdict['gl_LOCAL_DIR']:
@@ -533,11 +533,11 @@ class GLImport(object):
         if self.config.checkTestFlag(TESTS['tests']):
             emit += 'gl_WITH_TESTS\n'
         emit += 'gl_LIB([%s])\n' % libname
-        if lgpl != False:
+        if lgpl != None:
             if lgpl == True:
                 emit += 'gl_LGPL\n'
             else:  # if lgpl != True
-                emit += 'gl_LGPL([%d])\n' % lgpl
+                emit += 'gl_LGPL([%s])\n' % lgpl
         emit += 'gl_MAKEFILE_NAME([%s])\n' % makefile
         if conddeps:
             emit += 'gl_CONDITIONAL_DEPENDENCIES\n'
@@ -849,17 +849,18 @@ AC_DEFUN([%s_FILE_LIST], [\n''' % macro_prefix
         compatibilities['all'] = ['GPLv2+ build tool', 'GPLed build tool',
                                   'public domain', 'unlimited',
                                   'unmodifiable license text']
-        compatibilities[3] = ['LGPL', 'LGPLv2+', 'LGPLv3+']
-        compatibilities[2] = ['LGPLv2+']
+        compatibilities['3']        = ['LGPLv2+', 'LGPLv3+ or GPLv2', 'LGPLv3+', 'LGPL']
+        compatibilities['3orGPLv2'] = ['LGPLv2+', 'LGPLv3+ or GPLv2']
+        compatibilities['2']        = ['LGPLv2+']
         if lgpl:
             for module in main_modules:
                 license = module.getLicense()
                 if license not in compatibilities['all']:
-                    if lgpl == 3 or lgpl == True:
-                        if license not in compatibilities[3]:
+                    if lgpl == True:
+                        if license not in compatibilities['3']:
                             listing.append(tuple([str(module), license]))
-                    elif lgpl == 2:
-                        if license not in compatibilities[2]:
+                    else:
+                        if license not in compatibilities[lgpl]:
                             listing.append(tuple([str(module), license]))
             if listing:
                 raise GLError(11, listing)
@@ -889,18 +890,35 @@ AC_DEFUN([%s_FILE_LIST], [\n''' % macro_prefix
         sed_transform_main_lib_file = sed_transform_lib_file
         if copyrights:
             if lgpl:  # if lgpl is enabled
-                if lgpl == 3:
+                if lgpl == True or lgpl == '3':
                     sed_transform_main_lib_file += '''
             s/GNU General/GNU Lesser General/g
             s/General Public License/Lesser General Public License/g
-            s/Lesser Lesser General Public License/Lesser General Public''' \
-                        + ' License/g'
-                elif lgpl == 2:
+            s/Lesser Lesser General Public License/Lesser General Public License/g'''
+                elif lgpl == '3orGPLv2':
+                    sed_transform_main_lib_file += '''
+            /^ *This program is free software/i\\
+   This program is free software: you can redistribute it and\\/or\\
+   modify it under the terms of either:\\
+\\
+     * the GNU Lesser General Public License as published by the Free\\
+       Software Foundation; either version 3 of the License, or (at your\\
+       option) any later version.\\
+\\
+   or\\
+\\
+     * the GNU General Public License as published by the Free\\
+       Software Foundation; either version 2 of the License, or (at your\\
+       option) any later version.\\
+\\
+   or both in parallel, as here.
+            /^ *This program is free software/,/^$/d
+            '''
+                elif lgpl == '2':
                     sed_transform_main_lib_file += '''
             s/GNU General/GNU Lesser General/g
             s/General Public License/Lesser General Public License/g
-            s/Lesser Lesser General Public License/Lesser General Public''' \
-                        + '''License/g
+            s/Lesser Lesser General Public License/Lesser General Public License/g
             s/version [23]\\([ ,]\\)/version 2.1\\1/g'''
             else:  # if lgpl is disabled
                 sed_transform_main_lib_file += lgpl2gpl
diff --git a/pygnulib/GLInfo.py b/pygnulib/GLInfo.py
index 79c1ba0cf1..720e357c90 100644
--- a/pygnulib/GLInfo.py
+++ b/pygnulib/GLInfo.py
@@ -251,7 +251,8 @@ Options for --import, --add/remove-import:
                             placed (default \"tests\").
       --aux-dir=DIRECTORY   Directory relative to --dir where auxiliary build
                             tools are placed (default comes from configure.ac).
-      --lgpl[=2|=3]         Abort if modules aren't available under the LGPL.
+      --lgpl[=2|=3orGPLv2|=3]
+                            Abort if modules aren't available under the LGPL.
                             Also modify license template from GPL to LGPL.
                             The version number of the LGPL can be specified;
                             the default is currently LGPLv3.
diff --git a/pygnulib/GLTestDir.py b/pygnulib/GLTestDir.py
index 83555faad7..29c3010ce0 100644
--- a/pygnulib/GLTestDir.py
+++ b/pygnulib/GLTestDir.py
@@ -195,13 +195,19 @@ class GLTestDir(object):
                     if license not in ['GPLv2+ build tool', 'GPLed build tool',
                                        'public domain', 'unlimited', 'unmodifiable license text']:
                         incompatible = False
-                        if requested_licence == 'GPLv2+':
-                            if license not in ['GPLv2+', 'LGPLv2+']:
+                        if requested_licence == 'GPLv3+' or requested_licence == 'GPL':
+                            if license not in ['LGPLv2+', 'LGPLv3+ or GPLv2', 'LGPLv3+', 'LGPL', 'GPLv2+', 'GPLv3+', 'GPL']:
                                 incompatible = True
-                        elif requested_licence in ['LGPL']:
-                            if license not in ['LGPL', 'LGPLv2+']:
+                        elif requested_licence == 'GPLv2+':
+                            if license not in ['LGPLv2+', 'LGPLv3+ or GPLv2', 'GPLv2+']:
                                 incompatible = True
-                        elif requested_licence in ['LGPLv2+']:
+                        elif requested_licence == 'LGPLv3+' or requested_licence == 'LGPL':
+                            if license not in ['LGPLv2+', 'LGPLv3+ or GPLv2', 'LGPLv3+', 'LGPL']:
+                                incompatible = True
+                        elif requested_licence == 'LGPLv3+ or GPLv2':
+                            if license not in ['LGPLv2+', 'LGPLv3+ or GPLv2']:
+                                incompatible = True
+                        elif requested_licence == 'LGPLv2+':
                             if license not in ['LGPLv2+']:
                                 incompatible = True
                         if incompatible:
-- 
2.34.1

>From f531502a86fc921fa02b24ba9d7170ff3b8f93b4 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Wed, 3 Aug 2022 17:55:24 +0200
Subject: [PATCH 09/12] gnulib-tool.py: Fix broken 'for' loop.

* gnulib-tool.py (main): Canonicalize inctests before creating the
GLConfig. Rewrite a broken 'for' loop.
* pygnulib/GLConfig.py (GLConfig.setTestFlags): Remove unused statement.
---
 ChangeLog            |  5 +++++
 gnulib-tool.py       | 50 +++++++++++++++++++-------------------------
 pygnulib/GLConfig.py |  1 -
 3 files changed, 26 insertions(+), 30 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index b0d9b08a62..3447f433ac 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,10 @@
 2022-08-03  Bruno Haible  <br...@clisp.org>
 
+	gnulib-tool.py: Fix broken 'for' loop.
+	* gnulib-tool.py (main): Canonicalize inctests before creating the
+	GLConfig. Rewrite a broken 'for' loop.
+	* pygnulib/GLConfig.py (GLConfig.setTestFlags): Remove unused statement.
+
 	gnulib-tool.py: Follow gnulib-tool changes, part 23.
 	Follow gnulib-tool changes
 	2016-11-11  Bruno Haible  <br...@clisp.org>
diff --git a/gnulib-tool.py b/gnulib-tool.py
index 0864d0d8c2..cbef01b3b2 100755
--- a/gnulib-tool.py
+++ b/gnulib-tool.py
@@ -523,28 +523,27 @@ def main():
     dryrun = cmdargs.dryrun
     verbose = -cmdargs.quiet + cmdargs.verbose
     inctests = cmdargs.inctests
-    flags = [cmdargs.inctests, cmdargs.obsolete, cmdargs.cxx,
-             cmdargs.longrunning, cmdargs.privileged, cmdargs.unportable,
-             cmdargs.alltests]
-    testflags = list()
-    for flag in flags:
-        index = flags.index(flag)
-        if flag != None:
-            if flag:
-                if index == 0:
-                    testflags += [constants.TESTS['tests']]
-                elif index == 1:
-                    testflags += [constants.TESTS['obsolete']]
-                elif index == 2:
-                    testflags += [constants.TESTS['cxx-tests']]
-                elif index == 3:
-                    testflags += [constants.TESTS['longrunning-tests']]
-                elif index == 4:
-                    testflags += [constants.TESTS['privileged-tests']]
-                elif index == 5:
-                    testflags += [constants.TESTS['unportable-tests']]
-                elif index == 6:
-                    testflags += [constants.TESTS['all-tests']]
+    # Canonicalize the inctests variable.
+    if inctests == None:
+        if mode in ['import', 'add-import', 'remove-import', 'update']:
+            inctests = False
+        elif mode in ['create-testdir', 'create-megatestdir', 'test', 'megatest']:
+            inctests = True
+    testflags = []
+    if inctests:
+        testflags += [constants.TESTS['tests']]
+    if cmdargs.obsolete:
+        testflags += [constants.TESTS['obsolete']]
+    if cmdargs.cxx:
+        testflags += [constants.TESTS['cxx-tests']]
+    if cmdargs.longrunning:
+        testflags += [constants.TESTS['longrunning-tests']]
+    if cmdargs.privileged:
+        testflags += [constants.TESTS['privileged-tests']]
+    if cmdargs.unportable:
+        testflags += [constants.TESTS['unportable-tests']]
+    if cmdargs.alltests:
+        testflags += [constants.TESTS['all-tests']]
     lgpl = cmdargs.lgpl
     if lgpl != None:
         lgpl = lgpl[-1]
@@ -589,13 +588,6 @@ def main():
         dryrun=dryrun,
     )
 
-    # Canonicalize the inctests variable.
-    if inctests == None:
-        if mode in ['import', 'add-import', 'remove-import', 'update']:
-            config.disableTestFlag(TESTS['tests'])
-        elif mode in ['create-testdir', 'create-megatestdir', 'test', 'megatest']:
-            config.enableTestFlag(TESTS['tests'])
-
     # Work in the given mode.
     if mode in ['list']:
         modulesystem = classes.GLModuleSystem(config)
diff --git a/pygnulib/GLConfig.py b/pygnulib/GLConfig.py
index d126b35dbe..f59b883a91 100644
--- a/pygnulib/GLConfig.py
+++ b/pygnulib/GLConfig.py
@@ -712,7 +712,6 @@ class GLConfig(object):
     def setTestFlags(self, testflags):
         '''Specify test flags. You can get flags from TESTS variable.'''
         if type(testflags) is list or type(testflags) is tuple:
-            old_testflags = self.table['testflags']
             self.table['testflags'] = list()
             for flag in testflags:
                 try:  # Try to enable each flag
-- 
2.34.1

>From 1e57719d520683d08e8646fb2b778aa9d9afb294 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Wed, 3 Aug 2022 18:00:13 +0200
Subject: [PATCH 10/12] gnulib-tool.py: Implement option --without-tests.

* gnulib-tool.py (main): Accept option --without-tests.
---
 ChangeLog           | 3 +++
 gnulib-tool.py      | 4 ++++
 gnulib-tool.py.TODO | 1 -
 3 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/ChangeLog b/ChangeLog
index 3447f433ac..1d7b65eaf1 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,8 @@
 2022-08-03  Bruno Haible  <br...@clisp.org>
 
+	gnulib-tool.py: Implement option --without-tests.
+	* gnulib-tool.py (main): Accept option --without-tests.
+
 	gnulib-tool.py: Fix broken 'for' loop.
 	* gnulib-tool.py (main): Canonicalize inctests before creating the
 	GLConfig. Rewrite a broken 'for' loop.
diff --git a/gnulib-tool.py b/gnulib-tool.py
index cbef01b3b2..efe3b1e1d6 100755
--- a/gnulib-tool.py
+++ b/gnulib-tool.py
@@ -251,6 +251,10 @@ def main():
                         dest='inctests',
                         default=None,
                         action='store_true')
+    parser.add_argument('--without-tests',
+                        dest='inctests',
+                        default=None,
+                        action='store_false')
     # obsolete
     parser.add_argument('--with-obsolete',
                         dest='obsolete',
diff --git a/gnulib-tool.py.TODO b/gnulib-tool.py.TODO
index d6ba01657e..00c766cfe5 100644
--- a/gnulib-tool.py.TODO
+++ b/gnulib-tool.py.TODO
@@ -24,7 +24,6 @@ Implement the options:
   --extract-recursive-dependencies
   --extract-recursive-link-directive
   --extract-tests-module
-  --without-tests
   --without-c++-tests
   --without-longrunning-tests
   --without-privileged-tests
-- 
2.34.1

>From de028fc3637de15c3c0535e168bc5dda6a6084e6 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Wed, 3 Aug 2022 22:29:52 +0200
Subject: [PATCH 11/12] gnulib-tool.py: Implement options --without-c++-tests
 etc.

* gnulib-tool.py (main): Accept options --without-c++-tests,
--without-longrunning-tests, --without-privileged-tests,
--without-unportable-tests.
Improve error message for --copy-file with invalid number of arguments.
Check for invalid options given in --import, --add-import,
--remove-import, --update modes.
Pass both sets of test categories to the GLConfig constructor.
* pygnulib/GLConfig.py (GLConfig.__init__): Accept incl_test_categories
and excl_test_categories instead of testflags.
(checkInclTestCategory): Renamed from checkTestFlag.
(enableInclTestCategory): Renamed from enableTestFlag.
(disableInclTestCategory): Renamed from disableTestFlag.
(getInclTestCategories): Renamed from getTestFlags.
(setInclTestCategories): Renamed from setTestFlags.
(resetInclTestCategories): Renamed from resetTestFlags.
(setInclTestCategory, checkExclTestCategory, enableExclTestCategory,
disableExclTestCategory, getExclTestCategories, setExclTestCategories,
resetExclTestCategories): New methods.
* pygnulib/GLModuleSystem.py (GLModuleTable.__init__): Accept two
booleans as second and third constructor arguments.
(transitive_closure): Correct the determination of whether to include
each module, depending on the with-* and without-* options.
(transitive_closure_separately): Update.
* pygnulib/GLMakefileTable.py: Update.
* pygnulib/GLImport.py (__init__, actioncmd, gnulib_cache, execute):
Update.
* pygnulib/GLTestDir.py (GLTestDir.__init__, GLTestDir.execute,
GLMegaTestDir.__init__): Update.
---
 ChangeLog                   |  30 +++++++
 gnulib-tool.py              | 104 +++++++++++++++++------
 gnulib-tool.py.TODO         |   4 -
 pygnulib/GLConfig.py        | 159 ++++++++++++++++++++++++------------
 pygnulib/GLImport.py        |  59 +++++++------
 pygnulib/GLMakefileTable.py |   5 +-
 pygnulib/GLModuleSystem.py  | 106 ++++++++++++------------
 pygnulib/GLTestDir.py       |  17 ++--
 pygnulib/constants.py       |   2 +-
 9 files changed, 315 insertions(+), 171 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 1d7b65eaf1..6723ba9c08 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,35 @@
 2022-08-03  Bruno Haible  <br...@clisp.org>
 
+	gnulib-tool.py: Implement options --without-c++-tests etc.
+	* gnulib-tool.py (main): Accept options --without-c++-tests,
+	--without-longrunning-tests, --without-privileged-tests,
+	--without-unportable-tests.
+	Improve error message for --copy-file with invalid number of arguments.
+	Check for invalid options given in --import, --add-import,
+	--remove-import, --update modes.
+	Pass both sets of test categories to the GLConfig constructor.
+	* pygnulib/GLConfig.py (GLConfig.__init__): Accept incl_test_categories
+	and excl_test_categories instead of testflags.
+	(checkInclTestCategory): Renamed from checkTestFlag.
+	(enableInclTestCategory): Renamed from enableTestFlag.
+	(disableInclTestCategory): Renamed from disableTestFlag.
+	(getInclTestCategories): Renamed from getTestFlags.
+	(setInclTestCategories): Renamed from setTestFlags.
+	(resetInclTestCategories): Renamed from resetTestFlags.
+	(setInclTestCategory, checkExclTestCategory, enableExclTestCategory,
+	disableExclTestCategory, getExclTestCategories, setExclTestCategories,
+	resetExclTestCategories): New methods.
+	* pygnulib/GLModuleSystem.py (GLModuleTable.__init__): Accept two
+	booleans as second and third constructor arguments.
+	(transitive_closure): Correct the determination of whether to include
+	each module, depending on the with-* and without-* options.
+	(transitive_closure_separately): Update.
+	* pygnulib/GLMakefileTable.py: Update.
+	* pygnulib/GLImport.py (__init__, actioncmd, gnulib_cache, execute):
+	Update.
+	* pygnulib/GLTestDir.py (GLTestDir.__init__, GLTestDir.execute,
+	GLMegaTestDir.__init__): Update.
+
 	gnulib-tool.py: Implement option --without-tests.
 	* gnulib-tool.py (main): Accept option --without-tests.
 
diff --git a/gnulib-tool.py b/gnulib-tool.py
index efe3b1e1d6..bf7b98a61e 100755
--- a/gnulib-tool.py
+++ b/gnulib-tool.py
@@ -262,22 +262,38 @@ def main():
                         action='store_true')
     # c++-tests
     parser.add_argument('--with-c++-tests',
-                        dest='cxx',
+                        dest='inc_cxx_tests',
+                        default=None,
+                        action='store_true')
+    parser.add_argument('--without-c++-tests',
+                        dest='excl_cxx_tests',
                         default=None,
                         action='store_true')
     # longrunning-tests
     parser.add_argument('--with-longrunning-tests',
-                        dest='longrunning',
+                        dest='inc_longrunning_tests',
+                        default=None,
+                        action='store_true')
+    parser.add_argument('--without-longrunning-tests',
+                        dest='excl_longrunning_tests',
                         default=None,
                         action='store_true')
     # privileged-tests
     parser.add_argument('--with-privileged-tests',
-                        dest='privileged',
+                        dest='inc_privileged_tests',
+                        default=None,
+                        action='store_true')
+    parser.add_argument('--without-privileged-tests',
+                        dest='excl_privileged_tests',
                         default=None,
                         action='store_true')
     # unportable-tests
     parser.add_argument('--with-unportable-tests',
-                        dest='unportable',
+                        dest='inc_unportable_tests',
+                        default=None,
+                        action='store_true')
+    parser.add_argument('--without-unportable-tests',
+                        dest='excl_unportable_tests',
                         default=None,
                         action='store_true')
     # all-tests
@@ -491,12 +507,40 @@ def main():
         mode = 'copy-file'
         if len(cmdargs.non_option_arguments) < 1 or len(cmdargs.non_option_arguments) > 2:
             message = '%s: *** ' % constants.APP['name']
-            message += 'invalid number of arguments for --%s' % mode
-            message += '\n%s: *** Stop.\n' % constants.APP['name']
+            message += 'invalid number of arguments for --%s\n' % mode
+            message += 'Try \'gnulib-tool --help\' for more information.\n'
+            message += '%s: *** Stop.\n' % constants.APP['name']
             sys.stderr.write(message)
             sys.exit(1)
         files = list(cmdargs.non_option_arguments)
 
+    if ((mode in ['import', 'add-import', 'remove-import']
+         and (cmdargs.excl_cxx_tests or cmdargs.excl_longrunning_tests
+              or cmdargs.excl_privileged_tests or cmdargs.excl_unportable_tests))
+        or (mode == 'update'
+            and (cmdargs.localpath != None or cmdargs.libname != None
+                 or cmdargs.sourcebase != None or cmdargs.m4base != None
+                 or cmdargs.pobase != None or cmdargs.docbase != None
+                 or cmdargs.testsbase != None or cmdargs.auxdir != None
+                 or cmdargs.inctests != None or cmdargs.obsolete != None
+                 or cmdargs.inc_cxx_tests != None
+                 or cmdargs.inc_longrunning_tests != None
+                 or cmdargs.inc_privileged_tests != None
+                 or cmdargs.inc_unportable_tests != None
+                 or cmdargs.alltests != None
+                 or cmdargs.excl_cxx_tests != None
+                 or cmdargs.excl_longrunning_tests != None
+                 or cmdargs.excl_privileged_tests != None
+                 or cmdargs.excl_unportable_tests != None
+                 or cmdargs.avoids != None or cmdargs.lgpl != None
+                 or cmdargs.makefile != None))):
+        message = '%s: *** ' % constants.APP['name']
+        message += 'invalid options for --%s mode\n' % mode
+        message += 'Try \'gnulib-tool --help\' for more information.\n'
+        message += '%s: *** Stop.\n' % constants.APP['name']
+        sys.stderr.write(message)
+        sys.exit(1)
+
     # Determine specific settings.
     destdir = cmdargs.destdir
     if destdir != None:
@@ -533,21 +577,30 @@ def main():
             inctests = False
         elif mode in ['create-testdir', 'create-megatestdir', 'test', 'megatest']:
             inctests = True
-    testflags = []
+    incl_test_categories = []
     if inctests:
-        testflags += [constants.TESTS['tests']]
+        incl_test_categories += [constants.TESTS['tests']]
     if cmdargs.obsolete:
-        testflags += [constants.TESTS['obsolete']]
-    if cmdargs.cxx:
-        testflags += [constants.TESTS['cxx-tests']]
-    if cmdargs.longrunning:
-        testflags += [constants.TESTS['longrunning-tests']]
-    if cmdargs.privileged:
-        testflags += [constants.TESTS['privileged-tests']]
-    if cmdargs.unportable:
-        testflags += [constants.TESTS['unportable-tests']]
+        incl_test_categories += [constants.TESTS['obsolete']]
+    if cmdargs.inc_cxx_tests:
+        incl_test_categories += [constants.TESTS['cxx-tests']]
+    if cmdargs.inc_longrunning_tests:
+        incl_test_categories += [constants.TESTS['longrunning-tests']]
+    if cmdargs.inc_privileged_tests:
+        incl_test_categories += [constants.TESTS['privileged-tests']]
+    if cmdargs.inc_unportable_tests:
+        incl_test_categories += [constants.TESTS['unportable-tests']]
     if cmdargs.alltests:
-        testflags += [constants.TESTS['all-tests']]
+        incl_test_categories += [constants.TESTS['all-tests']]
+    excl_test_categories = []
+    if cmdargs.excl_cxx_tests:
+        excl_test_categories += [constants.TESTS['cxx-tests']]
+    if cmdargs.excl_longrunning_tests:
+        excl_test_categories += [constants.TESTS['longrunning-tests']]
+    if cmdargs.excl_privileged_tests:
+        excl_test_categories += [constants.TESTS['privileged-tests']]
+    if cmdargs.excl_unportable_tests:
+        excl_test_categories += [constants.TESTS['unportable-tests']]
     lgpl = cmdargs.lgpl
     if lgpl != None:
         lgpl = lgpl[-1]
@@ -575,7 +628,8 @@ def main():
         pobase=pobase,
         docbase=docbase,
         testsbase=testsbase,
-        testflags=testflags,
+        incl_test_categories=incl_test_categories,
+        excl_test_categories=excl_test_categories,
         libname=libname,
         lgpl=lgpl,
         makefile=makefile,
@@ -721,8 +775,8 @@ def main():
     elif mode == 'create-testdir':
         if not destdir:
             message = '%s: *** ' % constants.APP['name']
-            message += 'please specify --dir option'
-            message += '\n%s: *** Stop.\n' % constants.APP['name']
+            message += 'please specify --dir option\n'
+            message += '%s: *** Stop.\n' % constants.APP['name']
             sys.stderr.write(message)
             sys.exit(1)
         if not auxdir:
@@ -734,8 +788,8 @@ def main():
     elif mode == 'create-megatestdir':
         if not destdir:
             message = '%s: *** ' % constants.APP['name']
-            message += 'please specify --dir option'
-            message += '\n%s: *** Stop.\n' % constants.APP['name']
+            message += 'please specify --dir option\n'
+            message += '%s: *** Stop.\n' % constants.APP['name']
             sys.stderr.write(message)
             sys.exit(1)
         if not auxdir:
@@ -977,8 +1031,8 @@ def main():
 
     else:
         message = '%s: *** ' % constants.APP['name']
-        message += 'no mode specified'
-        message += '\n%s: *** Stop.\n' % constants.APP['name']
+        message += 'no mode specified\n'
+        message += '%s: *** Stop.\n' % constants.APP['name']
         sys.stderr.write(message)
         sys.exit(1)
 
diff --git a/gnulib-tool.py.TODO b/gnulib-tool.py.TODO
index 00c766cfe5..e4dcabef51 100644
--- a/gnulib-tool.py.TODO
+++ b/gnulib-tool.py.TODO
@@ -24,10 +24,6 @@ Implement the options:
   --extract-recursive-dependencies
   --extract-recursive-link-directive
   --extract-tests-module
-  --without-c++-tests
-  --without-longrunning-tests
-  --without-privileged-tests
-  --without-unportable-tests
   --single-configure
   --conditional-dependencies
   --no-conditional-dependencies
diff --git a/pygnulib/GLConfig.py b/pygnulib/GLConfig.py
index f59b883a91..37b30753ea 100644
--- a/pygnulib/GLConfig.py
+++ b/pygnulib/GLConfig.py
@@ -58,7 +58,8 @@ class GLConfig(object):
 
     def __init__(self, destdir=None, localpath=None, auxdir=None,
                  sourcebase=None, m4base=None, pobase=None, docbase=None, testsbase=None,
-                 modules=None, avoids=None, files=None, testflags=None, libname=None,
+                 modules=None, avoids=None, files=None,
+                 incl_test_categories=None, excl_test_categories=None, libname=None,
                  lgpl=None, makefile=None, libtool=None, conddeps=None, macro_prefix=None,
                  podomain=None, witness_c_macro=None, vc_files=None, symbolic=None,
                  lsymbolic=None, modcache=None, configure_ac=None, ac_version=None,
@@ -117,10 +118,14 @@ class GLConfig(object):
         self.resetFiles()
         if files != None:
             self.setFiles(files)
-        # testflags
-        self.resetTestFlags()
-        if testflags != None:
-            self.setTestFlags(testflags)
+        # test categories to include
+        self.resetInclTestCategories
+        if incl_test_categories != None:
+            self.setInclTestCategories(incl_test_categories)
+        # test categories to exclude
+        self.resetExclTestCategories
+        if excl_test_categories != None:
+            self.setExclTestCategories(excl_test_categories)
         # libname
         self.resetLibName()
         if libname != None:
@@ -351,7 +356,7 @@ class GLConfig(object):
                 return 0
             elif key == 'copyrights':
                 return True
-            elif key in ['modules', 'avoids', 'tests', 'testflags']:
+            elif key in ['modules', 'avoids', 'tests', 'incl_test_categories', 'excl_test_categories']:
                 return list()
             elif key in ['libtool', 'lgpl', 'conddeps', 'modcache', 'symbolic',
                          'lsymbolic', 'libtests', 'dryrun']:
@@ -681,52 +686,104 @@ class GLConfig(object):
         '''Reset the list of files.'''
         self.table['files'] = list()
 
-    # Define tests/testflags methods
-    def checkTestFlag(self, flag):
-        '''Return the status of the test flag.'''
-        if flag in TESTS.values():
-            return flag in self.table['testflags']
-        else:  # if flag is not in TESTS
-            raise TypeError('unknown flag: %s' % repr(flag))
-
-    def enableTestFlag(self, flag):
-        '''Enable test flag. You can get flags from TESTS variable.'''
-        if flag in TESTS.values():
-            if flag not in self.table['testflags']:
-                self.table['testflags'].append(flag)
-        else:  # if flag is not in TESTS
-            raise TypeError('unknown flag: %s' % repr(flag))
-
-    def disableTestFlag(self, flag):
-        '''Disable test flag. You can get flags from TESTS variable.'''
-        if flag in TESTS.values():
-            if flag in self.table['testflags']:
-                self.table['testflags'].remove(flag)
-        else:  # if flag is not in TESTS
-            raise TypeError('unknown flag: %s' % repr(flag))
-
-    def getTestFlags(self):
-        '''Return test flags. You can get flags from TESTS variable.'''
-        return list(self.table['testflags'])
-
-    def setTestFlags(self, testflags):
-        '''Specify test flags. You can get flags from TESTS variable.'''
-        if type(testflags) is list or type(testflags) is tuple:
-            self.table['testflags'] = list()
-            for flag in testflags:
-                try:  # Try to enable each flag
-                    self.enableTestFlag(flag)
+    # Define incl_test_categories methods
+    def checkInclTestCategory(self, category):
+        '''Tests whether the given test category is included.'''
+        if category in TESTS.values():
+            return category in self.table['incl_test_categories']
+        else:  # if category is not in TESTS
+            raise TypeError('unknown category: %s' % repr(category))
+
+    def enableInclTestCategory(self, category):
+        '''Enable the given test category.'''
+        if category in TESTS.values():
+            if category not in self.table['incl_test_categories']:
+                self.table['incl_test_categories'].append(category)
+        else:  # if category is not in TESTS
+            raise TypeError('unknown category: %s' % repr(category))
+
+    def disableInclTestCategory(self, category):
+        '''Disable the given test category.'''
+        if category in TESTS.values():
+            if category in self.table['incl_test_categories']:
+                self.table['incl_test_categories'].remove(category)
+        else:  # if category is not in TESTS
+            raise TypeError('unknown category: %s' % repr(category))
+
+    def setInclTestCategory(self, category, enable):
+        '''Enable or disable the given test category.'''
+        if (enable):
+            self.enableInclTestCategory(category)
+        else:
+            self.disableInclTestCategory(category)
+
+    def getInclTestCategories(self):
+        '''Return the test categories that should be included.
+        To get the list of all test categories, use the TESTS variable.'''
+        return list(self.table['incl_test_categories'])
+
+    def setInclTestCategories(self, categories):
+        '''Specify the test categories that should be included.'''
+        if type(categories) is list or type(categories) is tuple:
+            self.table['incl_test_categories'] = list()
+            for category in categories:
+                try:  # Try to enable each category
+                    self.enableInclTestCategory(category)
+                except TypeError as error:
+                    raise TypeError('each category must be one of TESTS integers')
+        else:  # if type of categories is not list or tuple
+            raise TypeError('categories must be a list or a tuple, not %s' %
+                            type(categories).__name__)
+
+    def resetInclTestCategories(self):
+        '''Reset test categories.'''
+        self.table['incl_test_categories'] = list()
+
+    # Define excl_test_categories methods
+    def checkExclTestCategory(self, category):
+        '''Tests whether the given test category is excluded.'''
+        if category in TESTS.values():
+            return category in self.table['excl_test_categories']
+        else:  # if category is not in TESTS
+            raise TypeError('unknown category: %s' % repr(category))
+
+    def enableExclTestCategory(self, category):
+        '''Enable the given test category.'''
+        if category in TESTS.values():
+            if category not in self.table['excl_test_categories']:
+                self.table['excl_test_categories'].append(category)
+        else:  # if category is not in TESTS
+            raise TypeError('unknown category: %s' % repr(category))
+
+    def disableExclTestCategory(self, category):
+        '''Disable the given test category.'''
+        if category in TESTS.values():
+            if category in self.table['excl_test_categories']:
+                self.table['excl_test_categories'].remove(category)
+        else:  # if category is not in TESTS
+            raise TypeError('unknown category: %s' % repr(category))
+
+    def getExclTestCategories(self):
+        '''Return the test categories that should be excluded.
+        To get the list of all test categories, use the TESTS variable.'''
+        return list(self.table['excl_test_categories'])
+
+    def setExclTestCategories(self, categories):
+        '''Specify the test categories that should be excluded.'''
+        if type(categories) is list or type(categories) is tuple:
+            self.table['excl_test_categories'] = list()
+            for category in categories:
+                try:  # Try to enable each category
+                    self.enableExclTestCategory(category)
                 except TypeError as error:
-                    raise TypeError('each flag must be one of TESTS integers')
-            self.table['testflags'] = testflags
-        else:  # if type of testflags is not list or tuple
-            raise TypeError('testflags must be a list or a tuple, not %s' %
-                            type(testflags).__name__)
-
-    def resetTestFlags(self):
-        '''Reset test flags (only default flag will be enabled).'''
-        self.table['testflags'] = list()
-        self.table['tests'] = self.table['testflags']
+                    raise TypeError('each category must be one of TESTS integers')
+        else:  # if type of categories is not list or tuple
+            raise TypeError('categories must be a list or a tuple, not %s' %
+                            type(categories).__name__)
+
+    def resetExclTestCategories(self):
+        '''Reset test categories.'''
+        self.table['excl_test_categories'] = list()
 
     # Define libname methods.
     def getLibName(self):
diff --git a/pygnulib/GLImport.py b/pygnulib/GLImport.py
index 04b3fddaab..58b8eaab9f 100644
--- a/pygnulib/GLImport.py
+++ b/pygnulib/GLImport.py
@@ -149,25 +149,25 @@ class GLImport(object):
                 self.cache.enableVCFiles()
                 data = data.replace('gl_VC_FILES', '')
             if 'gl_WITH_TESTS' in data:
-                self.cache.enableTestFlag(TESTS['tests'])
+                self.cache.enableInclTestCategory(TESTS['tests'])
                 data = data.replace('gl_WITH_TESTS', '')
             if 'gl_WITH_OBSOLETE' in data:
-                self.cache.enableTestFlag(TESTS['obsolete'])
+                self.cache.enableInclTestCategory(TESTS['obsolete'])
                 data = data.replace('gl_WITH_OBSOLETE', '')
             if 'gl_WITH_CXX_TESTS' in data:
-                self.cache.enableTestFlag(TESTS['c++-test'])
+                self.cache.enableInclTestCategory(TESTS['c++-test'])
                 data = data.replace('gl_WITH_CXX_TESTS', '')
             if 'gl_WITH_LONGRUNNING_TESTS' in data:
-                self.cache.enableTestFlag(TESTS['longrunning-test'])
+                self.cache.enableInclTestCategory(TESTS['longrunning-test'])
                 data = data.replace('gl_WITH_LONGRUNNING_TESTS', '')
             if 'gl_WITH_PRIVILEGED_TESTS' in data:
-                self.cache.enableTestFlag(TESTS['privileged-test'])
+                self.cache.enableInclTestCategory(TESTS['privileged-test'])
                 data = data.replace('gl_WITH_PRIVILEGED_TESTS', '')
             if 'gl_WITH_UNPORTABLE_TESTS' in data:
-                self.cache.enableTestFlag(TESTS['unportable-test'])
+                self.cache.enableInclTestCategory(TESTS['unportable-test'])
                 data = data.replace('gl_WITH_UNPORTABLE_TESTS', '')
             if 'gl_WITH_ALL_TESTS' in data:
-                self.cache.enableTestFlag(TESTS['all-test'])
+                self.cache.enableInclTestCategory(TESTS['all-test'])
                 data = data.replace('gl_WITH_ALL_TESTS', '')
             # Find string values
             result = dict(pattern.findall(data))
@@ -244,8 +244,8 @@ class GLImport(object):
             elif self.mode == MODES['update']:
                 modules = self.cache.getModules()
 
-            # If user tries to apply conddeps and testflag['tests'] together.
-            if self.config['tests'] and self.config['conddeps']:
+            # If user tries to apply conddeps and TESTS['tests'] together.
+            if self.checkInclTestCategory(TESTS['tests']) and self.config['conddeps']:
                 raise GLError(10, None)
 
             # Update configuration dictionary.
@@ -257,7 +257,7 @@ class GLImport(object):
             self.config.setModules(modules)
 
         # Check if conddeps is enabled together with inctests.
-        inctests = self.config.checkTestFlag(TESTS['tests'])
+        inctests = self.config.checkInclTestCategory(TESTS['tests'])
         if self.config['conddeps'] and inctests:
             raise GLError(10, None)
 
@@ -265,7 +265,9 @@ class GLImport(object):
         self.emiter = GLEmiter(self.config)
         self.filesystem = GLFileSystem(self.config)
         self.modulesystem = GLModuleSystem(self.config)
-        self.moduletable = GLModuleTable(self.config, list())
+        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):
@@ -366,7 +368,6 @@ class GLImport(object):
         docbase = self.config.getDocBase()
         pobase = self.config.getPoBase()
         testsbase = self.config.getTestsBase()
-        testflags = self.config.getTestFlags()
         conddeps = self.config.checkCondDeps()
         libname = self.config.getLibName()
         lgpl = self.config.getLGPL()
@@ -391,19 +392,19 @@ class GLImport(object):
         actioncmd += ' --doc-base=%s' % docbase
         actioncmd += ' --tests-base=%s' % testsbase
         actioncmd += ' --aux-dir=%s' % auxdir
-        if self.config.checkTestFlag(TESTS['tests']):
+        if self.config.checkInclTestCategory(TESTS['tests']):
             actioncmd += ' --with-tests'
-        if self.config.checkTestFlag(TESTS['obsolete']):
+        if self.config.checkInclTestCategory(TESTS['obsolete']):
             actioncmd += ' --with-obsolete'
-        if self.config.checkTestFlag(TESTS['c++-test']):
+        if self.config.checkInclTestCategory(TESTS['c++-test']):
             actioncmd += ' --with-c++-tests'
-        if self.config.checkTestFlag(TESTS['longrunning-test']):
+        if self.config.checkInclTestCategory(TESTS['longrunning-test']):
             actioncmd += ' --with-longrunning-tests'
-        if self.config.checkTestFlag(TESTS['privileged-test']):
+        if self.config.checkInclTestCategory(TESTS['privileged-test']):
             actioncmd += ' --with-privileged-test'
-        if self.config.checkTestFlag(TESTS['unportable-test']):
+        if self.config.checkInclTestCategory(TESTS['unportable-test']):
             actioncmd += ' --with-unportable-tests'
-        if self.config.checkTestFlag(TESTS['all-test']):
+        if self.config.checkInclTestCategory(TESTS['all-test']):
             actioncmd += ' --with-all-tests'
         for module in avoids:
             actioncmd += ' --avoid=%s' % module
@@ -478,7 +479,6 @@ class GLImport(object):
         moduletable = self.moduletable
         actioncmd = self.actioncmd()
         localpath = self.config['localpath']
-        testflags = list(self.config['testflags'])
         sourcebase = self.config['sourcebase']
         m4base = self.config['m4base']
         pobase = self.config['pobase']
@@ -514,15 +514,15 @@ class GLImport(object):
         emit += 'gl_MODULES([\n'
         emit += '  %s\n' % '\n  '.join(modules)
         emit += '])\n'
-        if self.config.checkTestFlag(TESTS['obsolete']):
+        if self.config.checkInclTestCategory(TESTS['obsolete']):
             emit += 'gl_WITH_OBSOLETE\n'
-        if self.config.checkTestFlag(TESTS['cxx-tests']):
+        if self.config.checkInclTestCategory(TESTS['cxx-tests']):
             emit += 'gl_WITH_CXX_TESTS\n'
-        if self.config.checkTestFlag(TESTS['privileged-tests']):
+        if self.config.checkInclTestCategory(TESTS['privileged-tests']):
             emit += 'gl_WITH_PRIVILEGED_TESTS\n'
-        if self.config.checkTestFlag(TESTS['unportable-tests']):
+        if self.config.checkInclTestCategory(TESTS['unportable-tests']):
             emit += 'gl_WITH_UNPORTABLE_TESTS\n'
-        if self.config.checkTestFlag(TESTS['all-tests']):
+        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
@@ -530,7 +530,7 @@ class GLImport(object):
         emit += 'gl_PO_BASE([%s])\n' % pobase
         emit += 'gl_DOC_BASE([%s])\n' % docbase
         emit += 'gl_TESTS_BASE([%s])\n' % testsbase
-        if self.config.checkTestFlag(TESTS['tests']):
+        if self.config.checkInclTestCategory(TESTS['tests']):
             emit += 'gl_WITH_TESTS\n'
         emit += 'gl_LIB([%s])\n' % libname
         if lgpl != None:
@@ -561,7 +561,6 @@ class GLImport(object):
         moduletable = self.moduletable
         destdir = self.config['destdir']
         auxdir = self.config['auxdir']
-        testflags = list(self.config['testflags'])
         sourcebase = self.config['sourcebase']
         m4base = self.config['m4base']
         pobase = self.config['pobase']
@@ -764,7 +763,6 @@ AC_DEFUN([%s_FILE_LIST], [\n''' % macro_prefix
         auxdir = self.config['auxdir']
         modules = list(self.config['modules'])
         avoids = list(self.config['avoids'])
-        testflags = list(self.config['testflags'])
         sourcebase = self.config['sourcebase']
         m4base = self.config['m4base']
         pobase = self.config['pobase']
@@ -1004,7 +1002,6 @@ AC_DEFUN([%s_FILE_LIST], [\n''' % macro_prefix
         auxdir = self.config['auxdir']
         modules = list(self.config['modules'])
         avoids = list(self.config['avoids'])
-        testflags = list(self.config['testflags'])
         sourcebase = self.config['sourcebase']
         m4base = self.config['m4base']
         pobase = self.config['pobase']
@@ -1122,7 +1119,7 @@ AC_DEFUN([%s_FILE_LIST], [\n''' % macro_prefix
             pobase_dir = os.path.dirname(pobase)
             pobase_base = os.path.basename(pobase)
             self.makefiletable.editor(pobase_dir, 'SUBDIRS', pobase_base)
-        if self.config.checkTestFlag(TESTS['tests']):
+        if self.config.checkInclTestCategory(TESTS['tests']):
             if makefile_am == 'Makefile.am':
                 testsbase_dir = os.path.dirname(testsbase)
                 testsbase_base = os.path.basename(testsbase)
@@ -1313,7 +1310,7 @@ AC_DEFUN([%s_FILE_LIST], [\n''' % macro_prefix
             os.remove(tmpfile)
 
         # Create tests Makefile.
-        inctests = self.config.checkTestFlag(TESTS['tests'])
+        inctests = self.config.checkInclTestCategory(TESTS['tests'])
         if inctests:
             basename = joinpath(testsbase, makefile_am)
             tmpfile = self.assistant.tmpfilename(basename)
diff --git a/pygnulib/GLMakefileTable.py b/pygnulib/GLMakefileTable.py
index 4ee85b4d0c..fdb15ac132 100644
--- a/pygnulib/GLMakefileTable.py
+++ b/pygnulib/GLMakefileTable.py
@@ -90,12 +90,13 @@ class GLMakefileTable(object):
 
         Add a special row to Makefile.am table with the first parent directory
         which contains or will contain Makefile.am file.
-        GLConfig: sourcebase, m4base, testsbase, testflags, makefile.'''
+        GLConfig: sourcebase, m4base, testsbase, incl_test_categories,
+        excl_test_categories, makefile.'''
         m4base = self.config['m4base']
         sourcebase = self.config['sourcebase']
         testsbase = self.config['testsbase']
         makefile = self.config['makefile']
-        inctests = self.config.checkTestFlag(TESTS['tests'])
+        inctests = self.config.checkInclTestCategory(TESTS['tests'])
         dir1 = '%s%s' % (m4base, os.path.sep)
         mfd = 'Makefile.am'
         if not makefile:
diff --git a/pygnulib/GLModuleSystem.py b/pygnulib/GLModuleSystem.py
index 25c34f8756..7e4c6148e3 100644
--- a/pygnulib/GLModuleSystem.py
+++ b/pygnulib/GLModuleSystem.py
@@ -868,18 +868,23 @@ Include:|Link:|License:|Maintainer:)'
 class GLModuleTable(object):
     '''GLModuleTable is used to work with the list of the modules.'''
 
-    def __init__(self, config, avoids=list()):
-        '''GLModuleTable.__init__(config, avoids) -> GLModuleTable
+    def __init__(self, config, inc_all_direct_tests, inc_all_indirect_tests):
+        '''GLModuleTable.__init__(config, inc_all_direct_tests, inc_all_indirect_tests) -> GLModuleTable
 
         Create new GLModuleTable instance. If modules are specified, then add
         every module from iterable as unconditional module. If avoids is specified,
         then in transitive_closure every dependency which is in avoids won't be
-        included in the final modules list. If testflags iterable is enabled, then
-        don't add module which status is in the testflags. If conddeps are enabled,
+        included in the final modules list. If conddeps are enabled,
         then store condition for each dependency if it has a condition.
         The only necessary argument is localpath, which is needed just to create
-        modulesystem instance to look for dependencies.'''
-        self.avoids = list()  # Avoids
+        modulesystem instance to look for dependencies.
+        inc_all_direct_tests = True if all kinds of problematic unit tests among
+                                    the unit tests of the specified modules
+                                    should be included
+        inc_all_indirect_tests = True if all kinds of problematic unit tests
+                                    among the unit tests of the dependencies
+                                    should be included
+        '''
         self.dependers = dict()  # Dependencies
         self.conditionals = dict()  # Conditional modules
         self.unconditionals = dict()  # Unconditional modules
@@ -890,11 +895,16 @@ class GLModuleTable(object):
         if type(config) is not GLConfig:
             raise TypeError('config must be a GLConfig, not %s' %
                             type(config).__name__)
-        for avoid in avoids:
+        self.config = config
+        if type(inc_all_direct_tests) is not bool:
+            raise TypeError('inc_all_direct_tests must be a bool, not %s' %
+                            type(inc_all_direct_tests).__name__)
+        self.inc_all_direct_tests = inc_all_direct_tests
+        self.inc_all_indirect_tests = inc_all_indirect_tests
+        self.avoids = list()  # Avoids
+        for avoid in self.avoids:
             if type(avoid) is not GLModule:
                 raise TypeError('each avoid must be a GLModule instance')
-            self.avoids += [avoids]
-        self.config = config
         self.filesystem = GLFileSystem(self.config)
         self.modulesystem = GLModuleSystem(self.config)
 
@@ -982,14 +992,16 @@ class GLModuleTable(object):
 
         Use transitive closure to add module and its dependencies. Add every
         module and its dependencies from modules list, but do not add dependencies
-        which contain in avoids list. If any testflag is enabled, then do not add
-        dependencies which have the status as this flag. If conddeps are enabled,
+        which contain in avoids list. If any incl_test_categories is enabled, then
+        add dependencies which are in these categories. If any excl_test_categories,
+        then do not add dependencies which are in these categories. If conddeps are enabled,
         then store condition for each dependency if it has a condition. This method
         is used to update final list of modules. Method returns list of modules.
-        GLConfig: testflags.'''
+        GLConfig: incl_test_categories, excl_test_categories.'''
         for module in modules:
             if type(module) is not GLModule:
                 raise TypeError('each module must be a GLModule instance')
+        inc_all_tests = self.inc_all_direct_tests
         handledmodules = list()
         inmodules = modules
         outmodules = list()
@@ -1011,7 +1023,7 @@ class GLModuleTable(object):
                 dependencies = module.getDependencies()
                 depmodules = [pair[0] for pair in dependencies]
                 conditions = [pair[1] for pair in dependencies]
-                if TESTS['tests'] in self.config['testflags']:
+                if self.config.checkInclTestCategory(TESTS['tests']):
                     testsname = module.getTestsName()
                     if self.modulesystem.exists(testsname):
                         testsmodule = self.modulesystem.find(testsname)
@@ -1019,34 +1031,34 @@ class GLModuleTable(object):
                         conditions += [None]
                 for depmodule in depmodules:
                     include = True
-                    includes = list()
                     status = depmodule.getStatus()
                     for word in status:
                         if word == 'obsolete':
-                            if TESTS['obsolete'] in self.config['testflags'] or \
-                                    TESTS['all-test'] in self.config['testflags']:
-                                includes += [False]
+                            if not self.config.checkInclTestCategory(TESTS['obsolete']):
+                                include = False
                         elif word == 'c++-test':
-                            if TESTS['c++-test'] in self.config['testflags'] or \
-                                    TESTS['all-test'] in self.config['testflags']:
-                                includes += [False]
+                            if self.config.checkExclTestCategory(TESTS['c++-test']):
+                                include = False
+                            if not (inc_all_tests or self.config.checkInclTestCategory(TESTS['c++-test'])):
+                                include = False
                         elif word == 'longrunning-test':
-                            if TESTS['longrunning-test'] in self.config['testflags'] or \
-                                    TESTS['all-test'] in self.config['testflags']:
-                                includes += [False]
+                            if self.config.checkExclTestCategory(TESTS['longrunning-test']):
+                                include = False
+                            if not (inc_all_tests or self.config.checkInclTestCategory(TESTS['longrunning-test'])):
+                                include = False
                         elif word == 'privileged-test':
-                            if TESTS['privileged-test'] in self.config['testflags'] or \
-                                    TESTS['all-test'] in self.config['testflags']:
-                                includes += [False]
-                        elif word == 'all-test':
-                            if TESTS['all-test'] in self.config['testflags'] or \
-                                    TESTS['all-test'] in self.config['testflags']:
-                                includes += [False]
-                        else:  # if any other word
-                            if word.endswith('-tests'):
-                                if TESTS['all-test'] in self.config['testflags']:
-                                    includes += [False]
-                        include = any(includes)
+                            if self.config.checkExclTestCategory(TESTS['privileged-test']):
+                                include = False
+                            if not (inc_all_tests or self.config.checkInclTestCategory(TESTS['privileged-test'])):
+                                include = False
+                        elif word == 'unportable-test':
+                            if self.config.checkExclTestCategory(TESTS['unportable-test']):
+                                include = False
+                            if not (inc_all_tests or self.config.checkInclTestCategory(TESTS['unportable-test'])):
+                                include = False
+                        elif word.endswith('-test'):
+                            if not inc_all_tests:
+                                include = False
                     if include and depmodule not in self.avoids:
                         inmodules += [depmodule]
                         if self.config['conddeps']:
@@ -1064,11 +1076,12 @@ class GLModuleTable(object):
             listing = list()  # Create empty list
             inmodules = sorted(set(inmodules))
             handledmodules = sorted(set(handledmodules + inmodules_this_round))
-            inmodules = \
-                [  # Begin to filter inmodules
-                    module for module in inmodules if module not in handledmodules
-                ]  # Finish to filter inmodules
+            # Remove handledmodules from inmodules.
+            inmodules = [module
+                         for module in inmodules
+                         if module not in handledmodules]
             inmodules = sorted(set(inmodules))
+            inc_all_tests = self.inc_all_indirect_tests
         modules = sorted(set(outmodules))
         self.modules = modules
         return list(modules)
@@ -1090,28 +1103,21 @@ class GLModuleTable(object):
         list after previous transitive_closure.
         Method returns tuple which contains two lists: the list of main modules and
         the list of tests-related modules. Both lists contain dependencies.
-        GLConfig: testflags.'''
-        inctests = False
-        main_modules = list()
-        tests_modules = list()
-        if TESTS['tests'] in self.config['testflags']:
-            self.config['testflags'].pop(TESTS['tests'])
-            inctests = True
+        GLConfig: incl_test_categories, excl_test_categories.'''
         for module in basemodules:
             if type(module) is not GLModule:
                 raise TypeError('each module must be a GLModule instance')
         for module in finalmodules:
             if type(module) is not GLModule:
                 raise TypeError('each module must be a GLModule instance')
+        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)
         tests_modules = \
             [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))
-        if inctests:
-            testflags = sorted(
-                set(self.config['testflags'] + [TESTS['tests']]))
-            self.config.setTestFlags(testflags)
         result = tuple([main_modules, tests_modules])
         return result
 
diff --git a/pygnulib/GLTestDir.py b/pygnulib/GLTestDir.py
index 29c3010ce0..cb75ba1eab 100644
--- a/pygnulib/GLTestDir.py
+++ b/pygnulib/GLTestDir.py
@@ -87,7 +87,9 @@ class GLTestDir(object):
         self.emiter = GLEmiter(self.config)
         self.filesystem = GLFileSystem(self.config)
         self.modulesystem = GLModuleSystem(self.config)
-        self.moduletable = GLModuleTable(self.config)
+        self.moduletable = GLModuleTable(self.config,
+                                         True,
+                                         self.config.checkInclTestCategory(TESTS['all-tests']))
         self.assistant = GLFileAssistant(self.config)
         self.makefiletable = GLMakefileTable(self.config)
 
@@ -147,7 +149,6 @@ class GLTestDir(object):
 
         Create a scratch package with the given modules.'''
         auxdir = self.config['auxdir']
-        testflags = list(self.config['testflags'])
         sourcebase = self.config['sourcebase']
         m4base = self.config['m4base']
         pobase = self.config['pobase']
@@ -180,8 +181,8 @@ class GLTestDir(object):
         # When computing transitive closures, don't consider $module to depend on
         # $module-tests. Need this because tests are implicitly GPL and may depend
         # on GPL modules - therefore we don't want a warning in this case.
-        saved_testflags = list(self.config['testflags'])
-        self.config.disableTestFlag(TESTS['tests'])
+        saved_inctests = self.config.checkInclTestCategory(TESTS['tests'])
+        self.config.disableInclTestCategory(TESTS['tests'])
         for requested_module in base_modules:
             requested_licence = requested_module.getLicense()
             if requested_licence != 'GPL':
@@ -213,7 +214,7 @@ class GLTestDir(object):
                         if incompatible:
                             errormsg = 'module %s depends on a module with an incompatible license: %s\n' % (requested_module, module)
                             sys.stderr.write(errormsg)
-        self.config.setTestFlags(saved_testflags)
+        self.config.setInclTestCategory(TESTS['tests'], saved_inctests)
 
         # Determine final module list.
         modules = self.moduletable.transitive_closure(base_modules)
@@ -389,7 +390,7 @@ class GLTestDir(object):
         subdirs_with_configure_ac = list()
 
         testsbase_appened = False
-        inctests = self.config.checkTestFlag(TESTS['tests'])
+        inctests = self.config.checkInclTestCategory(TESTS['tests'])
         if inctests:
             directory = joinpath(self.testdir, testsbase)
             if not isdir(directory):
@@ -863,7 +864,9 @@ class GLMegaTestDir(object):
         self.emiter = GLEmiter(self.config)
         self.filesystem = GLFileSystem(self.config)
         self.modulesystem = GLModuleSystem(self.config)
-        self.moduletable = GLModuleTable(self.config)
+        self.moduletable = GLModuleTable(self.config,
+                                         True,
+                                         self.config.checkInclTestCategory(TESTS['all-tests']))
         self.assistant = GLFileAssistant(self.config)
         self.makefiletable = GLMakefileTable(self.config)
 
diff --git a/pygnulib/constants.py b/pygnulib/constants.py
index ca5e3f79aa..bf01f86f39 100644
--- a/pygnulib/constants.py
+++ b/pygnulib/constants.py
@@ -104,7 +104,7 @@ MODES['verbose-min'] = -2
 MODES['verbose-default'] = 0
 MODES['verbose-max'] = 2
 
-# Set TESTS dictionary
+# Define TESTS categories
 TESTS = \
     {
         'tests':             0,
-- 
2.34.1

>From f04b5c3acdc08549fb5b25936d2d17faae83a89b Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Wed, 3 Aug 2022 22:42:25 +0200
Subject: [PATCH 12/12] gnulib-tool.py: Implement option --single-configure.

* gnulib-tool.py (main): Accept option --single-configure. Pass its
value to the GLConfig constructor.
* pygnulib/GLTestDir.py (GLTestDir.execute): Remove debugging output.
---
 ChangeLog             |  5 +++++
 gnulib-tool.py        | 10 +++++++++-
 gnulib-tool.py.TODO   |  1 -
 pygnulib/GLTestDir.py |  1 -
 4 files changed, 14 insertions(+), 3 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 6723ba9c08..58cb2b610a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,10 @@
 2022-08-03  Bruno Haible  <br...@clisp.org>
 
+	gnulib-tool.py: Implement option --single-configure.
+	* gnulib-tool.py (main): Accept option --single-configure. Pass its
+	value to the GLConfig constructor.
+	* pygnulib/GLTestDir.py (GLTestDir.execute): Remove debugging output.
+
 	gnulib-tool.py: Implement options --without-c++-tests etc.
 	* gnulib-tool.py (main): Accept options --without-c++-tests,
 	--without-longrunning-tests, --without-privileged-tests,
diff --git a/gnulib-tool.py b/gnulib-tool.py
index bf7b98a61e..55e94fb25a 100755
--- a/gnulib-tool.py
+++ b/gnulib-tool.py
@@ -359,6 +359,11 @@ def main():
                         dest="makefile",
                         default=None,
                         type=str)
+    # single-configure
+    parser.add_argument('--single-configure',
+                        dest='single_configure',
+                        default=None,
+                        action='store_true')
     # symlink
     parser.add_argument('-s', '--symbolic', '--symlink',
                         dest='symlink',
@@ -516,7 +521,8 @@ def main():
 
     if ((mode in ['import', 'add-import', 'remove-import']
          and (cmdargs.excl_cxx_tests or cmdargs.excl_longrunning_tests
-              or cmdargs.excl_privileged_tests or cmdargs.excl_unportable_tests))
+              or cmdargs.excl_privileged_tests or cmdargs.excl_unportable_tests
+              or cmdargs.single_configure))
         or (mode == 'update'
             and (cmdargs.localpath != None or cmdargs.libname != None
                  or cmdargs.sourcebase != None or cmdargs.m4base != None
@@ -615,6 +621,7 @@ def main():
                    for module in list1 ]
     symlink = cmdargs.symlink == True
     lsymlink = cmdargs.lsymlink == True
+    single_configure = cmdargs.single_configure
 
     # Create pygnulib configuration.
     config = classes.GLConfig(
@@ -642,6 +649,7 @@ def main():
         symbolic=symlink,
         lsymbolic=lsymlink,
         modcache=modcache,
+        single_configure=single_configure,
         verbose=verbose,
         dryrun=dryrun,
     )
diff --git a/gnulib-tool.py.TODO b/gnulib-tool.py.TODO
index e4dcabef51..8814a215a4 100644
--- a/gnulib-tool.py.TODO
+++ b/gnulib-tool.py.TODO
@@ -24,7 +24,6 @@ Implement the options:
   --extract-recursive-dependencies
   --extract-recursive-link-directive
   --extract-tests-module
-  --single-configure
   --conditional-dependencies
   --no-conditional-dependencies
   --gnu-make
diff --git a/pygnulib/GLTestDir.py b/pygnulib/GLTestDir.py
index cb75ba1eab..384b5f3aeb 100644
--- a/pygnulib/GLTestDir.py
+++ b/pygnulib/GLTestDir.py
@@ -398,7 +398,6 @@ class GLTestDir(object):
             if single_configure:
                 # Create $testsbase/Makefile.am.
                 destfile = joinpath(directory, 'Makefile.am')
-                print(repr(destfile))
                 witness_macro = '%stests_WITNESS' % macro_prefix
                 emit, uses_subdirs = self.emiter.tests_Makefile_am(destfile,
                                                                    tests_modules, self.makefiletable, witness_macro, for_test)
-- 
2.34.1

Reply via email to