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/restructure checks #61

Closed
wants to merge 56 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
be4a47c
Update scancode-toolkit requirement from <=31.2.6 to <=32.0.7
dependabot[bot] Oct 2, 2023
53dc988
working from 32.0.7
ct2034 Oct 9, 2023
85df286
is_license_text check fixed with new result semantics
ct2034 Oct 9, 2023
db494df
linting fix
ct2034 Oct 9, 2023
ca0e3e1
handling of ignored files
ct2034 Oct 9, 2023
c53c22b
formatting
ct2034 Oct 9, 2023
073e6fb
this is all WIP
ct2034 Oct 9, 2023
6555c19
Merge branch 'main' into dependabot/pip/scancode-toolkit-lte-32.0.7
ct2034 Oct 30, 2023
5312b77
Unnecessary "else" after "return"
ct2034 Oct 30, 2023
d747147
still bad with 32.0.8
ct2034 Oct 30, 2023
8f18304
ignoring those rules
ct2034 Oct 30, 2023
dada029
Merge branch 'main' into dependabot/pip/scancode-toolkit-lte-32.0.7
ant-u Feb 23, 2024
f633c28
changed method get_spdx_license_name back, WIP
ant-u Feb 23, 2024
1e7a499
scancode get_copyrights function filtering word 'anything', changed t…
ant-u Feb 27, 2024
b5d3b25
linter issues
ant-u Feb 27, 2024
fd8d6de
Added spaces around comment for scancode to recognize copyright message
ant-u Feb 27, 2024
af457d3
Merge branch 'main' of https://github.com/boschresearch/ros_license_t…
ant-u Feb 27, 2024
648de94
added check for only one license text which is in spdx to start handl…
ant-u Mar 4, 2024
59be16e
When only one SPDX License File exists, make a warning to change the …
ant-u Mar 4, 2024
27393ff
Added check if multiple files exist
ant-u Mar 5, 2024
28cede1
restructured LicenseTextExistsCheck
ant-u Mar 5, 2024
eec394c
reverted restructure of check since pytest has trouble with it
ant-u Mar 5, 2024
b7859b4
moved LicenseTextExists checks to seperate function
ant-u Mar 6, 2024
9aefe6a
made own evaluation function for licenseInCodeCheck
ant-u Mar 6, 2024
945cd93
added irgnore flag for too many attributes localy to package.py since…
ant-u Mar 6, 2024
622738e
Converted check_licenses mehtod to function
ant-u Mar 6, 2024
75c2d63
restructured Check LicensesInCode and TextExists to work with attribu…
ant-u Mar 6, 2024
6c9ba21
added test for two licenses with both tags not in spdx
ant-u Mar 6, 2024
5af585d
changed license from test to MPL
ant-u Mar 6, 2024
89f05d9
Added negative test for two not in spdx tags but one for own license …
ant-u Mar 6, 2024
f51e03b
added test for ignoring contents of readme
ant-u Mar 11, 2024
4590a29
added ignore file in json format to set all files, types and folders …
ant-u Mar 11, 2024
07f813e
added test for hidden folder that should also be ignored (simulating …
ant-u Mar 11, 2024
4ee05fe
merged two lines
ant-u Mar 11, 2024
065ff46
changed check for LicenceTag to Fail if tag and file are not same lic…
ant-u Mar 12, 2024
87bd4fc
Documenting requirements for pypi update (#56)
ct2034 Mar 13, 2024
690dfc6
Merge branch 'main' into feature/handling-false-positives
ant-u Mar 18, 2024
3a99d0e
linter issues
ant-u Mar 18, 2024
2c861e9
Merge branch 'main' into feature/error_for_spdx_license_and_tag
ant-u Mar 18, 2024
cecde49
deleted unnecessary attribute from pull request
ant-u Mar 18, 2024
371d631
introduced .scanignore file, keeping local var for ignoring default f…
ant-u Mar 18, 2024
0a9573b
Added list of default ignored items to readme
ant-u Mar 18, 2024
60b824e
Merge branch 'main' into feature/handling-false-positives
ant-u Mar 18, 2024
87b062a
Merge pull request #54 from boschresearch/feature/error_for_spdx_lice…
ant-u Mar 18, 2024
0cb67b7
Merge pull request #53 from boschresearch/feature/handling-false-posi…
ant-u Mar 18, 2024
90afb3e
Feature: add check for every license has a matching tag (#55)
ant-u Mar 19, 2024
011f104
moved is_in_package to common
ant-u Mar 20, 2024
53c4fe1
created own files for checks
ant-u Mar 20, 2024
15a8812
moved check license_text_exists to own file
ant-u Mar 20, 2024
26d0b9a
moved check files to own directory
ant-u Mar 20, 2024
bce5747
moved last two checks to own file
ant-u Mar 20, 2024
5f0eddd
LicenseFileReferenced restructured
ant-u Mar 20, 2024
a4c3c82
Merge branch 'main' of https://github.com/boschresearch/ros_license_t…
ant-u Mar 28, 2024
dd7a97d
Merge branch 'main' into feature/restructure-checks
ant-u Mar 28, 2024
c01c73e
changed version dependecies (see pr #62)
ant-u Mar 28, 2024
0374e34
added check for none type
ant-u Mar 28, 2024
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
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ keywords = ["ros", "license", "toolkit", "oss", "packages"]
dependencies = [
"gitpython",
"rospkg",
"scancode-toolkit>=32.0.8",
"spdx-tools"
"scancode-toolkit<=32.0.8",
"spdx-tools<=0.7.1"
]
requires-python = ">=3.7"

Expand Down
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