|
16 | 16 |
|
17 | 17 | """This module contains the checks for the linter.""" |
18 | 18 |
|
19 | | -import os |
20 | 19 | from enum import IntEnum |
21 | | -from pprint import pformat |
22 | | -from typing import Any, Dict, List, Optional |
23 | 20 |
|
24 | | -from ros_license_toolkit.common import get_spdx_license_name |
25 | | -from ros_license_toolkit.license_tag import (LicenseTag, |
26 | | - is_license_name_in_spdx_list) |
27 | 21 | from ros_license_toolkit.package import Package, PackageException |
28 | 22 | from ros_license_toolkit.ui_elements import NO_REASON_STR, green, red, yellow |
29 | 23 |
|
@@ -110,298 +104,3 @@ def check(self, package: Package): |
110 | 104 | def _check(self, package: Package): |
111 | 105 | """Check `package`. To be overwritten by subclasses.""" |
112 | 106 | raise NotImplementedError("Overwrite this") |
113 | | - |
114 | | - |
115 | | -class LicenseTagExistsCheck(Check): |
116 | | - """This ensures that a tag defining the license exists.""" |
117 | | - |
118 | | - def _check(self, package: Package): |
119 | | - if len(package.license_tags) == 0: |
120 | | - self._failed("No license tag defined.") |
121 | | - self.verbose_output = red(str(package.package_xml)) |
122 | | - else: |
123 | | - self._success( |
124 | | - f"Found licenses {list(map(str, package.license_tags))}") |
125 | | - |
126 | | - |
127 | | -class LicenseTagIsInSpdxListCheck(Check): |
128 | | - """This ensures that the license tag is in the SPDX list of licenses.""" |
129 | | - |
130 | | - def _check(self, package: Package): |
131 | | - licenses_not_in_spdx_list = [] |
132 | | - for license_tag in package.license_tags.keys(): |
133 | | - if not is_license_name_in_spdx_list( |
134 | | - license_tag): |
135 | | - licenses_not_in_spdx_list.append(license_tag) |
136 | | - if len(licenses_not_in_spdx_list) > 0: |
137 | | - self._warning( |
138 | | - f"Licenses {licenses_not_in_spdx_list} are " |
139 | | - "not in SPDX list of licenses. " |
140 | | - "Make sure to exactly match one of https://spdx.org/licenses/." |
141 | | - ) |
142 | | - else: |
143 | | - self._success("All license tags are in SPDX list of licenses.") |
144 | | - |
145 | | - |
146 | | -class LicenseTextExistsCheck(Check): |
147 | | - """This ensures that the license text file referenced by the tag exists.""" |
148 | | - |
149 | | - def __init__(self: 'LicenseTextExistsCheck'): |
150 | | - Check.__init__(self) |
151 | | - self.license_tags_without_license_text: Dict[LicenseTag, str] = {} |
152 | | - self.missing_license_texts_status: Dict[LicenseTag, Status] = {} |
153 | | - self.files_with_wrong_tags: Dict[LicenseTag, Dict[str, str]] = {} |
154 | | - self.found_license_texts: Dict[str, Any] = {} |
155 | | - |
156 | | - def _check(self, package: Package): |
157 | | - if len(package.license_tags) == 0: |
158 | | - self._failed("No license tag defined.") |
159 | | - return |
160 | | - |
161 | | - self._check_licenses(package) |
162 | | - self._evaluate_results() |
163 | | - |
164 | | - def _check_licenses(self, package: Package) -> None: |
165 | | - '''checks each license tag for the corresponding license text. Also |
166 | | - detects inofficial licenses when tag is not in the SPDX license list''' |
167 | | - self.found_license_texts = package.found_license_texts |
168 | | - for license_tag in package.license_tags.values(): |
169 | | - if not license_tag.has_license_text_file(): |
170 | | - self.license_tags_without_license_text[ |
171 | | - license_tag] = "No license text file defined." |
172 | | - self.missing_license_texts_status[license_tag] = Status.FAILURE |
173 | | - continue |
174 | | - license_text_file = license_tag.get_license_text_file() |
175 | | - if not os.path.exists( |
176 | | - os.path.join(package.abspath, license_text_file)): |
177 | | - self.license_tags_without_license_text[license_tag] =\ |
178 | | - f"License text file '{license_text_file}' does not exist." |
179 | | - self.missing_license_texts_status[license_tag] = Status.FAILURE |
180 | | - continue |
181 | | - if license_text_file not in self.found_license_texts: |
182 | | - self.license_tags_without_license_text[license_tag] =\ |
183 | | - f"License text file '{license_text_file}' not included" +\ |
184 | | - " in scan results." |
185 | | - self.missing_license_texts_status[license_tag] = Status.FAILURE |
186 | | - continue |
187 | | - if not get_spdx_license_name( |
188 | | - self.found_license_texts[license_text_file]): |
189 | | - self.license_tags_without_license_text[license_tag] =\ |
190 | | - f"License text file '{license_text_file}' is not " +\ |
191 | | - "recognized as license text." |
192 | | - self.missing_license_texts_status[license_tag] = Status.FAILURE |
193 | | - continue |
194 | | - actual_license: Optional[str] = get_spdx_license_name( |
195 | | - self.found_license_texts[license_text_file]) |
196 | | - if actual_license is None: |
197 | | - self.license_tags_without_license_text[ |
198 | | - license_tag |
199 | | - ] = f"License text file '{license_text_file}'" +\ |
200 | | - " is not recognized as license text." |
201 | | - self.missing_license_texts_status[license_tag] = Status.FAILURE |
202 | | - continue |
203 | | - if actual_license != license_tag.get_license_id(): |
204 | | - self.license_tags_without_license_text[license_tag] =\ |
205 | | - f"License text file '{license_text_file}' is " +\ |
206 | | - f"of license {actual_license} but tag is " +\ |
207 | | - f"{license_tag.get_license_id()}." |
208 | | - # If Tag and File both are in SPDX but don't match -> Error |
209 | | - if is_license_name_in_spdx_list(license_tag.get_license_id()): |
210 | | - self.missing_license_texts_status[license_tag] =\ |
211 | | - Status.FAILURE |
212 | | - else: |
213 | | - self.missing_license_texts_status[license_tag] =\ |
214 | | - Status.WARNING |
215 | | - self.files_with_wrong_tags[license_tag] = \ |
216 | | - {'actual_license': actual_license, |
217 | | - 'license_tag': license_tag.get_license_id()} |
218 | | - continue |
219 | | - |
220 | | - def _evaluate_results(self): |
221 | | - if len(self.license_tags_without_license_text) > 0: |
222 | | - if max(self.missing_license_texts_status.values()) \ |
223 | | - == Status.WARNING: |
224 | | - self._warning( |
225 | | - "Since they are not in the SPDX list, " |
226 | | - "we can not check if these tags have the correct " |
227 | | - "license text:\n" + "\n".join( |
228 | | - [f" '{x[0]}': {x[1]}" for x in |
229 | | - self.license_tags_without_license_text.items()])) |
230 | | - else: |
231 | | - self._failed( |
232 | | - "The following license tags do not " |
233 | | - "have a valid license text " |
234 | | - "file:\n" + "\n".join( |
235 | | - [f" '{x[0]}': {x[1]}" for x in |
236 | | - self.license_tags_without_license_text.items()])) |
237 | | - self.verbose_output = red( |
238 | | - "\n".join([f" '{x[0]}': {x[1]}" for x in |
239 | | - self.found_license_texts.items()])) |
240 | | - else: |
241 | | - self._success("All license tags have a valid license text file.") |
242 | | - |
243 | | - |
244 | | -class LicensesInCodeCheck(Check): |
245 | | - """Check if all found licenses have a declaration in the package.xml.""" |
246 | | - |
247 | | - def __init__(self: 'LicensesInCodeCheck'): |
248 | | - Check.__init__(self) |
249 | | - self.declared_licenses: Dict[str, LicenseTag] = {} |
250 | | - self.files_with_uncovered_licenses: Dict[str, List[str]] = {} |
251 | | - self.files_not_matched_by_any_license_tag: Dict[str, List[str]] = {} |
252 | | - self.files_with_inofficial_tag: Dict[str, List[str]] = {} |
253 | | - |
254 | | - def _check(self, package: Package): |
255 | | - if len(package.license_tags) == 0: |
256 | | - self._failed('No license tag defined.') |
257 | | - return |
258 | | - self.declared_licenses = package.license_tags |
259 | | - self._check_license_files(package) |
260 | | - self._evaluate_result(package) |
261 | | - |
262 | | - def _check_license_files(self, package: Package) -> None: |
263 | | - for fname, found_licenses in package.found_files_w_licenses.items(): |
264 | | - if fname in package.get_license_files(): |
265 | | - # the actual license text files are not relevant for this |
266 | | - continue |
267 | | - found_licenses_str = found_licenses[ |
268 | | - 'detected_license_expression_spdx'] |
269 | | - if not found_licenses_str: |
270 | | - continue |
271 | | - licenses = found_licenses_str.split(' AND ') |
272 | | - for license_str in licenses: |
273 | | - if license_str not in self.declared_licenses: |
274 | | - # this license has an inofficial tag |
275 | | - inofficial_licenses = { |
276 | | - lic_tag.id_from_license_text: key |
277 | | - for key, lic_tag in package.license_tags.items() |
278 | | - if lic_tag.id_from_license_text != ''} |
279 | | - if license_str in inofficial_licenses.keys(): |
280 | | - if fname not in self.files_with_inofficial_tag: |
281 | | - self.files_with_inofficial_tag[fname] = [] |
282 | | - self.files_with_inofficial_tag[fname].append( |
283 | | - license_str) |
284 | | - self.files_with_inofficial_tag[fname].append( |
285 | | - inofficial_licenses[license_str]) |
286 | | - continue |
287 | | - # this license is not declared by any license tag |
288 | | - if fname not in self.files_with_uncovered_licenses: |
289 | | - self.files_with_uncovered_licenses[fname] = [] |
290 | | - self.files_with_uncovered_licenses[fname].append( |
291 | | - license_str) |
292 | | - continue |
293 | | - if fname not in self.declared_licenses[ |
294 | | - license_str].source_files: |
295 | | - # this license is declared by a license tag but the file |
296 | | - # is not listed in the source files of the license tag |
297 | | - if fname not in self.files_not_matched_by_any_license_tag: |
298 | | - self.files_not_matched_by_any_license_tag[fname] = [] |
299 | | - self.files_not_matched_by_any_license_tag[fname].append( |
300 | | - license_str) |
301 | | - continue |
302 | | - |
303 | | - def _evaluate_result(self, package: Package) -> None: |
304 | | - if self.files_with_uncovered_licenses: |
305 | | - info_str = '' |
306 | | - info_str += '\nThe following files contain licenses that ' +\ |
307 | | - 'are not covered by any license tag:\n' + '\n'.join( |
308 | | - [f" '{x[0]}': {x[1]}" for x in |
309 | | - self.files_with_uncovered_licenses.items()]) |
310 | | - self._print_info( |
311 | | - info_str, |
312 | | - self.files_with_uncovered_licenses, |
313 | | - self.files_not_matched_by_any_license_tag, |
314 | | - package, |
315 | | - ) |
316 | | - elif len(self.files_not_matched_by_any_license_tag) > 0: |
317 | | - info_str = '' |
318 | | - info_str += '\nThe following files contain licenses that ' +\ |
319 | | - 'are covered by a license tag but are not listed in ' +\ |
320 | | - 'the source files of the license tag:\n' + '\n'.join( |
321 | | - [f" '{x[0]}': {x[1]}" for x in |
322 | | - self.files_not_matched_by_any_license_tag.items()]) |
323 | | - self._print_info( |
324 | | - info_str, |
325 | | - self.files_with_uncovered_licenses, |
326 | | - self.files_not_matched_by_any_license_tag, |
327 | | - package, |
328 | | - ) |
329 | | - elif self.files_with_inofficial_tag: |
330 | | - info_str = '' |
331 | | - info_str += 'For the following files, please change the ' +\ |
332 | | - 'License Tag in the package file to SPDX format:\n' +\ |
333 | | - '\n'.join( |
334 | | - [f" '{x[0]}' is of {x[1][0]} but its Tag is {x[1][1]}." |
335 | | - for x in self.files_with_inofficial_tag.items()]) |
336 | | - self._warning(info_str) |
337 | | - else: |
338 | | - self._success('All licenses found in the code are covered by a ' |
339 | | - 'license declaration.') |
340 | | - |
341 | | - def _print_info(self, info_str, files_with_uncovered_licenses, |
342 | | - files_not_matched_by_any_license_tag, package): |
343 | | - assert info_str != '' |
344 | | - self._failed(info_str) |
345 | | - self.verbose_output = red( |
346 | | - '\n Relevant scan results:\n' + pformat( |
347 | | - list(filter( |
348 | | - lambda x: x[0] in files_with_uncovered_licenses or ( |
349 | | - x[0] in files_not_matched_by_any_license_tag), |
350 | | - package.found_files_w_licenses.items())))) |
351 | | - |
352 | | - |
353 | | -class LicenseFilesReferencedCheck(Check): |
354 | | - """Check if all found License file have a reference in package.xml.""" |
355 | | - |
356 | | - def _check(self, package: Package): |
357 | | - not_covered_texts: Dict[str, str] = {} |
358 | | - inofficial_covered_texts: Dict[str, List[str]] = {} |
359 | | - for filename, license_text in package.found_license_texts.items(): |
360 | | - # skipping all declarations above the package |
361 | | - if not is_in_package(package, filename): |
362 | | - continue |
363 | | - if 'detected_license_expression_spdx' in license_text and \ |
364 | | - license_text['detected_license_expression_spdx'] not in \ |
365 | | - package.license_tags: |
366 | | - spdx_expression = license_text[ |
367 | | - 'detected_license_expression_spdx'] |
368 | | - inofficial_licenses = { |
369 | | - lic_tag.id_from_license_text: key |
370 | | - for key, lic_tag in package.license_tags.items() |
371 | | - if lic_tag.id_from_license_text != ''} |
372 | | - if spdx_expression in inofficial_licenses: |
373 | | - inofficial_covered_texts[filename] = \ |
374 | | - [spdx_expression, |
375 | | - inofficial_licenses[spdx_expression]] |
376 | | - else: |
377 | | - not_covered_texts[filename] = \ |
378 | | - spdx_expression |
379 | | - if not_covered_texts: |
380 | | - info_str = '' |
381 | | - info_str += 'The following license files are not' +\ |
382 | | - ' mentioned by any tag:\n' +\ |
383 | | - '\n'.join( |
384 | | - [f" '{x[0]}' is of {x[1]}." |
385 | | - for x in not_covered_texts.items()]) |
386 | | - self._failed(info_str) |
387 | | - elif inofficial_covered_texts: |
388 | | - info_str = '' |
389 | | - info_str += 'The following license files are not' +\ |
390 | | - ' mentioned by any tag:\n' +\ |
391 | | - '\n'.join( |
392 | | - [f" '{x[0]}' is of {x[1][0]} but its tag is {x[1][1]}." |
393 | | - for x in inofficial_covered_texts.items()]) |
394 | | - self._warning(info_str) |
395 | | - else: |
396 | | - self._success("All license declaration are referenced by a tag.") |
397 | | - |
398 | | - |
399 | | -def is_in_package(package: Package, file: str) -> bool: |
400 | | - """Return TRUE if the file is underneath the absolute package path. |
401 | | - Return FALSE if file is located above package.""" |
402 | | - parent = os.path.abspath(package.abspath) |
403 | | - child = os.path.abspath(package.abspath + '/' + file) |
404 | | - |
405 | | - comm_parent = os.path.commonpath([parent]) |
406 | | - comm_child_parent = os.path.commonpath([parent, child]) |
407 | | - return comm_parent == comm_child_parent |
0 commit comments