Skip to content

Commit

Permalink
Merge branch 'main' into bugfix/message_for_license_file_creation
Browse files Browse the repository at this point in the history
  • Loading branch information
ct2034 committed Apr 8, 2024
2 parents 3f5a18b + 6c96077 commit fadfb60
Show file tree
Hide file tree
Showing 8 changed files with 563 additions and 361 deletions.
301 changes: 0 additions & 301 deletions src/ros_license_toolkit/checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,8 @@

"""This module contains the checks for the linter."""

import os
from enum import IntEnum
from pprint import pformat
from typing import Any, Dict, List, Optional

from ros_license_toolkit.common import get_spdx_license_name
from ros_license_toolkit.license_tag import (LicenseTag,
is_license_name_in_spdx_list)
from ros_license_toolkit.package import Package, PackageException
from ros_license_toolkit.ui_elements import NO_REASON_STR, green, red, yellow

Expand Down Expand Up @@ -110,298 +104,3 @@ def check(self, package: Package):
def _check(self, package: Package):
"""Check `package`. To be overwritten by subclasses."""
raise NotImplementedError("Overwrite this")


class LicenseTagExistsCheck(Check):
"""This ensures that a tag defining the license exists."""

def _check(self, package: Package):
if len(package.license_tags) == 0:
self._failed("No license tag defined.")
self.verbose_output = red(str(package.package_xml))
else:
self._success(
f"Found licenses {list(map(str, package.license_tags))}")


class LicenseTagIsInSpdxListCheck(Check):
"""This ensures that the license tag is in the SPDX list of licenses."""

def _check(self, package: Package):
licenses_not_in_spdx_list = []
for license_tag in package.license_tags.keys():
if not is_license_name_in_spdx_list(
license_tag):
licenses_not_in_spdx_list.append(license_tag)
if len(licenses_not_in_spdx_list) > 0:
self._warning(
f"Licenses {licenses_not_in_spdx_list} are "
"not in SPDX list of licenses. "
"Make sure to exactly match one of https://spdx.org/licenses/."
)
else:
self._success("All license tags are in SPDX list of licenses.")


class LicenseTextExistsCheck(Check):
"""This ensures that the license text file referenced by the tag exists."""

def __init__(self: 'LicenseTextExistsCheck'):
Check.__init__(self)
self.license_tags_without_license_text: Dict[LicenseTag, str] = {}
self.missing_license_texts_status: Dict[LicenseTag, Status] = {}
self.files_with_wrong_tags: Dict[LicenseTag, Dict[str, str]] = {}
self.found_license_texts: Dict[str, Any] = {}

def _check(self, package: Package):
if len(package.license_tags) == 0:
self._failed("No license tag defined.")
return

self._check_licenses(package)
self._evaluate_results()

def _check_licenses(self, package: Package) -> None:
'''checks each license tag for the corresponding license text. Also
detects inofficial licenses when tag is not in the SPDX license list'''
self.found_license_texts = package.found_license_texts
for license_tag in package.license_tags.values():
if not license_tag.has_license_text_file():
self.license_tags_without_license_text[
license_tag] = "No license text file defined."
self.missing_license_texts_status[license_tag] = Status.FAILURE
continue
license_text_file = license_tag.get_license_text_file()
if not os.path.exists(
os.path.join(package.abspath, license_text_file)):
self.license_tags_without_license_text[license_tag] =\
f"License text file '{license_text_file}' does not exist."
self.missing_license_texts_status[license_tag] = Status.FAILURE
continue
if license_text_file not in self.found_license_texts:
self.license_tags_without_license_text[license_tag] =\
f"License text file '{license_text_file}' not included" +\
" in scan results."
self.missing_license_texts_status[license_tag] = Status.FAILURE
continue
if not get_spdx_license_name(
self.found_license_texts[license_text_file]):
self.license_tags_without_license_text[license_tag] =\
f"License text file '{license_text_file}' is not " +\
"recognized as license text."
self.missing_license_texts_status[license_tag] = Status.FAILURE
continue
actual_license: Optional[str] = get_spdx_license_name(
self.found_license_texts[license_text_file])
if actual_license is None:
self.license_tags_without_license_text[
license_tag
] = f"License text file '{license_text_file}'" +\
" is not recognized as license text."
self.missing_license_texts_status[license_tag] = Status.FAILURE
continue
if actual_license != license_tag.get_license_id():
self.license_tags_without_license_text[license_tag] =\
f"License text file '{license_text_file}' is " +\
f"of license {actual_license} but tag is " +\
f"{license_tag.get_license_id()}."
# If Tag and File both are in SPDX but don't match -> Error
if is_license_name_in_spdx_list(license_tag.get_license_id()):
self.missing_license_texts_status[license_tag] =\
Status.FAILURE
else:
self.missing_license_texts_status[license_tag] =\
Status.WARNING
self.files_with_wrong_tags[license_tag] = \
{'actual_license': actual_license,
'license_tag': license_tag.get_license_id()}
continue

def _evaluate_results(self):
if len(self.license_tags_without_license_text) > 0:
if max(self.missing_license_texts_status.values()) \
== Status.WARNING:
self._warning(
"Since they are not in the SPDX list, "
"we can not check if these tags have the correct "
"license text:\n" + "\n".join(
[f" '{x[0]}': {x[1]}" for x in
self.license_tags_without_license_text.items()]))
else:
self._failed(
"The following license tags do not "
"have a valid license text "
"file:\n" + "\n".join(
[f" '{x[0]}': {x[1]}" for x in
self.license_tags_without_license_text.items()]))
self.verbose_output = red(
"\n".join([f" '{x[0]}': {x[1]}" for x in
self.found_license_texts.items()]))
else:
self._success("All license tags have a valid license text file.")


class LicensesInCodeCheck(Check):
"""Check if all found licenses have a declaration in the package.xml."""

def __init__(self: 'LicensesInCodeCheck'):
Check.__init__(self)
self.declared_licenses: Dict[str, LicenseTag] = {}
self.files_with_uncovered_licenses: Dict[str, List[str]] = {}
self.files_not_matched_by_any_license_tag: Dict[str, List[str]] = {}
self.files_with_inofficial_tag: Dict[str, List[str]] = {}

def _check(self, package: Package):
if len(package.license_tags) == 0:
self._failed('No license tag defined.')
return
self.declared_licenses = package.license_tags
self._check_license_files(package)
self._evaluate_result(package)

def _check_license_files(self, package: Package) -> None:
for fname, found_licenses in package.found_files_w_licenses.items():
if fname in package.get_license_files():
# the actual license text files are not relevant for this
continue
found_licenses_str = found_licenses[
'detected_license_expression_spdx']
if not found_licenses_str:
continue
licenses = found_licenses_str.split(' AND ')
for license_str in licenses:
if license_str not in self.declared_licenses:
# this license has an inofficial tag
inofficial_licenses = {
lic_tag.id_from_license_text: key
for key, lic_tag in package.license_tags.items()
if lic_tag.id_from_license_text != ''}
if license_str in inofficial_licenses.keys():
if fname not in self.files_with_inofficial_tag:
self.files_with_inofficial_tag[fname] = []
self.files_with_inofficial_tag[fname].append(
license_str)
self.files_with_inofficial_tag[fname].append(
inofficial_licenses[license_str])
continue
# this license is not declared by any license tag
if fname not in self.files_with_uncovered_licenses:
self.files_with_uncovered_licenses[fname] = []
self.files_with_uncovered_licenses[fname].append(
license_str)
continue
if fname not in self.declared_licenses[
license_str].source_files:
# this license is declared by a license tag but the file
# is not listed in the source files of the license tag
if fname not in self.files_not_matched_by_any_license_tag:
self.files_not_matched_by_any_license_tag[fname] = []
self.files_not_matched_by_any_license_tag[fname].append(
license_str)
continue

def _evaluate_result(self, package: Package) -> None:
if self.files_with_uncovered_licenses:
info_str = ''
info_str += '\nThe following files contain licenses that ' +\
'are not covered by any license tag:\n' + '\n'.join(
[f" '{x[0]}': {x[1]}" for x in
self.files_with_uncovered_licenses.items()])
self._print_info(
info_str,
self.files_with_uncovered_licenses,
self.files_not_matched_by_any_license_tag,
package,
)
elif len(self.files_not_matched_by_any_license_tag) > 0:
info_str = ''
info_str += '\nThe following files contain licenses that ' +\
'are covered by a license tag but are not listed in ' +\
'the source files of the license tag:\n' + '\n'.join(
[f" '{x[0]}': {x[1]}" for x in
self.files_not_matched_by_any_license_tag.items()])
self._print_info(
info_str,
self.files_with_uncovered_licenses,
self.files_not_matched_by_any_license_tag,
package,
)
elif self.files_with_inofficial_tag:
info_str = ''
info_str += 'For the following files, please change the ' +\
'License Tag in the package file to SPDX format:\n' +\
'\n'.join(
[f" '{x[0]}' is of {x[1][0]} but its Tag is {x[1][1]}."
for x in self.files_with_inofficial_tag.items()])
self._warning(info_str)
else:
self._success('All licenses found in the code are covered by a '
'license declaration.')

def _print_info(self, info_str, files_with_uncovered_licenses,
files_not_matched_by_any_license_tag, package):
assert info_str != ''
self._failed(info_str)
self.verbose_output = red(
'\n Relevant scan results:\n' + pformat(
list(filter(
lambda x: x[0] in files_with_uncovered_licenses or (
x[0] in files_not_matched_by_any_license_tag),
package.found_files_w_licenses.items()))))


class LicenseFilesReferencedCheck(Check):
"""Check if all found License file have a reference in package.xml."""

def _check(self, package: Package):
not_covered_texts: Dict[str, str] = {}
inofficial_covered_texts: Dict[str, List[str]] = {}
for filename, license_text in package.found_license_texts.items():
# skipping all declarations above the package
if not is_in_package(package, filename):
continue
if 'detected_license_expression_spdx' in license_text and \
license_text['detected_license_expression_spdx'] not in \
package.license_tags:
spdx_expression = license_text[
'detected_license_expression_spdx']
inofficial_licenses = {
lic_tag.id_from_license_text: key
for key, lic_tag in package.license_tags.items()
if lic_tag.id_from_license_text != ''}
if spdx_expression in inofficial_licenses:
inofficial_covered_texts[filename] = \
[spdx_expression,
inofficial_licenses[spdx_expression]]
else:
not_covered_texts[filename] = \
spdx_expression
if not_covered_texts:
info_str = ''
info_str += 'The following license files are not' +\
' mentioned by any tag:\n' +\
'\n'.join(
[f" '{x[0]}' is of {x[1]}."
for x in not_covered_texts.items()])
self._failed(info_str)
elif inofficial_covered_texts:
info_str = ''
info_str += 'The following license files are not' +\
' mentioned by any tag:\n' +\
'\n'.join(
[f" '{x[0]}' is of {x[1][0]} but its tag is {x[1][1]}."
for x in inofficial_covered_texts.items()])
self._warning(info_str)
else:
self._success("All license declaration are referenced by a tag.")


def is_in_package(package: Package, file: str) -> bool:
"""Return TRUE if the file is underneath the absolute package path.
Return FALSE if file is located above package."""
parent = os.path.abspath(package.abspath)
child = os.path.abspath(package.abspath + '/' + file)

comm_parent = os.path.commonpath([parent])
comm_child_parent = os.path.commonpath([parent, child])
return comm_parent == comm_child_parent
Loading

0 comments on commit fadfb60

Please sign in to comment.