[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
gnulib-tool.py: Allow verifying license compatibility with GPLv2+
From: |
Bruno Haible |
Subject: |
gnulib-tool.py: Allow verifying license compatibility with GPLv2+ |
Date: |
Thu, 29 Aug 2024 18:34:47 +0200 |
Some packages that use Gnulib are under GPLv2+. It is useful for these packages
— just for packages under LGPL — to be able to verify that the modules they
import are compatible with this license choice.
This patch does it, by adding an option --gpl=2 to gnulib-tool.
The option --gpl=3 is also accepted, but is currently a no-op.
In the implementation of the serialization format (gnulib-cache.m4)
this adds a gl_GPL pseudo-macro. I considered merging gl_LGPL and gl_GPL
into a single pseudo-macro, but that would have become more complicated
(due to the need to handle new and old gnulib-cache.m4 files).
2024-08-29 Bruno Haible <bruno@clisp.org>
gnulib-tool.py: Allow verifying license compatibility with GPLv2+.
* pygnulib/GLInfo.py (GLInfo.usage): Document the --gpl option.
* pygnulib/main.py (main): Accept a --gpl option. Pass it to the
GLConfig.
* pygnulib/GLConfig.py (GLConfig): Add 'gpl' field and constructor
argument. Add getGPL, setGPL, resetGPL methods.
* m4/gnulib-tool.m4 (gl_GPL): New macro.
* doc/gnulib-tool.texi (Modified imports): Document the gl_GPL macro.
* pygnulib/GLImport.py (GLImport.__init__): Look for gl_GPL invocations
in gnulib-cache.m4.
(GLImport.actioncmd): Output --gpl option when option --gpl was given.
(GLImport.gnulib_cache): Emit a gl_GPL invocation when option --gpl was
given.
(GLImport.prepare): Do license compatibility checking when option --gpl
was given.
* pygnulib/GLModuleSystem.py: Update a comment.
diff --git a/doc/gnulib-tool.texi b/doc/gnulib-tool.texi
index 41eccb3019..470342722d 100644
--- a/doc/gnulib-tool.texi
+++ b/doc/gnulib-tool.texi
@@ -454,6 +454,10 @@
value must be 2 or 3) corresponds to the @samp{--lgpl=@var{arg}} command line
argument.
+@item gl_GPL
+The presence of this macro with an argument (whose value must be 2 or 3)
+corresponds to the @samp{--gpl=@var{arg}} command line argument.
+
@item gl_MAKEFILE_NAME
The argument is the name of the makefile in the source-base and tests-base
directories. Corresponds to the @samp{--makefile-name} command line argument.
diff --git a/m4/gnulib-tool.m4 b/m4/gnulib-tool.m4
index ef45f51fc8..2f517f1bbc 100644
--- a/m4/gnulib-tool.m4
+++ b/m4/gnulib-tool.m4
@@ -1,5 +1,5 @@
# gnulib-tool.m4
-# serial 4
+# serial 5
dnl Copyright (C) 2004-2005, 2009-2024 Free Software Foundation, Inc.
dnl This file is free software; the Free Software Foundation
dnl gives unlimited permission to copy and/or distribute it,
@@ -42,6 +42,9 @@ AC_DEFUN([gl_LIB]
dnl Usage: gl_LGPL or gl_LGPL([VERSION])
AC_DEFUN([gl_LGPL], [])
+dnl Usage: gl_GPL([VERSION])
+AC_DEFUN([gl_GPL], [])
+
dnl Usage: gl_MAKEFILE_NAME([FILENAME])
AC_DEFUN([gl_MAKEFILE_NAME], [])
diff --git a/pygnulib/GLConfig.py b/pygnulib/GLConfig.py
index b8a7fc5b0b..353701cd3b 100644
--- a/pygnulib/GLConfig.py
+++ b/pygnulib/GLConfig.py
@@ -60,6 +60,7 @@ def __init__(self,
excl_test_categories: list[int] | tuple[int] | None = None,
libname: str | None = None,
lgpl: str | bool | None = None,
+ gpl: str | None = None,
gnu_make: bool | None = None,
makefile_name: str | None = None,
tests_makefile_name: str | None = None,
@@ -151,6 +152,10 @@ def __init__(self,
self.resetLGPL()
if lgpl != None:
self.setLGPL(lgpl)
+ # gpl
+ self.resetGPL()
+ if gpl != None:
+ self.setGPL(gpl)
# gnu-make
self.resetGnuMake()
if gnu_make != None:
@@ -318,7 +323,7 @@ def default(self, key: str) -> Any:
return False
elif key in ['copymode', 'lcopymode']:
return CopyAction.Copy
- elif key in ['lgpl', 'libtool', 'conddeps', 'vc_files']:
+ elif key in ['lgpl', 'gpl', 'libtool', 'conddeps', 'vc_files']:
return None
elif key == 'errors':
return True
@@ -834,6 +839,25 @@ def resetLGPL(self) -> None:
Default value is None, which means that lgpl is disabled.'''
self.table['lgpl'] = None
+ # Define gpl methods.
+ def getGPL(self) -> str | None:
+ '''Check for abort if modules aren't available under the GPL.
+ Default value is None, which means that gpl is disabled.'''
+ return self.table['gpl']
+
+ def setGPL(self, gpl: str | bool | None) -> None:
+ '''Abort if modules aren't available under the GPL.
+ Default value is None, which means that gpl is disabled.'''
+ if gpl in [None, '2', '3']:
+ self.table['gpl'] = gpl
+ else:
+ raise TypeError('invalid GPL version: %s' % repr(gpl))
+
+ def resetGPL(self) -> None:
+ '''Disable abort if modules aren't available under the GPL.
+ Default value is None, which means that gpl is disabled.'''
+ self.table['gpl'] = None
+
# Define gnu-make methods.
def getGnuMake(self) -> bool:
'''Return a boolean value describing whether the --gnu-make argument
diff --git a/pygnulib/GLImport.py b/pygnulib/GLImport.py
index 5f090d4ea1..32ab54b29a 100644
--- a/pygnulib/GLImport.py
+++ b/pygnulib/GLImport.py
@@ -175,7 +175,8 @@ def __init__(self, config: GLConfig, mode: int, m4dirs:
list[str]) -> None:
[
'gl_LOCAL_DIR', 'gl_MODULES', 'gl_AVOID', 'gl_SOURCE_BASE',
'gl_M4_BASE', 'gl_PO_BASE', 'gl_DOC_BASE', 'gl_TESTS_BASE',
- 'gl_LIB', 'gl_LGPL', 'gl_MAKEFILE_NAME',
'gl_TESTS_MAKEFILE_NAME',
+ 'gl_LIB', 'gl_LGPL', 'gl_GPL',
+ 'gl_MAKEFILE_NAME', 'gl_TESTS_MAKEFILE_NAME',
'gl_MACRO_PREFIX', 'gl_PO_DOMAIN', 'gl_WITNESS_C_MACRO',
'gl_VC_FILES',
]
@@ -191,6 +192,8 @@ def __init__(self, config: GLConfig, mode: int, m4dirs:
list[str]) -> None:
self.cache.setLGPL(None)
if tempdict['gl_LIB']:
self.cache.setLibName(cleaner(tempdict['gl_LIB']))
+ if tempdict['gl_GPL']:
+ self.cache.setLibName(cleaner(tempdict['gl_GPL']))
if tempdict['gl_LOCAL_DIR']:
self.cache.setLocalPath(cleaner(tempdict['gl_LOCAL_DIR']).split(':'))
if tempdict['gl_MODULES']:
@@ -331,6 +334,7 @@ def actioncmd(self) -> str:
conddeps = self.config.checkCondDeps()
libname = self.config.getLibName()
lgpl = self.config.getLGPL()
+ gpl = self.config.getGPL()
gnu_make = self.config.getGnuMake()
makefile_name = self.config.getMakefileName()
tests_makefile_name = self.config.getTestsMakefileName()
@@ -380,6 +384,8 @@ def actioncmd(self) -> str:
actioncmd += ' \\\n# --lgpl'
else: # if lgpl != True
actioncmd += ' \\\n# --lgpl=%s' % lgpl
+ if gpl:
+ actioncmd += ' \\\n# --gpl=%s' % gpl
if gnu_make:
actioncmd += ' \\\n# --gnu-make'
if makefile_name:
@@ -455,6 +461,7 @@ def gnulib_cache(self) -> str:
docbase = self.config['docbase']
testsbase = self.config['testsbase']
lgpl = self.config['lgpl']
+ gpl = self.config['gpl']
libname = self.config['libname']
makefile_name = self.config['makefile_name']
tests_makefile_name = self.config['tests_makefile_name']
@@ -512,6 +519,8 @@ def gnulib_cache(self) -> str:
emit += 'gl_LGPL\n'
else: # if lgpl != True
emit += 'gl_LGPL([%s])\n' % lgpl
+ if gpl != None:
+ emit += 'gl_GPL([%s])\n' % gpl
emit += 'gl_MAKEFILE_NAME([%s])\n' % makefile_name
if tests_makefile_name:
emit += 'gl_TESTS_MAKEFILE_NAME([%s])\n' % tests_makefile_name
@@ -751,6 +760,7 @@ def prepare(self) -> tuple[GLFileTable, dict[str,
tuple[re.Pattern, str] | None]
modules = list(self.config['modules'])
m4base = self.config['m4base']
lgpl = self.config['lgpl']
+ gpl = self.config['gpl']
verbose = self.config['verbosity']
base_modules = set()
for name in modules:
@@ -815,18 +825,23 @@ def prepare(self) -> tuple[GLFileTable, dict[str,
tuple[re.Pattern, str] | None]
compatibilities['all'] = ['GPLv2+ build tool', 'GPLed build tool',
'public domain', 'unlimited',
'unmodifiable license text']
- compatibilities['3'] = ['LGPLv2+', 'LGPLv3+ or GPLv2+',
'LGPLv3+', 'LGPL']
- compatibilities['3orGPLv2'] = ['LGPLv2+', 'LGPLv3+ or GPLv2+']
- compatibilities['2'] = ['LGPLv2+']
- if lgpl:
+ compatibilities['GPLv3'] = ['LGPLv2+', 'LGPLv3+ or GPLv2+',
'LGPLv3+', 'LGPL', 'GPLv2+', 'GPLv3+', 'GPL']
+ compatibilities['GPLv2'] = ['LGPLv2+', 'LGPLv3+ or GPLv2+',
'GPLv2+']
+ compatibilities['LGPLv3'] = ['LGPLv2+', 'LGPLv3+ or GPLv2+',
'LGPLv3+', 'LGPL']
+ compatibilities['LGPLv3orGPLv2'] = ['LGPLv2+', 'LGPLv3+ or GPLv2+']
+ compatibilities['LGPLv2'] = ['LGPLv2+']
+ if lgpl or gpl:
for module in main_modules:
license = module.getLicense()
if license not in compatibilities['all']:
if lgpl == True:
- if license not in compatibilities['3']:
+ if license not in compatibilities['LGPLv3']:
+ listing.append(tuple([module.name, license]))
+ elif lgpl:
+ if license not in compatibilities['LGPLv'+lgpl]:
listing.append(tuple([module.name, license]))
- else:
- if license not in compatibilities[lgpl]:
+ elif gpl:
+ if license not in compatibilities['GPLv'+gpl]:
listing.append(tuple([module.name, license]))
if listing:
raise GLError(11, listing)
diff --git a/pygnulib/GLInfo.py b/pygnulib/GLInfo.py
index a8277f2471..1ade11a3e1 100644
--- a/pygnulib/GLInfo.py
+++ b/pygnulib/GLInfo.py
@@ -276,6 +276,8 @@ def usage(self) -> str:
Abort if modules aren't available under the LGPL.
The version number of the LGPL can be specified;
the default is currently LGPLv3.
+ --gpl=[2|3] Abort if modules aren't available under the
+ specified GPL version.
--makefile-name=NAME Name of makefile in the source-base and tests-base
directories (default \"Makefile.am\", or
\"Makefile.in\" if --gnu-make).
diff --git a/pygnulib/GLModuleSystem.py b/pygnulib/GLModuleSystem.py
index e9855f775c..4185765ab2 100644
--- a/pygnulib/GLModuleSystem.py
+++ b/pygnulib/GLModuleSystem.py
@@ -1000,7 +1000,9 @@ def transitive_closure_separately(self, basemodules:
list[GLModule],
'''Determine main module list and tests-related module list separately.
The main module list is the transitive closure of the specified
modules,
ignoring tests modules. Its lib/* sources go into $sourcebase/. If
lgpl is
- specified, it will consist only of LGPLed source.
+ specified, it will consist only of LGPLed source; if gpl is specified,
it
+ will consist of only source than can be relicensed under the specified
GPL
+ version.
The tests-related module list is the transitive closure of the
specified
modules, including tests modules, minus the main module list excluding
modules of applicability 'all'. Its lib/* sources (brought in through
diff --git a/pygnulib/main.py b/pygnulib/main.py
index f39a05bfd3..f6aa9d47ea 100644
--- a/pygnulib/main.py
+++ b/pygnulib/main.py
@@ -427,6 +427,12 @@ def main(temp_directory: str) -> None:
action='append',
choices=['2', '3orGPLv2', '3'],
nargs='?')
+ # gpl
+ parser.add_argument('--gpl',
+ dest='gpl',
+ default=None,
+ choices=['2', '3'],
+ nargs=1)
# gnu-make
parser.add_argument('--gnu-make',
dest='gnu_make',
@@ -714,7 +720,7 @@ def main(temp_directory: str) -> None:
or cmdargs.excl_privileged_tests != None
or cmdargs.excl_unportable_tests != None
or cmdargs.avoids != None or cmdargs.lgpl != None
- or cmdargs.makefile_name != None
+ or cmdargs.gpl != None or cmdargs.makefile_name != None
or cmdargs.tests_makefile_name != None
or cmdargs.automake_subdir != None
or cmdargs.automake_subdir_tests != None
@@ -733,6 +739,13 @@ def main(temp_directory: str) -> None:
message += '%s: *** Stop.\n' % APP['name']
sys.stderr.write(message)
sys.exit(1)
+ if cmdargs.lgpl != None and cmdargs.gpl != None:
+ message = '%s: *** ' % APP['name']
+ message += 'invalid options: cannot specify both --lgpl and --gpl\n'
+ message += 'Try \'gnulib-tool --help\' for more information.\n'
+ message += '%s: *** Stop.\n' % APP['name']
+ sys.stderr.write(message)
+ sys.exit(1)
if cmdargs.pobase == None and cmdargs.podomain != None:
message = 'gnulib-tool: warning: --po-domain has no effect without a
--po-base option\n'
sys.stderr.write(message)
@@ -808,6 +821,9 @@ def main(temp_directory: str) -> None:
lgpl = lgpl[-1]
if lgpl == None:
lgpl = True
+ gpl = cmdargs.gpl
+ if gpl != None:
+ gpl = gpl[0]
cond_dependencies = cmdargs.cond_dependencies
libtool = cmdargs.libtool
gnu_make = cmdargs.gnu_make == True
@@ -856,6 +872,7 @@ def main(temp_directory: str) -> None:
excl_test_categories=excl_test_categories,
libname=libname,
lgpl=lgpl,
+ gpl=gpl,
gnu_make=gnu_make,
makefile_name=makefile_name,
tests_makefile_name=tests_makefile_name,
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- gnulib-tool.py: Allow verifying license compatibility with GPLv2+,
Bruno Haible <=