Skip to content
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
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ dependencies = [
"pyparsing",
"scanoss>=1.45.0",
"XlsxWriter",
"fosslight_util>=2.2.14",
"fosslight_util>=2.2.18",
Comment thread
soimkim marked this conversation as resolved.
Resolved
"PyYAML",
"wheel>=0.38.1",
"intbitset",
Expand Down
46 changes: 30 additions & 16 deletions src/fosslight_source/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

import sys
import os
import platform
import time
import warnings
import logging
Expand Down Expand Up @@ -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:
Expand All @@ -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

Expand Down Expand Up @@ -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)
Comment thread
soimkim marked this conversation as resolved.
Resolved
break

finish_time = current_timestamp_utc()
scan_item.set_cover_finish_time(finish_time)

Expand Down Expand Up @@ -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,
Expand Down
54 changes: 54 additions & 0 deletions tests/test_spdx_license_text.py
Original file line number Diff line number Diff line change
@@ -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"}
Loading