scanoss 1.27.1__py3-none-any.whl → 1.43.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- protoc_gen_swagger/options/annotations_pb2.py +18 -12
- protoc_gen_swagger/options/annotations_pb2.pyi +48 -0
- protoc_gen_swagger/options/annotations_pb2_grpc.py +20 -0
- protoc_gen_swagger/options/openapiv2_pb2.py +110 -99
- protoc_gen_swagger/options/openapiv2_pb2.pyi +1317 -0
- protoc_gen_swagger/options/openapiv2_pb2_grpc.py +20 -0
- scanoss/__init__.py +1 -1
- scanoss/api/common/v2/scanoss_common_pb2.py +49 -22
- scanoss/api/common/v2/scanoss_common_pb2_grpc.py +25 -0
- scanoss/api/components/v2/scanoss_components_pb2.py +68 -43
- scanoss/api/components/v2/scanoss_components_pb2_grpc.py +83 -22
- scanoss/api/cryptography/v2/scanoss_cryptography_pb2.py +136 -47
- scanoss/api/cryptography/v2/scanoss_cryptography_pb2_grpc.py +650 -33
- scanoss/api/dependencies/v2/scanoss_dependencies_pb2.py +56 -37
- scanoss/api/dependencies/v2/scanoss_dependencies_pb2_grpc.py +64 -12
- scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2.py +74 -31
- scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2_grpc.py +252 -13
- scanoss/api/licenses/__init__.py +23 -0
- scanoss/api/licenses/v2/__init__.py +23 -0
- scanoss/api/licenses/v2/scanoss_licenses_pb2.py +84 -0
- scanoss/api/licenses/v2/scanoss_licenses_pb2_grpc.py +302 -0
- scanoss/api/scanning/v2/scanoss_scanning_pb2.py +32 -21
- scanoss/api/scanning/v2/scanoss_scanning_pb2_grpc.py +49 -8
- scanoss/api/semgrep/v2/scanoss_semgrep_pb2.py +50 -23
- scanoss/api/semgrep/v2/scanoss_semgrep_pb2_grpc.py +151 -16
- scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2.py +78 -31
- scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2_grpc.py +282 -18
- scanoss/cli.py +1000 -186
- scanoss/components.py +80 -50
- scanoss/constants.py +7 -1
- scanoss/cryptography.py +89 -55
- scanoss/csvoutput.py +13 -7
- scanoss/cyclonedx.py +141 -9
- scanoss/data/build_date.txt +1 -1
- scanoss/data/osadl-copyleft.json +133 -0
- scanoss/delta.py +197 -0
- scanoss/export/__init__.py +23 -0
- scanoss/export/dependency_track.py +227 -0
- scanoss/file_filters.py +2 -163
- scanoss/filecount.py +37 -38
- scanoss/gitlabqualityreport.py +214 -0
- scanoss/header_filter.py +563 -0
- scanoss/inspection/policy_check/__init__.py +0 -0
- scanoss/inspection/policy_check/dependency_track/__init__.py +0 -0
- scanoss/inspection/policy_check/dependency_track/project_violation.py +479 -0
- scanoss/inspection/{policy_check.py → policy_check/policy_check.py} +65 -72
- scanoss/inspection/policy_check/scanoss/__init__.py +0 -0
- scanoss/inspection/{copyleft.py → policy_check/scanoss/copyleft.py} +89 -73
- scanoss/inspection/{undeclared_component.py → policy_check/scanoss/undeclared_component.py} +52 -46
- scanoss/inspection/summary/__init__.py +0 -0
- scanoss/inspection/summary/component_summary.py +170 -0
- scanoss/inspection/{license_summary.py → summary/license_summary.py} +62 -12
- scanoss/inspection/summary/match_summary.py +341 -0
- scanoss/inspection/utils/file_utils.py +44 -0
- scanoss/inspection/utils/license_utils.py +57 -71
- scanoss/inspection/utils/markdown_utils.py +63 -0
- scanoss/inspection/{inspect_base.py → utils/scan_result_processor.py} +53 -67
- scanoss/osadl.py +125 -0
- scanoss/scanner.py +135 -253
- scanoss/scanners/folder_hasher.py +47 -32
- scanoss/scanners/scanner_hfh.py +50 -18
- scanoss/scanoss_settings.py +33 -3
- scanoss/scanossapi.py +23 -25
- scanoss/scanossbase.py +1 -1
- scanoss/scanossgrpc.py +543 -289
- scanoss/services/dependency_track_service.py +132 -0
- scanoss/spdxlite.py +11 -4
- scanoss/threadeddependencies.py +19 -18
- scanoss/threadedscanning.py +10 -0
- scanoss/utils/scanoss_scan_results_utils.py +41 -0
- scanoss/winnowing.py +71 -19
- {scanoss-1.27.1.dist-info → scanoss-1.43.1.dist-info}/METADATA +8 -5
- scanoss-1.43.1.dist-info/RECORD +110 -0
- scanoss/inspection/component_summary.py +0 -94
- scanoss-1.27.1.dist-info/RECORD +0 -87
- {scanoss-1.27.1.dist-info → scanoss-1.43.1.dist-info}/WHEEL +0 -0
- {scanoss-1.27.1.dist-info → scanoss-1.43.1.dist-info}/entry_points.txt +0 -0
- {scanoss-1.27.1.dist-info → scanoss-1.43.1.dist-info}/licenses/LICENSE +0 -0
- {scanoss-1.27.1.dist-info → scanoss-1.43.1.dist-info}/top_level.txt +0 -0
|
@@ -22,96 +22,90 @@ SPDX-License-Identifier: MIT
|
|
|
22
22
|
THE SOFTWARE.
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
|
-
from
|
|
25
|
+
from scanoss.osadl import Osadl
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
'agpl-3.0-only',
|
|
29
|
-
'artistic-1.0',
|
|
30
|
-
'artistic-2.0',
|
|
31
|
-
'cc-by-sa-4.0',
|
|
32
|
-
'cddl-1.0',
|
|
33
|
-
'cddl-1.1',
|
|
34
|
-
'cecill-2.1',
|
|
35
|
-
'epl-1.0',
|
|
36
|
-
'epl-2.0',
|
|
37
|
-
'gfdl-1.1-only',
|
|
38
|
-
'gfdl-1.2-only',
|
|
39
|
-
'gfdl-1.3-only',
|
|
40
|
-
'gpl-1.0-only',
|
|
41
|
-
'gpl-2.0-only',
|
|
42
|
-
'gpl-3.0-only',
|
|
43
|
-
'lgpl-2.1-only',
|
|
44
|
-
'lgpl-3.0-only',
|
|
45
|
-
'mpl-1.1',
|
|
46
|
-
'mpl-2.0',
|
|
47
|
-
'sleepycat',
|
|
48
|
-
'watcom-1.0',
|
|
49
|
-
}
|
|
27
|
+
from ...scanossbase import ScanossBase
|
|
50
28
|
|
|
51
29
|
|
|
52
30
|
class LicenseUtil(ScanossBase):
|
|
53
31
|
"""
|
|
54
32
|
A utility class for handling software licenses, particularly copyleft licenses.
|
|
55
33
|
|
|
56
|
-
|
|
57
|
-
|
|
34
|
+
Uses OSADL (Open Source Automation Development Lab) authoritative copyleft data
|
|
35
|
+
with optional include/exclude/explicit filters.
|
|
58
36
|
"""
|
|
59
37
|
|
|
60
38
|
BASE_SPDX_ORG_URL = 'https://spdx.org/licenses'
|
|
61
|
-
BASE_OSADL_URL = 'https://www.osadl.org/fileadmin/checklists/unreflicenses'
|
|
62
39
|
|
|
63
40
|
def __init__(self, debug: bool = False, trace: bool = True, quiet: bool = False):
|
|
64
41
|
super().__init__(debug, trace, quiet)
|
|
65
|
-
self.
|
|
66
|
-
self.
|
|
42
|
+
self.osadl = Osadl(debug=debug, trace=trace, quiet=quiet)
|
|
43
|
+
self.include_licenses = set()
|
|
44
|
+
self.exclude_licenses = set()
|
|
45
|
+
self.explicit_licenses = set()
|
|
67
46
|
|
|
68
47
|
def init(self, include: str = None, exclude: str = None, explicit: str = None):
|
|
69
48
|
"""
|
|
70
|
-
Initialize
|
|
71
|
-
|
|
72
|
-
This method allows for customization of the copyleft license set by:
|
|
73
|
-
- Setting an explicit list of licenses
|
|
74
|
-
- Including additional licenses to the default set
|
|
75
|
-
- Excluding specific licenses from the default set
|
|
49
|
+
Initialize copyleft license filters.
|
|
76
50
|
|
|
77
|
-
:param include: Comma-separated
|
|
78
|
-
:param exclude: Comma-separated
|
|
79
|
-
:param explicit: Comma-separated
|
|
51
|
+
:param include: Comma-separated licenses to mark as copyleft (in addition to OSADL)
|
|
52
|
+
:param exclude: Comma-separated licenses to mark as NOT copyleft (override OSADL)
|
|
53
|
+
:param explicit: Comma-separated licenses to use exclusively (ignore OSADL)
|
|
80
54
|
"""
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
55
|
+
# Reset previous filters so init() can be safely called multiple times
|
|
56
|
+
self.include_licenses.clear()
|
|
57
|
+
self.exclude_licenses.clear()
|
|
58
|
+
self.explicit_licenses.clear()
|
|
59
|
+
|
|
60
|
+
# Parse explicit list (if provided, ignore OSADL completely)
|
|
87
61
|
if explicit:
|
|
88
|
-
|
|
89
|
-
self.
|
|
90
|
-
self.print_debug(f'Copyleft licenses: ${self.copyleft_licenses}')
|
|
62
|
+
self.explicit_licenses = {lic.strip().lower() for lic in explicit.split(',') if lic.strip()}
|
|
63
|
+
self.print_debug(f'Explicit copyleft licenses: {self.explicit_licenses}')
|
|
91
64
|
return
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if include:
|
|
95
|
-
include = include.strip()
|
|
65
|
+
|
|
66
|
+
# Parse include list (mark these as copyleft in addition to OSADL)
|
|
96
67
|
if include:
|
|
97
|
-
|
|
98
|
-
self.
|
|
99
|
-
|
|
100
|
-
|
|
68
|
+
self.include_licenses = {lic.strip().lower() for lic in include.split(',') if lic.strip()}
|
|
69
|
+
self.print_debug(f'Include licenses: {self.include_licenses}')
|
|
70
|
+
|
|
71
|
+
# Parse exclude list (mark these as NOT copyleft, overriding OSADL)
|
|
101
72
|
if exclude:
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
self.copyleft_licenses.discard(lic)
|
|
105
|
-
self.print_debug(f'Copyleft licenses: ${self.copyleft_licenses}')
|
|
73
|
+
self.exclude_licenses = {lic.strip().lower() for lic in exclude.split(',') if lic.strip()}
|
|
74
|
+
self.print_debug(f'Exclude licenses: {self.exclude_licenses}')
|
|
106
75
|
|
|
107
76
|
def is_copyleft(self, spdxid: str) -> bool:
|
|
108
77
|
"""
|
|
109
|
-
Check if a
|
|
78
|
+
Check if a license is copyleft.
|
|
79
|
+
|
|
80
|
+
Logic:
|
|
81
|
+
1. If explicit list provided → check if license in explicit list
|
|
82
|
+
2. If license in include list → return True
|
|
83
|
+
3. If license in exclude list → return False
|
|
84
|
+
4. Otherwise → use OSADL authoritative data
|
|
110
85
|
|
|
111
|
-
:param spdxid:
|
|
112
|
-
:return: True if
|
|
86
|
+
:param spdxid: SPDX license identifier
|
|
87
|
+
:return: True if copyleft, False otherwise
|
|
113
88
|
"""
|
|
114
|
-
|
|
89
|
+
if not spdxid:
|
|
90
|
+
self.print_debug('No license ID provided for copyleft check')
|
|
91
|
+
return False
|
|
92
|
+
|
|
93
|
+
spdxid_lc = spdxid.lower()
|
|
94
|
+
|
|
95
|
+
# Explicit mode: use only the explicit list
|
|
96
|
+
if self.explicit_licenses:
|
|
97
|
+
return spdxid_lc in self.explicit_licenses
|
|
98
|
+
|
|
99
|
+
# Include filter: if license in include list, force copyleft=True
|
|
100
|
+
if spdxid_lc in self.include_licenses:
|
|
101
|
+
return True
|
|
102
|
+
|
|
103
|
+
# Exclude filter: if license in exclude list, force copyleft=False
|
|
104
|
+
if spdxid_lc in self.exclude_licenses:
|
|
105
|
+
return False
|
|
106
|
+
|
|
107
|
+
# No filters matched, use OSADL authoritative data
|
|
108
|
+
return self.osadl.is_copyleft(spdxid)
|
|
115
109
|
|
|
116
110
|
def get_spdx_url(self, spdxid: str) -> str:
|
|
117
111
|
"""
|
|
@@ -122,14 +116,6 @@ class LicenseUtil(ScanossBase):
|
|
|
122
116
|
"""
|
|
123
117
|
return f'{self.BASE_SPDX_ORG_URL}/{spdxid}.html'
|
|
124
118
|
|
|
125
|
-
def get_osadl_url(self, spdxid: str) -> str:
|
|
126
|
-
"""
|
|
127
|
-
Generate the URL for the OSADL (Open Source Automation Development Lab) page of a license.
|
|
128
|
-
|
|
129
|
-
:param spdxid: The SPDX identifier of the license
|
|
130
|
-
:return: The URL of the OSADL page for the given license
|
|
131
|
-
"""
|
|
132
|
-
return f'{self.BASE_OSADL_URL}/{spdxid}.txt'
|
|
133
119
|
|
|
134
120
|
|
|
135
121
|
#
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2025, 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
|
+
def generate_table(headers, rows, centered_columns=None):
|
|
26
|
+
"""
|
|
27
|
+
Generate a Markdown table.
|
|
28
|
+
|
|
29
|
+
:param headers: List of headers for the table.
|
|
30
|
+
:param rows: List of rows for the table.
|
|
31
|
+
:param centered_columns: List of column indices to be centered.
|
|
32
|
+
:return: A string representing the Markdown table.
|
|
33
|
+
"""
|
|
34
|
+
col_sep = ' | '
|
|
35
|
+
centered_column_set = set(centered_columns or [])
|
|
36
|
+
if headers is None:
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
# Decide which separator to use
|
|
40
|
+
def create_separator(index):
|
|
41
|
+
if centered_columns is None:
|
|
42
|
+
return '-'
|
|
43
|
+
return ':-:' if index in centered_column_set else '-'
|
|
44
|
+
|
|
45
|
+
# Build the row separator
|
|
46
|
+
row_separator = col_sep + col_sep.join(create_separator(index) for index, _ in enumerate(headers)) + col_sep
|
|
47
|
+
# build table rows
|
|
48
|
+
table_rows = [col_sep + col_sep.join(headers) + col_sep, row_separator]
|
|
49
|
+
table_rows.extend(col_sep + col_sep.join(row) + col_sep for row in rows)
|
|
50
|
+
return '\n'.join(table_rows)
|
|
51
|
+
|
|
52
|
+
def generate_jira_table(headers, rows, centered_columns=None):
|
|
53
|
+
col_sep = '*|*'
|
|
54
|
+
if headers is None:
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
table_header = '|*' + col_sep.join(headers) + '*|\n'
|
|
58
|
+
table = table_header
|
|
59
|
+
for row in rows:
|
|
60
|
+
if len(headers) == len(row):
|
|
61
|
+
table += '|' + '|'.join(row) + '|\n'
|
|
62
|
+
|
|
63
|
+
return table
|
|
@@ -22,14 +22,12 @@ SPDX-License-Identifier: MIT
|
|
|
22
22
|
THE SOFTWARE.
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
|
-
import json
|
|
26
|
-
import os.path
|
|
27
|
-
from abc import abstractmethod
|
|
28
25
|
from enum import Enum
|
|
29
|
-
from typing import Any, Dict
|
|
26
|
+
from typing import Any, Dict, TypeVar
|
|
30
27
|
|
|
31
|
-
from
|
|
32
|
-
from
|
|
28
|
+
from ...scanossbase import ScanossBase
|
|
29
|
+
from ..utils.file_utils import load_json_file
|
|
30
|
+
from ..utils.license_utils import LicenseUtil
|
|
33
31
|
|
|
34
32
|
|
|
35
33
|
class ComponentID(Enum):
|
|
@@ -51,13 +49,14 @@ class ComponentID(Enum):
|
|
|
51
49
|
# End of ComponentID Class
|
|
52
50
|
#
|
|
53
51
|
|
|
54
|
-
|
|
55
|
-
class
|
|
52
|
+
T = TypeVar('T')
|
|
53
|
+
class ScanResultProcessor(ScanossBase):
|
|
56
54
|
"""
|
|
57
|
-
A
|
|
55
|
+
A utility class for processing and transforming scan results.
|
|
58
56
|
|
|
59
|
-
This class provides
|
|
60
|
-
|
|
57
|
+
This class provides functionality for processing scan results, including methods for
|
|
58
|
+
loading, parsing, extracting, and aggregating component and license data from scan results.
|
|
59
|
+
It serves as a shared data processing layer used by both policy checks and summary generators.
|
|
61
60
|
|
|
62
61
|
Inherits from:
|
|
63
62
|
ScanossBase: A base class providing common functionality for SCANOSS-related operations.
|
|
@@ -68,37 +67,21 @@ class InspectBase(ScanossBase):
|
|
|
68
67
|
debug: bool = False,
|
|
69
68
|
trace: bool = False,
|
|
70
69
|
quiet: bool = False,
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
result_file_path: str = None,
|
|
71
|
+
include: str = None,
|
|
72
|
+
exclude: str = None,
|
|
73
|
+
explicit: str = None,
|
|
74
|
+
license_sources: list = None,
|
|
73
75
|
):
|
|
74
76
|
super().__init__(debug, trace, quiet)
|
|
77
|
+
self.result_file_path = result_file_path
|
|
75
78
|
self.license_util = LicenseUtil()
|
|
76
|
-
self.
|
|
77
|
-
self.
|
|
79
|
+
self.license_util.init(include, exclude, explicit)
|
|
80
|
+
self.license_sources = license_sources
|
|
78
81
|
self.results = self._load_input_file()
|
|
79
82
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
"""
|
|
83
|
-
Retrieve and process components from the preloaded results.
|
|
84
|
-
|
|
85
|
-
This method performs the following steps:
|
|
86
|
-
1. Checks if the results have been previously loaded (self.results).
|
|
87
|
-
2. Extracts and processes components from the loaded results.
|
|
88
|
-
|
|
89
|
-
:return: A list of processed components, or None if an error occurred during any step.
|
|
90
|
-
|
|
91
|
-
Possible reasons for returning None include:
|
|
92
|
-
- Results not loaded (self.results is None)
|
|
93
|
-
- Failure to extract components from the results
|
|
94
|
-
|
|
95
|
-
Note:
|
|
96
|
-
- This method assumes that the results have been previously loaded and stored in self.results.
|
|
97
|
-
- Implementations must extract components (e.g. via `_get_components_data`,
|
|
98
|
-
`_get_dependencies_data`, or other helpers).
|
|
99
|
-
- If `self.results` is `None`, simply return `None`.
|
|
100
|
-
"""
|
|
101
|
-
pass
|
|
83
|
+
def get_results(self) -> Dict[str, Any]:
|
|
84
|
+
return self.results
|
|
102
85
|
|
|
103
86
|
def _append_component(self, components: Dict[str, Any], new_component: Dict[str, Any]) -> Dict[str, Any]:
|
|
104
87
|
"""
|
|
@@ -181,9 +164,11 @@ class InspectBase(ScanossBase):
|
|
|
181
164
|
self.print_debug(f'WARNING: Results missing licenses. Skipping: {new_component}')
|
|
182
165
|
return
|
|
183
166
|
|
|
184
|
-
|
|
167
|
+
# Select licenses based on configuration (filtering or priority mode)
|
|
168
|
+
selected_licenses = self._select_licenses(new_component['licenses'])
|
|
169
|
+
|
|
185
170
|
# Process licenses for this component
|
|
186
|
-
for license_item in
|
|
171
|
+
for license_item in selected_licenses:
|
|
187
172
|
if license_item.get('name'):
|
|
188
173
|
spdxid = license_item['name']
|
|
189
174
|
source = license_item.get('source')
|
|
@@ -211,7 +196,7 @@ class InspectBase(ScanossBase):
|
|
|
211
196
|
else:
|
|
212
197
|
component['undeclared'] += 1
|
|
213
198
|
|
|
214
|
-
def
|
|
199
|
+
def get_components_data(self, components: Dict[str, Any]) -> Dict[str, Any]:
|
|
215
200
|
"""
|
|
216
201
|
Extract and process file and snippet components from results.
|
|
217
202
|
|
|
@@ -228,11 +213,11 @@ class InspectBase(ScanossBase):
|
|
|
228
213
|
which tracks the number of occurrences of each license
|
|
229
214
|
|
|
230
215
|
Args:
|
|
231
|
-
|
|
216
|
+
components: A dictionary containing the raw results of a component scan
|
|
232
217
|
Returns:
|
|
233
218
|
Updated components dictionary with file and snippet data
|
|
234
219
|
"""
|
|
235
|
-
for component in results.values():
|
|
220
|
+
for component in self.results.values():
|
|
236
221
|
for c in component:
|
|
237
222
|
component_id = c.get('id')
|
|
238
223
|
if not component_id:
|
|
@@ -264,15 +249,13 @@ class InspectBase(ScanossBase):
|
|
|
264
249
|
# End components loop
|
|
265
250
|
return components
|
|
266
251
|
|
|
267
|
-
def
|
|
252
|
+
def get_dependencies_data(self,components: Dict[str, Any]) -> Dict[str, Any]:
|
|
268
253
|
"""
|
|
269
254
|
Extract and process dependency components from results.
|
|
270
|
-
|
|
271
|
-
:param results: A dictionary containing the raw results of a component scan
|
|
272
255
|
:param components: Existing components dictionary to update
|
|
273
256
|
:return: Updated components dictionary with dependency data
|
|
274
257
|
"""
|
|
275
|
-
for component in results.values():
|
|
258
|
+
for component in self.results.values():
|
|
276
259
|
for c in component:
|
|
277
260
|
component_id = c.get('id')
|
|
278
261
|
if not component_id:
|
|
@@ -310,17 +293,13 @@ class InspectBase(ScanossBase):
|
|
|
310
293
|
Returns:
|
|
311
294
|
Dict[str, Any]: The parsed JSON data
|
|
312
295
|
"""
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
with open(self.filepath, 'r') as jsonfile:
|
|
317
|
-
try:
|
|
318
|
-
return json.load(jsonfile)
|
|
319
|
-
except Exception as e:
|
|
296
|
+
try:
|
|
297
|
+
return load_json_file(self.result_file_path)
|
|
298
|
+
except Exception as e:
|
|
320
299
|
self.print_stderr(f'ERROR: Problem parsing input JSON: {e}')
|
|
321
|
-
|
|
300
|
+
return None
|
|
322
301
|
|
|
323
|
-
def
|
|
302
|
+
def convert_components_to_list(self, components: dict):
|
|
324
303
|
if components is None:
|
|
325
304
|
self.print_debug(f'WARNING: Components is empty {self.results}')
|
|
326
305
|
return None
|
|
@@ -334,19 +313,26 @@ class InspectBase(ScanossBase):
|
|
|
334
313
|
component['licenses'] = []
|
|
335
314
|
return results_list
|
|
336
315
|
|
|
337
|
-
def
|
|
316
|
+
def _select_licenses(self, licenses_data):
|
|
338
317
|
"""
|
|
339
|
-
Select licenses based on
|
|
340
|
-
1. component_declared (highest priority)
|
|
341
|
-
2. license_file
|
|
342
|
-
3. file_header
|
|
343
|
-
4. scancode (lowest priority)
|
|
318
|
+
Select licenses based on configuration.
|
|
344
319
|
|
|
345
|
-
|
|
346
|
-
If
|
|
320
|
+
Two modes:
|
|
321
|
+
- Filtering mode: If license_sources specified, filter to those sources
|
|
322
|
+
- Priority mode: Otherwise, use original priority-based selection
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
licenses_data: List of license dictionaries
|
|
347
326
|
|
|
348
|
-
Returns:
|
|
327
|
+
Returns:
|
|
328
|
+
Filtered list of licenses based on configuration
|
|
349
329
|
"""
|
|
330
|
+
# Filtering mode, when license_sources is explicitly provided
|
|
331
|
+
if self.license_sources:
|
|
332
|
+
sources_to_include = set(self.license_sources) | {'unknown'}
|
|
333
|
+
return [lic for lic in licenses_data
|
|
334
|
+
if lic.get('source') in sources_to_include or lic.get('source') is None]
|
|
335
|
+
|
|
350
336
|
# Define priority order (highest to lowest)
|
|
351
337
|
priority_sources = ['component_declared', 'license_file', 'file_header', 'scancode']
|
|
352
338
|
|
|
@@ -374,7 +360,7 @@ class InspectBase(ScanossBase):
|
|
|
374
360
|
self.print_debug("No priority sources found, returning all licenses as list")
|
|
375
361
|
return licenses_data
|
|
376
362
|
|
|
377
|
-
def
|
|
363
|
+
def group_components_by_license(self,components):
|
|
378
364
|
"""
|
|
379
365
|
Groups components by their unique component-license pairs.
|
|
380
366
|
|
|
@@ -427,5 +413,5 @@ class InspectBase(ScanossBase):
|
|
|
427
413
|
|
|
428
414
|
|
|
429
415
|
#
|
|
430
|
-
# End of
|
|
431
|
-
#
|
|
416
|
+
# End of ScanResultProcessor Class
|
|
417
|
+
#
|
scanoss/osadl.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2025, 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 json
|
|
26
|
+
import sys
|
|
27
|
+
|
|
28
|
+
import importlib_resources
|
|
29
|
+
|
|
30
|
+
from scanoss.scanossbase import ScanossBase
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Osadl(ScanossBase):
|
|
34
|
+
"""
|
|
35
|
+
OSADL data accessor class.
|
|
36
|
+
|
|
37
|
+
Provides access to OSADL (Open Source Automation Development Lab) authoritative
|
|
38
|
+
checklist data for license analysis.
|
|
39
|
+
|
|
40
|
+
Data is loaded once at class level and shared across all instances for efficiency.
|
|
41
|
+
|
|
42
|
+
Data source: https://www.osadl.org/fileadmin/checklists/copyleft.json
|
|
43
|
+
License: CC-BY-4.0
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
_shared_copyleft_data = {}
|
|
47
|
+
_data_loaded = False
|
|
48
|
+
|
|
49
|
+
def __init__(self, debug: bool = False, trace: bool = True, quiet: bool = False):
|
|
50
|
+
"""
|
|
51
|
+
Initialize the Osadl class.
|
|
52
|
+
Data is loaded once at class level and shared across all instances.
|
|
53
|
+
"""
|
|
54
|
+
super().__init__(debug, trace, quiet)
|
|
55
|
+
self._load_copyleft_data()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _load_copyleft_data(self) -> bool:
|
|
59
|
+
"""
|
|
60
|
+
Load the embedded OSADL copyleft JSON file into class-level shared data.
|
|
61
|
+
Data is loaded only once and shared across all instances.
|
|
62
|
+
|
|
63
|
+
:return: True if successful, False otherwise
|
|
64
|
+
"""
|
|
65
|
+
if Osadl._data_loaded:
|
|
66
|
+
return True
|
|
67
|
+
|
|
68
|
+
# OSADL copyleft license checklist from: https://www.osadl.org/Checklists
|
|
69
|
+
# Data source: https://www.osadl.org/fileadmin/checklists/copyleft.json
|
|
70
|
+
# License: CC-BY-4.0 (Creative Commons Attribution 4.0 International)
|
|
71
|
+
# Copyright: (C) 2017 - 2024 Open Source Automation Development Lab (OSADL) eG
|
|
72
|
+
try:
|
|
73
|
+
f_name = importlib_resources.files(__name__) / 'data/osadl-copyleft.json'
|
|
74
|
+
with importlib_resources.as_file(f_name) as f:
|
|
75
|
+
with open(f, 'r', encoding='utf-8') as file:
|
|
76
|
+
data = json.load(file)
|
|
77
|
+
except Exception as e:
|
|
78
|
+
self.print_stderr(f'ERROR: Problem loading OSADL copyleft data: {e}')
|
|
79
|
+
return False
|
|
80
|
+
|
|
81
|
+
# Process copyleft data
|
|
82
|
+
copyleft = data.get('copyleft', {})
|
|
83
|
+
if not copyleft:
|
|
84
|
+
self.print_stderr('ERROR: No copyleft data found in OSADL JSON')
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
# Store in class-level shared dictionary
|
|
88
|
+
for lic_id, status in copyleft.items():
|
|
89
|
+
# Normalize license ID (lowercase) for consistent lookup
|
|
90
|
+
lic_id_lc = lic_id.lower()
|
|
91
|
+
Osadl._shared_copyleft_data[lic_id_lc] = status
|
|
92
|
+
|
|
93
|
+
Osadl._data_loaded = True
|
|
94
|
+
self.print_debug(f'Loaded {len(Osadl._shared_copyleft_data)} OSADL copyleft entries')
|
|
95
|
+
return True
|
|
96
|
+
|
|
97
|
+
def is_copyleft(self, spdx_id: str) -> bool:
|
|
98
|
+
"""
|
|
99
|
+
Check if a license is copyleft according to OSADL data.
|
|
100
|
+
|
|
101
|
+
Returns True for both strong copyleft ("Yes") and weak/restricted copyleft ("Yes (restricted)").
|
|
102
|
+
|
|
103
|
+
:param spdx_id: SPDX license identifier
|
|
104
|
+
:return: True if copyleft, False otherwise
|
|
105
|
+
"""
|
|
106
|
+
if not spdx_id:
|
|
107
|
+
self.print_debug('No license ID provided for copyleft check')
|
|
108
|
+
return False
|
|
109
|
+
|
|
110
|
+
# Normalize lookup
|
|
111
|
+
spdx_id_lc = spdx_id.lower()
|
|
112
|
+
# Use class-level shared data
|
|
113
|
+
status = Osadl._shared_copyleft_data.get(spdx_id_lc)
|
|
114
|
+
|
|
115
|
+
if not status:
|
|
116
|
+
self.print_debug(f'No OSADL copyleft data for license: {spdx_id}')
|
|
117
|
+
return False
|
|
118
|
+
|
|
119
|
+
# Consider both "Yes" and "Yes (restricted)" as copyleft (case-insensitive)
|
|
120
|
+
return status.lower().startswith('yes')
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
#
|
|
124
|
+
# End of Osadl Class
|
|
125
|
+
#
|