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.
Files changed (79) hide show
  1. protoc_gen_swagger/options/annotations_pb2.py +18 -12
  2. protoc_gen_swagger/options/annotations_pb2.pyi +48 -0
  3. protoc_gen_swagger/options/annotations_pb2_grpc.py +20 -0
  4. protoc_gen_swagger/options/openapiv2_pb2.py +110 -99
  5. protoc_gen_swagger/options/openapiv2_pb2.pyi +1317 -0
  6. protoc_gen_swagger/options/openapiv2_pb2_grpc.py +20 -0
  7. scanoss/__init__.py +1 -1
  8. scanoss/api/common/v2/scanoss_common_pb2.py +49 -22
  9. scanoss/api/common/v2/scanoss_common_pb2_grpc.py +25 -0
  10. scanoss/api/components/v2/scanoss_components_pb2.py +68 -43
  11. scanoss/api/components/v2/scanoss_components_pb2_grpc.py +83 -22
  12. scanoss/api/cryptography/v2/scanoss_cryptography_pb2.py +136 -47
  13. scanoss/api/cryptography/v2/scanoss_cryptography_pb2_grpc.py +650 -33
  14. scanoss/api/dependencies/v2/scanoss_dependencies_pb2.py +56 -37
  15. scanoss/api/dependencies/v2/scanoss_dependencies_pb2_grpc.py +64 -12
  16. scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2.py +74 -31
  17. scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2_grpc.py +252 -13
  18. scanoss/api/licenses/__init__.py +23 -0
  19. scanoss/api/licenses/v2/__init__.py +23 -0
  20. scanoss/api/licenses/v2/scanoss_licenses_pb2.py +84 -0
  21. scanoss/api/licenses/v2/scanoss_licenses_pb2_grpc.py +302 -0
  22. scanoss/api/scanning/v2/scanoss_scanning_pb2.py +32 -21
  23. scanoss/api/scanning/v2/scanoss_scanning_pb2_grpc.py +49 -8
  24. scanoss/api/semgrep/v2/scanoss_semgrep_pb2.py +50 -23
  25. scanoss/api/semgrep/v2/scanoss_semgrep_pb2_grpc.py +151 -16
  26. scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2.py +78 -31
  27. scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2_grpc.py +282 -18
  28. scanoss/cli.py +1000 -186
  29. scanoss/components.py +80 -50
  30. scanoss/constants.py +7 -1
  31. scanoss/cryptography.py +89 -55
  32. scanoss/csvoutput.py +13 -7
  33. scanoss/cyclonedx.py +141 -9
  34. scanoss/data/build_date.txt +1 -1
  35. scanoss/data/osadl-copyleft.json +133 -0
  36. scanoss/delta.py +197 -0
  37. scanoss/export/__init__.py +23 -0
  38. scanoss/export/dependency_track.py +227 -0
  39. scanoss/file_filters.py +2 -163
  40. scanoss/filecount.py +37 -38
  41. scanoss/gitlabqualityreport.py +214 -0
  42. scanoss/header_filter.py +563 -0
  43. scanoss/inspection/policy_check/__init__.py +0 -0
  44. scanoss/inspection/policy_check/dependency_track/__init__.py +0 -0
  45. scanoss/inspection/policy_check/dependency_track/project_violation.py +479 -0
  46. scanoss/inspection/{policy_check.py → policy_check/policy_check.py} +65 -72
  47. scanoss/inspection/policy_check/scanoss/__init__.py +0 -0
  48. scanoss/inspection/{copyleft.py → policy_check/scanoss/copyleft.py} +89 -73
  49. scanoss/inspection/{undeclared_component.py → policy_check/scanoss/undeclared_component.py} +52 -46
  50. scanoss/inspection/summary/__init__.py +0 -0
  51. scanoss/inspection/summary/component_summary.py +170 -0
  52. scanoss/inspection/{license_summary.py → summary/license_summary.py} +62 -12
  53. scanoss/inspection/summary/match_summary.py +341 -0
  54. scanoss/inspection/utils/file_utils.py +44 -0
  55. scanoss/inspection/utils/license_utils.py +57 -71
  56. scanoss/inspection/utils/markdown_utils.py +63 -0
  57. scanoss/inspection/{inspect_base.py → utils/scan_result_processor.py} +53 -67
  58. scanoss/osadl.py +125 -0
  59. scanoss/scanner.py +135 -253
  60. scanoss/scanners/folder_hasher.py +47 -32
  61. scanoss/scanners/scanner_hfh.py +50 -18
  62. scanoss/scanoss_settings.py +33 -3
  63. scanoss/scanossapi.py +23 -25
  64. scanoss/scanossbase.py +1 -1
  65. scanoss/scanossgrpc.py +543 -289
  66. scanoss/services/dependency_track_service.py +132 -0
  67. scanoss/spdxlite.py +11 -4
  68. scanoss/threadeddependencies.py +19 -18
  69. scanoss/threadedscanning.py +10 -0
  70. scanoss/utils/scanoss_scan_results_utils.py +41 -0
  71. scanoss/winnowing.py +71 -19
  72. {scanoss-1.27.1.dist-info → scanoss-1.43.1.dist-info}/METADATA +8 -5
  73. scanoss-1.43.1.dist-info/RECORD +110 -0
  74. scanoss/inspection/component_summary.py +0 -94
  75. scanoss-1.27.1.dist-info/RECORD +0 -87
  76. {scanoss-1.27.1.dist-info → scanoss-1.43.1.dist-info}/WHEEL +0 -0
  77. {scanoss-1.27.1.dist-info → scanoss-1.43.1.dist-info}/entry_points.txt +0 -0
  78. {scanoss-1.27.1.dist-info → scanoss-1.43.1.dist-info}/licenses/LICENSE +0 -0
  79. {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 ...scanossbase import ScanossBase
25
+ from scanoss.osadl import Osadl
26
26
 
27
- DEFAULT_COPYLEFT_LICENSES = {
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
- This class provides functionality to initialize, manage, and query a set of
57
- copyleft licenses. It also offers a method to generate URLs for license information.
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.default_copyleft_licenses = set(DEFAULT_COPYLEFT_LICENSES)
66
- self.copyleft_licenses = set()
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 the set of copyleft licenses based on user input.
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 string of licenses to include
78
- :param exclude: Comma-separated string of licenses to exclude
79
- :param explicit: Comma-separated string of licenses to use exclusively
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
- if self.debug:
82
- self.print_stderr(f'Include Copyleft licenses: ${include}')
83
- self.print_stderr(f'Exclude Copyleft licenses: ${exclude}')
84
- self.print_stderr(f'Explicit Copyleft licenses: ${explicit}')
85
- if explicit:
86
- explicit = explicit.strip()
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
- exp = [item.strip().lower() for item in explicit.split(',')]
89
- self.copyleft_licenses = set(exp)
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
- # If no explicit licenses were set, set default ones
93
- self.copyleft_licenses = self.default_copyleft_licenses.copy()
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
- inc = [item.strip().lower() for item in include.split(',')]
98
- self.copyleft_licenses.update(inc)
99
- if exclude:
100
- exclude = exclude.strip()
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
- inc = [item.strip().lower() for item in exclude.split(',')]
103
- for lic in inc:
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 given license is considered copyleft.
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: The SPDX identifier of the license to check
112
- :return: True if the license is copyleft, False otherwise
86
+ :param spdxid: SPDX license identifier
87
+ :return: True if copyleft, False otherwise
113
88
  """
114
- return spdxid.lower() in self.copyleft_licenses
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 ..scanossbase import ScanossBase
32
- from .utils.license_utils import LicenseUtil
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 InspectBase(ScanossBase):
52
+ T = TypeVar('T')
53
+ class ScanResultProcessor(ScanossBase):
56
54
  """
57
- A base class to perform inspections over scan results.
55
+ A utility class for processing and transforming scan results.
58
56
 
59
- This class provides a basic for scan results inspection, including methods for
60
- processing scan results components and licenses.
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
- filepath: str = None,
72
- output: str = None,
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.filepath = filepath
77
- self.output = output
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
- @abstractmethod
81
- def _get_components(self):
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
- licenses_order_by_source_priority = self._get_licenses_order_by_source_priority(new_component['licenses'])
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 licenses_order_by_source_priority:
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 _get_components_data(self, results: Dict[str, Any], components: Dict[str, Any]) -> Dict[str, Any]:
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
- results: A dictionary containing the raw results of a component scan
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 _get_dependencies_data(self, results: Dict[str, Any], components: Dict[str, Any]) -> Dict[str, Any]:
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
- if not os.path.exists(self.filepath):
314
- self.print_stderr(f'ERROR: The file "{self.filepath}" does not exist.')
315
- return None
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
- return None
300
+ return None
322
301
 
323
- def _convert_components_to_list(self, components: dict):
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 _get_licenses_order_by_source_priority(self,licenses_data):
316
+ def _select_licenses(self, licenses_data):
338
317
  """
339
- Select licenses based on source priority:
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
- If any high-priority source is found, return only licenses from that source.
346
- If none found, return all licenses.
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: list with ordered licenses by source.
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 _group_components_by_license(self,components):
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 PolicyCheck Class
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
+ #