Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/ relocate checks #67

Merged
merged 4 commits into from
Apr 8, 2024
Merged
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
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
Loading