Skip to content

Bug/inspect subcommand #124

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

Merged
merged 5 commits into from
Jun 18, 2025
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
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Upcoming changes...

## [1.25.2] - 2025-06-18
### Fixed
- Fixed errors when no versions are declared in scanner results for `inspect` subcommand
### Changed
- Prioritized licenses by source priority in `inspect copyleft` subcommand

## [1.25.1] - 2025-06-12
### Fixed
- Removed dependency components from the undeclared component list in the `inspect` subcommand.
Expand Down Expand Up @@ -534,4 +540,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[1.23.0]: https://github.com/scanoss/scanoss.py/compare/v1.22.0...v1.23.0
[1.24.0]: https://github.com/scanoss/scanoss.py/compare/v1.23.0...v1.24.0
[1.25.0]: https://github.com/scanoss/scanoss.py/compare/v1.24.0...v1.25.0
[1.25.1]: https://github.com/scanoss/scanoss.py/compare/v1.25.0...v1.25.1
[1.25.1]: https://github.com/scanoss/scanoss.py/compare/v1.25.0...v1.25.1
[1.25.2]: https://github.com/scanoss/scanoss.py/compare/v1.25.1...v1.25.2
2 changes: 1 addition & 1 deletion src/scanoss/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@
THE SOFTWARE.
"""

__version__ = '1.25.1'
__version__ = '1.25.2'
6 changes: 1 addition & 5 deletions src/scanoss/inspection/copyleft.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,11 +177,7 @@ def _get_components(self):
# Extract component and license data from file and dependency results. Both helpers mutate `components`
self._get_components_data(self.results, components)
self._get_dependencies_data(self.results, components)
# Convert to list and process licenses
results_list = list(components.values())
for component in results_list:
component['licenses'] = list(component['licenses'].values())
return results_list
return self._convert_components_to_list(components)

def run(self):
"""
Expand Down
88 changes: 79 additions & 9 deletions src/scanoss/inspection/policy_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,10 @@ def _append_component(
else:
purl = new_component['purl']

if not purl:
self.print_debug(f'WARNING: _append_component: No purl found for new component: {new_component}')
return components

component_key = f'{purl}@{new_component["version"]}'
components[component_key] = {
'purl': purl,
Expand All @@ -222,14 +226,21 @@ def _append_component(
if not new_component.get('licenses'):
self.print_debug(f'WARNING: Results missing licenses. Skipping: {new_component}')
return components


licenses_order_by_source_priority = self._get_licenses_order_by_source_priority(new_component['licenses'])
# Process licenses for this component
for license_item in new_component['licenses']:
for license_item in licenses_order_by_source_priority:
if license_item.get('name'):
spdxid = license_item['name']
source = license_item.get('source')
if not source:
source = 'unknown'
components[component_key]['licenses'][spdxid] = {
'spdxid': spdxid,
'copyleft': self.license_util.is_copyleft(spdxid),
'url': self.license_util.get_spdx_url(spdxid),
'source': source,
}
return components

Expand Down Expand Up @@ -261,10 +272,12 @@ def _get_components_data(self, results: Dict[str, Any], components: Dict[str, An
if len(c.get('purl')) <= 0:
self.print_debug(f'WARNING: Result missing purls. Skipping: {c}')
continue
if not c.get('version'):
self.print_msg(f'WARNING: Result missing version. Skipping: {c}')
continue
component_key = f'{c["purl"][0]}@{c["version"]}'
version = c.get('version')
if not version:
self.print_debug(f'WARNING: Result missing version. Setting it to unknown: {c}')
version = 'unknown'
c['version'] = version #If no version exists. Set 'unknown' version to current component
component_key = f'{c["purl"][0]}@{version}'
if component_key not in components:
components = self._append_component(components, c, component_id, status)
# End component loop
Expand Down Expand Up @@ -296,10 +309,12 @@ def _get_dependencies_data(self, results: Dict[str, Any], components: Dict[str,
if not dependency.get('purl'):
self.print_debug(f'WARNING: Dependency result missing purl. Skipping: {dependency}')
continue
if not dependency.get('version'):
self.print_msg(f'WARNING: Dependency result missing version. Skipping: {dependency}')
continue
component_key = f'{dependency["purl"]}@{dependency["version"]}'
version = c.get('version')
if not version:
self.print_debug(f'WARNING: Result missing version. Setting it to unknown: {c}')
version = 'unknown'
c['version'] = version # If no version exists. Set 'unknown' version to current component
component_key = f'{dependency["purl"]}@{version}'
if component_key not in components:
components = self._append_component(components, dependency, component_id, status)
# End dependency loop
Expand Down Expand Up @@ -411,6 +426,61 @@ def _load_input_file(self):
self.print_stderr(f'ERROR: Problem parsing input JSON: {e}')
return None

def _convert_components_to_list(self, components: dict):
if components is None:
self.print_debug(f'WARNING: Components is empty {self.results}')
return None
results_list = list(components.values())
for component in results_list:
licenses = component.get('licenses')
if licenses is not None:
component['licenses'] = list(licenses.values())
else:
self.print_debug(f'WARNING: Licenses missing for: {component}')
component['licenses'] = []
return results_list

def _get_licenses_order_by_source_priority(self,licenses_data):
"""
Select licenses based on source priority:
1. component_declared (highest priority)
2. license_file
3. file_header
4. scancode (lowest priority)

If any high-priority source is found, return only licenses from that source.
If none found, return all licenses.

Returns: list with ordered licenses by source.
"""
# Define priority order (highest to lowest)
priority_sources = ['component_declared', 'license_file', 'file_header', 'scancode']

# Group licenses by source
licenses_by_source = {}
for license_item in licenses_data:

source = license_item.get('source', 'unknown')
if source not in licenses_by_source:
licenses_by_source[source] = {}

license_name = license_item.get('name')
if license_name:
# Use license name as key, store full license object as value
# If duplicate license names exist in same source, the last one wins
licenses_by_source[source][license_name] = license_item

# Find the highest priority source that has licenses
for priority_source in priority_sources:
if priority_source in licenses_by_source:
self.print_trace(f'Choosing {priority_source} as source')
return list(licenses_by_source[priority_source].values())

# If no priority sources found, combine all licenses into a single list
self.print_debug("No priority sources found, returning all licenses as list")
return licenses_data


#
# End of PolicyCheck Class
#
5 changes: 1 addition & 4 deletions src/scanoss/inspection/undeclared_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,10 +254,7 @@ def _get_components(self):
# Extract file and snippet components
components = self._get_components_data(self.results, components)
# Convert to list and process licenses
results_list = list(components.values())
for component in results_list:
component['licenses'] = list(component['licenses'].values())
return results_list
return self._convert_components_to_list(components)

def run(self):
"""
Expand Down
17 changes: 8 additions & 9 deletions tests/test_policy_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ def test_copyleft_policy_explicit(self):
copyleft = Copyleft(filepath=input_file_name, format_type='json', explicit='MIT')
status, results = copyleft.run()
details = json.loads(results['details'])
self.assertEqual(len(details['components']), 3)
self.assertEqual(len(details['components']), 2)
self.assertEqual(status, 0)

"""
Expand Down Expand Up @@ -144,11 +144,10 @@ def test_copyleft_policy_markdown(self):
expected_detail_output = (
'### Copyleft licenses \n | Component | Version | License | URL | Copyleft |\n'
' | - | :-: | - | - | :-: |\n'
'| pkg:github/scanoss/engine | 4.0.4 | MIT | https://spdx.org/licenses/MIT.html | YES | \n'
' | pkg:npm/%40electron/rebuild | 3.7.0 | MIT | https://spdx.org/licenses/MIT.html | YES |\n'
'| pkg:npm/%40emotion/react | 11.13.3 | MIT | https://spdx.org/licenses/MIT.html | YES | \n'
)
expected_summary_output = '3 component(s) with copyleft licenses were found.\n'
expected_summary_output = '2 component(s) with copyleft licenses were found.\n'
self.assertEqual(
re.sub(r'\s|\\(?!`)|\\(?=`)', '', results['details']),
re.sub(r'\s|\\(?!`)|\\(?=`)', '', expected_detail_output),
Expand Down Expand Up @@ -214,9 +213,9 @@ def test_undeclared_policy_markdown(self):
expected_details_output = """ ### Undeclared components
| Component | Version | License |
| - | - | - |
| pkg:github/scanoss/scanner.c | 1.3.3 | BSD-2-Clause - GPL-2.0-only |
| pkg:github/scanoss/scanner.c | 1.3.3 | GPL-2.0-only |
| pkg:github/scanoss/scanner.c | 1.1.4 | GPL-2.0-only |
| pkg:github/scanoss/wfp | 6afc1f6 | Zlib - GPL-2.0-only | """
| pkg:github/scanoss/wfp | 6afc1f6 | GPL-2.0-only | """

expected_summary_output = """3 undeclared component(s) were found.
Add the following snippet into your `sbom.json` file
Expand Down Expand Up @@ -257,9 +256,9 @@ def test_undeclared_policy_markdown_scanoss_summary(self):
expected_details_output = """ ### Undeclared components
| Component | Version | License |
| - | - | - |
| pkg:github/scanoss/scanner.c | 1.3.3 | BSD-2-Clause - GPL-2.0-only |
| pkg:github/scanoss/scanner.c | 1.3.3 | GPL-2.0-only |
| pkg:github/scanoss/scanner.c | 1.1.4 | GPL-2.0-only |
| pkg:github/scanoss/wfp | 6afc1f6 | Zlib - GPL-2.0-only | """
| pkg:github/scanoss/wfp | 6afc1f6 | GPL-2.0-only | """

expected_summary_output = """3 undeclared component(s) were found.
Add the following snippet into your `scanoss.json` file
Expand Down Expand Up @@ -332,9 +331,9 @@ def test_undeclared_policy_jira_markdown_output(self):
details = results['details']
summary = results['summary']
expected_details_output = """|*Component*|*Version*|*License*|
|pkg:github/scanoss/scanner.c|1.3.3|BSD-2-Clause - GPL-2.0-only|
|pkg:github/scanoss/scanner.c|1.3.3|GPL-2.0-only|
|pkg:github/scanoss/scanner.c|1.1.4|GPL-2.0-only|
|pkg:github/scanoss/wfp|6afc1f6|Zlib - GPL-2.0-only|
|pkg:github/scanoss/wfp|6afc1f6|GPL-2.0-only|
"""
expected_summary_output = """3 undeclared component(s) were found.
Add the following snippet into your `scanoss.json` file
Expand Down