Skip to content

Commit 02f0092

Browse files
authored
Bug/inspect subcommand
* bug:SP-2745 Avoids errors on inspect subcommand when no version is declared on results * chore:Upgrades version to 1.25.2 * chore:Updates CHANGELOG.md file * chore:SP-2746 Prioritizes licenses by source in inspect copyleft subcommand
1 parent 7e19648 commit 02f0092

File tree

6 files changed

+98
-29
lines changed

6 files changed

+98
-29
lines changed

CHANGELOG.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99
### Added
1010
- Upcoming changes...
1111

12+
## [1.25.2] - 2025-06-18
13+
### Fixed
14+
- Fixed errors when no versions are declared in scanner results for `inspect` subcommand
15+
### Changed
16+
- Prioritized licenses by source priority in `inspect copyleft` subcommand
17+
1218
## [1.25.1] - 2025-06-12
1319
### Fixed
1420
- Removed dependency components from the undeclared component list in the `inspect` subcommand.
@@ -534,4 +540,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
534540
[1.23.0]: https://github.com/scanoss/scanoss.py/compare/v1.22.0...v1.23.0
535541
[1.24.0]: https://github.com/scanoss/scanoss.py/compare/v1.23.0...v1.24.0
536542
[1.25.0]: https://github.com/scanoss/scanoss.py/compare/v1.24.0...v1.25.0
537-
[1.25.1]: https://github.com/scanoss/scanoss.py/compare/v1.25.0...v1.25.1
543+
[1.25.1]: https://github.com/scanoss/scanoss.py/compare/v1.25.0...v1.25.1
544+
[1.25.2]: https://github.com/scanoss/scanoss.py/compare/v1.25.1...v1.25.2

src/scanoss/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,4 @@
2222
THE SOFTWARE.
2323
"""
2424

25-
__version__ = '1.25.1'
25+
__version__ = '1.25.2'

src/scanoss/inspection/copyleft.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -177,11 +177,7 @@ def _get_components(self):
177177
# Extract component and license data from file and dependency results. Both helpers mutate `components`
178178
self._get_components_data(self.results, components)
179179
self._get_dependencies_data(self.results, components)
180-
# Convert to list and process licenses
181-
results_list = list(components.values())
182-
for component in results_list:
183-
component['licenses'] = list(component['licenses'].values())
184-
return results_list
180+
return self._convert_components_to_list(components)
185181

186182
def run(self):
187183
"""

src/scanoss/inspection/policy_check.py

Lines changed: 79 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,10 @@ def _append_component(
212212
else:
213213
purl = new_component['purl']
214214

215+
if not purl:
216+
self.print_debug(f'WARNING: _append_component: No purl found for new component: {new_component}')
217+
return components
218+
215219
component_key = f'{purl}@{new_component["version"]}'
216220
components[component_key] = {
217221
'purl': purl,
@@ -222,14 +226,21 @@ def _append_component(
222226
if not new_component.get('licenses'):
223227
self.print_debug(f'WARNING: Results missing licenses. Skipping: {new_component}')
224228
return components
229+
230+
231+
licenses_order_by_source_priority = self._get_licenses_order_by_source_priority(new_component['licenses'])
225232
# Process licenses for this component
226-
for license_item in new_component['licenses']:
233+
for license_item in licenses_order_by_source_priority:
227234
if license_item.get('name'):
228235
spdxid = license_item['name']
236+
source = license_item.get('source')
237+
if not source:
238+
source = 'unknown'
229239
components[component_key]['licenses'][spdxid] = {
230240
'spdxid': spdxid,
231241
'copyleft': self.license_util.is_copyleft(spdxid),
232242
'url': self.license_util.get_spdx_url(spdxid),
243+
'source': source,
233244
}
234245
return components
235246

@@ -261,10 +272,12 @@ def _get_components_data(self, results: Dict[str, Any], components: Dict[str, An
261272
if len(c.get('purl')) <= 0:
262273
self.print_debug(f'WARNING: Result missing purls. Skipping: {c}')
263274
continue
264-
if not c.get('version'):
265-
self.print_msg(f'WARNING: Result missing version. Skipping: {c}')
266-
continue
267-
component_key = f'{c["purl"][0]}@{c["version"]}'
275+
version = c.get('version')
276+
if not version:
277+
self.print_debug(f'WARNING: Result missing version. Setting it to unknown: {c}')
278+
version = 'unknown'
279+
c['version'] = version #If no version exists. Set 'unknown' version to current component
280+
component_key = f'{c["purl"][0]}@{version}'
268281
if component_key not in components:
269282
components = self._append_component(components, c, component_id, status)
270283
# End component loop
@@ -296,10 +309,12 @@ def _get_dependencies_data(self, results: Dict[str, Any], components: Dict[str,
296309
if not dependency.get('purl'):
297310
self.print_debug(f'WARNING: Dependency result missing purl. Skipping: {dependency}')
298311
continue
299-
if not dependency.get('version'):
300-
self.print_msg(f'WARNING: Dependency result missing version. Skipping: {dependency}')
301-
continue
302-
component_key = f'{dependency["purl"]}@{dependency["version"]}'
312+
version = c.get('version')
313+
if not version:
314+
self.print_debug(f'WARNING: Result missing version. Setting it to unknown: {c}')
315+
version = 'unknown'
316+
c['version'] = version # If no version exists. Set 'unknown' version to current component
317+
component_key = f'{dependency["purl"]}@{version}'
303318
if component_key not in components:
304319
components = self._append_component(components, dependency, component_id, status)
305320
# End dependency loop
@@ -411,6 +426,61 @@ def _load_input_file(self):
411426
self.print_stderr(f'ERROR: Problem parsing input JSON: {e}')
412427
return None
413428

429+
def _convert_components_to_list(self, components: dict):
430+
if components is None:
431+
self.print_debug(f'WARNING: Components is empty {self.results}')
432+
return None
433+
results_list = list(components.values())
434+
for component in results_list:
435+
licenses = component.get('licenses')
436+
if licenses is not None:
437+
component['licenses'] = list(licenses.values())
438+
else:
439+
self.print_debug(f'WARNING: Licenses missing for: {component}')
440+
component['licenses'] = []
441+
return results_list
442+
443+
def _get_licenses_order_by_source_priority(self,licenses_data):
444+
"""
445+
Select licenses based on source priority:
446+
1. component_declared (highest priority)
447+
2. license_file
448+
3. file_header
449+
4. scancode (lowest priority)
450+
451+
If any high-priority source is found, return only licenses from that source.
452+
If none found, return all licenses.
453+
454+
Returns: list with ordered licenses by source.
455+
"""
456+
# Define priority order (highest to lowest)
457+
priority_sources = ['component_declared', 'license_file', 'file_header', 'scancode']
458+
459+
# Group licenses by source
460+
licenses_by_source = {}
461+
for license_item in licenses_data:
462+
463+
source = license_item.get('source', 'unknown')
464+
if source not in licenses_by_source:
465+
licenses_by_source[source] = {}
466+
467+
license_name = license_item.get('name')
468+
if license_name:
469+
# Use license name as key, store full license object as value
470+
# If duplicate license names exist in same source, the last one wins
471+
licenses_by_source[source][license_name] = license_item
472+
473+
# Find the highest priority source that has licenses
474+
for priority_source in priority_sources:
475+
if priority_source in licenses_by_source:
476+
self.print_trace(f'Choosing {priority_source} as source')
477+
return list(licenses_by_source[priority_source].values())
478+
479+
# If no priority sources found, combine all licenses into a single list
480+
self.print_debug("No priority sources found, returning all licenses as list")
481+
return licenses_data
482+
483+
414484
#
415485
# End of PolicyCheck Class
416486
#

src/scanoss/inspection/undeclared_component.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -254,10 +254,7 @@ def _get_components(self):
254254
# Extract file and snippet components
255255
components = self._get_components_data(self.results, components)
256256
# Convert to list and process licenses
257-
results_list = list(components.values())
258-
for component in results_list:
259-
component['licenses'] = list(component['licenses'].values())
260-
return results_list
257+
return self._convert_components_to_list(components)
261258

262259
def run(self):
263260
"""

tests/test_policy_inspect.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ def test_copyleft_policy_explicit(self):
114114
copyleft = Copyleft(filepath=input_file_name, format_type='json', explicit='MIT')
115115
status, results = copyleft.run()
116116
details = json.loads(results['details'])
117-
self.assertEqual(len(details['components']), 3)
117+
self.assertEqual(len(details['components']), 2)
118118
self.assertEqual(status, 0)
119119

120120
"""
@@ -144,11 +144,10 @@ def test_copyleft_policy_markdown(self):
144144
expected_detail_output = (
145145
'### Copyleft licenses \n | Component | Version | License | URL | Copyleft |\n'
146146
' | - | :-: | - | - | :-: |\n'
147-
'| pkg:github/scanoss/engine | 4.0.4 | MIT | https://spdx.org/licenses/MIT.html | YES | \n'
148147
' | pkg:npm/%40electron/rebuild | 3.7.0 | MIT | https://spdx.org/licenses/MIT.html | YES |\n'
149148
'| pkg:npm/%40emotion/react | 11.13.3 | MIT | https://spdx.org/licenses/MIT.html | YES | \n'
150149
)
151-
expected_summary_output = '3 component(s) with copyleft licenses were found.\n'
150+
expected_summary_output = '2 component(s) with copyleft licenses were found.\n'
152151
self.assertEqual(
153152
re.sub(r'\s|\\(?!`)|\\(?=`)', '', results['details']),
154153
re.sub(r'\s|\\(?!`)|\\(?=`)', '', expected_detail_output),
@@ -214,9 +213,9 @@ def test_undeclared_policy_markdown(self):
214213
expected_details_output = """ ### Undeclared components
215214
| Component | Version | License |
216215
| - | - | - |
217-
| pkg:github/scanoss/scanner.c | 1.3.3 | BSD-2-Clause - GPL-2.0-only |
216+
| pkg:github/scanoss/scanner.c | 1.3.3 | GPL-2.0-only |
218217
| pkg:github/scanoss/scanner.c | 1.1.4 | GPL-2.0-only |
219-
| pkg:github/scanoss/wfp | 6afc1f6 | Zlib - GPL-2.0-only | """
218+
| pkg:github/scanoss/wfp | 6afc1f6 | GPL-2.0-only | """
220219

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

264263
expected_summary_output = """3 undeclared component(s) were found.
265264
Add the following snippet into your `scanoss.json` file
@@ -332,9 +331,9 @@ def test_undeclared_policy_jira_markdown_output(self):
332331
details = results['details']
333332
summary = results['summary']
334333
expected_details_output = """|*Component*|*Version*|*License*|
335-
|pkg:github/scanoss/scanner.c|1.3.3|BSD-2-Clause - GPL-2.0-only|
334+
|pkg:github/scanoss/scanner.c|1.3.3|GPL-2.0-only|
336335
|pkg:github/scanoss/scanner.c|1.1.4|GPL-2.0-only|
337-
|pkg:github/scanoss/wfp|6afc1f6|Zlib - GPL-2.0-only|
336+
|pkg:github/scanoss/wfp|6afc1f6|GPL-2.0-only|
338337
"""
339338
expected_summary_output = """3 undeclared component(s) were found.
340339
Add the following snippet into your `scanoss.json` file

0 commit comments

Comments
 (0)