Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions flit_core/flit_core/_spdx_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -649,3 +649,80 @@
'zpl-1.1': {'id': 'ZPL-1.1'},
'zpl-2.0': {'id': 'ZPL-2.0'},
'zpl-2.1': {'id': 'ZPL-2.1'}}
exceptions = \
{'389-exception': {'id': '389-exception'},
'asterisk-exception': {'id': 'Asterisk-exception'},
'asterisk-linking-protocols-exception': {'id': 'Asterisk-linking-protocols-exception'},
'autoconf-exception-2.0': {'id': 'Autoconf-exception-2.0'},
'autoconf-exception-3.0': {'id': 'Autoconf-exception-3.0'},
'autoconf-exception-generic': {'id': 'Autoconf-exception-generic'},
'autoconf-exception-generic-3.0': {'id': 'Autoconf-exception-generic-3.0'},
'autoconf-exception-macro': {'id': 'Autoconf-exception-macro'},
'bison-exception-1.24': {'id': 'Bison-exception-1.24'},
'bison-exception-2.2': {'id': 'Bison-exception-2.2'},
'bootloader-exception': {'id': 'Bootloader-exception'},
'cgal-linking-exception': {'id': 'CGAL-linking-exception'},
'classpath-exception-2.0': {'id': 'Classpath-exception-2.0'},
'clisp-exception-2.0': {'id': 'CLISP-exception-2.0'},
'cryptsetup-openssl-exception': {'id': 'cryptsetup-OpenSSL-exception'},
'digirule-foss-exception': {'id': 'DigiRule-FOSS-exception'},
'ecos-exception-2.0': {'id': 'eCos-exception-2.0'},
'erlang-otp-linking-exception': {'id': 'erlang-otp-linking-exception'},
'fawkes-runtime-exception': {'id': 'Fawkes-Runtime-exception'},
'fltk-exception': {'id': 'FLTK-exception'},
'fmt-exception': {'id': 'fmt-exception'},
'font-exception-2.0': {'id': 'Font-exception-2.0'},
'freertos-exception-2.0': {'id': 'freertos-exception-2.0'},
'gcc-exception-2.0': {'id': 'GCC-exception-2.0'},
'gcc-exception-2.0-note': {'id': 'GCC-exception-2.0-note'},
'gcc-exception-3.1': {'id': 'GCC-exception-3.1'},
'gmsh-exception': {'id': 'Gmsh-exception'},
'gnat-exception': {'id': 'GNAT-exception'},
'gnome-examples-exception': {'id': 'GNOME-examples-exception'},
'gnu-compiler-exception': {'id': 'GNU-compiler-exception'},
'gnu-javamail-exception': {'id': 'gnu-javamail-exception'},
'gpl-3.0-389-ds-base-exception': {'id': 'GPL-3.0-389-ds-base-exception'},
'gpl-3.0-interface-exception': {'id': 'GPL-3.0-interface-exception'},
'gpl-3.0-linking-exception': {'id': 'GPL-3.0-linking-exception'},
'gpl-3.0-linking-source-exception': {'id': 'GPL-3.0-linking-source-exception'},
'gpl-cc-1.0': {'id': 'GPL-CC-1.0'},
'gstreamer-exception-2005': {'id': 'GStreamer-exception-2005'},
'gstreamer-exception-2008': {'id': 'GStreamer-exception-2008'},
'harbour-exception': {'id': 'harbour-exception'},
'i2p-gpl-java-exception': {'id': 'i2p-gpl-java-exception'},
'independent-modules-exception': {'id': 'Independent-modules-exception'},
'kicad-libraries-exception': {'id': 'KiCad-libraries-exception'},
'lgpl-3.0-linking-exception': {'id': 'LGPL-3.0-linking-exception'},
'libpri-openh323-exception': {'id': 'libpri-OpenH323-exception'},
'libtool-exception': {'id': 'Libtool-exception'},
'linux-syscall-note': {'id': 'Linux-syscall-note'},
'llgpl': {'id': 'LLGPL'},
'llvm-exception': {'id': 'LLVM-exception'},
'lzma-exception': {'id': 'LZMA-exception'},
'mif-exception': {'id': 'mif-exception'},
'mxml-exception': {'id': 'mxml-exception'},
'ocaml-lgpl-linking-exception': {'id': 'OCaml-LGPL-linking-exception'},
'occt-exception-1.0': {'id': 'OCCT-exception-1.0'},
'openjdk-assembly-exception-1.0': {'id': 'OpenJDK-assembly-exception-1.0'},
'openvpn-openssl-exception': {'id': 'openvpn-openssl-exception'},
'pcre2-exception': {'id': 'PCRE2-exception'},
'ps-or-pdf-font-exception-20170817': {'id': 'PS-or-PDF-font-exception-20170817'},
'qpl-1.0-inria-2004-exception': {'id': 'QPL-1.0-INRIA-2004-exception'},
'qt-gpl-exception-1.0': {'id': 'Qt-GPL-exception-1.0'},
'qt-lgpl-exception-1.1': {'id': 'Qt-LGPL-exception-1.1'},
'qwt-exception-1.0': {'id': 'Qwt-exception-1.0'},
'romic-exception': {'id': 'romic-exception'},
'rrdtool-floss-exception-2.0': {'id': 'RRDtool-FLOSS-exception-2.0'},
'sane-exception': {'id': 'SANE-exception'},
'shl-2.0': {'id': 'SHL-2.0'},
'shl-2.1': {'id': 'SHL-2.1'},
'stunnel-exception': {'id': 'stunnel-exception'},
'swi-exception': {'id': 'SWI-exception'},
'swift-exception': {'id': 'Swift-exception'},
'texinfo-exception': {'id': 'Texinfo-exception'},
'u-boot-exception-2.0': {'id': 'u-boot-exception-2.0'},
'ubdl-exception': {'id': 'UBDL-exception'},
'universal-foss-exception-1.0': {'id': 'Universal-FOSS-exception-1.0'},
'vsftpd-openssl-exception': {'id': 'vsftpd-openssl-exception'},
'wxwindows-exception-3.1': {'id': 'WxWindows-exception-3.1'},
'x11vnc-openssl-exception': {'id': 'x11vnc-openssl-exception'}}
31 changes: 24 additions & 7 deletions flit_core/flit_core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
except ImportError:
import tomli as tomllib

from ._spdx_data import licenses
from ._spdx_data import exceptions, licenses
from .common import normalise_core_metadata_name
from .versionno import normalise_version

Expand Down Expand Up @@ -700,7 +700,7 @@ def normalise_compound_license_expr(s: str) -> str:
"""Validate and normalise a compound SPDX license expression.

Per the specification, licence expression operators (AND, OR and WITH)
are matched case-sensitively. The WITH operator is not currently supported.
are matched case-sensitively.

Spec: https://spdx.github.io/spdx-spec/v2.2.2/SPDX-license-expressions/
"""
Expand All @@ -712,9 +712,15 @@ def normalise_compound_license_expr(s: str) -> str:
parts = []
try:
for part in filter(None, re.split(r' +|([()])', s)):
if part.upper() == 'WITH':
# provide a sensible error message for the WITH operator
raise ConfigError("The SPDX 'WITH' operator is not yet supported!")
if part == 'WITH':
if (
not parts
or parts[-1] in {' AND ', ' OR ', ' WITH ', '('}
or parts[-1].lower() in exceptions
):
reason = "a license ID is missing before 'WITH'"
raise ConfigError(invalid_msg.format(s=s, reason=reason))
parts.append(' WITH ')
elif part in {'AND', 'OR'}:
if not parts or parts[-1] in {' AND ', ' OR ', ' WITH ', '('}:
reason = f"a license ID is missing before '{part}'"
Expand All @@ -740,10 +746,13 @@ def normalise_compound_license_expr(s: str) -> str:
raise ConfigError(invalid_msg.format(s=s, reason=reason))
parts.append(part)
else:
if parts and parts[-1] not in {' AND ', ' OR ', '('}:
if parts and parts[-1] not in {' AND ', ' OR ', ' WITH ', '('}:
reason = "a license ID must follow either AND, OR, or '('"
raise ConfigError(invalid_msg.format(s=s, reason=reason))
simple_expr = normalise_simple_license_expr(part)
if parts and parts[-1] == ' WITH ':
simple_expr = normalise_license_exception(part)
else:
simple_expr = normalise_simple_license_expr(part)
parts.append(simple_expr)

if stack != 0:
Expand Down Expand Up @@ -790,3 +799,11 @@ def normalise_simple_license_expr(s: str) -> str:
if or_later:
return f'{normalised_id}+'
return normalised_id


def normalise_license_exception(s: str) -> str:
"""Normalise an SPDX license exception ID."""
try:
return exceptions[s.lower()]['id']
except KeyError:
raise ConfigError(f"{s!r} is not a recognised SPDX license exception ID")
11 changes: 9 additions & 2 deletions flit_core/tests_core/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,8 @@ def test_bad_pep621_readme(readme, err_match):
("MIT AND Apache-2.0", "MIT AND Apache-2.0"),
("MIT AND Apache-2.0+ OR 0BSD", "MIT AND Apache-2.0+ OR 0BSD"),
("MIT AND (Apache-2.0+ OR (0BSD))", "MIT AND (Apache-2.0+ OR (0BSD))"),
("mit WITH classpath-exception-2.0", "MIT WITH Classpath-exception-2.0"),
("MIT OR MIT WITH Classpath-exception-2.0", "MIT OR MIT WITH Classpath-exception-2.0"),
("MIT OR(mit)", "MIT OR (MIT)"),
("(mit)AND mit", "(MIT) AND MIT"),
("MIT OR (MIT OR ( MIT )) AND ((MIT) AND MIT) OR MIT", "MIT OR (MIT OR (MIT)) AND ((MIT) AND MIT) OR MIT"),
Expand Down Expand Up @@ -342,6 +344,7 @@ def test_license_expr_error_empty(invalid_expr: str):
"MIT and MIT",
"MIT AND MIT or MIT",
"MIT AND (MIT or MIT)",
"MIT with Classpath-exception-2.0",
])
def test_license_expr_error_lowercase(invalid_expr: str):
proj = {
Expand All @@ -360,14 +363,18 @@ def test_license_expr_error_lowercase(invalid_expr: str):
"MIT WITH MIT-Exception",
"(MIT WITH MIT-Exception)",
"MIT OR MIT WITH MIT-Exception",
"MIT WITH Classpath-exception-2.0 WITH Autoconf-exception-2.0",
"MIT WITH MIT-Exception OR (MIT AND MIT)",
])
def test_license_expr_error_unsupported_with(invalid_expr: str):
def test_license_expr_error_with(invalid_expr: str):
proj = {
'name': 'module1', 'version': '1.0', 'description': 'x',
'license': invalid_expr,
}
with pytest.raises(config.ConfigError, match="not yet supported"):
with pytest.raises(
config.ConfigError,
match="(license ID is missing before|must be uppercase|not a recognised SPDX license exception ID)",
):
config.read_pep621_metadata(proj, samples_dir / 'pep621' / 'pyproject.toml')


Expand Down
14 changes: 11 additions & 3 deletions prepare_license_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,23 @@
list_data_repo = Path(sys.argv[1])
with (list_data_repo / 'json' / 'licenses.json').open('rb') as f:
licenses_json = json.load(f)
with (list_data_repo / 'json' / 'exceptions.json').open('rb') as f:
exceptions_json = json.load(f)

condensed = {
licenses = {
l['licenseId'].lower(): {'id': l['licenseId']}
for l in licenses_json['licenses']
if not l['isDeprecatedLicenseId']
}
exceptions = {
e['licenseExceptionId'].lower(): {'id': e['licenseExceptionId']}
for e in exceptions_json['exceptions']
if not e['isDeprecatedLicenseId']
}

with Path('flit_core', 'flit_core', '_spdx_data.py').open('w') as f:
f.write("# This file is generated from SPDX license data; don't edit it manually.\n\n")

f.write("licenses = \\\n")
pprint.pprint(condensed, f)
for name, data in (("licenses", licenses), ("exceptions", exceptions)):
f.write(f"{name} = \\\n")
pprint.pprint(data, f)