Skip to content

Commit 7974777

Browse files
committed
fix(deps):SP-4165 avoid requirement field lost during dependency decoration
1 parent 74a7aba commit 7974777

4 files changed

Lines changed: 94 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [1.50.0] - 2026-03-17
11+
### Fixed
12+
- Fixed `requirement` field being lost during dependency decoration in scan command
13+
- Sanitized scancode `extracted_requirement` to strip redundant package name prefix (e.g., `gtest==1.17.0``1.17.0`)
14+
1015
## [1.49.1] - 2026-03-17
1116
### Fixed
1217
- When an error occurs during the scan, do not write a partial scan result file. Leave it empty.
@@ -842,3 +847,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
842847
[1.48.0]: https://github.com/scanoss/scanoss.py/compare/v1.47.0...v1.48.0
843848
[1.49.0]: https://github.com/scanoss/scanoss.py/compare/v1.48.0...v1.49.0
844849
[1.49.1]: https://github.com/scanoss/scanoss.py/compare/v1.49.0...v1.49.1
850+
[1.50.0]: https://github.com/scanoss/scanoss.py/compare/v1.49.1...v1.50.0

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.49.1'
25+
__version__ = '1.50.0'

src/scanoss/scancodedeps.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,17 @@
2424

2525
import json
2626
import os.path
27+
import re
2728
import subprocess
2829

2930
from pathspec import GitIgnoreSpec
3031

3132
from .scanossbase import ScanossBase
3233

34+
# Regex to strip package name prefix from a requirement, keeping only the version specifier.
35+
# e.g. 'gtest==1.17.0' -> '==1.17.0', 'boost>=1.83.0' -> '>=1.83.0', '^4.18.0' -> '^4.18.0'
36+
REQUIREMENT_NAME_PREFIX_RE = re.compile(r'^\s*[^<>=!^]+\s*(?===|!=|>=|<=|=|>|<|\^)')
37+
3338

3439
class ScancodeDeps(ScanossBase):
3540
"""
@@ -134,8 +139,12 @@ def produce_from_json(self, data: json) -> dict: # noqa: PLR0912
134139
if not rq or rq == '':
135140
rq = d.get('requirement') # scancode format 1.0
136141
# skip requirement if it ends with the purl (i.e. exact version) or if it's local (file)
137-
if rq and rq != '' and not dp.endswith(rq) and not rq.startswith('file:'):
138-
dp_data['requirement'] = rq
142+
if rq and rq != '':
143+
# Strip any prefix data before a version comparator
144+
rq = REQUIREMENT_NAME_PREFIX_RE.sub('', rq)
145+
# skip if it ends with the purl (exact version) or is local (file)
146+
if not dp.endswith(rq) and not rq.startswith('file:'):
147+
dp_data['requirement'] = rq
139148

140149
# Gets dependency scope
141150
scope = d.get('scope')
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
"""
2+
SPDX-License-Identifier: MIT
3+
4+
Copyright (c) 2026, SCANOSS
5+
6+
Permission is hereby granted, free of charge, to any person obtaining a copy
7+
of this software and associated documentation files (the "Software"), to deal
8+
in the Software without restriction, including without limitation the rights
9+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
copies of the Software, and to permit persons to whom the Software is
11+
furnished to do so, subject to the following conditions:
12+
13+
The above copyright notice and this permission notice shall be included in
14+
all copies or substantial portions of the Software.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
THE SOFTWARE.
23+
"""
24+
25+
import os
26+
import unittest
27+
28+
from scanoss.scancodedeps import REQUIREMENT_NAME_PREFIX_RE, ScancodeDeps
29+
30+
TEST_DIR = os.path.dirname(os.path.abspath(__file__))
31+
32+
33+
def _sanitize(rq):
34+
return REQUIREMENT_NAME_PREFIX_RE.sub('', rq)
35+
36+
37+
class TestSanitizeRequirement(unittest.TestCase):
38+
"""Test the regex that strips package names from requirements"""
39+
40+
def test_pip_exact_version(self):
41+
"""pip exact match: strip name, keep '=='"""
42+
self.assertEqual(_sanitize('gtest==1.17.0'), '==1.17.0')
43+
44+
def test_pip_less_equal(self):
45+
self.assertEqual(_sanitize('boost<=1.83.0'), '<=1.83.0')
46+
47+
def test_pip_greater_equal(self):
48+
self.assertEqual(_sanitize('requests>=2.25.1'), '>=2.25.1')
49+
50+
def test_pip_range(self):
51+
self.assertEqual(_sanitize('requests>=2.25.1,<3'), '>=2.25.1,<3')
52+
53+
def test_pip_not_equal(self):
54+
self.assertEqual(_sanitize('foo!=1.0'), '!=1.0')
55+
56+
def test_npm_caret_unchanged(self):
57+
"""npm ^: no operator after name, unchanged"""
58+
self.assertEqual(_sanitize('^4.18.0'), '^4.18.0')
59+
60+
def test_npm_tilde_unchanged(self):
61+
self.assertEqual(_sanitize('~4.18.0'), '~4.18.0')
62+
63+
def test_npm_greater_equal(self):
64+
self.assertEqual(_sanitize('>=1.0.0'), '>=1.0.0')
65+
66+
def test_bare_version_unchanged(self):
67+
"""Plain version number with no operator: unchanged"""
68+
self.assertEqual(_sanitize('1.17.0'), '1.17.0')
69+
70+
def test_name_prefix_of_another_package(self):
71+
"""'requests-toolbelt>=1.0' strips full name, not partial"""
72+
self.assertEqual(_sanitize('requests-toolbelt>=1.0'), '>=1.0')
73+
74+
75+
if __name__ == '__main__':
76+
unittest.main()

0 commit comments

Comments
 (0)