ci/package_checks: fix SPDX license check for WITH/AND/OR

SPDX license identifiers may be a 'simple' or 'compound' expression,
meaning that the following formats are also allowed:

- A document reference instead of license identifier.
- `+` after the license identifier.
- License identifier followed `WITH` and an exception identifier.
- The above split by `AND` or `OR`.
- The above contained between `()`.

All but the first case should now be valid according to the package checks.
The first case is still invalid, as we want license identifiers and not document references in our packages.

See: https://spdx.github.io/spdx-spec/v2.3/SPDX-license-expressions/

Resolves #956
This commit is contained in:
Silke Hofstra 2023-12-03 12:56:40 +01:00
parent e0d651f994
commit aa3b66e09c

View file

@ -317,7 +317,9 @@ class Patch(PullRequestCheck):
class SPDXLicense(PullRequestCheck):
_licenses_url = 'https://raw.githubusercontent.com/spdx/license-list-data/main/json/licenses.json'
_exceptions_url = 'https://raw.githubusercontent.com/spdx/license-list-data/main/json/exceptions.json'
_licenses: Optional[List[str]] = None
_exceptions: Optional[List[str]] = None
_extra_licenses = ['Distributable', 'Public-Domain']
_error = 'Invalid license identifier: '
_level = Level.WARNING
@ -334,13 +336,30 @@ class SPDXLicense(PullRequestCheck):
return [self._validate_license(file, license)]
def _validate_license(self, file: str, identifier: str) -> Optional[Result]:
if identifier in self._license_ids():
if self._valid_license(identifier):
return None
return Result(file=file, level=self._level,
message=f'invalid license identifier: {repr(identifier)}',
line=self.file_line(file, r'^license\s*:'))
def _valid_license(self, identifier: str) -> bool:
identifier = identifier.strip(" ()+")
identifiers = [id_o
for id_a in identifier.split(' AND ')
for id_o in id_a.split(' OR ')]
if len(identifiers) > 1:
return all([self._valid_license(id) for id in identifiers])
if ' WITH ' in identifier:
identifier, exception = identifier.split(' WITH ', 1)
if exception not in self._exception_ids():
return False
return identifier in self._license_ids()
def _license_ids(self) -> List[str]:
if self._licenses is None:
with request.urlopen(self._licenses_url) as f:
@ -348,6 +367,13 @@ class SPDXLicense(PullRequestCheck):
return self._licenses + self._extra_licenses
def _exception_ids(self) -> List[str]:
if self._exceptions is None:
with request.urlopen(self._exceptions_url) as f:
self._exceptions = [exception['licenseExceptionId'] for exception in json.load(f)['exceptions']]
return self._exceptions
class UnwantedFiles(PullRequestCheck):
_patterns = ['Makefile^', r'.*\.eopkg^', r'.*\.tar\..*', r'^packages/[^/]+(/[^/]+)?$']