diff --git a/pyproject.toml b/pyproject.toml index 3ba7697..fdfd463 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,7 @@ dependencies = [ "pyparsing", "scanoss>=1.45.0", "XlsxWriter", - "fosslight_util>=2.2.14", + "fosslight_util>=2.2.18", "PyYAML", "wheel>=0.38.1", "intbitset", diff --git a/src/fosslight_source/cli.py b/src/fosslight_source/cli.py index 00b64a6..e787875 100755 --- a/src/fosslight_source/cli.py +++ b/src/fosslight_source/cli.py @@ -5,7 +5,6 @@ import sys import os -import platform import time import warnings import logging @@ -171,16 +170,11 @@ def create_report_file( # If -o does not contains file name, set default name while len(output_files) < len(output_extensions): output_files.append(None) - to_remove = [] # elements of spdx format on windows that should be removed for i, output_extension in enumerate(output_extensions): if output_files[i] is None or output_files[i] == "": if formats: if formats[i].startswith('spdx'): - if platform.system() == 'Windows': - logger.warning(f'{formats[i]} is not supported on Windows.Please remove {formats[i]} from format.') - to_remove.append(i) - else: - output_files[i] = f"fosslight_spdx_src_{name_time}" + output_files[i] = f"fosslight_spdx_src_{name_time}" elif formats[i].startswith('cyclonedx'): output_files[i] = f'fosslight_cyclonedx_src_{name_time}' else: @@ -193,14 +187,6 @@ def create_report_file( output_files[i] = f"fosslight_opossum_src_{name_time}" else: output_files[i] = f"fosslight_report_src_{name_time}" - for index in sorted(to_remove, reverse=True): - # remove elements of spdx format on windows - del output_files[index] - del output_extensions[index] - del formats[index] - if len(output_extensions) < 1: - sys.exit(0) - if not correct_filepath: correct_filepath = path_to_scan @@ -259,6 +245,31 @@ def create_report_file( scan_item = correct_item logger.info("Success to correct with yaml.") + if license_list and any(output_format.startswith("spdx") for output_format in formats): + matched_texts_by_file = {} + for matched_license in license_list.values(): + if not matched_license.matched_text: + continue + for matched_path in matched_license.files: + path_key = os.path.normcase(os.path.normpath(matched_path.replace("\\", "/"))) + matched_texts_by_file.setdefault(path_key, []).append(matched_license) + + for file_item in scan_item.file_items.get(PKG_NAME, []): + path_key = os.path.normcase(os.path.normpath(file_item.source_name_or_path.replace("\\", "/"))) + for oss_item in file_item.oss_items: + if oss_item.exclude: + continue + for license_name in oss_item.license: + for matched_license in matched_texts_by_file.get(path_key, []): + declares_license = re.search( + r"(?:SPDX[-\s]+License[-\s]+Identifier\s*:\s*|\blicense\s*=\s*['\"]\s*)" + + re.escape(license_name) + r"(?=[,'\"\s]|$)", + matched_license.matched_text, re.IGNORECASE, + ) + if matched_license.license.casefold() == license_name.casefold() or declares_license: + oss_item.license_texts.setdefault(license_name, matched_license.matched_text) + break + finish_time = current_timestamp_utc() scan_item.set_cover_finish_time(finish_time) @@ -652,9 +663,12 @@ def run_scanners( if not selected_scanner: selected_scanner = ALL_MODE if selected_scanner in ['scancode', ALL_MODE]: + need_matched_license = print_matched_text or any( + output_format.startswith("spdx") for output_format in formats + ) success, result_log[RESULT_KEY], scancode_result, license_list = run_scan( path_to_scan, output_file_name, write_json_file, num_cores, True, - print_matched_text, formats, called_by_cli, time_out, correct_mode, + need_matched_license, formats, called_by_cli, time_out, correct_mode, correct_filepath, path_to_exclude, hide_progress=hide_progress, ui_mode=ui_mode, diff --git a/tests/test_spdx_license_text.py b/tests/test_spdx_license_text.py new file mode 100644 index 0000000..0f37ad0 --- /dev/null +++ b/tests/test_spdx_license_text.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (c) 2026 LG Electronics Inc. +# SPDX-License-Identifier: Apache-2.0 + +import pytest +from spdx_tools.spdx.parser.parse_anything import parse_file +from spdx_tools.spdx.validation.document_validator import validate_full_spdx_document + +from fosslight_source import cli as source_cli +from fosslight_source._license_matched import MatchedLicense +from fosslight_source._scan_item import SourceItem + + +@pytest.mark.parametrize("matched_text, expected_text", [ + (' license="BSD",', ' license="BSD",'), + ("", "BSD"), +]) +def test_spdx_uses_scancode_matched_text_without_reference_sheet( + tmp_path, monkeypatch, matched_text, expected_text +): + project = tmp_path / "project" + project.mkdir() + (project / "setup.py").write_text('license="BSD"\n', encoding="utf-8") + output = tmp_path / "reports" + source_item = SourceItem("setup.py") + source_item.checksum = "a" * 40 + source_item.licenses = ["BSD-3-Clause"] + matched_license = MatchedLicense("BSD-3-Clause", "", matched_text, "setup.py") + license_list = {matched_license.license + matched_license.matched_text: matched_license} + received_need_matched_text = [] + + def run_scancode(*args, **kwargs): + received_need_matched_text.append(args[5]) + return True, "True", [source_item], license_list + + monkeypatch.chdir(tmp_path) + monkeypatch.setattr(source_cli, "run_scan", run_scancode) + + success, message, scan_item, _licenses, _scanoss_result = source_cli.run_scanners( + str(project), str(output), selected_scanner="scancode", formats=["spdx-json"], + print_matched_text=False, correct_mode=False, all_exclude_mode=([], [], set(), 1), + hide_progress=True, + ) + + assert success, message + assert received_need_matched_text == [True] + assert "scancode_reference" not in scan_item.external_sheets + assert scan_item.file_items[source_cli.PKG_NAME][0].oss_items[0].license == ["BSD"] + document = parse_file(str(next(output.glob("*.json")))) + assert validate_full_spdx_document(document) == [] + assert document.extracted_licensing_info[0].license_name == "BSD" + assert document.extracted_licensing_info[0].extracted_text == expected_text + assert {str(value) for value in document.files[0].license_info_in_file} == {"LicenseRef-BSD"}